---
sensitivity: public
schema_version: 1
last_validated: 2026-04-30
status: stable
---

# `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

1. Reads every line of `fastpace/audit.log` in order.
2. For each entry, computes:
   - `seq` — monotonically increasing from 0
   - `prev_hash` — the previous entry's `entry_hash` (or the genesis hash
     `0000…` 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 over `entry_hash` using the F0.1 install
     identity, **if an identity exists**. If you never ran
     `fastpace identity show` before migrating, the migration writes
     unsigned entries. They still chain-verify; they just don't
     attribute to a specific install identity.
3. Writes the new chain-format file at `fastpace/audit.log`.
4. Backs the original up to
   `fastpace/audit.log.audit-migrated-<TIMESTAMP>.bak.jsonl` —
   **never deletes**. You can compare with diff or `wc -l` afterwards.

## 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, run `fastpace 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; the `signature_unsigned_count` field 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 auditor
- `fastpace 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).
