Repository navigation
v7.201.1
Erratum: v7.200.11 route-receipt hardening narrative
The v7.200.11 fragment described the #3380 fix's hardening layer with five
inaccurate claims. The delivered behavior (verified at the release commit and
re-verified by the 3509-3516 trace at bf65b6efb) is corrected below.
-
Mis-attribution to the in-memory forget alone. The fragment said "a
failed persist now forgets the previous dispatch's in-memory route record
for the task (a stale receipt could otherwise authorize the failed
dispatch)". The forget was a no-op at its only call site: the dispatch
whose persist failed never remembered a record (the persist failed first),
so the slot's in-memory record always belonged to a PRIOR dispatch, and
deleting the forget was proven suite-green by the #3516 mutation probes.
What actually withheld the failed dispatch's settlement was the
per-callID invalidation record — which the fragment omitted entirely. The
follow-up PR (#3509/#3516) removes the dead forget and replaces the
per-hook-instance invalidation map with a process-local registry. -
Omitted the
ROUTE_RECEIPT_INVALIDATEDsettlement drop. The fragment
never mentioned that a settlement consult drops the failed dispatch's
Stage-B settlement with theroute_blockedreason class. That drop — and
its enforcement half being unpinned by the shipped suite (the suite never
calledtoolAfter) — is the subject of #3516 items 2, 3, and 8. -
Omitted the #3509 caveat. The per-callID invalidation was recorded only
whenroutedTaskIdwas already assigned. A routing fault BEFORE task
resolution (including corrupt gate state that makes the routing block's
raw read throw) recorded nothing, so a stale prior receipt could authorize
exactly the dispatch the fix was meant to fence. #3509 tracks this; the
follow-up PR records a whole-call fault marker for every pre-persist
routing fault. -
False "proven at the settlement entry point". The fragment claimed
Stage-B fail-closure was "proven at the settlement entry point". The proof
actually calledenforcePersistedReviewRouteReceiptdirectly from the test
— the REAL settlement call site (wired from
config.review_routing.enforce_receipts) was never executed by the suite,
and mutating the call site left the suite green (#3516 item 3). The
follow-up PR pins the real call site through the realtoolAfter
settlement path. -
Stale "#3499 is being delivered" sentence. PR #3499 had already merged
before v7.200.11 was published; the sentence read as if the sibling work
were still pending. It is deleted here.
The empty-binding-skip claim in the same fragment was accurate as written but
unpinned (deletable with the suite green); the follow-up PR pins it with a
leg whose removal reddens the suite. The capacity pre-flight claim ("a
capacity refusal can no longer burn the token") was overstated — the follow-up
PR pins the pre-flight with its own leg AND restates it as best-effort across
the reservation await: a concurrent dispatch admitted during the await can
still trip the remember-time capacity throw and burn the consumed token in
that window (the pre-flight narrows the window; it does not close it).
Route-receipt hardening: call-scoped invalidation registry, fail-closed binding reads, per-task read isolation
Issues #3509 and #3516 (collated trace 3509-3516-route-receipt-hardening).
What changed
- New route-invalidation registry (
src/review/route-invalidation-registry.ts):
a process-localMap<canonicalDir, Map<sessionID, Map<callID, {taskKeys, callFault}>>>. A reviewer/test_engineer dispatch whose routing faults
BEFORE its pending receipt persists now records a whole-call fault marker
(plus the per-task routing key when the task was resolved) — closing the
#3509 gap where a pre-assignment fault recorded nothing and a stale prior
receipt could authorize the failed dispatch's Stage-B settlement. The marker
blocks every verdict task of the call at every enforcement seam that
consults it, regardless of disk state. - Every seam consults. The foreground
toolAftersettlement loop, the
background ingestion admission and completeness reads, the
parent-unavailable recovery arm, andapplyStageBStateCompletion's
completion/advancement decisions all consult the registry before
authorizing advancement. Route-refusal and parent-unavailable invalid
returns in the background ingestion are now TERMINAL drops
({ok:false, consumed:true}) instead of retryable, and the completion
observer commits them durably (the commit predicate is
applied.ok || applied.consumed). - Fail-closed corruption reads.
readPrReviewReentryBindingContextSafe
rethrows the persistence layer's deliberateBLOCKED:corruption refusals
(previously swallowed to null, which skipped the PR-review re-entry
admission machinery entirely) and degrades only genuine fs faults, with a
bounded, home-path-redacted warn on the always-emittedCRITICAL-WARN
channel. - Per-task receipt-read isolation. Both receipt-enforcement reads in the
settlement loop are wrapped per task: a non-ENOENT read fault (POSIX
ENOTDIR beneath a file-ancestor, corruption refusals) drops THAT task with
the newroute_read_faultdrop class while sibling tasks in the same
settlement still settle. - In-memory sibling-clobber window closed.
rememberReviewRoutestamps
the writer callID and enforces kind-monotonicity: a PENDING or router-error
record from one callID can no longer overwrite a live FULL receipt written
by a different callID IN THE IN-MEMORY route projection. Scope note: the
DURABLE per-(session, task) receipt file is still a single shared file, and
a concurrent sibling dispatch's pending write can transiently replace a
live full receipt on disk (settlement in that window fail-closes with
ROUTE_RECEIPT_PENDING). That durable sharing predates this PR and is
unchanged by it. The dead task-scopedforgetReviewRouteis removed (it
never removed the failed dispatch's own record; see the erratum fragment). - Canonical directory keying. Registry keys are MEMOIZED PER INPUT
SPELLING (first computation wins: realpath when it resolves, else a
lexical:-prefixed resolve), so record, consult, and retirement always
derive the same key for the same spelling even when realpath availability
flips mid-dispatch. realpathSync does not canonicalize filename case on
every runtime (Bun/Windows preserves input case), so two DIFFERENT
spellings of one root can key differently; production always passes a
single spelling (bootstrapRoot); the memo holds at most 64 spellings
(FIFO), and a record's key stays stable for as long as its spelling stays
memoized. Degenerate (non-string or empty) directories share one sentinel key (and
therefore one capacity bucket) and never throw. - Loud capacity semantics. The registry never evicts: at
MAX_LIVE_CALLS_PER_SESSION=256,MAX_LIVE_SESSIONS_PER_DIRECTORY=128, or
MAX_LIVE_DIRECTORIES=64the RECORD operation fails,criticalWarnnames
the bound, and the routing catch REJECTS the dispatch with
ROUTE_INVALIDATION_CAPACITY(fail-closed admission — a bounded
availability trade: an unprotected dispatch is worse than a refused one).
Records retire at terminal outcomes (settlement loop end, denied calls,
failed outputs, the background observer's confirmed consumed commit) and
empty containers are pruned, so settled traffic leaves no residue.
Migration / caveats
- Narrowed invariant surface (disclosed). The "every seam applies the
invalidation refusal" claim is narrowed to the gate settlement surfaces
that carry a callID: the delegation-gate settlement loop, the background
stage-b-gates ingestion/completion sites, and the completion observer.
src/tools/update-task-status.ts(both enforcement reads) has no callID to
consult with; its route enforcement is unchanged and the residual is
disclosed here rather than plumbed through (callID plumbing was evaluated
and rejected as a design change out of proportion to the advisory surface). legacy_unroutedcarve-out (scoped). Tasks whose durable task-gate
requirements predate route receipts (a legacy receipts chain with no route
binding) remain authorized under the pre-v1legacy_unroutedpath when
their dispatch routes and persists normally. One narrowing is intentional:
the new invalidation consults run BEFORE the legacy determination, so a
dispatch whose OWN routing faulted is refused even if its task would
otherwise qualify as legacy-unrouted (the faulted dispatch must not settle
on any prior authority — the #3509 property). Untouched legacy tasks keep
the carve-out unchanged.- AC6 monotonicity substitution. Issue #3516's AC6 wording ("forget
deletion reddens") is reconciled to the shipped design: the forget is
removed as DEAD code, and the live protection is the remember-monotonicity
guard, whose deletion reddens the shipped interleaving legs. The mutation
probe deletes the guard (the live mechanism), not the removed forget. - Best-effort capacity pre-flight. The PRR-009 pre-flights before the
one-use re-entry reservation remain, restated as best-effort across the
reservation await: a concurrent dispatch admitted during the await can
still trip the remember-timeSTAGE_B_CONTEXT_CAPACITYthrow and burn the
consumed token in that window. The pre-flight narrows the window; it does
not close it. - Erratum.
docs/releases/pending/3509-3516-route-receipt-erratum.md
corrects the v7.200.11 hardening narrative (mis-attribution to the
in-memory forget, omitted per-callID invalidation and
ROUTE_RECEIPT_INVALIDATEDdrop, omitted #3509 caveat, false
"proven at the settlement entry point" claim, stale #3499 sentence).
Testing
New suites: tests/unit/hooks/review-route-settlement-3509.test.ts (real
toolAfter settlement-integration legs, real-call-site enforcement legs,
single-instance cross-dispatch + monotonicity interleavings),
review-route-hardening-legs-3516.test.ts (capacity pre-flight, registry
consumption/no-eviction/retention, sibling-clobber, runtime warn channel and
redaction), review-route-seams-3509.test.ts (background seam consults,
block-only semantics, the real-observer durable-commit retirement, the
preserving no-task control, the update-task-status narrowed-contract pin),
review-route-enotdir-3509.test.ts (per-task read-fault isolation), and the
restructured delegation-gate-review-route-persist-failure.test.ts (a
majority of discriminating legs; the persist attempt is observable through a
DI seam).