phase: 187_sync_peak_memory_line
**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:
@@ -457,6 +457,21 @@
|
||||
* rules stop walking a full tree — folderKnownIn() only claims what
|
||||
* the HELD levels prove — and treeStats() prefers kbTree.totals (the
|
||||
* walk is the pre-phase-167 fallback only).
|
||||
*
|
||||
* Phase 187 (task 03, D4/A4) — the sync result line carries the run's
|
||||
* PEAK MEMORY. The two additive `rss_mb` / `rss_peak_mb` keys on
|
||||
* GET /api/sync/status feed ONE clause builder (fmtSyncPeakClause),
|
||||
* which APPENDS ` · peak 512 MB` to the result text the settle already
|
||||
* wrote (the fmtSyncResult counts line, "Sync cancelled") — no new
|
||||
* control, no new state string, no new live region, and the failed
|
||||
* terminal's empty box is untouched (the banner + modal keep the error
|
||||
* copy byte-identical, phase 150). Null — idle, a run that predates the
|
||||
* phase, a platform that cannot know, and the upload job (whose status
|
||||
* has no memory keys at all) — renders NOTHING, never "peak ? MB". The
|
||||
* clause rides the single box writer, so the sole aria-live region
|
||||
* announces today's wording plus the clause, and the number is the
|
||||
* PROCESS high-water mark app/core/memory.py reports (never the host's
|
||||
* cgroup figure — that one counts page cache).
|
||||
*/
|
||||
|
||||
import { fetchIsAdmin } from "./header.js";
|
||||
@@ -1103,6 +1118,37 @@ export async function mount(root) {
|
||||
return { main, count, chunk, image };
|
||||
}
|
||||
|
||||
/* Phase 187 (task 03, D4/A4): the run's PEAK-memory clause — the one
|
||||
* number the 2026-10-15 incident had to reconstruct from three boots
|
||||
* of systemd teardown lines, now on the page that started the run.
|
||||
* It is a CLAUSE on the result line that already exists (the
|
||||
* fmtSyncResult counts line, "Sync cancelled"), never a new control,
|
||||
* never a state string of its own, and never a replacement: the
|
||||
* existing copy keeps its exact wording and the clause trails it
|
||||
* (" · peak 512 MB"). The value is the status's rss_peak_mb (the
|
||||
* process high-water mark, the number the incident was about) or,
|
||||
* where only a current reading exists, rss_mb — both PROCESS numbers
|
||||
* from app/core/memory.py, never the host's cgroup figure (that one
|
||||
* counts page cache; the caveat owns app/core/memory.py).
|
||||
* NULL IS THE ABSENCE CONTRACT: both keys null (idle, a run predating
|
||||
* this phase, a platform that cannot know) renders NOTHING — never
|
||||
* "peak ? MB", never "peak null MB", never 0. The clause rides the
|
||||
* SAME string the single box writer (setSyncProgress) lands on
|
||||
* #sync-progress-text, so the box — the sole sync text surface AND
|
||||
* the sole aria-live region (phase 150) — announces today's wording
|
||||
* plus the clause, with no second announcement and no new region. It
|
||||
* appears in the TERMINAL renders only (success + cancelled): the
|
||||
* running label keeps its byte-identical shape, the failed terminal's
|
||||
* box stays empty by the phase-150 contract (the banner + modal carry
|
||||
* the error, byte-identical), and the upload settle never has the keys
|
||||
* at all — which is the null case again. Text wraps inside the
|
||||
* existing line (no clip, no .cell-clip, no layout shift at 375px). */
|
||||
function fmtSyncPeakClause(status) {
|
||||
const peak = status ? status.rss_peak_mb ?? status.rss_mb : null;
|
||||
if (typeof peak !== "number" || !Number.isFinite(peak) || peak < 0) return "";
|
||||
return ` · peak ${peak} MB`;
|
||||
}
|
||||
|
||||
function fmtSyncResult(detail) {
|
||||
const d = detail || {};
|
||||
const added = d.added || 0;
|
||||
@@ -1284,8 +1330,11 @@ export async function mount(root) {
|
||||
// Phase 150 (task 02): the settle's counts line IS the box's
|
||||
// result line (the sole live region) — the string builder is
|
||||
// unchanged (A2: the "Synced <stamp>" label stays on the button;
|
||||
// the stamp is date-qualified, phase 154).
|
||||
setSyncProgress(fmtSyncResult(status.detail));
|
||||
// the stamp is date-qualified, phase 154). Phase 187 (task 03)
|
||||
// APPENDS the peak-memory clause to that same string — the counts
|
||||
// line keeps its exact wording, the clause trails it, and a null
|
||||
// peak contributes nothing (fmtSyncPeakClause).
|
||||
setSyncProgress(fmtSyncResult(status.detail) + fmtSyncPeakClause(status));
|
||||
hideSyncError();
|
||||
emitSyncStatus(status);
|
||||
// Refresh the catalog live — the KB just changed.
|
||||
@@ -1322,7 +1371,10 @@ export async function mount(root) {
|
||||
* else. */
|
||||
function applySyncCancelled(status) {
|
||||
settleSyncButton("Sync sources");
|
||||
setSyncProgress("Sync cancelled"); // the box's result line (phase 150)
|
||||
// The box's result line (phase 150) + the phase-187 peak clause —
|
||||
// a cancelled run's memory is still the number the operator asked
|
||||
// for ("Sync cancelled · peak 512 MB", nothing when it is null).
|
||||
setSyncProgress("Sync cancelled" + fmtSyncPeakClause(status));
|
||||
hideSyncError();
|
||||
emitSyncStatus(status);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user