Result so far

Repeat discovery latency is fixed for every mounted project. The three projects that previously never cached at all — nova, imas-codex and imas-ambix — now answer a repeat request in 6 to 15 milliseconds instead of paying their full cold cost on every call. The plan's repeat target of 100 ms is met with two orders of magnitude to spare. The cold-scan target is not met and is discussed under Cold scan remains slow.

Discovery cache repair

Commit 5922bcf, merged as cb16b49. Two files: reckon/serve.py (+251/−61) and a new tests/test_discovery_cache.py (+219).

Measured latency, same server, before and after

Wall-clock through GET /_discover/<project>. "Before" is the first call after a clean restart followed by two repeats; "after" is the same sequence against the rebuilt server.

Project Before, repeat After, repeat Change
nova 5.39 s 0.012 s 449×
imas-codex 4.15 s 0.006 s 692×
imas-ambix 2.36 s 0.012 s 197×
imas-efit 0.023 s 0.014 s already cached
imas-standard-names 0.004 s 0.003 s already cached

What changed in the code

_has_external_dependencies is deleted. It was a boolean veto that disabled the cache for the entire project whenever any plan declared a cross-project dependency. In its place, _external_dependency_projects collects the specific mounted projects actually referenced, and their own discovery signatures are folded into the cache entry alongside the local one. An unchanged external input is now a cache hit; a changed one invalidates only what depends on it.

_git_first_committed keeps its output shape but splits the HEAD lookup from history population. A per-repository {head, times} record is retained in memory; an unchanged HEAD performs only rev-parse and reuses the map, while an advanced HEAD queries the old..new range alone and merges the additions with setdefault, so an existing first-commit timestamp is never overwritten by a later one. The bare except Exception: return {} is replaced by a _run_git helper that logs timeout, non-zero exit and start failure distinctly and still returns the ctime fallback.

Gate evidence

Five new tests in tests/test_discovery_cache.py, all passing in 0.63 s. They prove: repeat discovery for two mounted fixture projects — including the cross-project dependant — runs exactly 0 git subprocesses and 0 parse_meta calls; mutating a referenced provider flips the dependant's effective_status from blocked to active; an unchanged HEAD issues only rev-parse; an advanced HEAD issues exactly one range query; and a timeout plus an exit-code-7 failure each log a distinct warning while returning the file's ctime.

Full suite after merge: 1,944 passed, 4 failed in 65 s. The four failures reproduce identically on a clean main with no changes applied, so they are pre-existing and unrelated. They are being repaired under Pre-existing failures.

Cold scan remains slow — an open negative result

The plan's second latency criterion, a cold scan of the largest repository under 3 s, is not met: imas-efit still takes 14.8 s and nova 7.1 s on the first call after a server restart. This is a direct consequence of the locked git-creation-source decision, which chose an in-memory HEAD-keyed cache and explicitly declined disk persistence on the grounds that restarts are rare next to the per-request cost being removed. That reasoning holds for steady-state use and the measured result confirms it, but it leaves every server restart re-paying a full history walk.

The criterion and the decision were authored in the same session and contradict each other; the criterion is the one that was wrong. Recorded here rather than quietly amended, because the honest options are to persist the creation map to disk — the option the decision deferred — or to restate the criterion as a steady-state target. That is the lead's call.

Reconnaissance

Run r-20260821T135033054402-prior-art-scout, read-only, 442 s, gate passed. Censused four repositories and classified 17 candidate module groups and 36 named tests. Its binding verdicts shaped the implementation above: extend the existing cache and git helper in serve.py rather than adding a module; reject _has_external_dependencies as the hit predicate outright; reuse capabilities.py's explicit-version and atomic-replace invariants as pattern only, never as an import.

It confirmed the test gap that let the defect survive: no existing test counted git subprocesses or parse calls across a repeated discover_plans. It also returned two findings outside its brief — the ReadyGate rejection gap, and SHA-256 evidence that the imas-ambix and imas-efit SPA asset copies are byte-identical to each other but differ from canonical reckon in 4 of 4 sampled files, making reckon the only safe edit point and reckon sync the only safe propagation path.

SPA legibility audit

Run r-20260821T135054536357-spa-legibility-audit, commit 38ac267, merged as 1d38e38, gate passed. Published at docs/research/spa-legibility-audit.html: 15 ranked findings against live served payloads — 2 critical, 6 high, 5 medium, 2 low; by class, 6 not-shown, 4 shown-but-unexplained, 5 stale-or-wrong.

Measured inputs: a live GET /_discover/reckon of 171,172 bytes carrying 67 artifacts and 9 sprint resources, with S5 and S9 both marked active while focus stayed on S5; one plan whose authored status disagrees with its effective status because of 3 open gates; 12 resource-version tokens the shell discards; and a live GET /crew returning 9 runs spanning three projects through a table with no project column.

The two critical findings are both about the surface lying by omission. Rank 1: the loader completes the whole discovery fetch before rendering, and ReadyGate shows only "Loading plan state…" with no elapsed time and no rejection branch at all, so a failed discovery renders as an indefinite load — the same appearance as an unreachable server, and the likely explanation for this plan's originating report. Rank 2: the status menu flashes "updated" without awaiting the canonical write, and that write reports ok: true even when the server is unreachable and only local storage changed.

SPA legibility fixes landed

Six of the fifteen audit findings are fixed, including both criticals. Every node ran in an isolated worktree on codex; each commit touched only its declared paths.

Rank What was wrong Commit
1 · critical The ready gate had no rejection branch, so a failed discovery rendered as an indefinite load — indistinguishable from an unreachable server. Now reports elapsed seconds while pending and an endpoint-specific alert on rejection. 85082f3
2 · critical A status write flashed success without awaiting persistence, and that write returned ok even when the server was unreachable and only local storage changed. Writes now await the promise and distinguish canonical save with version, local-only fallback, and conflict that reverts the optimistic value. dbad7d7
4 · high Sprint cards were draggable and the drop target changed their visible status, but the override lived only in component state and a refresh discarded it. Affordance removed: 2 state hooks, 3 drag handlers, 3 card attributes and the drop label, pending a canonical sprint mutation. 4e0ee94
5 · high The board read a justification field that does not exist and never read done_when, so all nine sprint resources lost their item rationale and completion contract on screen. 4e2fc2b
7 · high The crew table rendered nine runs spanning three projects with no project identity, under a project-branded shell. fc79eac
8 · high The fleet page twice directed the reader to a hard-coded legacy home path the server may not read. The resolved mounts file is now published in the projects payload and rendered from that value; zero legacy occurrences remain. 8bdf35b

Verified against the live server after restart: mounts_path resolves to ~/.config/reckon/mounts.json; repeat discovery measured at 13 ms (nova), 37 ms (imas-efit) and 4 ms (reckon). Suite 1,951 passed / 0 failed.

Carried, not landed: ranks 3, 6, and 9 through 15. Rank 3 is the live integrity case — S5 and S9 are both marked active while the legacy index names S5 as focus, and the shell silently picks one. Its SPA remedy is carried; whether S5 should close is a sprint decision left to the lead. Rank 6 needs authored-versus-effective status rendered on compact surfaces. Ranks 9 to 15 are medium and low.

Pre-existing failures found in passing

The four suite failures are unrelated to this plan but were repaired under it rather than walked past. Both root causes were established by reproduction on a clean tree.