phase: 187_sync_peak_memory_line
Build and Push Containers / build-and-push-app (push) Successful in 1m47s
Build and Push Containers / build-and-push-db (push) Successful in 13s

**Phase 187 — final verification pass: all criteria met, repo 100% green.**

Verified (all four tasks already in `complete/`)
- `app/core/memory.py`: `rss_mb` / `rss_peak_mb` / `cgroup_peak_mb` / `fmt_mb`, pure stdlib, never-raise, **100%** coverage, 24 branch tests.
- Lifecycle lines append-only: `sync: started … rss_mb=`, all three terminals `… rss_mb= rss_peak_mb=` (failed = sanitized one-liner beside the surviving `logger.exception`), `import: summary … rss_mb=`; prefixes byte-identical.
- `GET /api/sync/status`: additive `rss_mb`/`rss_peak_mb` (`int|null`), keyed off the ONE `_sample_rss` (run start, phase transitions, forced terminals, 5 s-throttled hook), `peak >= current` invariant, anonymous 403.
- `sources.js` `fmtSyncPeakClause`: ` · peak <N> MB` trailing the counts line / "Sync cancelled", nothing when null, no new control/live region.

Gates (exact commands)
- `uv run pytest --cov=app --cov-report=term-missing` → **4570 passed**, TOTAL **99%**, `app/api/sync.py` **100%**, `app/core/memory.py` **100%**
- `uv run ruff check .` → clean · `uv run pyright` → **0 errors**
- `uv run pytest tests/e2e/test_sync_peak_memory_visible.py -v --no-cov` → **5 passed in isolation** (ran twice)
- All 27 touched E2E families re-run **in isolation**, all green (progress_box 8, cancel 3, status_freshness 4, summary_visibility 3, sync_button 3, session_reset 4, git_freeze 2, git_log_stream_dates 5, PDF/image suites, …)

Completion criteria: every checkbox ✅ except the commit + phase-dir move, which belong to the harness.

Documentation
- README reviewed in full (pages, commands, config tables cross-checked against `app/config.py` — 85 env rows, all defaults match; phase-187 ops section, greps, process-vs-cgroup caveat): consistent, no changes needed.
- `AGENTS.md`: **fixed one pre-existing inconsistency** — rule 6 claimed the no-CDN test covers "the index page"; it actually parametrizes 10 pages (all but `/doc-edit.html`).

Notable
- Two failures early in the pass were self-inflicted by running the E2E module concurrently with the full suite on the shared Postgres (TRUNCATE deadlock + one order-dependent `test_overview` flake); both vanish under the documented serial isolation — no code defect, no change made.
- **PLAN §9 still needs the owner's reconciliation** (phase 187 adds the sync memory fields + two status keys; PLAN is never agent-edited — AGENTS rule 10 carries the spec). `TODO.md` holds an unrelated owner note, left untouched.

Next pending phase: `188_chunks_hnsw_index`.
This commit is contained in:
2026-10-16 15:22:51 -04:00
parent 644f7de696
commit c054b47df5
72 changed files with 3826 additions and 96 deletions
+485 -11
View File
@@ -203,6 +203,10 @@ RETIRED_ID = "-".join(("sync", "result")) # built at runtime: 3.12+ folds
HAVE_NODE = shutil.which("node") is not None
#: Phase 187 (task 02): what :func:`app.core.memory.fmt_mb` may render for
#: an MB field — ``?`` when the platform cannot know, else a positive int.
MB = r"(?:\?|[1-9][0-9]*)"
def _text(path: Path) -> str:
assert path.is_file(), f"missing frontend file: {path}"
@@ -1112,9 +1116,13 @@ def test_sources_js_renders_result_line_and_banner_directly() -> None:
for other subscribers."""
js = _text(SOURCES_JS)
success = _body(js, "applySyncSuccess")
assert "setSyncProgress(fmtSyncResult(status.detail))" in success, (
"the counts live in status.detail — a bare status renders all zeros"
)
# Phase 187 (task 03): the counts line keeps its builder and gains
# the TRAILING peak clause — a null pair appends the empty string, so
# every pre-phase line stays byte-identical.
assert (
"setSyncProgress(fmtSyncResult(status.detail) + fmtSyncPeakClause(status))"
in success
), "the counts live in status.detail — a bare status renders all zeros"
assert "loadTree()" in success, (
"the catalog re-fetches live on a successful sync (phase 97: the tree)"
)
@@ -1319,14 +1327,21 @@ def fresh_sync_status() -> Iterator[None]:
Phase 134 (task 01): the module ``_cancel_event`` joins the reset —
a stale (set) event from a previous test's cancelled run would
otherwise bleed into the next run's boundary checks."""
otherwise bleed into the next run's boundary checks.
Phase 187 (task 03): the memory-sampling guard (``_rss_sampled_at``)
joins the reset — it is module-global state beside ``_status``, and a
test that pins "the per-file hook sampled" has to know whether the
guard says "never read" or "still inside the 5 s window"."""
sync_api._status = sync_api.SyncStatus()
sync_api._task = None
sync_api._cancel_event = asyncio.Event()
sync_api._rss_sampled_at = float("-inf")
yield
sync_api._status = sync_api.SyncStatus()
sync_api._task = None
sync_api._cancel_event = asyncio.Event()
sync_api._rss_sampled_at = float("-inf")
class _GatedClone:
@@ -1567,9 +1582,11 @@ def test_idle_status_pins_full_shape_including_progress_keys(
) -> None:
"""Idle: the three phase-64 progress keys ride along as null/0/0,
the two phase-129 chunk keys as 0/0, the two phase-152 image keys
as 0/0, the four phase-98 phase keys as null/0/0/0, and EVERY
pre-existing key is unchanged — the full response dict is pinned,
so the current UI and every existing consumer keep working."""
as 0/0, the four phase-98 phase keys as null/0/0/0, the two phase-187
memory keys as null/null (NO reading is not 0 — and the Sources page
renders no peak clause for it), and EVERY pre-existing key is
unchanged — the full response dict is pinned, so the current UI and
every existing consumer keep working."""
assert sync_api.sync_status() == {
"state": "idle",
"started_at": None,
@@ -1592,6 +1609,10 @@ def test_idle_status_pins_full_shape_including_progress_keys(
"current_summary": None,
"summaries_done": 0,
"summaries_total": 0,
# Phase 187 (task 03): the memory keys — null/null idle (never
# sampled: never 0, never an invented number).
"rss_mb": None,
"rss_peak_mb": None,
}
@@ -2562,10 +2583,15 @@ def test_cancel_endpoint_stops_the_in_flight_run(
# through the closure in flight is gone; the FILE counts above
# keep their final values).
assert s["images_done"] == 0 and s["images_total"] == 0
# PLAN §9: the ample-logging line for the converted cancel.
# PLAN §9: the ample-logging line for the converted cancel. Phase
# 187 (task 02) APPENDS the memory fields (LOCKED A2) — the pre-phase
# prefix stays byte-identical, the tail is `?` or a positive int.
assert any(
r.getMessage()
== "sync: cancelled by user state=cancelled files_done=1 files_total=3"
re.fullmatch(
r"sync: cancelled by user state=cancelled files_done=1 files_total=3"
rf" rss_mb={MB} rss_peak_mb={MB}",
r.getMessage(),
)
for r in caplog.records
)
@@ -3091,7 +3117,9 @@ def test_sources_js_apply_sync_cancelled_is_neutral() -> None:
js = _text(SOURCES_JS)
body = _body(js, "applySyncCancelled")
assert 'settleSyncButton("Sync sources")' in body
assert 'setSyncProgress("Sync cancelled")' in body
# Phase 187 (task 03): the neutral copy keeps its exact wording and
# the peak clause trails it (a null pair appends the empty string).
assert 'setSyncProgress("Sync cancelled" + fmtSyncPeakClause(status))' in body
assert "hideSyncError()" in body
assert "emitSyncStatus(status)" in body
assert "showSyncModal" not in body, "cancel is intent — no modal"
@@ -3123,3 +3151,449 @@ def test_sources_js_section_header_documents_the_cancel_contract() -> None:
assert "5. sync cancelled" in header, "the tree's cancelled branch (the 5th)"
assert "8. both idle" in header, "the tree is now 8 branches"
assert "#sync-cancel-btn" in header, "the control's contract is documented"
# ---------- phase 187 (task 03): the run's memory on the status ----------
#
# The owner incident (2026-10-15): "how much RAM did that sync use?" was
# answered by reading systemd unit-teardown lines out of three boots —
# and the boot that killed the VM had none. Tasks 01/02 made the log
# carry the number; task 03 puts the SAME pair on the status object, so
# the page that started the run answers without a shell. The pins below
# hold the three things that make that honest: the pair is ADDITIVE (the
# key set is exactly the old set plus two), it is SAMPLED from ONE place
# (forced at the run start / every phase transition / every terminal,
# throttled to one read per 5 s in the per-file hook — LOCKED A4), and a
# reading that does not exist stays null (never 0, never invented).
#: The status payload's key set BEFORE phase 187 — the additive pin's
#: left-hand side: the payload is exactly this set plus
#: :data:`_MEMORY_KEYS`, nothing else, forever.
_PRE_187_STATUS_KEYS = frozenset(
{
"state",
"started_at",
"finished_at",
"detail",
"error",
"current_file",
"files_done",
"files_total",
"current_chunk",
"chunks_total",
"images_done",
"images_total",
"phase",
"current_summary",
"summaries_done",
"summaries_total",
}
)
#: Phase 187 (task 03): the two additive memory keys.
_MEMORY_KEYS = frozenset({"rss_mb", "rss_peak_mb"})
def test_status_key_set_is_the_old_set_plus_the_memory_pair(
fresh_sync_status: None,
) -> None:
"""The key-set pin (D4: "Additive only — every pre-existing key
keeps its name, type, and meaning"): the payload is EXACTLY the
pre-phase-187 set ``∪ {rss_mb, rss_peak_mb}`` — a fifth sub-position
or a renamed field fails here even when the value assertions pass."""
keys = set(sync_api.sync_status())
assert keys == _PRE_187_STATUS_KEYS | _MEMORY_KEYS, keys
assert _MEMORY_KEYS.isdisjoint(_PRE_187_STATUS_KEYS)
class _FakeClock:
"""``sync_api.time`` stubbed: the sampler is the module's only
``time.monotonic()`` reader, so the 5 s guard is assertable without
sleeping (the test moves the clock, never the wall)."""
def __init__(self, start: float = 10_000.0) -> None:
self.now = start
def monotonic(self) -> float:
return self.now
class _StubMemory:
"""The two ``app.core.memory`` reads as a scripted pair-per-read
source: sample *n* returns ``readings[n]`` (the last pair repeats)
and :attr:`reads` counts the samples, so "sampled at most once per
5 s" is a number and not a comment.
Both helpers are patched on ``app.api.sync`` (the module under test
imports them by name), which is also what proves the sampler reads
them from ONE place: a call counter that disagrees with
``_status`` is a second, unsampled write path."""
def __init__(
self,
monkeypatch: pytest.MonkeyPatch,
readings: Sequence[tuple[int | None, int | None]],
) -> None:
self.readings = list(readings)
self.reads = 0
monkeypatch.setattr(sync_api, "rss_mb", self._current)
monkeypatch.setattr(sync_api, "rss_peak_mb", self._peak)
def _pair(self) -> tuple[int | None, int | None]:
return self.readings[min(self.reads, len(self.readings) - 1)]
def _current(self) -> int | None:
return self._pair()[0]
def _peak(self) -> int | None:
value = self._pair()[1]
self.reads += 1 # the PAIR is one sample — counted when it completes
return value
def test_sample_rss_stores_both_readings_from_one_sample(
fresh_sync_status: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""``_sample_rss`` is the ONE writer of the pair: both fields land
from the same read, so the status and the lifecycle line can never
report two different instants."""
monkeypatch.setattr(sync_api, "time", _FakeClock())
memory = _StubMemory(monkeypatch, [(412, 1_936)])
sync_api._sample_rss(force=True)
assert sync_api._status.rss_mb == 412
assert sync_api._status.rss_peak_mb == 1_936
assert memory.reads == 1
def test_sample_rss_throttles_the_unforced_call_and_force_always_reads(
fresh_sync_status: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""LOCKED A4 / D3, pinned on the guard itself: the FIRST call reads
(never sampled), the next one inside ``_RSS_SAMPLE_INTERVAL_S`` is a
NO-OP, one past the interval reads again, and ``force=True`` reads
regardless AND re-arms the window (which is what keeps the 27k-file
walk at ~600 reads)."""
clock = _FakeClock(10_000.0)
monkeypatch.setattr(sync_api, "time", clock)
memory = _StubMemory(
monkeypatch, [(100, 200), (101, 201), (102, 202), (103, 203), (104, 204)]
)
assert sync_api._RSS_SAMPLE_INTERVAL_S == 5.0, "the phase's sampling window"
sync_api._sample_rss() # never sampled → reads
assert (sync_api._status.rss_mb, sync_api._status.rss_peak_mb) == (100, 200)
clock.now += sync_api._RSS_SAMPLE_INTERVAL_S - 0.001
sync_api._sample_rss() # inside the window → no-op
assert memory.reads == 1
assert (sync_api._status.rss_mb, sync_api._status.rss_peak_mb) == (100, 200)
clock.now += 0.002 # the window closed
sync_api._sample_rss()
assert (sync_api._status.rss_mb, sync_api._status.rss_peak_mb) == (101, 201)
sync_api._sample_rss(force=True) # forced, immediately → reads
assert (sync_api._status.rss_mb, sync_api._status.rss_peak_mb) == (102, 202)
sync_api._sample_rss() # …and the forced read re-armed the window
assert memory.reads == 3, "the forced sample closed the window it opened"
clock.now += sync_api._RSS_SAMPLE_INTERVAL_S
sync_api._sample_rss()
assert (sync_api._status.rss_mb, sync_api._status.rss_peak_mb) == (103, 203)
assert memory.reads == 4
def test_sample_rss_keeps_none_as_unknown(
fresh_sync_status: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A platform that cannot know (no ``/proc``, no ``resource``) stays
``None`` on the status — never 0, never a plausible number. The wire
form is JSON null and the UI renders no clause for it (the same
``?``-is-not-0 rule :func:`app.core.memory.fmt_mb` holds for the
log)."""
monkeypatch.setattr(sync_api, "time", _FakeClock())
_StubMemory(monkeypatch, [(None, None)])
sync_api._sample_rss(force=True)
assert sync_api._status.rss_mb is None
assert sync_api._status.rss_peak_mb is None
body = sync_api.sync_status()
assert body["rss_mb"] is None and body["rss_peak_mb"] is None
def test_sample_rss_never_publishes_a_peak_below_the_current_reading(
fresh_sync_status: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The pair's invariant, pinned at the ONE sampling point (task 04).
``VmRSS`` and ``ru_maxrss`` are two kernel counters read at two
instants, both floored to whole MB, and the high-water refreshes
LAZILY — so a growing run really does report ``rss_mb`` above
``rss_peak_mb`` (measured on this repo's own suite at 164 vs 162).
A "peak" under the process's own current footprint is a FALSE
number, not a conservative one, so the sampler lifts it to the
current reading: a lower bound on the truth, never an invention.
Without this the invariant every consumer (both lifecycle lines, the
status payload, the page's clause) now relies on would be a coin
flip — and the phase's own integration gate proved it."""
monkeypatch.setattr(sync_api, "time", _FakeClock())
_StubMemory(monkeypatch, [(412, 410)]) # the lazy-kernel case
sync_api._sample_rss(force=True)
assert sync_api._status.rss_mb == 412
assert sync_api._status.rss_peak_mb == 412, "the lagging peak is raised"
assert sync_api.sync_status()["rss_peak_mb"] == 412, "the wire agrees"
def test_sample_rss_does_not_fabricate_an_unknown_peak(
fresh_sync_status: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The normalization's edge: an UNKNOWABLE peak stays ``None``.
Lifting a lagging reading is a bound on a real measurement; inventing
a peak from the current reading on a platform where
:func:`app.core.memory.rss_peak_mb` answered ``None`` would be the
invented number the phase forbids (``null`` renders no clause, ``0``
is a value). So the clamp needs BOTH readings to be real."""
monkeypatch.setattr(sync_api, "time", _FakeClock())
_StubMemory(monkeypatch, [(412, None)])
sync_api._sample_rss(force=True)
assert sync_api._status.rss_mb == 412
assert sync_api._status.rss_peak_mb is None
def test_run_samples_at_start_phase_transitions_and_terminal(
fresh_sync_status: None,
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""The sampling POINTS, walked through the real runner: run start →
``import`` → ``overview`` → ``summaries`` → the success terminal, one
forced read each, and the per-file progress hook's throttled call
adds NOTHING inside the window the run start just armed (LOCKED A4).
The last pair is the number the incident was about: the terminal
KEEPS it (the phase-64 keep-final-values convention) and the
``sync: done`` line logs the very same pair the payload reports —
one reading, two surfaces, no disagreement between the page and the
log an operator could be argued with."""
clock = _FakeClock(10_000.0)
monkeypatch.setattr(sync_api, "time", clock)
# run start, import, overview, summaries, done (the hook is throttled).
memory = _StubMemory(
monkeypatch,
[(10, 20), (11, 21), (12, 22), (13, 23), (14, 4_321)],
)
clone = _GatedClone(threading.Event(), threading.Event())
fake_import = _GatedImport(
ImportSummary(files=3, added=1, unchanged=2),
threading.Event(),
threading.Event(),
)
_patch_sync_seams(monkeypatch, tmp_path, fake_import, clone)
with caplog.at_level(logging.INFO, logger="app.api.sync"):
thread, errors = _start_run()
try:
assert clone.started.wait(5.0), "the run never reached the clone phase"
# The run start sampled BEFORE any pipeline step: a run that
# fails in the clone already has a number on the status.
s = sync_api.sync_status()
assert s["state"] == "running"
assert (s["rss_mb"], s["rss_peak_mb"]) == (10, 20)
clone.release.set()
assert fake_import.started.wait(5.0), "the run never reached the import"
# Import phase: the transition forced the second read, and
# the hook's own call (this fake fires one file) is inside
# the window — still 2 reads, not 3 (A4 in numbers).
s = sync_api.sync_status()
assert s["phase"] == "import"
assert fake_import.hook_calls == [("repo", "notes/deep.md", 1, 3)]
assert (s["rss_mb"], s["rss_peak_mb"]) == (11, 21)
assert memory.reads == 2, "the per-file hook never bypasses the guard"
fake_import.release.set()
finally:
clone.release.set()
fake_import.release.set()
thread.join(10.0)
assert errors == [], f"the runner raised: {errors!r}"
# The terminal pair is the FINAL reading, kept next to the counts,
# and it survives the run (the page shows it after the settle).
s = sync_api.sync_status()
assert s["state"] == "success"
assert (s["rss_mb"], s["rss_peak_mb"]) == (14, 4_321)
assert s["files_done"] == 1 and s["files_total"] == 3
done = [
r.getMessage()
for r in caplog.records
if r.getMessage().startswith("sync: done detail=")
]
assert len(done) == 1, done
assert done[0].endswith("rss_mb=14 rss_peak_mb=4321"), done[0]
def test_per_file_hook_samples_once_the_window_closes(
fresh_sync_status: None,
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""The other half of D3: the number on the page MOVES. The hook
fires once per file, and once the 5 s window has closed the next
file's call re-reads — so a long walk reports the memory climbing
(PLAN §7.4's never-stale contract) at ~600 reads per 27k files
instead of 27k."""
clock = _FakeClock(10_000.0)
monkeypatch.setattr(sync_api, "time", clock)
memory = _StubMemory(monkeypatch, [(100, 200), (110, 210), (140, 260)])
clone = _GatedClone(threading.Event(), threading.Event())
fake_import = _GatedImport(
ImportSummary(files=1, added=1),
threading.Event(),
threading.Event(),
)
# The fake fires ONE file, parks, then returns — so the hook is
# called once mid-run and the test calls it a second time with the
# window closed (the second file of a real walk).
_patch_sync_seams(monkeypatch, tmp_path, fake_import, clone)
thread, errors = _start_run()
try:
assert clone.started.wait(5.0), "the run never reached the clone phase"
clone.release.set()
assert fake_import.started.wait(5.0), "the run never reached the import"
s = sync_api.sync_status()
assert s["phase"] == "import"
assert (s["rss_mb"], s["rss_peak_mb"]) == (110, 210) # the transition
assert memory.reads == 2, "the hook's call inside the window read nothing"
clock.now += sync_api._RSS_SAMPLE_INTERVAL_S + 1.0
sync_api._sample_rss() # the NEXT file's hook call
assert (sync_api._status.rss_mb, sync_api._status.rss_peak_mb) == (140, 260)
fake_import.release.set()
finally:
clone.release.set()
fake_import.release.set()
thread.join(10.0)
assert errors == [], f"the runner raised: {errors!r}"
assert memory.reads >= 3
# ---------- phase 187 (task 03): the peak clause on the result line ------
def test_sources_js_peak_clause_is_a_trailing_clause_with_a_null_guard() -> None:
"""The frontend SOURCE pin (the browser behavior is
tests/e2e/test_sync_peak_memory_visible.py, phase 187 task 04):
* ONE builder, reading ``status.rss_peak_mb ?? status.rss_mb`` (the
high-water mark, the current reading as the fall-back);
* a non-number (null / undefined / garbage) renders the EMPTY string
— never ``peak ? MB``, never ``peak null MB``;
* it is APPENDED to the two result strings that already exist
(``fmtSyncResult(...)``, ``"Sync cancelled"``) through the single
box writer — no new control, no new live region, no replaced
state string;
* NOTHING else reads it: the running label, the failed box (the
banner + modal own the error, byte-identical per phase 150) and
the upload settles never grow a clause;
* the shell markup is untouched — the box is still one text span."""
js = _text(SOURCES_JS)
builder = _body(js, "fmtSyncPeakClause")
assert "status.rss_peak_mb ?? status.rss_mb" in builder
assert 'return "";' in builder, "null renders NOTHING"
assert "` · peak ${peak} MB`" in builder, "the clause's exact shape"
success = _body(js, "applySyncSuccess")
assert (
"setSyncProgress(fmtSyncResult(status.detail) + fmtSyncPeakClause(status))"
in success
)
cancelled = _body(js, "applySyncCancelled")
assert 'setSyncProgress("Sync cancelled" + fmtSyncPeakClause(status))' in cancelled
for untouched in (
"enterSyncRunningState",
"fmtSyncPhaseLabel",
"fmtSyncLabel",
"fmtSyncResult",
"setSyncLabel",
"setSyncProgress",
"applySyncFailure",
"applySyncIdle",
):
assert "fmtSyncPeakClause" not in _body(js, untouched), (
f"{untouched} must not grow a clause — the terminal result "
"line is the only surface (D4)"
)
html = _text(SHELL_HTML)
assert '<span class="sync-progress-text" id="sync-progress-text"></span>' in html
assert 'id="sync-progress-peak"' not in html, "no new element for the clause"
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_sources_js_peak_clause_renders_under_node() -> None:
"""The REAL extracted ``fmtSyncResult`` + ``fmtSyncPeakClause`` under
node, composed exactly as ``applySyncSuccess`` composes them: the
counts line keeps its exact wording and the clause trails it
(``3 added · 2 unchanged · peak 1936 MB``); a null peak falls back to
the current reading; BOTH null (idle, a pre-phase-187 run, the
upload job, a platform that cannot know) render the counts line with
NO clause — never ``peak null MB``; ``0`` is a VALUE, not missing
(the ``fmt_mb(0) == "0"`` rule, mirrored); and a negative or
non-numeric value renders nothing."""
src = (
_body(_text(SOURCES_JS), "fmtSyncResult")
+ "\n"
+ _body(_text(SOURCES_JS), "fmtSyncPeakClause")
)
driver_expr = (
"const CASES = ["
"{ detail: { added: 3, unchanged: 2 }, rss_mb: 512, rss_peak_mb: 1936 },"
"{ detail: { added: 0, unchanged: 1 }, rss_mb: 512, rss_peak_mb: null },"
"{ detail: { added: 0, unchanged: 1 }, rss_mb: null, rss_peak_mb: null },"
"{ detail: { added: 1 } },"
"{ detail: { added: 2, pruned: 4 }, rss_mb: 0, rss_peak_mb: 0 },"
"{ detail: { added: 1 }, rss_peak_mb: -5 },"
"{ detail: { added: 1 }, rss_peak_mb: 'big' },"
"];\n"
"const LINES = CASES.map("
" (s) => fmtSyncResult(s.detail) + fmtSyncPeakClause(s)\n"
");\n"
"const CANCELLED = CASES.map("
" (s) => 'Sync cancelled' + fmtSyncPeakClause(s)\n"
");\n"
"console.log(JSON.stringify({ LINES, CANCELLED }));"
)
proc = subprocess.run(
["node", "-e", f"{src}\n{driver_expr}"], capture_output=True, text=True, timeout=60
)
assert proc.returncode == 0, f"node failed: {proc.stderr}"
out = json.loads(proc.stdout)
assert out["LINES"] == [
"3 added · 2 unchanged · peak 1936 MB",
"0 added · 1 unchanged · peak 512 MB", # the current-reading fallback
"0 added · 1 unchanged", # both null → no clause at all
"1 added", # idle / pre-phase / upload status: no keys, no clause
"2 added · 4 pruned · peak 0 MB", # 0 is a value, not missing
"1 added", # a negative reading is not a number to show
"1 added", # garbage is not a number either
], out
assert out["CANCELLED"] == [
"Sync cancelled · peak 1936 MB",
"Sync cancelled · peak 512 MB",
"Sync cancelled",
"Sync cancelled",
"Sync cancelled · peak 0 MB",
"Sync cancelled",
"Sync cancelled",
], out