78 lines
4.3 KiB
Markdown
78 lines
4.3 KiB
Markdown
# 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.
|