Resolves #14883
#14882 shipped the honest-states classifier but wired the caller with a digested-history proxy (a summary% node count), so REM_STALLED could never fire — the render could only reach FRONTIER_UNANCHORED / UNATTRIBUTED. This fetches the real REM pipeline state — HealthService.getRemPipelineState() ({undigested, digested, recentCycles}) — at the already-async caller and passes it into classifyFrontierEmptyCause. A genuine stall (undigested backlog, no recent cycle) now renders REM_STALLED, and the real digested figure replaces the proxy. The async fetch lives in the caller (matching the existing await import('../../services/memory-core/…') pattern) to keep the SQLite fallback builder sync; a missing/failed remState degrades to the honest UNATTRIBUTED phrase.
Evidence: L2 (unit — GoldenPathSynthesizer + goldenPathPickupBridge specs, 55 passed; a new whitebox test drives REM_STALLED / FRONTIER_UNANCHORED / UNATTRIBUTED through the wiring) → L2 sufficient: the ACs are the classifier inputs + the render string, both unit-covered. Residual: a live sandman run confirming REM_STALLED under a real stall (post-merge check).
Deltas from ticket
- The REM accessor is
HealthService.getRemPipelineState() (default export → .getRemPipelineState(), the verified toolService pattern), dynamic-imported at the async caller — no cross-service static import at module top.
- Non-breaking:
buildDeclaredIntentFallback(remState = {}) — existing no-arg calls (and the two existing specs) still work, degrading to UNATTRIBUTED.
Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/graph/GoldenPathSynthesizer.spec.mjs test/playwright/unit/ai/services/graph/goldenPathPickupBridge.spec.mjs → 55 passed (worktree, exit 0). New test buildDeclaredIntentFallback attributes the MEASURED REM cause…: a stall remState (undigested:40, recentCycles:[]) renders REM consolidation stalled and never REM-starved; a healthy-digestion-empty-anchor remState renders frontier unanchored; no remState renders unattributed.
Post-Merge Validation
Merge coordination
Both this PR and #14888 touch GoldenPathSynthesizer.mjs in different regions (this = the buildDeclaredIntentFallback method + its caller; #14888 = the Concept Slice append/write). No overlapping hunks, but merge #14888 first (it is green + cross-family-routed and ahead); this branch rebases trivially onto the result.
Decision Record
aligned-with ADR-0019 — reads aiConfig.goldenPathTopNodeRenderLimit at the use site (existing); the REM accessor is a service method, not config; no threading/mutation. No ADR amended.
Related: #14882 (parent fix this completes), #14472 (epic), #14565 (direction-attribution vocabulary).
Authored by Ada (Claude Opus 4.8, Claude Code). Session 9360840f-5d7a-4680-8110-86877722735b.
Resolves #14883
#14882 shipped the honest-states classifier but wired the caller with a digested-history proxy (a
summary%node count), soREM_STALLEDcould never fire — the render could only reachFRONTIER_UNANCHORED/UNATTRIBUTED. This fetches the real REM pipeline state —HealthService.getRemPipelineState()({undigested, digested, recentCycles}) — at the already-async caller and passes it intoclassifyFrontierEmptyCause. A genuine stall (undigested backlog, no recent cycle) now rendersREM_STALLED, and the realdigestedfigure replaces the proxy. The async fetch lives in the caller (matching the existingawait import('../../services/memory-core/…')pattern) to keep the SQLite fallback builder sync; a missing/failedremStatedegrades to the honestUNATTRIBUTEDphrase.Evidence: L2 (unit — GoldenPathSynthesizer + goldenPathPickupBridge specs, 55 passed; a new whitebox test drives REM_STALLED / FRONTIER_UNANCHORED / UNATTRIBUTED through the wiring) → L2 sufficient: the ACs are the classifier inputs + the render string, both unit-covered. Residual: a live sandman run confirming REM_STALLED under a real stall (post-merge check).
Deltas from ticket
HealthService.getRemPipelineState()(default export →.getRemPipelineState(), the verifiedtoolServicepattern), dynamic-imported at the async caller — no cross-service static import at module top.buildDeclaredIntentFallback(remState = {})— existing no-arg calls (and the two existing specs) still work, degrading toUNATTRIBUTED.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/graph/GoldenPathSynthesizer.spec.mjs test/playwright/unit/ai/services/graph/goldenPathPickupBridge.spec.mjs→ 55 passed (worktree, exit 0). New testbuildDeclaredIntentFallback attributes the MEASURED REM cause…: a stallremState(undigested:40, recentCycles:[]) rendersREM consolidation stalledand neverREM-starved; a healthy-digestion-empty-anchorremStaterendersfrontier unanchored; noremStaterendersunattributed.Post-Merge Validation
REM consolidation stalled….Merge coordination
Both this PR and #14888 touch
GoldenPathSynthesizer.mjsin different regions (this = thebuildDeclaredIntentFallbackmethod + its caller; #14888 = the Concept Slice append/write). No overlapping hunks, but merge #14888 first (it is green + cross-family-routed and ahead); this branch rebases trivially onto the result.Decision Record
aligned-withADR-0019 — readsaiConfig.goldenPathTopNodeRenderLimitat the use site (existing); the REM accessor is a service method, not config; no threading/mutation. No ADR amended.Related: #14882 (parent fix this completes), #14472 (epic), #14565 (direction-attribution vocabulary).
Authored by Ada (Claude Opus 4.8, Claude Code). Session 9360840f-5d7a-4680-8110-86877722735b.