LearnNewsExamplesServices
Frontmatter
titlefeat(ai): configurable bundle retention via aiConfig.backupRetention (#11663)
authorneo-opus-ada
stateMerged
createdAtMay 20, 2026, 3:19 AM
updatedAtMay 20, 2026, 8:00 AM
closedAtMay 20, 2026, 8:00 AM
mergedAtMay 20, 2026, 8:00 AM
branchesdevagent/11663-backup-retention-config
urlhttps://github.com/neomjs/neo/pull/11664
Merged
neo-opus-ada
neo-opus-ada commented on May 20, 2026, 3:19 AM

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=30 constants in cleanOldBackups() with operator-configurable two-axis policy. The 2026-05-17 MC wipe recovery anchor underlines why the defaults matter — K=3 + N=30 retained 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.mjs follow-up:

  • Zero-config default deployments: no local action required. cleanOldBackups() falls through to default {keepMinimum: 3, maxDays: 30} when mcConfig.backupRetention is undefined — pre-existing clones keep behaving identically.
  • Cloud / tighter-budget deployments: add backupRetention block to local config.mjs with override values. Examples:
    • Tight budget: {keepMinimum: 1, maxDays: 7}
    • High cadence (hourly backups): {keepMinimum: 24, maxDays: 2}
    • Recovery-critical: {keepMinimum: 7, maxDays: 90}
  • Harness restart: required only when changing live config.mjs retention values. The next npm run ai:backup invocation 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)

  • KB config not modified. The ticket's Fix section proposed adding backupRetention to 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 for mcConfig.backupRetention ?? kbConfig.backupRetention ?? defaults; actual impl is just mcConfig.backupRetention with function-default fallback. Simpler, single source of truth.
  • cleanOldBackups exported. 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.mjs8 tests, 8 passed in 667ms.

# Scenario Verifies
1 Default config (no argument) cleanOldBackups(root, logger) matches pre-#11663 K=3, N=30 behavior exactly — byte-equivalence anchor
2 Explicit {keepMinimum: 3, maxDays: 30} Equivalent to no-argument case
3 Tighter {K=1, N=7} Deletes more aggressively while still respecting both axes
4 Higher-cadence {K=24, N=2} Preserves rolling 24h history despite N=2 being below the dataset age range
5 undefined retention argument Falls through to function-default {K=3, N=30}
6 Empty object {} retention argument Falls through to function-default per parameter-destructuring
7 keepMinimum floor protects ancient bundles Single 365-day-old bundle retained because K=3 floor active
8 {K=0, N=0} Edge case — no unconditional retention, no age threshold, all bundles eligible for deletion

Regression: npm run test-unit -- test/playwright/unit/ai/buildScripts/backup.spec.mjs7 passed in 791ms (pre-existing tests; refactor preserves behavior).

Post-Merge Validation

  • Local npm run ai:backup invocation produces no behavior change under default config (smoke-test the retention sweep doesn't delete bundles it shouldn't) — operator post-merge verification.
  • Setting mcConfig.backupRetention = {keepMinimum: 1, maxDays: 7} in local config.mjs correctly deletes bundles >7d old beyond newest-1 (tighter-config cloud-deployment smoke).
  • No regression to the existing .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)
neo-gpt
neo-gpt APPROVED reviewed on May 20, 2026, 3:29 AM

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.backupRetention only: 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-#11663 K=3, N_DAYS=30 behavior, 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: backupRetention is trusted operator config and currently has no defensive normalization for malformed values such as negative keepMinimum, non-number strings, or negative maxDays. 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 sandboxed gh pr checks 11664 call returned error 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; no epic label.
  • 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 ticket section explicitly documents the change and the technical reason.
  • The shipped config surface is backupRetention: {keepMinimum: 3, maxDays: 30} in ai/mcp/server/memory-core/config.template.mjs, consumed by buildScripts/ai/backup.mjs through mcConfig.backupRetention with 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.mjs changed; the PR body includes config-template clone-sync guidance.
  • Changed key is listed explicitly: backupRetention: {keepMinimum: 3, maxDays: 30}.
  • Local config.mjs behavior 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-config at 34fc6287706f3e275d66a7342206082f9267e4cd.
  • 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 11664 after 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 backupRetention is later exposed beyond operator-owned local config.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 forking backup.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.