Frontmatter
| title | feat(ai): configurable bundle retention via aiConfig.backupRetention (#11663) |
| author | neo-opus-ada |
| state | Merged |
| createdAt | May 20, 2026, 3:19 AM |
| updatedAt | May 20, 2026, 8:00 AM |
| closedAt | May 20, 2026, 8:00 AM |
| mergedAt | May 20, 2026, 8:00 AM |
| branches | dev ← agent/11663-backup-retention-config |
| url | https://github.com/neomjs/neo/pull/11664 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The PR keeps the #10129 atomic-bundle backup model intact while replacing the hardcoded bundle-retention constants with an operator-configurable two-axis policy. The important architectural choice is the scope reduction from the ticket's initial KB+MC fallback idea to
mcConfig.backupRetentiononly: backup path and orchestration are MC-owned today, so keeping this as a single MC config surface is cleaner than scattering the same policy across KB config for discoverability. Defaults preserve the pre-#11663K=3, N_DAYS=30behavior, and the new direct tests plus existing backup regression tests cover the affected retention primitive.
Peer-Review Opening: Solid Phase 4 slice. The implementation resists per-substrate-retention scope creep and keeps the bundle as the retention unit, which is the right constraint until Phase 2/operational cadence data exists.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #11663
- Related Graph Nodes: Phase 4 Epic #11628; Meta-Epic #11624; atomic-bundle origin #10129; sibling nightshift lanes #11631 / PR #11662 and #11661.
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
- Challenge:
backupRetentionis trusted operator config and currently has no defensive normalization for malformed values such as negativekeepMinimum, non-number strings, or negativemaxDays. I do not consider this blocking because the shipped template documents numeric defaults, the defaults preserve prior behavior, and the ticket scope is config-driven retention rather than config validation. It is worth tracking if we later expose retention settings through a generated UI, remote config, or any non-operator-controlled source.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; this parameterizes bundle-level retention and explicitly defers per-substrate retention asymmetry.
- Anchor & Echo summaries: JSDoc and comments stay tied to
keepMinimum,maxDays, and atomic bundle behavior. -
[RETROSPECTIVE]tag: N/A — no retrospective tag in the PR body. - Linked anchors: #11628, #11624, #10129, and #11663 are the correct lineage for this Phase 4 retention surface.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: The first sandboxedgh pr checks 11664call returnederror connecting to api.github.com; the escalated read-only rerun succeeded and all checks passed.[RETROSPECTIVE]: Bundle-level retention parameterization is the right intermediate shape for Phase 4: it gives operators control over disk/cadence/recovery tradeoffs without breaking the atomic backup bundle into partial-retention substrates.
🛂 Provenance Audit
- Internal Origin: #11663 from Phase 4 #11628 / meta-epic #11624, grounded in the existing backup bundle architecture from #10129 and the 2026-05-17 Memory Core recovery pressure.
Findings: Pass.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #11663 - For #11663: verified labels are
enhancement,ai; noepiclabel. - Commit body repeats
(#11663)but does not introduce a stale epic close-target.
Findings: Pass.
📑 Contract Completeness Audit
- The originating ticket defines the intended config contract through acceptance criteria rather than a formal Contract Ledger matrix.
- Implemented PR diff intentionally narrows the ticket's KB+MC config proposal to MC-only config ownership; the PR body's
Deltas from ticketsection explicitly documents the change and the technical reason. - The shipped config surface is
backupRetention: {keepMinimum: 3, maxDays: 30}inai/mcp/server/memory-core/config.template.mjs, consumed bybuildScripts/ai/backup.mjsthroughmcConfig.backupRetentionwith function-default fallback.
Findings: Pass — the live implementation and PR body define a coherent MC-owned contract. No RA needed for the older KB+MC ticket sketch.
🪜 Evidence Audit
The PR body declares Evidence: L2 (8 new unit tests covering default byte-equivalence anchor + tighter / higher-cadence / missing-config / empty-config / extreme-zero scenarios; existing 7 backup.spec tests pass as regression) → L2 required (retention policy is mechanically verifiable in unit-test scope; no runtime-only surfaces). No residuals.
- Evidence declaration line present.
- Achieved L2 matches the close-target requirement: retention behavior is mechanically testable at unit scope.
- No residuals claimed; post-merge smoke items are operator validation for live config/history, not missing acceptance coverage.
- No evidence-class collapse: review and PR body keep this at L2, not runtime smoke or live deployment proof.
Findings: Pass.
📜 Source-of-Authority Audit
N/A — this review does not cite operator or peer authority as the basis for a demand. The approval is based on diff, ticket, local tests, and CI state.
📡 MCP-Tool-Description Budget Audit
N/A — no ai/mcp/server/*/openapi.yaml tool descriptions changed.
🔌 Wire-Format Compatibility Audit
N/A — no JSON-RPC, A2A, MCP tool, or persisted record wire format changed. This is a local config-template and backup-script behavior change.
🔗 Cross-Skill Integration Audit
-
ai/mcp/server/memory-core/config.template.mjschanged; the PR body includes config-template clone-sync guidance. - Changed key is listed explicitly:
backupRetention: {keepMinimum: 3, maxDays: 30}. - Local
config.mjsbehavior is documented: zero-config deployments need no action; cloud/tighter-budget deployments can add overrides; restart/read timing is described. - No skill files,
AGENTS_STARTUP.md, or MCP tool surfaces require updates for this implementation.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Branch checked out locally:
agent/11663-backup-retention-configat34fc6287706f3e275d66a7342206082f9267e4cd. - Canonical Location: new spec is under
test/playwright/unit/ai/buildScripts/backup-retention.spec.mjs, matching the existing backup-script unit-test location. - Ran
npm run test-unit -- test/playwright/unit/ai/buildScripts/backup-retention.spec.mjs test/playwright/unit/ai/buildScripts/backup.spec.mjs-> 15 passed (710ms). - Code changed and has direct coverage: 8 new retention tests plus 7 existing backup regression tests.
Findings: Tests pass; location is canonical.
🛡️ CI / Security Checks Audit
- Ran
gh pr checks 11664after the sandboxed call hit a transient API connection error. - All checks pass: Analyze (javascript), CodeQL, check, integration-unified, lint-pr-body, unit.
- No checks pending or failing.
Findings: Pass - all checks green.
📏 Measurement Payload
Static surface:
- pr-review-guide.md: 58968 bytes
- pr-review-template.md: 13561 bytes
- ci-security-audit.md: 2348 bytes
- measurement-methodology.md: 3225 bytes
Static total: 78102 bytes
Dynamic surface used in-review: PR body/commits/closingRefs + #11663 issue body + files-only diff + local test output + gh checks output.
📋 Required Actions
No required actions — eligible for human merge.
Optional follow-up, not merge-blocking:
- Add explicit validation/normalization for malformed retention config values if
backupRetentionis later exposed beyond operator-owned localconfig.mjs.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 — 5 points deducted because the ticket-to-implementation contract needed a documented scope correction from KB+MC fallback to MC-only ownership; the correction is architecturally better, but future readers must notice the PR body's delta note.[CONTENT_COMPLETENESS]: 95 — 5 points deducted because the originating ticket uses acceptance criteria rather than a formal Contract Ledger for this new config surface. PR body, JSDoc, config-template docs, and test comments are otherwise complete and precise.[EXECUTION_QUALITY]: 95 — 5 points deducted for the non-blocking absence of malformed config normalization. Direct behavior, defaults, edge cases, and regression coverage all pass locally and in CI.[PRODUCTIVITY]: 100 — I actively considered ticket AC drift, default behavior preservation, per-substrate retention scope creep, local config clone-sync, and test placement, and confirmed none block this PR.[IMPACT]: 80 — Major operational substrate improvement: operators can tune backup retention for cloud disk budgets, high cadence, and recovery windows without forkingbackup.mjs, while preserving the existing default safety floor.[COMPLEXITY]: 45 — Moderate-low: one config-template key, one exported retention function argument, one call-site change, and focused tests. The cognitive load is mostly policy semantics rather than broad code churn.[EFFORT_PROFILE]: Quick Win — High operational ROI with small, well-contained implementation and strong unit-test coverage.
Eligible for @tobiu's human merge gate.
Resolves #11663
Authored by Claude Opus 4.7 (Claude Code). Session 7360e917-1733-4cdd-a6f3-5ac51c34b838.
FAIR-band: in-band [15/30] — operator nightshift-mode directive 2026-05-20 ~01:00Z: "if parallel tracks are not possible, you 2 can pick up items from the todo and in progress tickets ... anti-pattern: wake-up message -> ack, nothing to do." Parallel lane to @neo-gpt's #11631 (write-side tenant stamping in VectorService). Different files —
buildScripts/ai/backup.mjs+ai/mcp/server/memory-core/config.template.mjs— zero merge collision risk.Phase 4 sub-ticket of #11628. Replaces the hardcoded
K=3, N_DAYS=30constants incleanOldBackups()with operator-configurable two-axis policy. The 2026-05-17 MC wipe recovery anchor underlines why the defaults matter —K=3 + N=30retained the 5/16 bundle that became the recovery source. This PR preserves that protective floor as the default while allowing operator tuning for tighter disk budgets, higher cadence, or recovery-critical scenarios.Evidence: L2 (8 new unit tests covering default byte-equivalence anchor + tighter / higher-cadence / missing-config / empty-config / extreme-zero scenarios; existing 7 backup.spec tests pass as regression) → L2 required (retention policy is mechanically verifiable in unit-test scope; no runtime-only surfaces). No residuals.
Config-Template Clone-Sync Guidance (per
mcp-config-template-change-guide.md)This PR changes
ai/mcp/server/memory-core/config.template.mjs.Changed config keys:
backupRetention: {keepMinimum: 3, maxDays: 30}— new key with defaults matching pre-#11663 hardcoded constants.Local
config.mjsfollow-up:cleanOldBackups()falls through to default{keepMinimum: 3, maxDays: 30}whenmcConfig.backupRetentionis undefined — pre-existing clones keep behaving identically.backupRetentionblock to localconfig.mjswith override values. Examples:{keepMinimum: 1, maxDays: 7}{keepMinimum: 24, maxDays: 2}{keepMinimum: 7, maxDays: 90}config.mjsretention values. The nextnpm run ai:backupinvocation reads the updated config.Peer A2A notification: sent direct-DM per operator routing correction (Gemini harness unstable; no AGENT:* broadcast).
Deltas from ticket (if any)
backupRetentionto BOTH KB + MC config templates for discoverability. Discovered during impl that KB config has no backup-related keys (backup path + orchestration lives entirely in MC config). Adding to only MC keeps the contract local to the substrate that owns it — better than scattering the same key across two configs with a fallback chain. The ticket plan called formcConfig.backupRetention ?? kbConfig.backupRetention ?? defaults; actual impl is justmcConfig.backupRetentionwith function-default fallback. Simpler, single source of truth.cleanOldBackupsexported. The pre-#11663 function was module-private. Exporting it (required for the new spec to import + test directly) is a minor public-surface change, but a defensible one — the function is the canonical retention primitive that any future Phase 4 retention work will build on.Test Evidence
New spec:
test/playwright/unit/ai/buildScripts/backup-retention.spec.mjs— 8 tests, 8 passed in 667ms.cleanOldBackups(root, logger)matches pre-#11663K=3, N=30behavior exactly — byte-equivalence anchor{keepMinimum: 3, maxDays: 30}{K=1, N=7}{K=24, N=2}undefinedretention argument{K=3, N=30}{}retention argumentkeepMinimumfloor protects ancient bundles{K=0, N=0}Regression:
npm run test-unit -- test/playwright/unit/ai/buildScripts/backup.spec.mjs→ 7 passed in 791ms (pre-existing tests; refactor preserves behavior).Post-Merge Validation
npm run ai:backupinvocation produces no behavior change under default config (smoke-test the retention sweep doesn't delete bundles it shouldn't) — operator post-merge verification.mcConfig.backupRetention = {keepMinimum: 1, maxDays: 7}in local config.mjs correctly deletes bundles >7d old beyond newest-1 (tighter-config cloud-deployment smoke)..neo-ai-data/backups/history retention behavior (manual operator check that the recovery-source 5/16 bundle is still present in the live deployment).Commits
34fc62877— feat(ai): configurable bundle retention via aiConfig.backupRetention (#11663)