Context
The 2026-07-12 19:11 UTC Golden Path pass is fresh but operationally wrong. Its Current Release / Incident Focus sees 55 live candidates, while the machine-consumed route recommends:
issue-14788 — a v13.3 scoping wave;
discussion-13846 — the target-architecture ADR;
discussion-14561 — post-v13.1 goal scoping.
That is not a stale-file failure. It is a live candidate-admission failure during the v13.2 convergence window.
The first attempted repair, PR #15104, multiplied semantic relevance by raw age. It was correctly closed unmerged: it recreated the dropped #13793 recency anti-pattern, could not admit candidates outside Chroma's raw top 20, and made unrelated comment touches a ranking input.
The prior type-scope work remains valid. #13844 / PR #13837 made ISSUE + DISCUSSION the semantic population and intersected it with OPEN graph state. This ticket addresses the next boundary that work did not specify: filters run only after the fixed raw top-20 query, and OPEN is currently treated as sufficient proof that a Discussion is still convergence-to-drive.
Structure-map gate: npm run --silent ai:structure-map -- --files --loc passed on 2026-07-12. Owning substrate is ai/services/graph/, with source lifecycle projection owned by ai/services/github-workflow/ and graph persistence owned by ai/services/ingestion/.
The Problem
Two independent defects compose into one misleading route:
- Raw top-K is mistaken for the candidate contract.
GoldenPathSynthesizer.synthesizeGoldenPath() queries exactly 20 ISSUE/DISCUSSION vectors, then applies SQLite OPEN state, blocker, label/actionability, and routing guards. If all 20 are rejected, a legitimate rank-21 candidate is invisible. Increasing K to another fixed number only moves the cliff.
- Discussion OPEN state is mistaken for routing liveness.
computedGoldenPathRouting.isActionableComputedRecommendation() accepts any ISSUE or DISCUSSION without exclusion labels. A graduated, resolved, superseded, or dormant Discussion can therefore remain an immediate route merely because GitHub keeps it open for archaeology.
The source pipeline exposes enough material to classify this honestly, but no typed contract joins the layers:
DiscussionSyncer.#renderDiscussionMarkdown() persists createdAt, updatedAt, closed, closedAt, and the body/comments.
IssueIngestor.ingestDiscussionStates() persists only state, closed, closedAt, and category to the Native Edge Graph.
audit-discussion-lifecycle.mjs contains a partial regex classifier for graduated/resolved/stale states, but production routing cannot import a diagnostic script and must not fork another regex island.
- Raw
updatedAt is observability, not authority: an unrelated comment must not reactivate a route.
The Architectural Reality
ADR 0023 governs this exact boundary:
- the route must be earned-and-forgetting;
- structural scent decays;
- Current Focus remains visibility-only;
- no non-decaying recency/release/label boost may synthesize priority;
- any PR changing candidate-pool composition/filter must cite the ADR.
ADR 0024 defines ISSUE/DISCUSSION as the current Golden Path ranking population. ADR 0033 keeps Direction-GP additive and fail-open; this leaf does not add a direction gate. ADR 0035 keeps lifecycle facts separate from canonical ranking and requires one Golden Path result for all consumers.
The smallest correct composition is therefore:
- source-owned Discussion lifecycle facts — pure, typed, and reused;
- graph-persisted projection — no route-time Markdown parsing;
- adaptive candidate admission — enough admissible candidates for the render limit or verified corpus exhaustion;
- unchanged ranking math —
2 × semantic + 1 × structural, with existing Hebbian reinforcement/decay ordering live candidates.
A Discussion source disposition is one of:
active: an explicit recognized convergence/revalidation signal exists;
terminal: graduated/resolved-with-no-open-scope/superseded archaeology;
undetermined: neither fact is established.
undetermined is not silently promoted by age or comment activity. It is routable only while current decaying structural support demonstrates live swarm motion; otherwise it is visibility-only dormant work. Classification failure never coerces a Discussion to active.
The Fix
One coherent PR:
- Add a pure shared source classifier at
ai/services/github-workflow/shared/discussionRoutingDisposition.mjs. It owns marker recognition and returns the typed disposition plus machine-readable evidence/reason. DiscussionSyncer and the lifecycle diagnostic both consume it; no copied regex set remains.
- Persist the source projection in synced Discussion frontmatter, then carry it through
IssueIngestor.ingestDiscussionStates() into Discussion graph properties. Route-time code reads typed graph facts, not Markdown.
- Add a pure graph-side routability composition that combines the source disposition with current decaying structural support. OPEN remains necessary; it is no longer sufficient for Discussions.
- Replace fixed raw-top-20-as-contract with bounded adaptive admission: widen/retrieve until the render limit has enough candidates that survive state, blocker, actionability, and Discussion-liveness filters, or the eligible ISSUE/DISCUSSION corpus is exhausted. Final scoring remains one merged pool with the existing formula.
- Emit Discussion-liveness rejections through the existing
#15076 type-gate rejection ledger seam (new stage/bucket, no third ledger). Candidate-pool exhaustion stays in the synthesis stats. #15057 remains the owner of accumulated filter-disposition analysis.
- Record and execute the July-12 hindcast through the real sync → ingest → graph → Golden Path chain.
Intake revalidation fold
The implementation contract is narrowed by the independent intake falsifiers as follows:
- Marker authority: only the trusted current Discussion root body can establish a source disposition. Comments and replies are diagnostic-only and never classify; truncation cannot alter routing. Instructional, historical, quoted, and fenced-code marker mentions are ignored. Tests include external-comment spoofing, truncated-comment absence, quoted markers, and fenced examples.
- Decaying support: the graph layer exports the canonical frozen
PROTECTED_EDGE_TYPES registry and owns one RLS-safe inbound-support primitive. It requires the root node, inbound source node, and edge to be visible; excludes BLOCKS; and separates total positive inbound weight from the positive decaying subset. An undetermined Discussion is live only when decayingWeight > 0 after current decay/pruning. Protected facts alone remain dormant.
- Gate separation: actionability remains the existing type/label gate. Discussion liveness is a subsequent pure gate with buckets
terminal and undetermined-no-decaying-support; a candidate is recorded only at the first gate that rejects it.
- One physical, multi-stage rejection ledger:
type-gate-rejection.jsonl remains the sole file/config surface. It gains discussion-liveness-gate alongside actionability-type-gate. Read/summarize/query default to the actionability stage so #15057's existing view is unchanged; an explicit stage selects liveness. Missing or unknown stages do not enter either stage-specific view.
- Adaptive whole-prefix admission: query widths widen
20 → 40 → 80 → … with the same ISSUE/DISCUSSION filter. Every pass rebuilds the full positional id/distance prefix and all admission gates; no delta stability is assumed. Only the final successful attempt publishes stats or ledger rows. Stop when enough fully admitted candidates satisfy the render limit, or when returnedCount < requestedWidth proves exhaustion. Equality is not proof. A later query/mapping failure discards earlier partial attempts and uses the existing fail-loud surface.
- Graph-proxy fact correction: the current graph collection is a plain
CollectionProxy; StorageRouter.getGraphCollection() does not inject the 3× ANN/topology reranker used by memory, summary, and temporal-summary collections. The present defect therefore is the literal fixed raw top-20 graph query described above. A defensive _degraded check remains valid for compatible/future proxy envelopes, but current graph query failures throw. Tests cover first-pass and later-pass failure/degradation without false exhaustion.
- Stats and math: record final-prefix
semanticQueryPasses, requested width, Discussion-liveness rejection count, exhaustion boolean, and stop reason. Preserve exact distances—including zero—and keep 2 × semantic + 1 × structural unchanged.
Required falsifiers include rank 21 surviving 20 rejected neighbors; short-corpus exhaustion; later-pass failure with no partial healthy route or ledger writes; protected-only versus decaying support; root/source/edge RLS isolation; actionability/liveness stage isolation; and the real sync → ingest → graph → fresh-handoff hindcast.
Pre-Flight (structural fast-path): the new shared helper matches the pure, no-I/O sibling pattern of conversationTrust.mjs and contentPath.mjs; no novel directory or Architecture Overview map change is introduced.
Contract Ledger
| Target surface |
Source of Authority |
Proposed behavior |
Fallback / failure behavior |
Docs |
Evidence |
| Synced Discussion lifecycle projection |
DiscussionSyncer.#renderDiscussionMarkdown() + shared pure classifier |
Emit routingDisposition: active | terminal | undetermined plus recognized evidence/reason; raw timestamps remain observability only |
Unknown/malformed marker set → undetermined; never active by error |
Anchor & Echo JSDoc on classifier and frontmatter field |
Table-driven marker corpus, including graduated-open archaeology |
| Discussion graph properties |
IssueIngestor.ingestDiscussionStates() |
Preserve the typed source disposition and evidence through SQLite + Chroma metadata |
Missing legacy field → undetermined; no body reparse in the graph layer |
Ingestor JSDoc + schema comment |
Real DiscussionSyncer → IssueIngestor → graph round-trip |
| Discussion route eligibility |
computedGoldenPathRouting |
OPEN + active is eligible; terminal is visibility-only; undetermined requires current decaying structural support |
Classifier/metadata degradation cannot fabricate active convergence |
Routing contract JSDoc |
Dormant, evergreen/revalidated, comment-touch, terminal fixtures |
| Candidate admission |
GoldenPathSynthesizer.synthesizeGoldenPath() |
Adaptively replenish after all admission filters until render limit or corpus exhaustion |
Query/graph failure keeps existing fail-loud outcome; no partial “healthy” route |
Synthesizer JSDoc + stats names |
20 rejected semantic neighbors + legitimate rank-21 candidate |
| Final priority |
ADR 0023 + existing synthesizer constants |
Preserve 2 × semantic + 1 × structural; one merged ISSUE/DISCUSSION pool |
No recency, release, milestone, Current Focus, or ID boost |
Existing ADR citations |
Equal-live-candidate reinforcement/decay ordering test |
| Rejection evidence |
Existing typeGateRejectionLedgerStore from #15076 |
Record Discussion-liveness rejection under a distinct stage/bucket in the existing sibling ledger |
Write failure remains fail-open observability; no new ledger or config leaf |
Store/recorder JSDoc |
Hermetic separation + query test; feeds #15057's accumulated disposition window |
| Lifecycle audit |
ai/scripts/diagnostics/audit-discussion-lifecycle.mjs |
Import the shared classifier; audit-specific age reporting stays diagnostic only |
No production import from ai/scripts/ |
Script JSDoc |
Existing audit suite plus shared-classifier parity test |
Decision Record impact
Aligned with ADR 0023, ADR 0024, ADR 0033, and ADR 0035. No amendment. The ticket adds no node/edge class, protected edge, ranking formula, second scorer, or lifecycle authority. It repairs candidate admission and projects source-owned lifecycle facts into the existing graph/ranking boundary.
Decision Record
Not needed. Existing accepted/graduated architecture already decides the relevant invariants; this is one executable repair leaf under Epic #14472.
Acceptance Criteria
Out of Scope
Recency multipliers · raw-age cutoffs · milestone/release boosts · hardcoded v13.2 identifiers · Current Focus promotion · Direction-GP weighting changes · Bird Views · hook rendering · a general lifecycle service · reopening #13844 · a new rejection ledger · #15057's accumulated keep/narrow/rank-with-discount decision · Fable review.
Avoided Traps
- Reopen
#13844: rejected. That leaf correctly delivered type scope + OPEN intersection; retroactively expanding it hides the new policy boundary.
- Recency relevance (
#13793 / PR #15104): rejected. It cannot recover rank-21 candidates, fights decay, and is comment-touch gameable.
- A larger fixed K: rejected. It moves rather than removes the admission cliff.
- Current Focus/release boost: rejected. Visibility is not earned-scent routing.
- Route-time Markdown parsing: rejected. Lifecycle is projected once at the source and consumed as typed graph facts.
- Second lifecycle regex island: rejected. The diagnostic and production projection share one pure classifier.
Related
Parent: #14472
Predecessors / evidence: #13750 · #13844 · #13837 · #13801
Diagnostics: #15057 · #15076
Anti-precedents: #13793 · #15104
Composes with: #14565 · #15100
Architecture: ADR 0023 · ADR 0024 · ADR 0033 · ADR 0035
Origin Session ID: 837ad74b-c2d2-413d-9aab-b7165a93a82a
Retrieval Hint: query_raw_memories("July 12 Golden Path candidate admission Discussion liveness top-20 #15104 #13844")
Retrieval Hint: query_summaries("Golden Path fresh but wrong v13.3 discussion archaeology route July 12")
Live duplicate sweep: latest 30 open GitHub issues + latest 30 all-state A2A messages re-checked at 2026-07-12T19:55:34.890Z; no equivalent filed or in-flight. Semantic KB sweep found predecessors #13750/#13844 and adjacent ledger #15057/#15076, but no ticket owning adaptive post-filter replenishment plus typed Discussion routing liveness. Exact active+archive corpus sweep found no equivalent.
Context
The 2026-07-12 19:11 UTC Golden Path pass is fresh but operationally wrong. Its Current Release / Incident Focus sees 55 live candidates, while the machine-consumed route recommends:
issue-14788— a v13.3 scoping wave;discussion-13846— the target-architecture ADR;discussion-14561— post-v13.1 goal scoping.That is not a stale-file failure. It is a live candidate-admission failure during the v13.2 convergence window.
The first attempted repair, PR
#15104, multiplied semantic relevance by raw age. It was correctly closed unmerged: it recreated the dropped#13793recency anti-pattern, could not admit candidates outside Chroma's raw top 20, and made unrelated comment touches a ranking input.The prior type-scope work remains valid.
#13844/ PR#13837made ISSUE + DISCUSSION the semantic population and intersected it with OPEN graph state. This ticket addresses the next boundary that work did not specify: filters run only after the fixed raw top-20 query, and OPEN is currently treated as sufficient proof that a Discussion is still convergence-to-drive.Structure-map gate:
npm run --silent ai:structure-map -- --files --locpassed on 2026-07-12. Owning substrate isai/services/graph/, with source lifecycle projection owned byai/services/github-workflow/and graph persistence owned byai/services/ingestion/.The Problem
Two independent defects compose into one misleading route:
GoldenPathSynthesizer.synthesizeGoldenPath()queries exactly 20 ISSUE/DISCUSSION vectors, then applies SQLite OPEN state, blocker, label/actionability, and routing guards. If all 20 are rejected, a legitimate rank-21 candidate is invisible. Increasing K to another fixed number only moves the cliff.computedGoldenPathRouting.isActionableComputedRecommendation()accepts any ISSUE or DISCUSSION without exclusion labels. A graduated, resolved, superseded, or dormant Discussion can therefore remain an immediate route merely because GitHub keeps it open for archaeology.The source pipeline exposes enough material to classify this honestly, but no typed contract joins the layers:
DiscussionSyncer.#renderDiscussionMarkdown()persistscreatedAt,updatedAt,closed,closedAt, and the body/comments.IssueIngestor.ingestDiscussionStates()persists onlystate,closed,closedAt, andcategoryto the Native Edge Graph.audit-discussion-lifecycle.mjscontains a partial regex classifier for graduated/resolved/stale states, but production routing cannot import a diagnostic script and must not fork another regex island.updatedAtis observability, not authority: an unrelated comment must not reactivate a route.The Architectural Reality
ADR 0023 governs this exact boundary:
ADR 0024 defines ISSUE/DISCUSSION as the current Golden Path ranking population. ADR 0033 keeps Direction-GP additive and fail-open; this leaf does not add a direction gate. ADR 0035 keeps lifecycle facts separate from canonical ranking and requires one Golden Path result for all consumers.
The smallest correct composition is therefore:
2 × semantic + 1 × structural, with existing Hebbian reinforcement/decay ordering live candidates.A Discussion source disposition is one of:
active: an explicit recognized convergence/revalidation signal exists;terminal: graduated/resolved-with-no-open-scope/superseded archaeology;undetermined: neither fact is established.undeterminedis not silently promoted by age or comment activity. It is routable only while current decaying structural support demonstrates live swarm motion; otherwise it is visibility-only dormant work. Classification failure never coerces a Discussion toactive.The Fix
One coherent PR:
ai/services/github-workflow/shared/discussionRoutingDisposition.mjs. It owns marker recognition and returns the typed disposition plus machine-readable evidence/reason.DiscussionSyncerand the lifecycle diagnostic both consume it; no copied regex set remains.IssueIngestor.ingestDiscussionStates()into Discussion graph properties. Route-time code reads typed graph facts, not Markdown.#15076type-gate rejection ledger seam (new stage/bucket, no third ledger). Candidate-pool exhaustion stays in the synthesis stats.#15057remains the owner of accumulated filter-disposition analysis.Intake revalidation fold
The implementation contract is narrowed by the independent intake falsifiers as follows:
PROTECTED_EDGE_TYPESregistry and owns one RLS-safe inbound-support primitive. It requires the root node, inbound source node, and edge to be visible; excludesBLOCKS; and separates total positive inbound weight from the positive decaying subset. An undetermined Discussion is live only whendecayingWeight > 0after current decay/pruning. Protected facts alone remain dormant.terminalandundetermined-no-decaying-support; a candidate is recorded only at the first gate that rejects it.type-gate-rejection.jsonlremains the sole file/config surface. It gainsdiscussion-liveness-gatealongsideactionability-type-gate. Read/summarize/query default to the actionability stage so #15057's existing view is unchanged; an explicit stage selects liveness. Missing or unknown stages do not enter either stage-specific view.20 → 40 → 80 → …with the same ISSUE/DISCUSSION filter. Every pass rebuilds the full positional id/distance prefix and all admission gates; no delta stability is assumed. Only the final successful attempt publishes stats or ledger rows. Stop when enough fully admitted candidates satisfy the render limit, or whenreturnedCount < requestedWidthproves exhaustion. Equality is not proof. A later query/mapping failure discards earlier partial attempts and uses the existing fail-loud surface.CollectionProxy;StorageRouter.getGraphCollection()does not inject the 3× ANN/topology reranker used by memory, summary, and temporal-summary collections. The present defect therefore is the literal fixed raw top-20 graph query described above. A defensive_degradedcheck remains valid for compatible/future proxy envelopes, but current graph query failures throw. Tests cover first-pass and later-pass failure/degradation without false exhaustion.semanticQueryPasses, requested width, Discussion-liveness rejection count, exhaustion boolean, and stop reason. Preserve exact distances—including zero—and keep2 × semantic + 1 × structuralunchanged.Required falsifiers include rank 21 surviving 20 rejected neighbors; short-corpus exhaustion; later-pass failure with no partial healthy route or ledger writes; protected-only versus decaying support; root/source/edge RLS isolation; actionability/liveness stage isolation; and the real sync → ingest → graph → fresh-handoff hindcast.
Pre-Flight (structural fast-path): the new shared helper matches the pure, no-I/O sibling pattern of
conversationTrust.mjsandcontentPath.mjs; no novel directory or Architecture Overview map change is introduced.Contract Ledger
DiscussionSyncer.#renderDiscussionMarkdown()+ shared pure classifierroutingDisposition: active | terminal | undeterminedplus recognized evidence/reason; raw timestamps remain observability onlyundetermined; never active by errorIssueIngestor.ingestDiscussionStates()undetermined; no body reparse in the graph layercomputedGoldenPathRoutingactiveis eligible;terminalis visibility-only;undeterminedrequires current decaying structural supportGoldenPathSynthesizer.synthesizeGoldenPath()2 × semantic + 1 × structural; one merged ISSUE/DISCUSSION pooltypeGateRejectionLedgerStorefrom #15076ai/scripts/diagnostics/audit-discussion-lifecycle.mjsai/scripts/Decision Record impact
Aligned with ADR 0023, ADR 0024, ADR 0033, and ADR 0035. No amendment. The ticket adds no node/edge class, protected edge, ranking formula, second scorer, or lifecycle authority. It repairs candidate admission and projects source-owned lifecycle facts into the existing graph/ranking boundary.
Decision Record
Not needed. Existing accepted/graduated architecture already decides the relevant invariants; this is one executable repair leaf under Epic #14472.
Acceptance Criteria
updatedAttouch cannot reactivate a Discussion.discussion-10289shape, is excluded.2 × semantic + 1 × structuralis byte-for-byte unchanged.Out of Scope
Recency multipliers · raw-age cutoffs · milestone/release boosts · hardcoded v13.2 identifiers · Current Focus promotion · Direction-GP weighting changes · Bird Views · hook rendering · a general lifecycle service · reopening
#13844· a new rejection ledger · #15057's accumulated keep/narrow/rank-with-discount decision · Fable review.Avoided Traps
#13844: rejected. That leaf correctly delivered type scope + OPEN intersection; retroactively expanding it hides the new policy boundary.#13793/ PR#15104): rejected. It cannot recover rank-21 candidates, fights decay, and is comment-touch gameable.Related
Parent: #14472
Predecessors / evidence: #13750 · #13844 · #13837 · #13801
Diagnostics: #15057 · #15076
Anti-precedents: #13793 · #15104
Composes with: #14565 · #15100
Architecture: ADR 0023 · ADR 0024 · ADR 0033 · ADR 0035
Origin Session ID: 837ad74b-c2d2-413d-9aab-b7165a93a82a
Retrieval Hint:
query_raw_memories("July 12 Golden Path candidate admission Discussion liveness top-20 #15104 #13844")Retrieval Hint:
query_summaries("Golden Path fresh but wrong v13.3 discussion archaeology route July 12")Live duplicate sweep: latest 30 open GitHub issues + latest 30 all-state A2A messages re-checked at 2026-07-12T19:55:34.890Z; no equivalent filed or in-flight. Semantic KB sweep found predecessors #13750/#13844 and adjacent ledger #15057/#15076, but no ticket owning adaptive post-filter replenishment plus typed Discussion routing liveness. Exact active+archive corpus sweep found no equivalent.