Files
ModelForge/docs/architecture/RECOVERY_COMPATIBILITY.md

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.