Context
Split from #14609 behavior 2 (guard-filter ledger recording) per that ticket's own AC4 "each behavior lands separately reviewable … split PRs if any grows." #14609's substantive behaviors — behavior 1 (no-survivor focus-as-route, the never-empty floor) + behavior 3 (release-window config leaf, the release-gate-blind fix) — landed in their PR; this diagnostic half grew and separates cleanly.
The Problem
Behavior 2 as scoped was "blocked candidates land in the #14454 route-attribution ledger (which candidates the guard filters, how often, under which focus reasons)." A V-B-A while implementing #14609 found the blocking reality: there is no live code record-seam for the #14454 route-attribution ledger. The only artifact is a static one-time committed dataset — learn/agentos/measurements/golden-path-route-attribution-2026-07-02.md (from #14454 / PR #14458) — and nothing in the routing / synthesizer flow writes a live route-attribution ledger (grep for route-attribution / rejectionBucket / recordRoute across ai/ → zero writers). So wiring the guard-filtered candidates into "the ledger" is not a wire — it requires first building the same-run record mechanism the one-shot dataset was a manual snapshot of.
The Architectural Reality
computedGoldenPathRouting.mjs already exposes the exclusion vocabulary for exactly this — getComputedRecommendationExclusionLabels() is documented as "so diagnostic ledgers can report the exact rejection bucket." The consumer (a live ledger) is what's missing.
findComputedFocusContradiction() already computes the guard's blockedNodes; the synthesizer branch that renders the no-survivor section (behavior 1) is the natural emit point.
- The 42.2% label/actionability disposition (Clio's folded-in residual from #14503) is gated on this — "once the route-attribution ledger carries a real window of guard-filtered candidates with focus-reason attribution, the disposition gets its evidence-based decision." Until the record-seam exists, that disposition stays a guess-number.
The Fix
- Establish the live same-run route-attribution record-seam (the mechanism the #14454 dataset was a one-shot of) — a real writer, not a committed static file.
- Record the guard's
blockedNodes + their focus reasons into it per synthesis run (which candidates the guard filters, how often, under which focus reasons).
- Feed the accumulated window into the #14503 type-gate disposition decision.
Acceptance Criteria
Out of Scope
Behaviors 1 + 3 of #14609 (shipped). The release-window publish.mjs clearing follow-on (behavior 3's second half, tracked on #14609).
Related
Parent behavior: #14609 (behaviors 1 + 3 shipped; this is its split behavior-2 half) · #14454 / PR #14458 (the one-time route-attribution dataset) · #14503 (the 42.2% type-gate disposition this unblocks) · #14472 (GP-v2 epic).
Origin Session ID: 01f4cc68-8b8e-43e6-b51c-55b4f421f4e0
Retrieval Hint: "GP guard-filter route-attribution ledger record-seam blocked candidates focus reasons 42.2% type-gate disposition"
Context
Split from #14609 behavior 2 (guard-filter ledger recording) per that ticket's own AC4 "each behavior lands separately reviewable … split PRs if any grows." #14609's substantive behaviors — behavior 1 (no-survivor focus-as-route, the never-empty floor) + behavior 3 (release-window config leaf, the release-gate-blind fix) — landed in their PR; this diagnostic half grew and separates cleanly.
The Problem
Behavior 2 as scoped was "blocked candidates land in the #14454 route-attribution ledger (which candidates the guard filters, how often, under which focus reasons)." A V-B-A while implementing #14609 found the blocking reality: there is no live code record-seam for the #14454 route-attribution ledger. The only artifact is a static one-time committed dataset —
learn/agentos/measurements/golden-path-route-attribution-2026-07-02.md(from #14454 / PR #14458) — and nothing in the routing / synthesizer flow writes a live route-attribution ledger (grep forroute-attribution/rejectionBucket/recordRouteacrossai/→ zero writers). So wiring the guard-filtered candidates into "the ledger" is not a wire — it requires first building the same-run record mechanism the one-shot dataset was a manual snapshot of.The Architectural Reality
computedGoldenPathRouting.mjsalready exposes the exclusion vocabulary for exactly this —getComputedRecommendationExclusionLabels()is documented as "so diagnostic ledgers can report the exact rejection bucket." The consumer (a live ledger) is what's missing.findComputedFocusContradiction()already computes the guard'sblockedNodes; the synthesizer branch that renders the no-survivor section (behavior 1) is the natural emit point.The Fix
blockedNodes+ their focus reasons into it per synthesis run (which candidates the guard filters, how often, under which focus reasons).Acceptance Criteria
Out of Scope
Behaviors 1 + 3 of #14609 (shipped). The release-window
publish.mjsclearing follow-on (behavior 3's second half, tracked on #14609).Related
Parent behavior: #14609 (behaviors 1 + 3 shipped; this is its split behavior-2 half) · #14454 / PR #14458 (the one-time route-attribution dataset) · #14503 (the 42.2% type-gate disposition this unblocks) · #14472 (GP-v2 epic).
Origin Session ID: 01f4cc68-8b8e-43e6-b51c-55b4f421f4e0 Retrieval Hint: "GP guard-filter route-attribution ledger record-seam blocked candidates focus reasons 42.2% type-gate disposition"