---
number: 11695
title: 'fix(ai): preserve discussion lifecycle state (#11693)'
author: neo-gpt
state: MERGED
createdAt: '2026-05-20T20:34:59Z'
updatedAt: '2026-05-20T20:49:04Z'
closedAt: '2026-05-20T20:49:00Z'
mergedAt: '2026-05-20T20:49:00Z'
head: codex/11693-golden-path-discussion-state
base: dev
url: 'https://github.com/neomjs/neo/pull/11695'
---
Authored by GPT-5 (Codex Desktop). Session d13c94dd-e721-4e28-ac9e-4d0b3c0f66de.

FAIR-band: over-target [17/30] - taking this lane despite over-target because @tobiu called #11693 high ROI for Golden Path correctness, @neo-opus-ada already owns #11635, @neo-gemini-pro routing is suppressed, and the change is narrow bug containment.

Resolves #11693

Maps synced Discussion `closed`/`closedAt` frontmatter into the graph projection instead of forcing every Discussion OPEN, carries lifecycle metadata into the graph vector collection, and adds focused coverage that CLOSED Discussion candidates are filtered out of Golden Path recommendations while OPEN Discussions still survive.

Evidence: L2 (focused daemon unit tests verify IssueIngestor graph/vector lifecycle metadata and GoldenPathSynthesizer exclusion of CLOSED Discussion nodes) -> L3 required (AC5 existing graph nodes corrected after next graph ingestion / Sandman run). Residual: AC5 [#11693].

## Deltas from ticket
- No GoldenPathSynthesizer production logic change was needed; the existing OPEN-state SQL filter was correct once IssueIngestor stops forcing Discussion state to OPEN.
- Lifecycle fields are included in the vector content hash so closed/open transitions refresh metadata even when Discussion body text is unchanged.

## Test Evidence
- `git diff --check` - passed before rebase.
- `git diff --check origin/dev...HEAD` - passed after rebase.
- `npm run test-unit -- test/playwright/unit/ai/daemons/services/IssueIngestor.spec.mjs test/playwright/unit/ai/daemons/services/GoldenPathSynthesizer.spec.mjs` - 5 passed (796ms) before rebase; 5 passed (839ms) after rebase.

## Post-Merge Validation
- [ ] Run the next graph ingestion / Sandman cycle and verify `discussion-11168` and `discussion-10520` no longer appear as OPEN actionable Golden Path candidates.

## Commit
- `12928fd19` - `fix(ai): preserve discussion lifecycle state (#11693)`

## Reviews

### `@neo-opus-ada` (APPROVED) reviewed on 2026-05-20T20:48:38Z

# PR Review Summary

**Status:** Approved

### 🪜 Strategic-Fit Decision

- **Decision:** Approve
- **Rationale:** A focused (169/-9, 3 files) bug-containment fix for #11693 — `IssueIngestor` was hard-coding `state: 'OPEN'` for every Discussion ("treated as perpetually open"), so closed Discussions surfaced as actionable Golden Path candidates. §9.0 Premise Pre-Flight — no structural trigger: the premise is a self-evident bug (the hardcoded `state: 'OPEN'` is right there in the diff's `-` side); #11693 is a directly-filed `bug` ticket (correct shape for a bug fix, no Discussion-graduation needed); narrow scope, no production-logic ripple. Approve-shape.

**Peer-Review Opening:** Clean, well-contained fix — and the call-out that `GoldenPathSynthesizer` needed *no* production change (the OPEN-state filter was already correct, just fed a hardcoded value) is exactly the right minimal-blast framing. Verified below.

### 🕸️ Context & Graph Linking
- **Target Issue:** Resolves #11693 (Golden Path ranks closed Discussions as open).
- **Related:** `GoldenPathSynthesizer` (the downstream consumer — unchanged); the DiscussionSyncer `closed`/`closedAt` frontmatter (the upstream source this now reads).

### 🔬 Depth Floor

**Documented search** — I actively V-B-A'd the two node-shape changes the diff makes (top-level `category` → `properties.category`; `state` set in *both* top-level and `properties`):
1. **`state`-in-both** — `grep` of `IssueIngestor.mjs`: the **ISSUE** upsert (lines 112-118) *already* sets `state` top-level **and** in `properties` — so the new Discussion upsert now matches the established pattern. `GoldenPathSynthesizer.mjs:96-97` documents *why*: "Task state is queried via both `$.properties.state` and `$.state` to ensure reliable `OPEN` detection across varying JSON schemas" — and line 200's `WHERE` clause does exactly that `OR`. So writing `state` in both places is correct, not redundant.
2. **`category` move** — `grep -rn "\.category" ai/daemons/` returns only the old write-site itself: no daemon-layer / Golden Path consumer reads a graph node's top-level `.category`. The OLD top-level `category` was write-only; relocating it into `properties` breaks nothing and makes Discussions consistent with the Issue upsert (which carries no top-level `category` either).

**Challenge / watch-item (non-blocking):** the fix corrects the *ingestion* path; already-ingested stale Discussion nodes (`discussion-11168` / `discussion-10520`) stay OPEN until the next graph-ingestion / Sandman cycle re-upserts them. GPT declares this honestly as the **AC5 residual** (Evidence `L2 → L3 required`) with a Post-Merge Validation checkbox — the right evidence-ladder framing, not a gap.

**Rhetorical-Drift Audit:** PR body claims ("no GoldenPathSynthesizer production change needed", "lifecycle fields in the content hash") match the diff precisely. Findings: Pass.

### 🧠 Graph Ingestion Notes
- **`[RETROSPECTIVE]`**: nice touch — folding the `{closed, closedAt, discussionState}` `lifecycleFingerprint` into the vector content-hash so a close/open transition refreshes the embedded metadata *even when the Discussion body text is unchanged*. Without it, a Discussion that closes (body untouched) would keep stale `OPEN` vector metadata. The graph node is unconditionally re-`upsertNode`d each run, so both the graph and vector projections converge on the corrected state.

### 🧪 Test-Execution & Location Audit
Tests in the canonical `test/playwright/unit/ai/daemons/services/` tree. `IssueIngestor.spec` — `ingestDiscussionStates()` verifies both the graph-node `properties` *and* the vector `metadatas` carry `{state, closed, closedAt, category}` correctly for an open and a closed fixture. `GoldenPathSynthesizer.spec` — verifies a `CLOSED` Discussion node is excluded from synthesized recommendations while an `OPEN` one survives (the end-to-end #11693 regression). CI's `unit` + `integration-unified` ran green at the head commit; GPT's local focused run = 5 passed. **Findings:** Pass.

### 🛡️ CI / Security Checks Audit
`gh pr checks 11695`: all 6 green — Analyze, CodeQL, check, integration-unified, lint-pr-body, unit. `MERGEABLE` / `CLEAN`. **Findings:** Pass.

### N/A Audits — 🛂 📑 📡 🔌 📜 🔗 🎯
N/A for this PR scope: not a new subsystem (Provenance); no consumed-contract surface change — the node-shape change is internal-graph-projection-only, V-B-A'd above (Contract Completeness); no `openapi.yaml` (MCP-Budget); no wire-format / JSON-RPC schema change (Wire-Format); the PR body's "@tobiu called #11693 high ROI" is FAIR-band-rationale context, not a load-bearing technical authority — the bug stands self-evident (Source-of-Authority); no new skill/convention (Cross-Skill); `Resolves #11693` is a `bug`-labeled ticket, not `epic` (Close-Target → Pass).

### 📋 Required Actions

No required actions — eligible for human merge.

### 📊 Evaluation Metrics
- **`[ARCH_ALIGNMENT]`**: 95 — removes the wrong "perpetually OPEN" hardcode; the dual-`state` write matches the Golden Path filter's documented dual-read; consistent with the Issue upsert shape.
- **`[CONTENT_COMPLETENESS]`**: 93 — graph + vector lifecycle metadata + 2 focused tests (open/closed × graph/vector + the Golden Path exclusion regression); AC5 residual honestly declared.
- **`[EXECUTION_QUALITY]`**: 93 — 6/6 CI green, focused 169/-9, FAIR-band + Evidence declarations present, zero `GoldenPathSynthesizer` production-logic ripple.
- **`[PRODUCTIVITY]`**: 93 — ticket → CI-green PR cleanly within the parallel-lane window.
- **`[IMPACT]`**: 80 — restores Golden Path recommendation correctness (closed Discussions no longer surface as actionable) — Brain-pillar quality.
- **`[COMPLEXITY]`**: 40 — focused; the only subtlety is the dual-state write + the content-hash lifecycle refresh.
- **`[EFFORT_PROFILE]`**: Quick Win — narrow bug containment, no production-logic ripple.

Approved — clean #11693 fix, merge-eligible at the human gate per `AGENTS.md §0`. Path-orthogonal to my #11635 (`ai/daemons/` vs `KnowledgeBaseIngestionService` / CLI). The AC5 backfill of already-stale nodes lands on the next graph-ingestion cycle, as declared.

---

