Context
#14030 AC2: "backups are assumed restorable but never verified — a corrupt/incomplete backup means no recovery." The check splits cleanly into two halves: a verdict-producing restorability check (independent, buildable now) and the alert-on-failure routing (escalation-gated — it shares the AC1 escalation-mechanism decision currently routed to @neo-gpt).
The Fix (this slice — the check-half)
verifyLatestBackupRestorable({backupRoot}) in ai/scripts/maintenance/restore.mjs: finds the newest backup-<ISO-ts>/ bundle and runs the same pre-flight validateBundle gate the restore path uses (required subdirs present, JSONL parseable, bundle-meta.json parseable) against it, returning a structured verdict {restorable, bundleRoot, reason, checkedAt}. Read-only — NO writes, NO live-store import. Reuses the existing tested validator (DRY); complements #14048 (empty-parity) and #14042 (the timeline diagnostic).
Acceptance Criteria
Out of Scope (stays on #14030)
- Alert-on-failure routing — escalation-mechanism-gated (shares AC1's sink decision, A2A-routed to @neo-gpt: no existing primitive fits a maintenance-task/backup alert cleanly;
emitConsumerFriction is embedding-specific, ContainerHealthDiagnosisService/RecoveryActuatorService are container/recovery-target-specific).
- The full "sampled logical import into a throwaway store" — this check is the parseable/structural restorability probe; a throwaway-daemon import is the heavier follow-up (infra-gated).
- AC1 (failure/overdue alert) + AC3 (detect-cadence < retention SLA).
Contract Ledger Matrix
| Consumed surface |
Field |
Type / values |
Change |
Consumer impact |
Fallback / semantics |
verifyLatestBackupRestorable() return (exported from restore.mjs) |
the verdict object |
restorable: Boolean · bundleRoot: String|null (newest backup-* probed, or null when none) · reason: String|null (null when restorable; the validateBundle failure message otherwise) · checkedAt: String (ISO timestamp) |
New exported function (additive — no signature change to existing exports) |
No current consumer (new surface). Intended consumers: the AC1 escalation/canary (alert-on-failure routing — restorable:false → escalate with reason) and an operator/scheduled restorability probe. |
Read-only probe (NO writes, NO live-store import). A consumer reads restorable (false ⇒ not a usable recovery source) + reason; bundleRoot:null + a no backup-* bundles reason means an empty/absent backup root. |
- Docs: the function's JSDoc documents the verdict shape + the read-only guarantee.
- Evidence: L2 unit — valid →
restorable:true; torn-newest (+ newest-selection) → restorable:false with reason; empty/absent root → restorable:false.
Related
- #14030 — parent (AC2); #14039 — v13.1 epic.
- #14048 (empty-parity) / #14042 (timeline) — sibling backup-integrity slices.
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)
Context
#14030 AC2: "backups are assumed restorable but never verified — a corrupt/incomplete backup means no recovery." The check splits cleanly into two halves: a verdict-producing restorability check (independent, buildable now) and the alert-on-failure routing (escalation-gated — it shares the AC1 escalation-mechanism decision currently routed to @neo-gpt).
The Fix (this slice — the check-half)
verifyLatestBackupRestorable({backupRoot})inai/scripts/maintenance/restore.mjs: finds the newestbackup-<ISO-ts>/bundle and runs the same pre-flightvalidateBundlegate the restore path uses (required subdirs present, JSONL parseable,bundle-meta.jsonparseable) against it, returning a structured verdict{restorable, bundleRoot, reason, checkedAt}. Read-only — NO writes, NO live-store import. Reuses the existing tested validator (DRY); complements #14048 (empty-parity) and #14042 (the timeline diagnostic).Acceptance Criteria
verifyLatestBackupRestorableselects the newestbackup-*bundle and returns a structured restorability verdict viavalidateBundle(no writes, no import).restorable: true; a torn-JSONL / missing-subdir bundle →restorable: falsewith areason; an empty/absent backup root →restorable: false.Out of Scope (stays on #14030)
emitConsumerFrictionis embedding-specific,ContainerHealthDiagnosisService/RecoveryActuatorServiceare container/recovery-target-specific).Contract Ledger Matrix
verifyLatestBackupRestorable()return (exported fromrestore.mjs)restorable: Boolean·bundleRoot: String|null(newestbackup-*probed, or null when none) ·reason: String|null(null when restorable; thevalidateBundlefailure message otherwise) ·checkedAt: String(ISO timestamp)restorable:false→ escalate withreason) and an operator/scheduled restorability probe.restorable(false ⇒ not a usable recovery source) +reason;bundleRoot:null+ ano backup-* bundlesreason means an empty/absent backup root.restorable:true; torn-newest (+ newest-selection) →restorable:falsewithreason; empty/absent root →restorable:false.Related
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)