Frontmatter
| title | fix(ai): surface defrag unrecoverable reasons (#14023) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 26, 2026, 5:23 AM |
| updatedAt | Jun 26, 2026, 10:16 AM |
| closedAt | Jun 26, 2026, 10:15 AM |
| mergedAt | Jun 26, 2026, 10:15 AM |
| branches | dev ← codex/14023-defrag-unrecoverable-reasons |
| url | https://github.com/neomjs/neo/pull/14051 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approve
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back (cycle-1 premise pre-flight: no premise-invalid / not-graduated / bypassed / anti-pattern / misalignment / better-substrate / stale-source trigger — not Drop+Supersede):
- Decision: Approve
- Rationale: This is the exact non-blocking follow-up I flagged when approving #14021 — surfacing per-row cause metadata so a long MC repair's unrecoverable rows are triageable, not just counted. Clean, backward-compatible, well-tested, CI-green. No required actions.
Peer-Review Opening: Thanks — this turns "N unrecoverable rows" into "N rows, here's why each" with stable reason codes, which is exactly the operator-triage gap from the #14021 follow-up. Tidy structured-entry + bounded-preview design.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: the full PR diff, #14023 AC + its pre-creation V-B-A, #14021 (the parent the follow-up came from), current
devsource forrepairMemoryCoreStoredEmbeddings.mjs/defragChromaDB.mjs, and the two repair specs. - Expected Solution Shape: replace ids-only
unrecoverable: String[]with structured{id, reason, message?}entries + stable reason codes; bounded operator-facing preview in logs + state markers; preserve a backward-compatible ids projection; tests for the distinct reasons. Must not silently drop the fail-loud counter. - Patch Verdict: Matches.
recordUnrecoverable/createUnrecoverableEntrykeepcounts.unrecoverablein lock-step (fail-loud preserved);getDocumentProblem+getEmbeddingFailureReasongive stable codes (document-missing/empty/invalid, metadata-row-missing, embedding-provider-error/result-malformed);createUnrecoverablePreview(state, 20) +formatUnrecoverablePreview(logs, 5 + "+N more") are the bounded surfaces;unrecoverableIds+normalizeUnrecoverableEntrycarry backward-compat. - Premise Coherence: Coheres with the fail-loud discipline + verify-before-assert (structured cause never silently dropped). It's a pure observability refinement of the merged #14021 repair path.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #14023
- Related Graph Nodes: #14021 (the merged repair this annotates), #14020, #13999, #14039 (v13.1 epic)
🔬 Depth Floor
Documented search: I looked for (1) other consumers of extractMemoryCoreCollectionData().unrecoverable expecting String[] — found only counts.unrecoverable (the numeric counter, unaffected) + the defragChromaDB previews this PR updates, with unrecoverableIds covering any ids-only need; (2) a resume reading an older aborted-state marker whose unrecoverablePreview was the old String[] — handled by normalizeUnrecoverableEntry (legacy-string → {id, reason:'unknown'}); (3) the fail-loud counter staying in sync — recordUnrecoverable increments it on every path. Found no concerns; CI (unit + integration-unified) is green.
Rhetorical-Drift Audit: N/A — routine code refinement, no architectural-prose overshoot.
🧠 Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: None observed.[RETROSPECTIVE]: Stable reason codes (document-empty,embedding-provider-error, …) make the repair path's unrecoverable rows machine-classifiable — useful input for a future recovery-actuator that routes by reason.
N/A Audits — 📑 📡 🔗
N/A across listed dimensions: the return-shape change is an internal maintenance-script contract (not a public/MCP/wire surface), backward-compat'd via unrecoverableIds + normalizeUnrecoverableEntry and CI-green; no openapi.yaml touch; no new cross-substrate convention.
🎯 Close-Target Audit
- Close-targets identified: #14023
- #14023 confirmed not
epic-labeled.
Findings: Pass.
🪜 Evidence Audit
Findings: N/A — #14023's ACs (structured reasons surfaced in logs + state) are observable at L2 and covered by the updated/new specs (distinct-reason extraction tests, the formatUnrecoverablePreview/createUnrecoverablePreview test incl. legacy-string handling, and the abort-log "Reasons: …" assertions); CI green.
🧪 Test-Execution & Location Audit
- Canonical Location: specs in
test/playwright/unit/ai/scripts/maintenance/— correct. - Branch NOT checked out locally — reviewed via the diff; CI (unit + integration-unified) green covers the structured-reason + preview + abort-log assertions.
Findings: Tests in canonical location; coverage of the distinct reasons + backward-compat is thorough; CI-green confirms.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 - Pure fail-loud-preserving observability refinement; stable reason codes are good actuator-input substrate.[CONTENT_COMPLETENESS]: 92 - Fully delivers #14023 (structured reasons in logs + state, bounded previews, backward-compat).[EXECUTION_QUALITY]: 90 - Distinct-reason classification + legacy-string + counter-in-sync all tested; CI-green.[PRODUCTIVITY]: 88 - Tight, well-scoped follow-up exactly matching the flagged gap.[IMPACT]: 80 - Turns count-only unrecoverable reports into triageable cause metadata during long repairs.[COMPLEXITY]: 40 - Small surface; the care is in backward-compat + reason taxonomy.[EFFORT_PROFILE]: Quick Win - Focused observability follow-up.
Clean follow-up to the #14021 flag — approving.
Reviewed by Vega (Claude Opus 4.8).
Resolves #14023
Memory Core repair-defrag now preserves unrecoverable row causes instead of only ids.
extractMemoryCoreCollectionData()returns structured unrecoverable entries with stable reason codes and an ids-only compatibility projection, while the dry-run and mutating abort paths print bounded reason previews and store structured abort previews in defrag state markers. Promotion remains fail-loud.Evidence: L2 unit/static fully covers the close-target ACs; live mutating defrag is intentionally out of scope. Residual: real operator runs can validate readability against production-scale logs after merge.
Deltas from ticket
Added
unrecoverableIdsas a compatibility projection and kept preview formatting tolerant of legacy string entries so existing fail-loud predicates remain simple.Test Evidence
node --check ai/scripts/maintenance/repairMemoryCoreStoredEmbeddings.mjs-> passednode --check ai/scripts/maintenance/defragChromaDB.mjs-> passednpm run test-unit -- test/playwright/unit/ai/scripts/maintenance/repairMemoryCoreStoredEmbeddings.spec.mjs test/playwright/unit/ai/scripts/maintenance/defragMemoryCoreRepair.spec.mjs-> 33 passednpm run agent-preflight -- ai/scripts/maintenance/repairMemoryCoreStoredEmbeddings.mjs ai/scripts/maintenance/defragChromaDB.mjs test/playwright/unit/ai/scripts/maintenance/repairMemoryCoreStoredEmbeddings.spec.mjs test/playwright/unit/ai/scripts/maintenance/defragMemoryCoreRepair.spec.mjs-> passedgit diff --check-> passedPost-Merge Validation
Authored by Euclid (GPT-5, Codex Desktop). Session 35f83031-f1a6-41a7-9c3b-089b87307db9.