4.3 KiB
Recovery compatibility
M17 will use this matrix for upgrades; M15 establishes it and proves the fail-closed directions.
Backup schema versus ModelForge build
| Backup Alembic revision | Restoring build | Result |
|---|---|---|
| equals the build's head | same or newer build | restored as is |
| older, present in the build's chain | newer build | restored, then migrated forward to head |
| absent from the build's chain | older build | SCHEMA_TOO_NEW — refused |
| recorded as none | any build | preflight fails; the backup is not restore eligible |
| any | build shipping no Alembic chain | SCHEMA_UNKNOWN — refused |
The chain is walked by down_revision, not by filename order, so a revision that sorts earlier but
descends later is still placed correctly. Discovery resolves the source checkout, the installed
image and an explicitly configured MODELFORGE_ALEMBIC_DIRECTORY.
A newer backup is never silently downgraded onto older code. Restoring an older backup onto a newer build runs the forward migrations that build ships and nothing else.
PostgreSQL
| Source major | Destination major | Result |
|---|---|---|
| 17 | 17 | supported |
| 17 | 18+ | permitted; preflight records both majors |
| 17 | 16 or lower | POSTGRES_VERSION_INCOMPATIBLE |
The control-plane image pins its client tools to the server major version. pg_dump from an older
major cannot read a newer server, which is why the tools ship with the control plane rather than
being inherited from the host.
Manifest schema
manifest_schema_version is m15.1. Verification requires the manifest file and the journaled
backup_manifest_entries to agree exactly, so a manifest written by a different schema version is
rejected rather than partially interpreted.
Semantic fingerprint
FINGERPRINT_VERSION is m15.2; m15.2 adds the schema-0024 audit_chain_heads singleton to the
audit group. Current schema-0024 fingerprints are comparable only when both sides are m15.2 and
both contain exactly one complete checkpoint row. Both fingerprints must enumerate the complete
authoritative table contract: a missing expected table, an unexpected table, an absent/sparse
entry or a malformed count/digest is incompatible. After reconciliation, every non-current-truth
table must retain its exact count and digest except the enumerated mutations: one canonical audit
marker, the corresponding singleton-head advance, and same-count suppression changes in
operational_alerts.
Every fingerprint also records the exact physical table-name set, including current-truth tables
whose rows are intentionally excluded. Dropping host_telemetry_latest, adding an attacker table,
or omitting any authoritative table is therefore incompatible. Table digests and group/overall
digests use typed, length-prefixed cells and rows; separators, nulls and different scalar types
cannot alias. A table exceeding the 250,000-row cap is marked BOUNDED; that is useful diagnostic
evidence but can never authorize READY because it is not a complete count-and-digest proof.
There is one explicit cross-version rule: a backup whose recorded Alembic revision is exactly
20260828_0022 and whose fingerprint is m15.1 may be compared with a restored/migrated m15.2
fingerprint when the old side has no checkpoint and the new side has exactly one. Strict audit-chain
verification must pass independently. The transition still permits exactly one canonical audit
marker and no other digest/count change (apart from same-count alert suppression). No other absent
or introduced table, fingerprint-version pair or schema revision receives that allowance.
Auth migration 20260830_0023 adds constraints only, so it introduces no physical-table exception
inside this schema-0022-to-0024 comparison.
Encryption
MFBK1 frames the encrypted payload with the key id it was sealed under. A payload sealed by a
different key id is still authenticated by GCM and fails closed. The algorithm is recorded per
backup set, so introducing a second algorithm later does not invalidate existing backups.
Recovery contract
Recovery API responses never carry a DSN with its password, a credential hash, or an enrolment
token. RestorePlanResponse exposes database_destination_redacted and has no
database_destination field at all, which is asserted against the published OpenAPI document.