Surfaced as the last remaining victim of #15874's order-dependent pollution sweep, but it is not a test-isolation defect — the test failure is the symptom of a product bug, which is why it lands here rather than riding #15874's frame.
After PR #15980 closed the receipt-durability fixture class, one victim survived. Re-verified at post-#15980 dev (e3bd3243c8) rather than assumed:
Failing test: test/playwright/unit/ai/services/graph/GoldenPathSynthesizer.spec.mjs:1482 — "a prior Golden Path GUIDES edge cannot self-authorize an undetermined Discussion on the next run" — at :1517, expect(priorRouteSupport.totalWeight).toBeGreaterThan(0).
The Problem
Instrumented output, printed in both contexts before theorising (the autoSave hypothesis this ticket's parent chased was a measured red herring — it is true in the passing run too):
support.totalWeight
totalEdgeCount
autoSave
vicinity
failing (adapter spec first)
0
0
true
nodes=1 edges=0
passing (synthesizer alone)
10
1
true
nodes=2 edges=1 GUIDES->discussion-92210
The vicinity holds one node instead of two, so the edge's source endpoint is absent and linkNodes' FK guard culls the edge — its own error text: "FK guard may have culled the edge. FK endpoint count: 2". Nothing throws; synthesizeGoldenPath reports success and the reinforcement is simply gone.
This is the same family as the closed precedents #10174 (Mailbox SENT_TO edges silently culled by linkNodes FK check) and #10284 (MailboxService.addMessage silently succeeds when routing edges are culled), both resolved by adding post-linkNodes verification rather than by changing the guard.
The Architectural Reality
The missing source endpoint is the literal-id node 'frontier'.
ai/services/graph/GoldenPathSynthesizer.mjs:1413 — GraphService.linkNodes('frontier', row.id, 'GUIDES', row.scored?.score ?? row.score ?? 0). No existence guard.
ai/services/graph/SemanticGraphExtractor.mjs:813-815 — the sibling writer does guard, and unconditionally: if (!GraphService.db.nodes.has('frontier')) { … upsert id: 'frontier' … } before it links.
ai/graph/bootSeedManifest.mjs:22 — seeds id: 'frontier' at boot, and :37 uses it as an edge source.
ai/services/graph/frontierConsolidation.mjs:31,33 — reads adjacency and edges.getByIndex('source', 'frontier'), i.e. the node is a load-bearing hub, not an incidental id.
The asymmetry is the defect. Two writers link from the same hub node; one self-heals when it is missing, the other loses its write silently. On any graph where 'frontier' is absent the extractor recovers and the synthesizer degrades — and the degradation is invisible because a culled edge is not an error.
Why this is not test-only. The reproducer merely produces a graph without 'frontier'. Two production routes reach the same state: a process where the boot seed has not run for this plane, and — more importantly — prune. Grace's #15973 established that the ambient decay floor sits below the prune threshold, so hub nodes are prune-reachable rather than permanent. In either case the Golden Path stops recording reinforcement while every caller reports success, which degrades frontier weighting and recall ranking with no signal.
The Fix
AMENDED 2026-07-26T13:0xZ — my own original prescription here was wrong, in two evidenced ways. It said to ensure the hub "lifting the shape from SemanticGraphExtractor.mjs:813-815 rather than inventing a second idiom." Reading the sibling before copying it falsified that: the sibling is itself the violation. Both falsifiers below are source-read, not inferred. Original text preserved in the ticket history.
Falsifier 1 — the sibling diverges from the canonical manifest, which that manifest forbids by name.ai/graph/bootSeedManifest.mjs is the SSOT for boot seeds and its own header states: "Boot and fresh-target recovery both consume this module. Adding a boot seed anywhere else makes the recovery predicate fail closed because the persisted graph can no longer equal this complete manifest." The two frontier definitions already disagree:
source
description
extra
bootSeedManifest.mjs:22-26 (canonical)
"The shifting focal point of the active Neo OS agent session."
—
SemanticGraphExtractor.mjs:813-821
"The actively tracked development front for the current project scope."
semanticVectorId: null
Copying the sibling would have propagated a manifest inequality into a third site.
Falsifier 2 — and the divergence is tenancy, not prose. The manifest's specs are documented as "suitable for GraphService.upsertGlobalNode()", and upsertGlobalNode (GraphService.mjs:379-381) forces properties.userId = null so the node is RLS-visible to every tenant. The sibling uses plain upsertNode, which "stamp[s] the canonical normalized isolation key" — the active request's identity. So a sibling-created frontier is tenant-stamped where the manifest requires global. On any other tenant that hub is RLS-invisible — and this is the same defect with a multi-tenant face, which the original ticket did not capture at all.
Mechanism correction (@neo-gpt-emmy, cycle 3). These are two different mechanisms with one symptom, and an earlier draft merged them. Absent hub → genuine write-side cull: linkNodes counts endpoints via SELECT count(*) FROM Nodes WHERE id IN (?, ?) and refuses. Tenant-stamped hub → that same count is RLS-blind, so the row satisfies it and the edge is written; the loss happens on the READ, where getInboundStructuralSupport skips any edge whose source node fails isRlsVisible (GraphService.mjs:1150). Raw FK verification does not apply RLS.
linkGlobalNodes' own JSDoc (GraphService.mjs:384-390) documents the adjacent trap: traversal requires both node and edge RLS-visible, so a global sentinel reached only through a tenant-stamped edge still vanishes for non-booting tenants.
Corrected prescription:
ai/services/graph/GoldenPathSynthesizer.mjs:1413 — ensure the hub from the canonical manifest spec, not from a hand-written literal, and via upsertGlobalNode so it is manifest-declared-field compliant and global (see the promise note below — deliberately not "manifest-equal"). FIXED_NODE_SPECS is currently module-local, so bootSeedManifest.mjs needs a narrow accessor export (single named spec or a getBootSeedNodeSpec(id)) — that export is the point of the fix, not incidental to it.
Emit a loud diagnostic when the ensure actually fires. A missing frontier means boot/recovery left the graph non-manifest-equal; healing it silently would replace one silent failure with another. Ensure-and-warn, not ensure-and-forget.
Keep linkNodes for the GUIDES edge itself. Golden Path reinforcement is per-tenant learning, so a tenant-stamped edge to a global hub is the intended shape — not a defect to "fix" into linkGlobalNodes.
Correct SemanticGraphExtractor.mjs:813-821 to the same manifest-derived path. Leaving it divergent means the manifest inequality — and the tenant-stamped-hub variant of this very bug — survives the fix.
RED-first: the two-file reproducer above is the witness; it must fail before and pass after, and the spec must cover the guard directly so the invariant does not depend on test ordering.
Contract Ledger Matrix
Added at @neo-gpt-emmy's cycle-3 request. Two new consumed surfaces, then every persisted-state class the reconciliation predicate must classify.
Returns a detached clone of the named fixed seed spec
None — throws on an unknown id; a silent miss would invite a hand-written spec back
module JSDoc
bootSeedManifest.spec.mjs — direct detached-clone, manifest-non-corruption, unknown-id and identity-root-not-addressable witnesses (added at cycle 3.5; the earlier citation was false, that spec did not import the accessor)
GraphService#ensureGlobalBootSeedNode(id)
the manifest spec above, projected through createGraphBootSeedNodeRecord
Guarantees a manifest-declared-field compliant, global (userId: null) row; returns true iff it wrote
None — throws on unknown id; never silently no-ops on a non-compliant row
method JSDoc, incl. the open-contract statement
five state rows below
Reconciliation is OPEN, not closed. Reconciled axes: properties.userId === null, label, and every property the manifest declares. Undeclared properties (semanticVectorId, state, updatedAt) are runtime-owned, asserted-on never, and preserved — which is also the only honest contract, since upsertNodemerges and a repair physically cannot strip an undeclared leftover.
State class
Behavior
Failure mode if wrong
Evidence
1. Absent
Create from canonical spec via upsertGlobalNode; logger.warn loud-heal; return true
Write-side cull: linkNodes' SELECT count(*) … WHERE id IN (?, ?) refuses, edge never written, support reads 0
Write churn and a false "restored" log on every synthesis run
idempotency assertion (false on second call)
3. Present + tenant-stamped
Repair to userId: null; warn naming the violated invariant; return true
Read-side invisibility — the edge IS written (endpoint check is RLS-blind) and then skipped by getInboundStructuralSupport's isRlsVisible check (GraphService.mjs:1150)
Emmy's reproduction, now a standing spec, RED without the change (Expected: true, Received: false); plus the dedicated RLS-read test asserting edge-written-yet-support-zero, then support>0 after repair with no new edge
4. Present + drifted declared field
Repair the declared field; warn; return true
Persisted graph is non-manifest-equal, so the fresh-target recovery predicate fails closed
drifted-description spec
5. Present + undeclared runtime extras
Preserve untouched; no warn; return false when otherwise compliant
Stripping a populated semanticVectorId would destroy legitimate embedding enrichment; asserting on it would make every enriched seat log a false heal
A squatting row under an unknown id would bypass the fail-loud contract entirely
unknown-id throw asserted both with and without a pre-existing row
Promise renamed at @neo-gpt-emmy's cycle-3.5 audit. "Manifest-equal" was a false promise given the open contract this ticket itself defines. She executed evaluateGraphBootSeedFreshness() against the exact 15-node/1-edge manifest with only the preserved legacy semanticVectorId: null added: fresh: false, expected sha256:86d4…, observed sha256:c367…. So a repaired row is not manifest-equal, by the manifest's own predicate. The guarantee is manifest-declared-field compliant and global. The full fresh-target predicate is the authority on whole-graph freshness and is deliberately untouched.
Acceptance Criteria
GoldenPathSynthesizer cannot emit a GUIDES edge whose source hub is absent — the hub is ensured, or the failed write surfaces.
The ensured hub is manifest-declared-field compliant and global: derived from bootSeedManifest's canonical spec via upsertGlobalNode (userId: null), never a hand-written literal and never tenant-stamped. Explicitly NOT full manifest equality — the open contract preserves undeclared runtime fields, so the whole-graph fingerprint predicate can still read fresh: false, and that predicate stays untouched.
bootSeedManifest.mjs exposes the narrow accessor that makes the above possible, so no consumer re-declares a boot-seed node.
The ensure path emits a loud diagnostic when it fires, because firing means boot/recovery left the graph non-manifest-equal.
SemanticGraphExtractor.mjs:813-821 no longer declares its own divergent frontier spec and no longer creates it tenant-stamped.
Unit spec asserts the guard directly: on a graph with no 'frontier' node, a synthesis run still produces the GUIDES edge (or loudly reports it could not), independent of test ordering.
A spec witnesses the tenancy property — the ensured hub is reachable from a second tenant, which the pre-fix sibling shape would fail.
The reproducer above is RED before the change and GREEN after, receipted with both runs.
GoldenPathSynthesizer.spec.mjs:1482 passes in the polluted ordering, closing #15874's last victim.
Out of Scope
Changing linkNodes' FK-guard semantics globally — the guard is correct; silent culling at call sites is the defect, and the precedent chain (#10174 / #10284) fixes it per-caller. A global throw would have blast radius across every caller and belongs in its own ticket if ever wanted.
Seeding 'frontier' unconditionally from GraphService init — that trades a silent-loss bug for a boot-order coupling.
#15874's config-mutation and destroy-before-initAsync axes, which PR #15980 and its parent already own.
Prune policy for hub nodes — #15973's territory; this ticket only notes prune as a reachability route.
Avoided Traps
Reasoning from the polluter's file class instead of measuring.fleetMailboxMirrorAdapter.spec.mjs is one of eleven specs that mutate GraphService.db.autoSave, so autoSave was the attractive answer. Printing the value in both contexts rules it out — it is true in the passing run. That would have been a confident wrong fix.
Filing this as test isolation. The parent ticket's frame would have produced a fixture patch that makes the symptom disappear while leaving production silently lossy.
Attributing the bisect to the wrong window. The victim is intermittent under parallel workers; the reproducer is deterministic only at --workers=1, so the intermittency is scheduling — whether the two specs land on one worker in that order.
Decision Record impact
none. This touches graph-write behaviour in ai/services/graph/, not AiConfig or any config leaf, so ADR-0019's reactive-provider SSOT is not engaged and no ADR authority is amended or challenged.
Related
#15874 (parent sweep; this closes its last victim) · PR #15980 (the fixture-class half, merged)
#10174 · #10284 (closed precedents, same silent-cull family, per-caller remedy)
Live latest-open sweep: checked latest 20 open issues (created-descending) at 2026-07-26T12:12:05Z; no equivalent found. A2A in-flight claim sweep: 30 most recent messages across all read-states; active claims in the herd window are #15906, #15807 and #15984 — none overlapping this scope.
Context
Surfaced as the last remaining victim of #15874's order-dependent pollution sweep, but it is not a test-isolation defect — the test failure is the symptom of a product bug, which is why it lands here rather than riding #15874's frame.
After PR #15980 closed the receipt-durability fixture class, one victim survived. Re-verified at post-#15980
dev(e3bd3243c8) rather than assumed:CI=true npx playwright test -c test/playwright/playwright.config.unit.mjs \ --workers=1 --retries=0 \ "ai/services/fleet/fleetMailboxMirrorAdapter.spec.mjs" \ "ai/services/graph/GoldenPathSynthesizer.spec.mjs" <h1 class="neo-h1" data-record-id="3">1 failed · 61 passed (8.8s)</h1>Failing test:
test/playwright/unit/ai/services/graph/GoldenPathSynthesizer.spec.mjs:1482— "a prior Golden Path GUIDES edge cannot self-authorize an undetermined Discussion on the next run" — at:1517,expect(priorRouteSupport.totalWeight).toBeGreaterThan(0).The Problem
Instrumented output, printed in both contexts before theorising (the
autoSavehypothesis this ticket's parent chased was a measured red herring — it istruein the passing run too):support.totalWeighttotalEdgeCountautoSavetruenodes=1 edges=0truenodes=2 edges=1 GUIDES->discussion-92210The vicinity holds one node instead of two, so the edge's source endpoint is absent and
linkNodes' FK guard culls the edge — its own error text: "FK guard may have culled the edge. FK endpoint count: 2". Nothing throws;synthesizeGoldenPathreports success and the reinforcement is simply gone.This is the same family as the closed precedents #10174 (Mailbox SENT_TO edges silently culled by linkNodes FK check) and #10284 (MailboxService.addMessage silently succeeds when routing edges are culled), both resolved by adding post-
linkNodesverification rather than by changing the guard.The Architectural Reality
The missing source endpoint is the literal-id node
'frontier'.ai/services/graph/GoldenPathSynthesizer.mjs:1413—GraphService.linkNodes('frontier', row.id, 'GUIDES', row.scored?.score ?? row.score ?? 0). No existence guard.ai/services/graph/SemanticGraphExtractor.mjs:813-815— the sibling writer does guard, and unconditionally:if (!GraphService.db.nodes.has('frontier')) { … upsert id: 'frontier' … }before it links.ai/graph/bootSeedManifest.mjs:22— seedsid: 'frontier'at boot, and:37uses it as an edgesource.ai/services/graph/frontierConsolidation.mjs:31,33— reads adjacency andedges.getByIndex('source', 'frontier'), i.e. the node is a load-bearing hub, not an incidental id.The asymmetry is the defect. Two writers link from the same hub node; one self-heals when it is missing, the other loses its write silently. On any graph where
'frontier'is absent the extractor recovers and the synthesizer degrades — and the degradation is invisible because a culled edge is not an error.Why this is not test-only. The reproducer merely produces a graph without
'frontier'. Two production routes reach the same state: a process where the boot seed has not run for this plane, and — more importantly — prune. Grace's #15973 established that the ambient decay floor sits below the prune threshold, so hub nodes are prune-reachable rather than permanent. In either case the Golden Path stops recording reinforcement while every caller reports success, which degrades frontier weighting and recall ranking with no signal.The Fix
Falsifier 1 — the sibling diverges from the canonical manifest, which that manifest forbids by name.
ai/graph/bootSeedManifest.mjsis the SSOT for boot seeds and its own header states: "Boot and fresh-target recovery both consume this module. Adding a boot seed anywhere else makes the recovery predicate fail closed because the persisted graph can no longer equal this complete manifest." The twofrontierdefinitions already disagree:bootSeedManifest.mjs:22-26(canonical)"The shifting focal point of the active Neo OS agent session."SemanticGraphExtractor.mjs:813-821"The actively tracked development front for the current project scope."semanticVectorId: nullCopying the sibling would have propagated a manifest inequality into a third site.
Falsifier 2 — and the divergence is tenancy, not prose. The manifest's specs are documented as "suitable for
GraphService.upsertGlobalNode()", andupsertGlobalNode(GraphService.mjs:379-381) forcesproperties.userId = nullso the node is RLS-visible to every tenant. The sibling uses plainupsertNode, which "stamp[s] the canonical normalized isolation key" — the active request's identity. So a sibling-createdfrontieris tenant-stamped where the manifest requires global. On any other tenant that hub is RLS-invisible — and this is the same defect with a multi-tenant face, which the original ticket did not capture at all.linkGlobalNodes' own JSDoc (GraphService.mjs:384-390) documents the adjacent trap: traversal requires both node and edge RLS-visible, so a global sentinel reached only through a tenant-stamped edge still vanishes for non-booting tenants.Corrected prescription:
ai/services/graph/GoldenPathSynthesizer.mjs:1413— ensure the hub from the canonical manifest spec, not from a hand-written literal, and viaupsertGlobalNodeso it is manifest-declared-field compliant and global (see the promise note below — deliberately not "manifest-equal").FIXED_NODE_SPECSis currently module-local, sobootSeedManifest.mjsneeds a narrow accessor export (single named spec or agetBootSeedNodeSpec(id)) — that export is the point of the fix, not incidental to it.frontiermeans boot/recovery left the graph non-manifest-equal; healing it silently would replace one silent failure with another. Ensure-and-warn, not ensure-and-forget.linkNodesfor theGUIDESedge itself. Golden Path reinforcement is per-tenant learning, so a tenant-stamped edge to a global hub is the intended shape — not a defect to "fix" intolinkGlobalNodes.SemanticGraphExtractor.mjs:813-821to the same manifest-derived path. Leaving it divergent means the manifest inequality — and the tenant-stamped-hub variant of this very bug — survives the fix.Contract Ledger Matrix
Added at @neo-gpt-emmy's cycle-3 request. Two new consumed surfaces, then every persisted-state class the reconciliation predicate must classify.
getGraphBootSeedNodeSpec(id)(ai/graph/bootSeedManifest.mjs)FIXED_NODE_SPECSin the same modulebootSeedManifest.spec.mjs— direct detached-clone, manifest-non-corruption, unknown-id and identity-root-not-addressable witnesses (added at cycle 3.5; the earlier citation was false, that spec did not import the accessor)GraphService#ensureGlobalBootSeedNode(id)createGraphBootSeedNodeRecorduserId: null) row; returnstrueiff it wroteReconciliation is OPEN, not closed. Reconciled axes:
properties.userId === null,label, and every property the manifest declares. Undeclared properties (semanticVectorId,state,updatedAt) are runtime-owned, asserted-on never, and preserved — which is also the only honest contract, sinceupsertNodemerges and a repair physically cannot strip an undeclared leftover.upsertGlobalNode;logger.warnloud-heal; returntruelinkNodes'SELECT count(*) … WHERE id IN (?, ?)refuses, edge never written, support reads 01 failed/61/25-skipped→87 passed); absent-branch specfalsefalseon second call)userId: null; warn naming the violated invariant; returntruegetInboundStructuralSupport'sisRlsVisiblecheck (GraphService.mjs:1150)Expected: true, Received: false); plus the dedicated RLS-read test asserting edge-written-yet-support-zero, then support>0 after repair with no new edgetruefalsewhen otherwise compliantsemanticVectorIdwould destroy legitimate embedding enrichment; asserting on it would make every enriched seat log a false healsemanticVectorId: 'vec-runtime-owned'survives, returnsfalse)Acceptance Criteria
GoldenPathSynthesizercannot emit aGUIDESedge whose source hub is absent — the hub is ensured, or the failed write surfaces.bootSeedManifest's canonical spec viaupsertGlobalNode(userId: null), never a hand-written literal and never tenant-stamped. Explicitly NOT full manifest equality — the open contract preserves undeclared runtime fields, so the whole-graph fingerprint predicate can still readfresh: false, and that predicate stays untouched.bootSeedManifest.mjsexposes the narrow accessor that makes the above possible, so no consumer re-declares a boot-seed node.SemanticGraphExtractor.mjs:813-821no longer declares its own divergentfrontierspec and no longer creates it tenant-stamped.'frontier'node, a synthesis run still produces theGUIDESedge (or loudly reports it could not), independent of test ordering.GoldenPathSynthesizer.spec.mjs:1482passes in the polluted ordering, closing #15874's last victim.Out of Scope
linkNodes' FK-guard semantics globally — the guard is correct; silent culling at call sites is the defect, and the precedent chain (#10174 / #10284) fixes it per-caller. A global throw would have blast radius across every caller and belongs in its own ticket if ever wanted.'frontier'unconditionally fromGraphServiceinit — that trades a silent-loss bug for a boot-order coupling.initAsyncaxes, which PR #15980 and its parent already own.Avoided Traps
fleetMailboxMirrorAdapter.spec.mjsis one of eleven specs that mutateGraphService.db.autoSave, soautoSavewas the attractive answer. Printing the value in both contexts rules it out — it istruein the passing run. That would have been a confident wrong fix.--workers=1, so the intermittency is scheduling — whether the two specs land on one worker in that order.Decision Record impact
none. This touches graph-write behaviour inai/services/graph/, not AiConfig or any config leaf, so ADR-0019's reactive-provider SSOT is not engaged and no ADR authority is amended or challenged.Related
Live latest-open sweep: checked latest 20 open issues (created-descending) at 2026-07-26T12:12:05Z; no equivalent found. A2A in-flight claim sweep: 30 most recent messages across all read-states; active claims in the herd window are #15906, #15807 and #15984 — none overlapping this scope.
Origin Session ID: 9a94e287-2a1a-412d-88f0-d1ae477fdfe6
Retrieval Hint: "GoldenPathSynthesizer frontier node GUIDES edge silently culled linkNodes FK guard"