Context
#14030 AC3: "if corruption isn't detected within the backup-retention window, the last good backup ages out and recovery becomes impossible." The relationship must be documented and guarded — the data-integrity detect cadence (#14026) must be provably faster than backup retention, so a last-good backup never ages out before corruption is caught.
Like AC2 (#14053), this splits into a verdict-producing pure check (buildable now) and the config-wiring + CI-guard (gated on #14026's cadence config + the live retention config).
The Fix (this slice — the verdict-half)
A pure function evaluateDetectionRetentionSla({detectCadenceMs, backupRetentionMs, safetyFactor}) in ai/scripts/maintenance/detectionRetentionSla.mjs: given the worst-case detect cadence and the backup-retention window, returns a structured verdict {withinSla, marginMs, requiredMaxDetectMs, reason} — withinSla true iff detectCadenceMs <= backupRetentionMs / safetyFactor (detection catches corruption with enough margin that recovery can run before the last-good backup is pruned). Pure, no I/O. JSDoc documents the relationship + the safety-factor rationale.
Acceptance Criteria
Out of Scope (stays on #14030 / gated)
- The CI-guard wiring that reads #14026's live detect-cadence config + the live backup-retention config and fails CI on a breach (gated on #14026 being built + the retention config surfaced).
- AC1 (#14055 / #14058) + AC2 (#14053).
Contract Ledger Matrix
| Consumed surface |
Field |
Type / values |
Change |
Consumer impact |
Fallback / semantics |
evaluateDetectionRetentionSla() return (exported from detectionRetentionSla.mjs) |
the verdict object |
withinSla: Boolean · marginMs: Number (retention/safetyFactor − cadence; negative when breached) · requiredMaxDetectMs: Number (retention/safetyFactor) · reason: String|null (null when within; the breach explanation otherwise) |
New exported function (additive — no signature change to existing exports) |
No current consumer (new surface). Intended consumers: the CI-guard (next slice — breach → red) and an operator SLA probe. |
Pure; throws TypeError on non-positive/missing detectCadenceMs/backupRetentionMs; safetyFactor defaults to 2 (detection ≤ half the retention window). |
- Docs: the function's JSDoc documents the verdict shape + the detection-latency<retention relationship + the safety-factor rationale.
- Evidence: L2 unit — within / breached / boundary / invalid-input.
Related
- #14030 — parent (AC3); #14039 — v13.1 epic.
- #14026 — the detect-signal whose cadence this SLA constrains (the wiring gates on it).
- #14053 (AC2 check-half) / #14055 (AC1 producer-core) — sibling verdict-half slices.
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)
Context
#14030 AC3: "if corruption isn't detected within the backup-retention window, the last good backup ages out and recovery becomes impossible." The relationship must be documented and guarded — the data-integrity detect cadence (#14026) must be provably faster than backup retention, so a last-good backup never ages out before corruption is caught.
Like AC2 (#14053), this splits into a verdict-producing pure check (buildable now) and the config-wiring + CI-guard (gated on #14026's cadence config + the live retention config).
The Fix (this slice — the verdict-half)
A pure function
evaluateDetectionRetentionSla({detectCadenceMs, backupRetentionMs, safetyFactor})inai/scripts/maintenance/detectionRetentionSla.mjs: given the worst-case detect cadence and the backup-retention window, returns a structured verdict{withinSla, marginMs, requiredMaxDetectMs, reason}—withinSlatrue iffdetectCadenceMs <= backupRetentionMs / safetyFactor(detection catches corruption with enough margin that recovery can run before the last-good backup is pruned). Pure, no I/O. JSDoc documents the relationship + the safety-factor rationale.Acceptance Criteria
evaluateDetectionRetentionSlareturnswithinSla:truewhen detect cadence ≤ retention/safetyFactor,false(with areason+requiredMaxDetectMs) otherwise.TypeError) — no silent pass.Out of Scope (stays on #14030 / gated)
Contract Ledger Matrix
evaluateDetectionRetentionSla()return (exported fromdetectionRetentionSla.mjs)withinSla: Boolean·marginMs: Number(retention/safetyFactor − cadence; negative when breached) ·requiredMaxDetectMs: Number(retention/safetyFactor) ·reason: String|null(null when within; the breach explanation otherwise)TypeErroron non-positive/missingdetectCadenceMs/backupRetentionMs;safetyFactordefaults to 2 (detection ≤ half the retention window).Related
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)