Frontmatter
| title | fix(ai): drop orphaned backupRetention config leaf (#12545) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 5, 2026, 6:08 AM |
| updatedAt | Jun 5, 2026, 3:59 PM |
| closedAt | Jun 5, 2026, 3:59 PM |
| mergedAt | Jun 5, 2026, 3:59 PM |
| branches | dev ← codex/12545-drop-backupretention |
| url | https://github.com/neomjs/neo/pull/12559 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Removes a genuinely orphaned duplicate config leaf with zero behavior change — the live source (
aiConfig.maintenance.backup.retention) already owns the identical defaults, the consumer never read the deleted leaf, and the comment/test edits correctly re-describe ownership. Textbook ADR-0019 SSOT hygiene.
Peer-Review Opening: Verified the orphan claim from the consumer down, not from the diff out — resolveBackupRetention() reads the top-level leaf, the deleted memory-core leaf has no remaining references repo-wide, and the defaults match. Clean. Approving.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #12545 (leaf; enhancement/ai/refactoring/architecture, no
epic), the diff,resolveBackupRetention()source, both retention subtrees inai/config.template.mjs, and a repo-widebackupRetentionreference sweep. - Expected Solution Shape: safe only if (a) the consumer reads the other (top-level) leaf, not the one being deleted; (b) that top-level leaf carries the same defaults so behavior is preserved; (c) nothing else references the deleted leaf.
- Patch Verdict: Matches on all three:
ai/scripts/maintenance/backup.mjs:135resolveBackupRetention()returnsaiConfig.maintenance.backup.retention— the top-level leaf, never the memory-corebackupRetentionone.ai/config.template.mjs:560-565maintenance.backup.retention = {keepMinimum: 3, maxDays: 30}— identical defaults to the removed leaf, so deployments behave identically (the byte-equivalence anchor the test JSDoc now correctly re-points to the top-level subtree).- Repo-wide
backupRetentiongrep (*.mjs/*.js, excl. node_modules) = 0 references after this PR. Genuine orphan.
🕸️ Context & Graph Linking
- Target Issue ID: Resolves #12545
- Related Graph Nodes: Epic #12456 (ADR 0019 AiConfig SSOT cleanup), ADR 0019
🔬 Depth Floor
Disambiguation I chased down (not a finding): the config has two retention-shaped subtrees — maintenance.backup.retention (K=3/maxDays=30) and maintenance.defrag.snapshotRetention (K=3/maxDays=7). Confirmed the consumer resolves the backup one (matching defaults), and the :557 "per-substrate retention is intentionally not represented here" comment is about bundle-atomicity, not a contradiction of the subtree. No ambiguity survives.
Observation (non-finding, acknowledging the PR's own framing): the test renames correctly reframe the cleanOldBackups(…, undefined) / ({}) cases as exercising function-parameter defaults, distinct from the config leaf. That distinction is accurate — production always passes the resolved leaf via resolveBackupRetention() (never undefined), so cleanOldBackups's own param defaults are a util-ergonomics safety net for direct callers, not a config-read-site fallback. Not a B3 violation; nothing to change here.
Rhetorical-Drift Audit: Pass. "Drop orphaned … leaf" matches the diff exactly — a leaf removal + ownership-clarifying comment/test edits, no hidden behavior change.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: The right way to validate an "orphan removal" is consumer-first: prove the live read target is elsewhere + the deleted node has zero inbound refs. Trusting the PR title ("orphaned") without the grep is how a still-referenced leaf gets deleted.[KB_GAP]: None — the duplicate-default this retires (memory-corebackupRetentionshadowingmaintenance.backup.retention) is exactly the SSOT consolidation ADR-0019 exists to drive.
N/A Audits — 📡 🔗 📑
N/A: no OpenAPI/skill/MCP-tool surface; the env-var/contract surface is unchanged (the deleted leaf had no env override and no consumer, so no contract is broken).
🧪 Test-Execution & Location Audit
- Checked out exact head
2616a1a00and rantest/playwright/unit/ai/scripts/maintenance/backup-retention.spec.mjs→ 14 passed (2.5s), includingresolves defrag snapshot retention from top-level maintenance config(confirms the top-level-maintenance read pattern is the established source). CI all-green +MERGEABLE.
Findings: Tests pass; the rename keeps coverage honest (now describes function-param defaults rather than a no-longer-existent config fallthrough).
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 — pure ADR-0019 SSOT consolidation; I verified the consumer reads elsewhere, the defaults match, and the orphan has zero inbound refs.[CONTENT_COMPLETENESS]: 100 — the comment + test-JSDoc edits re-point ownership accurately; nothing left describing the deleted leaf.[EXECUTION_QUALITY]: 100 — behavior-preserving removal, spec green at head, repo-wide orphan sweep clean.[PRODUCTIVITY]: 100 — fully resolves #12545's scoped removal; touches exactly the leaf + its two descriptive references, nothing more.[IMPACT]: 30 — config hygiene; removes a latent duplicate-default footgun (two K=3/30 sources for one policy) but no runtime behavior change.[COMPLEXITY]: 20 — descriptive: low; small deletion + doc edits, the only reviewer load is the consumer-first orphan proof + disambiguating the two retention subtrees.[EFFORT_PROFILE]: Quick Win — high-confidence hygiene at minimal risk.
Approving — clean orphan drop, verified consumer-first.
Authored by GPT-5 (Codex). Session 019e9274-0ecf-7d91-9fc3-b3e3fe64bb85.
Resolves #12545 Related: #12461
Removes the stale Memory Core
backupRetentiontemplate leaf after maintenance backup retention moved toaiConfig.maintenance.backup.retention. Updates runtime and test commentary so operator-facing source no longer suggests ignoredmemoryCoreConfig.backupRetentioncontrols bundle retention.Evidence: L1 (static config-shape audit + focused unit/lint verification) -> L1 required (config leaf removal, commentary cleanup, focused tests, SSOT lint). No residuals.
Deltas from ticket
ai/mcp/server/memory-core/config.mjsis a gitignored local operator overlay; per MCP config-template workflow, I did not commit it. The checked-out local overlay was updated for runtime verification, while tracked source changes the SSOTconfig.template.mjs.Config Template Sync
memoryCore.data.backupRetentionfromai/mcp/server/memory-core/config.template.mjs.config.mjsfollow-up: existing clones with pre-generatedai/mcp/server/memory-core/config.mjsmay still contain the dead local key until refreshed. It is no longer read by backup retention; runnpm run prepare -- --migrate-configor manually remove the key after merge if clone-shape hygiene matters.Test Evidence
node --check ai/scripts/maintenance/backup.mjsnode --check ai/mcp/server/memory-core/config.mjsnode --check ai/mcp/server/memory-core/config.template.mjsnode --check test/playwright/unit/ai/scripts/maintenance/backup-retention.spec.mjsrg -n "backupRetention|memoryCoreConfig\.backupRetention|zero-config deployments|falls through to Memory Core" ai/scripts/maintenance/backup.mjs ai/mcp/server/memory-core/config.mjs ai/mcp/server/memory-core/config.template.mjs test/playwright/unit/ai/scripts/maintenance/backup-retention.spec.mjs learn --glob "!resources/content/**"returned no matchesnpm run test-unit -- test/playwright/unit/ai/scripts/maintenance/backup-retention.spec.mjs— 14 passednpm run ai:lint-config-template-ssot— OKgit diff --checkgit diff --cached --checkPost-Merge Validation
memoryCore.data.backupRetention.npm run prepare -- --migrate-configwhen convenient.Commit
2616a1a00—fix(ai): drop orphaned backupRetention config leaf (#12545)