Schema 2 upgrade
import { Aside } from ‘@astrojs/starlight/components’;
The v11 major moves every durable store into one database, <data_dir>/warden-db/
(schema 2). A fresh install starts there. An existing install is cut over once,
offline, by a journaled migration. There is no dual-writer mode and no silent
fallback: the daemon runs on warden-db only.
For update planning, rollback, repair, factory reset, and the backup-gated retirement of retained stores, see the Operator guide: updates and data safety.
The normal path: warden update
Section titled “The normal path: warden update”warden update handles the whole thing: it inspects the data directory before stopping the
daemon, recognizing the running daemon and expected live index lag as non-blocking notes
(genuine corruption safely blocks before any binary swap or modification). It then stops the
daemon, takes a data snapshot, swaps the binary, and the new binary runs warden migrate --apply
under the ownership lock. Malformed records in savings/ledger.jsonl with valid line boundaries
are safely quarantined into <data_dir>/quarantine/savings/ with SHA-256 manifests while valid
events are preserved. Post-stop verification is strict: any foreign lock or unquarantineable
corruption safely refuses before change (ErrRefusedBeforeChange). The daemon is restarted only
afterwards and must report the new version and schema_version: 2. If anything fails, the
previous binary and data are restored without mutation and the old daemon is verified healthy
again (a promoted warden-db/ is moved to backups/abandoned-schema2-<timestamp>/, never deleted).
Preview first with warden update --plan.
The manual path
Section titled “The manual path”With the daemon stopped and the new binary installed:
warden migrate --plan # read-only: sources, target, backup path, anything that blockswarden migrate --apply # preflight → backup → stage → verify → promote → ledgerwarden version # Data schema: 2What --apply does, in order: takes the data-dir ownership lock (a running daemon
makes it refuse); strict preflight (corruption, conflicting history, duplicate
identities, stale indexes, unsafe artifacts — nothing is repaired or guessed);
creates and verifies the immutable backup; exports to a staging store and proves it
equal to the legacy data (record counts, canonical hashes, relationships); atomically
promotes it to warden-db/; verifies again; and only then moves the schema ledger to 2.
Guards
Section titled “Guards”| You run… | …on… | Result |
|---|---|---|
| new daemon | schema-1 data | refuses before opening any store: must be cut over to the single warden-db |
| old daemon | schema-2 data | refuses (data_newer): there is no automatic downgrade |
| any binary | warden-db/ beside a schema-1 ledger | refuses (schema2_residue): two candidate authorities, nothing is adopted |
| any binary | an interrupted migration | refuses until you --resume or --restore |
wd update | non-quarantineable JSONL or genuine index damage | refuses preflight before any change; points directly to store without broad repair |
wd update | rogue process taking lock post-stop | cutover refuses without mutation; updater rolls back cleanly |
Pipelines deleted after an earlier import
Section titled “Pipelines deleted after an earlier import”Schema 1 kept a pipeline twice: pipelines-db and the old pipelines/<id>.json files.
The cutover treats pipelines-db as authoritative only where the files agree with it,
and never guesses between them:
| Situation | Outcome |
|---|---|
a file identical to its pipelines-db record | deduplicated |
a file that differs from its record (pipeline_legacy_conflict) | refuses; names the differing fields, never their values |
a file for a pipeline pipelines-db shows was deleted (delete tombstone) | not resurrected; the file stays untouched |
a file with no record and no tombstone (pipeline_unproven_deletion) | refuses: it cannot be proven deleted rather than never imported |
| an archived or finished agent or plan of a deleted pipeline | kept; its pipeline_id/job_id move to a historical_pipeline provenance record |
a live agent or plan of a missing pipeline (pipeline_active_ref) | refuses; the reference is never nulled and no parent is invented |
warden migrate --check previews all of this on a private copy before anything is stopped,
and a refused cutover lists its blockers (first ten) in the error. To resolve a
conflict or an unproven deletion, move the stale pipelines/<id>.json aside (the
cutover never modifies it) or, if the pipeline must be kept, restore it into
pipelines-db with the schema-1 binary first.
If it is interrupted
Section titled “If it is interrupted”Power loss or a killed process leaves the migration journaled. No daemon will start until you choose:
warden migrate --resume # continue from the journaled step with the same backupwarden migrate --restore # go back; a promoted warden-db is preserved under backups/A refusal before any live change (a corrupt or ambiguous source) leaves the data and ledger exactly as they were; fix the finding and re-run.
Offline tools on schema 2
Section titled “Offline tools on schema 2”The schema-1 tools (warden repair sessions|agents|backends|all, offline
wd models, wd savings --calibrate) refuse on schema 2 with guidance, because the
legacy directories are now inert copies. Use the daemon-backed interfaces, or
warden repair warden-db --dry-run to verify the store offline.
Legacy store retirement and safe cleanup
Section titled “Legacy store retirement and safe cleanup”After cutover, legacy stores (agents-db, projects, backends, plans/plans-db, etc.)
and the immutable backups/pre-schema2-* backup are kept for at least 90 days.
Legacy stores are never an active runtime fallback.
1. Inspecting legacy stores (warden inspect legacy)
Section titled “1. Inspecting legacy stores (warden inspect legacy)”Inspect the retained schema-1 stores and compare per-collection records against warden-db:
warden inspect legacy # read-only summarywarden inspect legacy --json # machine-readable inspectionThis command opens only temporary private copies, fingerprints before and after to guarantee zero mutations, re-verifies the cutover backup against its SHA-256 manifest, and refuses on any directory that is not at schema 2.
2. Evaluating retirement criteria (warden migrate --retirement)
Section titled “2. Evaluating retirement criteria (warden migrate --retirement)”Before removing legacy stores, verify that your installation satisfies all 9 retirement criteria:
warden migrate --retirement # check criteria statuswarden migrate --retirement --json # machine-readable criteria evaluationRetirement requires:
schema2_active: ledger is schema ≥ 2 without an open migration journalwarden_db_present:warden-db/existsmigration_succeeded: cutover committed in ledger historybackup_verified:backups/pre-schema2-*verifies againstMANIFEST.sha256no_unresolved_failures: no migration or recovery failures since cutoverno_rollback: no updater rollbacks or abandoned targetszero_legacy_runtime_opens: zero legacy open attempts on schema 2schema2_runtime_observed: at least 3 daemon boots on schema 2support_window_elapsed: at least 90 days elapsed since cutover
3. Safe backup-gated cleanup (warden migrate --cleanup-legacy)
Section titled “3. Safe backup-gated cleanup (warden migrate --cleanup-legacy)”When warden migrate --retirement reports eligible: true:
# Preview what would be movedwarden migrate --cleanup-legacy --dry-run
# Confirm and quarantine legacy storeswarden migrate --cleanup-legacy --yesSafety guarantees:
- Never deletes directly: Migrated legacy directories are moved to
backups/legacy-quarantine-<timestamp>/. - Preserves external content: Directories holding external content not in
warden-db(such assnapshots/*.transcript) remain in place. - Audit logged: A
legacy_cleanupevent is recorded inaudit.jsonland.adoption-evidence.jsonl. - Idempotent: Re-running when already clean is a no-op.
- Uninterrupted operation:
warden-dbcontinues serving your agents and pipelines uninterrupted. - Recoverable: Restore by moving directories back from
backups/legacy-quarantine-*or restoring the immutable pre-schema2 backup. Once verified, delete the quarantine directory manually.