fix(#670): self-healing recovery for installer-migration checksum drift (#675)

Editing the body of an already-released installer migration drifts its computed
checksum (it hashes plan.toString()). The integrity guard then hard-aborted
every prior install on upgrade with "applied migration checksum changed" — a
100% reproducible blocker (v1.3.0, all platforms).

Already-applied migrations are filtered out of `pending` and never re-run, so
a drifted checksum is functionally inert. ADR-0008 anticipates checksum-mismatch
state as something the install-state layer must handle gracefully (plan -> apply
-> recover/report), not abort on.

This supersedes the published-checksum allowlist merged in #674 (per-release
maintenance debt — every historical checksum hand-pinned, still throws for any
unregistered value) with a general, self-healing recovery:

- Replace the throwing guard with non-fatal `collectAppliedChecksumDrift`,
  surfaced on `plan.checksumDrift`.
- Reconcile drifted stored checksums durably on the next state write
  (`reconcileDriftedChecksums`), idempotently (no perpetual writes).
- Relocate the "shipped migration bodies are immutable" rule to a CI baseline
  test that locks every shipped migration's checksum and fails on body drift —
  where #615 should have been caught, instead of blocking users.

Removes #674's legacyChecksums field, per-migration checksum pins,
published-checksums.json fixture, and compat test.

Fixes #670

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-04 13:44:05 -04:00
committed by GitHub
parent cb09aed208
commit cdd78bd2aa
14 changed files with 279 additions and 226 deletions

View File

@@ -121,14 +121,17 @@ Required fields:
}
```
The checksum is calculated from the migration definition. If an applied
migration's checksum changes, the installer must warn and refuse to silently
re-run it. Fix-forward migrations should use a new migration id.
The checksum is calculated from the migration definition.
Migration records may pin a stable `checksum` and list `legacyChecksums` for
checksums already written by published packages. This is only for compatibility
with released install state; new behavior should still ship as a new migration
id rather than mutating an already-applied migration in place.
An already-applied migration is never re-run, so a drifted checksum is
tolerated at runtime: it is collected in `plan.checksumDrift` and reconciled
into install state on the next write, rather than aborting the user's upgrade
(this unblocks upgrades — see issue #670).
The "shipped migration bodies are immutable" rule is enforced in CI by a
committed checksum-baseline test in `tests/installer-migrations.test.cjs`.
If you need to change the behaviour of a released migration, add a NEW
fix-forward migration id instead of editing the shipped body.
## Migration Record