Audit migration runbook
fastpace audit migrate — runbook
Pre-0.5.0 fastpace installs wrote fastpace/audit.log as plain JSONL — one tool call per line, no chain links, no signatures. From 0.5.0 onward we ship the F0.2 hash-chained signed audit log. This runbook walks you through migrating the legacy file to the chain format without losing any history.
If you're on 0.5.0 or later from day one, you don't need this — your audit log was born in chain format. You can confirm with:
$ fastpace verify
[ok] chain integrity: N entries
When to run it
Run fastpace audit migrate once, after upgrading to 0.5.0+, if and only if fastpace verify reports a "legacy_format" status. The command is a no-op when the log is already a chain.
$ fastpace verify
[err] legacy_format detected at line 1: missing entry_hash
run `fastpace audit migrate` to upgrade
What the migration does
- Reads every line of
fastpace/audit.login order. - For each entry, computes: -
seq— monotonically increasing from 0 -prev_hash— the previous entry'sentry_hash(or the genesis hash0000…for the first entry) -entry_hash—sha256(canonical_json(body))over a body that includes seq + prev_hash but excludes entry_hash + signature -signature— Ed25519 overentry_hashusing the F0.1 install identity, if an identity exists. If you never ranfastpace identity showbefore migrating, the migration writes unsigned entries. They still chain-verify; they just don't attribute to a specific install identity. - Writes the new chain-format file at
fastpace/audit.log. - Backs the original up to
fastpace/audit.log.audit-migrated-<TIMESTAMP>.bak.jsonl— never deletes. You can compare with diff orwc -lafterwards.
Pre-flight checklist
Before running:
- [ ] Stop any process that's writing to the audit log (the local UI's
fastpace ui, any active agent run). The migration is atomic on most filesystems but concurrent writers can race the rename. - [ ] Confirm you have a fastpace install identity: ``
$ fastpace identity show install_id 3b63e2d3d7f44635 fingerprint sha256:3b63e2d3… status active`If not, runfastpace identity show` to bootstrap one. The migration will sign every entry with this key going forward. - [ ] Skim the legacy log so you know what's in there: ``
$ wc -l fastpace/audit.log $ head -2 fastpace/audit.log``
The migration command
$ fastpace audit migrate
[ok] migrated 1247 entries into the chain format.
backup fastpace/audit.log.audit-migrated-2026-04-30T14-21-08-921Z.bak.jsonl
signed yes
Run `fastpace verify` to confirm the migrated chain is intact.
JSON-output mode (for CI / scripting):
$ fastpace audit migrate --json
{
"ok": true,
"migrated": 1247,
"signed": true,
"path": "/repo/fastpace/audit.log",
"backup_path": "/repo/fastpace/audit.log.audit-migrated-2026-04-30T14-21-08-921Z.bak.jsonl"
}
Verify after migrating
$ fastpace verify
[ok] chain integrity: 1247 entries
signatures verified 1247
If fastpace verify reports chain BROKEN, don't touch anything — your .bak.jsonl file is the source of truth. Run with debug logging to capture the failure context:
$ FASTPACE_DEBUG=1 fastpace verify > verify.log 2>&1
…and email verify.log to security@fastpace.net. Migration is deterministic — if it broke once on your inputs, it'll break the same way on ours.
Restoring from backup
If you need to roll back the migration entirely (e.g. a downstream tool only understands the legacy format):
$ cp fastpace/audit.log.audit-migrated-<TS>.bak.jsonl fastpace/audit.log
You can keep the backup file in the repo — it's gitignored by default via the fastpace/.audit-migrated-* rule. If you want to permanently discard it once you're confident in the migration:
$ rm fastpace/audit.log.audit-migrated-*.bak.jsonl
Edge cases
The legacy log was empty
Migration is a no-op; the chain-format file is written with zero entries. fastpace verify reports 0 entries and passes.
The legacy log has a malformed line
Migration stops at the first parse error and leaves the original file untouched. The error report identifies the line:
[err] migrate: line 412 parse failed: Unexpected token in JSON
the original file is unchanged at fastpace/audit.log
Open the offending line, fix it (typical cause: a partial write from a crash mid-append), then re-run fastpace audit migrate.
You ran the migration twice
Idempotent — once the file is chain-format, the second run reports:
$ fastpace audit migrate
[ok] audit log is already in chain format — nothing to migrate.
You want to migrate a subset
Not supported. Migration walks the full file in order; the chain has to be contiguous from seq=0 for fastpace verify to pass.
What the migration doesn't do
- It doesn't backfill signatures on entries that predate the F0.1 identity. They migrate with
signature: ""and chain-verify cleanly; thesignature_unsigned_countfield in the verify report tells you how many. - It doesn't change any audit entry's body. Tool name, summary, ts — all preserved verbatim. Only the chain-binding fields (seq, prev_hash, entry_hash, signature) are added.
- It doesn't delete the backup. You decide when to drop it.
Related commands
fastpace verify— chain integrity check (run this after migrating)fastpace audit export-bundle— pack audit.log + public key + offline verifier into a directory you can hand to an external auditorfastpace audit list-exports— enumerate prior export bundles
Reporting an issue
security@fastpace.net. Include the verify output and the first 5 lines of the failing legacy log (redact prompt content; we only need the structural fields).