Check the migration actually kept everything, and let report say so

internal/validate was written and tested and then never called: `run` ended
at cutover, so the tool performed a migration and never confirmed it had
carried the data across, and `report` was an error message pointing at the
package that would have answered.

`run` now compares the migrated instance against the snapshot preflight took
and fails if an account or a domain that existed before is missing from it.
The comparison runs against the service cutover has just started, which is
the instance people will actually use - its real config, its real ports,
under its real service manager - and costs no extra downtime; booting a
second copy inside the maintenance window would. BootCheck stays as the
equivalent for an instance the tool boots itself.

The service is left running on a failure. By that point the store has been
migrated in place, so stopping it undoes nothing, and only the operator can
weigh the finding against their recovery point.

A check that could not run is reported as skipped, never as a pass. Preflight
only captures the "before" when it has an admin URL, and a run without one
has to say it compared nothing rather than imply everything survived - which
is the exact failure ARCHITECTURE.md §4.7 warns about. `report <run-id>`
re-reads the recorded verdict rather than re-checking: run again next week
and you would be asking how the instance looks now, not how it looked when
it was migrated.

§4.7 said validation ran after cutover while the only implementation booted
its own copy, and listed a suite far larger than what exists. It now says
which of the two happens, and which checks are real.
This commit is contained in:
2026-08-24 12:27:36 -07:00
parent 558af1005f
commit 28128633ef
9 changed files with 515 additions and 7 deletions
+11 -2
View File
@@ -22,10 +22,11 @@ has been performed end to end.
| `stalwart-migrate rehearse` | **Works** — read-only; converts your settings and reports what won't carry over |
| `stalwart-migrate run` | **Works** — performs the migration; `--recovery-point-confirmed --yes` |
| `stalwart-migrate status <id>` | **Works** |
| `stalwart-migrate report <id>` | Not implemented |
| `stalwart-migrate report <id>` | **Works** — prints what validation found for a run |
**`run` performs the migration**, in the order
preflight → stage → dump → stop → convert → recovery-mode → cutover. It
preflight → stage → dump → stop → convert → recovery-mode → cutover
validate. It
needs two flags: `--yes` (intent) and `--recovery-point-confirmed` (a claim
that you have a snapshot or backup you have verified you can restore — this
tool cannot undo a migration and will not start without it).
@@ -37,6 +38,14 @@ Measured on a full migration: the store converts in seconds, and the service
was down for **6 seconds** end to end. Plan the window around verification,
not data volume.
After cutover, `run` compares the migrated instance against the snapshot
preflight took, and fails the command if an account or a domain that existed
before is missing from it. The service is left running either way — by that
point the store has been migrated in place, so stopping it would not undo
anything; your recovery point is the way back. `report <run-id>` prints the
same finding again later. Where preflight had no admin URL to snapshot from,
validation reports itself as skipped rather than passed.
Package state:
Lines are implementation only; each package carries its tests alongside.