Refuse multi-tenant installs in preflight, not after the service is stopped

A second live attempt failed in the same shape as the first: preflight
clean, settings dumped and converted, then a failure during recovery-mode
migration with the mail server already stopped.

    created Tenant (1)
    created Domain (9)
    create Account restore-13: invalidForeignKey | Object id: Domain#d

migrate_v016.py carries the Tenant and the Domains but emits every Account
with a null tenantId, so the account references a tenant-owned domain while
belonging to no tenant and the foreign key is rejected. That is Stalwart's
converter and there is nothing this tool can do about it: a multi-tenant
install has to be migrated by hand until the converter handles tenants.

What this tool got wrong was the timing. Tenant principals are one API call
away and were readable the entire time the server was running. Preflight now
queries them and fails before anything is touched, with an explanation of
exactly what would otherwise fail and when.

This is the same lesson as the stalwart-cli check: knowable in advance,
discovered after a production mail server had been stopped, twice. Any
dependency of the conversion belongs in preflight, not in the phase that
consumes it.

Also makes the external-tool checks advisory during `rehearse`, which never
invokes stalwart-cli - refusing to run read-only reconnaissance because the
operator lacks a tool that reconnaissance would tell them to get was
backwards.
This commit is contained in:
2026-08-23 23:51:43 -07:00
parent 9faa21f4f1
commit c29140b6b3
6 changed files with 141 additions and 2 deletions
+25
View File
@@ -829,6 +829,31 @@ happens to need them. `preflight.DeploymentKind` is a type alias for
- `tenant-admin` has no v0.16 equivalent and is reported as unrestorable
rather than silently dropped.
- **Multi-tenant installs cannot be migrated by this path, and preflight now
refuses them.** A second live attempt on 2026-08-24 got further - preflight
clean, binary staged, settings dumped and converted - and then failed
during recovery-mode migration, again with the service already stopped:
created Tenant (1)
created Domain (9)
create Account restore-13: invalidForeignKey | Object id: Domain#d
`migrate_v016.py` carries the Tenant and the Domains but leaves every
Account's `tenantId` null, so an account references a tenant-owned domain
while belonging to no tenant and the foreign key is rejected. This is
Stalwart's converter, not this tool, and there is no way around it from
here - a multi-tenant install has to be migrated by hand until the
converter handles it.
The tool's failure was in *when* this was discovered. Tenant principals
are one API call away and were readable while the server was running, so
preflight now queries them (`stalwartapi.Client.TenantNames`) and fails
before anything is touched. That is the same lesson as the stalwart-cli
check immediately below: both were knowable in advance, and both were
found after a production mail server had been stopped. Any future
dependency of the *conversion* belongs in preflight, not in the phase that
needs it.
- **A live migration attempt failed and cost a restore. Three defects, all
fixed, all now proven against a reproduction.** On 2026-08-24 a real
migration stopped a production mail server and then discovered the host's