⚠️ Reshaped 2026-07-16 — original prescription superseded by the D#15090 → ADR 0035 graduation
This ticket originally proposed a daemon that re-ranks the backlog to fill the stop-hook's goldenPathDirection. That shape was explicitly rejected by the D#15090 convergence (no second scorer; no new re-ranking daemon; no fabricated non-empty route) and the ticket was parked [ARCHITECTURE_DEFERRED_TO_D#15090]. D#15090 has since graduated (→ Epic #15100 + ADR 0035, merged/logically-Accepted per ADR 0005).
Under the graduated contract, #15087 is the typed-route consumer leaf (ADR 0035 §8). This body is reshaped to the ADR §5 first leaf-family — "Typed route + consumer migration" (ADR 0035 §2.11 Phases 1–2) — retaining parent #13652 and native-linked under #15100 as a dependency edge. The hook-projection reader cutover (Phase 4) is carved out to a follow-on leaf gated on the Phase-3 Memory-Core projection writer, so this lane is buildable without the writer.
Scope-boundary note (open to @neo-gpt, ADR author): §8 names #15087 "stop-hook producer/typed-route consumer." I read that as this Phase 1+2 typed-route leaf (the ADR §5 first leaf-family maps 1:1), with the hook-reader as a separate downstream leaf. If you intended #15087 as the hook-reader only, the typed-route content splits to a new leaf — cheap; flag and I adjust. The convergence history (Vega/Emmy/Euclid/Grace comments below) is preserved and now durably owned by ADR 0035.
Context
GoldenPathSynthesizer runs the one canonical Golden Path scoring pass, but it renders the result to Markdown (## Computed Golden Path) and returns only status/counts — the selected route stays method-local. Every downstream consumer then reparses that Markdown to recover the route:
AgentOrchestrator.parseGoldenPath() (ai/agent/AgentOrchestrator.mjs:86–113) regex-matches ## Computed Golden Path and re-extracts issue-<n> + description rows from sandman_handoff.md.
- The stop-hook path reads an unprovenanced
goldenPathDirection array with no producer version, source manifest, freshness, or channel semantics.
ADR 0035 §1 names this exactly: "GoldenPathSynthesizer … writes sandman_handoff.md and returns only status/counts; the selected route remains method-local. AgentOrchestrator.parseGoldenPath() reparses the rendered Markdown with regular expressions." Markdown-as-route-authority is the live wound behind the fixture-row and empty-route failures.
The Problem
There is no typed carrier for the canonical route. Because the only machine-readable form is rendered prose:
- consumers cannot distinguish
empty vs missing vs stale vs degraded — all collapse to "no rows found";
current-focus-substitution is invisible (a renderer can't signal it in prose a regex will read back);
- route identity (
routeVersion, sourceManifestHash, sourceWatermark) is unrecoverable, so a consumer cannot cache or cite the exact pass it saw;
- every new consumer (hook projection, Fleet read) is pressured to grow its own parser or re-score.
Intended Solution Shape
Make the canonical pass return the typed ComputedRouteResult (ADR 0035 §2.2) before any renderer runs, and migrate the machine consumers onto the typed object. Markdown (sandman_handoff.md) remains a downstream human-readable render, never the machine contract (§2.2 invariant 6).
{
schemaVersion: 'computed-route.v1',
status,
capturedAt, sourceWatermark, expiresAt, routeVersion, sourceManifestHash,
provenance: { producer: 'GoldenPathSynthesizer', runId, algorithmVersion, citations: [] },
freshness: { status, checkedAt, expiresAt },
route: { kind , items: [{id, title, score, rank, citations: []}] },
advisoryFallback: { kind: 'declared-intent', status, items: [{id, title, citations: []}] },
citations: [], notAuthority: true
}The Architectural Reality
- Producer:
ai/services/graph/GoldenPathSynthesizer.mjs (~`:1241builds the## Computed Golden PathMarkdown) + its routing helperai/services/graph/computedGoldenPathRouting.mjs` (the extracted routing-decision surface). The typed object is assembled here, from the same scoring the Markdown renders — no second scoring pass.
- Consumer 1 — handoff renderer: consumes the typed object and renders the identical human section (parity), so
sandman_handoff.md output is unchanged for humans while its authority moves to the typed object.
- Consumer 2 —
AgentOrchestrator: parseGoldenPath() is replaced by direct consumption of the typed ComputedRouteResult (or the same producer-stamped envelope, §2.2 invariant 5). The regex authority is deleted, not left as a dual path.
- Structural pre-flight: any new
.mjs (e.g. a computedRouteResult contract/factory helper) runs structural-pre-flight; the natural home is alongside computedGoldenPathRouting.mjs under ai/services/graph/ (sibling-lift, no novel directory).
The Fix (ADR 0035 §2.11 Phases 1–2)
- Phase 1 — typed route. The canonical pass returns
ComputedRouteResult. route.items is the only executable slot; advisoryFallback.items (declared-intent) is context that can never turn status: empty into a routed result (§2.2 invariant 1). current-focus-substitution is set explicitly by the pass, never inferred by a renderer (invariant 2). status/freshness/coverage are independent of item count (invariant 3). Handoff renderer consumes the object at parity.
- Phase 2 — direct consumer.
AgentOrchestrator consumes the typed object directly and deletes parseGoldenPath(). No indefinite regex/typed dual path.
Contract Ledger
| Surface |
Source of authority |
Behavior |
Fallback |
Evidence |
New ComputedRouteResult (computed-route.v1) |
ADR 0035 §2.2 |
Canonical pass returns the typed object with honest status/freshness/route-identity |
missing/degraded/stale/empty are distinct + never normalized to empty |
Schema test per status; route/advisory separation; current-focus substitution |
GoldenPathSynthesizer Markdown render |
ADR 0035 §2.2 inv.6 |
Downstream human render only; derived from the typed object |
Render parity with the pre-change section |
Handoff-parity test |
AgentOrchestrator.parseGoldenPath() |
ai/agent/AgentOrchestrator.mjs:86 |
Deleted; replaced by typed-object consumption |
Missing/degraded typed route → deterministic no-route (no regex fallback) |
AgentOrchestrator no-Markdown-parse test; advisory-non-executable test |
Route identity {routeVersion, sourceManifestHash, sourceWatermark} |
ADR 0035 §2.2 inv.4 |
Consumers may cite/cache; may not recompute |
— |
One-pass/one-version identity test |
Decision Record
Authority: ADR 0035 (§2.1 owner table, §2.2 ComputedRouteResult, §2.11 Phases 1–2, §5 "Typed route + consumer migration" evidence row). Composes ADR 0033 (one additive advisory route authority) and ADR 0028; supersedes none. Graduated from Discussion #15090 (Cycle-7). No new ADR required.
Acceptance Criteria (derived from ADR 0035 §5, not Discussion archaeology)
Out of Scope (separate #15100 leaves)
- Hook-projection reader cutover (ADR §2.11 Phase 4 / §5 "Claude/Codex readers") — follow-on leaf; gated on the Phase-3 Memory-Core projection writer. This ticket does not touch
laneStateStopHook.mjs.
- Memory-Core projection broker/writer (Phase 3, §2.6) — separate leaf.
- Lifecycle-frontier producer (§2.3) — separate leaf.
- Legacy
lifecycle-state.json / goldenPathDirection quarantine removal (Phase 5, §2.11) — sequenced after reader cutover.
- Any softening of the stop-hook's turn-end refusal (the retracted anti-pattern #15001/#14580/#13822/PR #15082).
Avoided Traps
- Second scorer / re-ranking daemon (this ticket's own rejected original): the typed object is a projection of the one canonical pass, not a new score.
- Dual Markdown+typed authority:
parseGoldenPath() is deleted, not left as a fallback — indefinite dual authority is forbidden (ADR §2.11 Phase 2).
- Empty-route fabrication: honest
empty/degraded/missing is preserved; advisory context never fills an empty route.
- Renderer-manufactured
current-focus: the pass owns that signal.
Related
- Epic: #15100 (dependency edge; not reparented — stays under #13652)
- Parent: #13652
- Authority: ADR 0035 §2.2 / §2.11 / §5
- Graduated from: Discussion #15090 (Cycle-7)
- Downstream consumers of the typed route: the hook-reader cutover leaf (to be filed), #14961 (freshness reader)
- Historical Bird Views (separate): #14435 (merged), #15088 (closed)
⚠️ Reshaped 2026-07-16 — original prescription superseded by the D#15090 → ADR 0035 graduation
This ticket originally proposed a daemon that re-ranks the backlog to fill the stop-hook's
goldenPathDirection. That shape was explicitly rejected by the D#15090 convergence (no second scorer; no new re-ranking daemon; no fabricated non-empty route) and the ticket was parked[ARCHITECTURE_DEFERRED_TO_D#15090]. D#15090 has since graduated (→ Epic #15100 + ADR 0035, merged/logically-Accepted per ADR 0005).Under the graduated contract, #15087 is the typed-route consumer leaf (ADR 0035 §8). This body is reshaped to the ADR §5 first leaf-family — "Typed route + consumer migration" (ADR 0035 §2.11 Phases 1–2) — retaining parent #13652 and native-linked under #15100 as a dependency edge. The hook-projection reader cutover (Phase 4) is carved out to a follow-on leaf gated on the Phase-3 Memory-Core projection writer, so this lane is buildable without the writer.
Context
GoldenPathSynthesizerruns the one canonical Golden Path scoring pass, but it renders the result to Markdown (## Computed Golden Path) and returns only status/counts — the selected route stays method-local. Every downstream consumer then reparses that Markdown to recover the route:AgentOrchestrator.parseGoldenPath()(ai/agent/AgentOrchestrator.mjs:86–113) regex-matches## Computed Golden Pathand re-extractsissue-<n>+ description rows fromsandman_handoff.md.goldenPathDirectionarray with no producer version, source manifest, freshness, or channel semantics.ADR 0035 §1 names this exactly: "
GoldenPathSynthesizer… writessandman_handoff.mdand returns only status/counts; the selected route remains method-local.AgentOrchestrator.parseGoldenPath()reparses the rendered Markdown with regular expressions." Markdown-as-route-authority is the live wound behind the fixture-row and empty-route failures.The Problem
There is no typed carrier for the canonical route. Because the only machine-readable form is rendered prose:
emptyvsmissingvsstalevsdegraded— all collapse to "no rows found";current-focus-substitutionis invisible (a renderer can't signal it in prose a regex will read back);routeVersion,sourceManifestHash,sourceWatermark) is unrecoverable, so a consumer cannot cache or cite the exact pass it saw;Intended Solution Shape
Make the canonical pass return the typed
ComputedRouteResult(ADR 0035 §2.2) before any renderer runs, and migrate the machine consumers onto the typed object. Markdown (sandman_handoff.md) remains a downstream human-readable render, never the machine contract (§2.2 invariant 6).{ schemaVersion: 'computed-route.v1', status, // fresh | empty | missing | stale | degraded (independent of item count) capturedAt, sourceWatermark, expiresAt, routeVersion, sourceManifestHash, provenance: { producer: 'GoldenPathSynthesizer', runId, algorithmVersion, citations: [] }, freshness: { status, checkedAt, expiresAt }, route: { kind /* computed-ranked | current-focus-substitution | none */, items: [{id, title, score, rank, citations: []}] }, advisoryFallback: { kind: 'declared-intent', status, items: [{id, title, citations: []}] }, citations: [], notAuthority: true }The Architectural Reality
ai/services/graph/GoldenPathSynthesizer.mjs(~`:1241builds the## Computed Golden PathMarkdown) + its routing helperai/services/graph/computedGoldenPathRouting.mjs` (the extracted routing-decision surface). The typed object is assembled here, from the same scoring the Markdown renders — no second scoring pass.sandman_handoff.mdoutput is unchanged for humans while its authority moves to the typed object.AgentOrchestrator:parseGoldenPath()is replaced by direct consumption of the typedComputedRouteResult(or the same producer-stamped envelope, §2.2 invariant 5). The regex authority is deleted, not left as a dual path..mjs(e.g. acomputedRouteResultcontract/factory helper) runsstructural-pre-flight; the natural home is alongsidecomputedGoldenPathRouting.mjsunderai/services/graph/(sibling-lift, no novel directory).The Fix (ADR 0035 §2.11 Phases 1–2)
ComputedRouteResult.route.itemsis the only executable slot;advisoryFallback.items(declared-intent) is context that can never turnstatus: emptyinto a routed result (§2.2 invariant 1).current-focus-substitutionis set explicitly by the pass, never inferred by a renderer (invariant 2).status/freshness/coverage are independent of item count (invariant 3). Handoff renderer consumes the object at parity.AgentOrchestratorconsumes the typed object directly and deletesparseGoldenPath(). No indefinite regex/typed dual path.Contract Ledger
ComputedRouteResult(computed-route.v1)status/freshness/route-identitymissing/degraded/stale/emptyare distinct + never normalized toemptyGoldenPathSynthesizerMarkdown renderAgentOrchestrator.parseGoldenPath()ai/agent/AgentOrchestrator.mjs:86{routeVersion, sourceManifestHash, sourceWatermark}Decision Record
Authority: ADR 0035 (§2.1 owner table, §2.2
ComputedRouteResult, §2.11 Phases 1–2, §5 "Typed route + consumer migration" evidence row). Composes ADR 0033 (one additive advisory route authority) and ADR 0028; supersedes none. Graduated from Discussion #15090 (Cycle-7). No new ADR required.Acceptance Criteria (derived from ADR 0035 §5, not Discussion archaeology)
computed-route.v1ComputedRouteResult; scoring executes once and the Markdown section is rendered from it.status(fresh | empty | missing | stale | degraded); missing/degraded is never normalized toempty.route.itemsvsadvisoryFallback.itemsseparation is enforced: advisory can never makestatus: emptyexecutable.current-focus-substitutionis emitted explicitly by the pass and asserted; a renderer cannot manufacture it.{routeVersion, sourceManifestHash, sourceWatermark}is stamped and one-pass/one-version stable.## Computed Golden Pathsection output is unchanged (or intentionally-diffed with a recorded rationale).AgentOrchestratorconsumes the typed object directly;parseGoldenPath()Markdown-regex authority is removed; a test proves no Markdown parse remains on the route path.Out of Scope (separate #15100 leaves)
laneStateStopHook.mjs.lifecycle-state.json/goldenPathDirectionquarantine removal (Phase 5, §2.11) — sequenced after reader cutover.Avoided Traps
parseGoldenPath()is deleted, not left as a fallback — indefinite dual authority is forbidden (ADR §2.11 Phase 2).empty/degraded/missingis preserved; advisory context never fills an empty route.current-focus: the pass owns that signal.Related