.vouch/ layout is the durable contract: yaml claims, markdown pages with
frontmatter, json sessions, the jsonl audit log. As the pydantic models in
src/vouch/models.py
evolve, a KB created against an older model would become a
load-time error. vouch migrate gives that evolution a versioned, reversible,
audit-logged upgrade path so a schema change never silently breaks a KB in the
wild.
Two version axes
vouch tracks two independent versions, andvouch migrate covers both:
| Stamp | What it governs | Reached by | |
|---|---|---|---|
| Format (integer) | config.yaml version | the .vouch/ directory layout (subdirs, .gitignore) | vouch migrate (no subcommand) |
| Schema (semver) | .vouch/schema_version | the model schema of each artifact | vouch migrate <subcommand> |
.vouch/schema_version file is treated as the baseline (0.1.0),
so existing KBs keep loading until their first migrate. vouch init stamps the
current version on bootstrap.
Commands
Manifests
Migrations are data, not code: yaml manifests in the repo-rootmigrations/
directory, one consecutive version step each. See
migrations/README.md
for the full format and the
transform verbs (rename, default, drop, split, merge). Only consecutive
manifests apply — a 0.1 → 0.5 KB walks through 0.2, 0.3, 0.4 in order.
Safety model
- Atomic per file. Each artifact is rewritten to a temp file,
fsync-ed, thenos.replace-d into place. A file is always its old bytes or its new bytes — never a torn mix. - Reversible. Before any rewrite, the prior content of every file the step
touches is journalled to
.vouch/migrations/rollback-<id>.jsonl.vouch migrate rollbackreplays it, restoring the exact prior bytes — so apply → rollback is byte-equivalent (modulo the audit-log entries recording the round trip). - Crash-safe. The journal is written and fsynced before the first rewrite,
and
.vouch/schema_versionis bumped last. An interrupted apply therefore leaves the KB reporting its prior version, with a journal to recover from. - Precondition. Apply refuses if the KB has pending proposals — it won’t
rewrite a reviewer’s in-flight queue out from under them. Resolve them with
vouch list-pendingfirst. state.dbis disposable. The runner does not migrate the FTS5 / embedding cache; the CLI rebuilds it after a successful apply.
Audit trail
Each applied manifest logs onekb.migrate.apply event (manifest id, file count,
rollback-journal id); a rollback logs kb.migrate.rollback. The legacy format
migration continues to log kb.migrate.
Out of scope (v1)
- Cross-major jumps in a single manifest (consecutive only).
custom: path/to/migration.pyscript transforms (lands once the verb taxonomy stabilises).- Migrating
state.db(disposable; rebuilt byvouch index). - Bundle version embedding / refusing mismatched imports — a follow-up.
- Concurrent multi-process migrate runs (single-writer assumption).