KB-57DE

O8E pre-production hardening (Contabo) — 03-backup-readiness

5 min read Revision 1
dieu44iu-cutterv0.6o8epre-production-hardening

O8E Report 03 — Backup readiness (G3 / F4)

  • macro: v0.6-o8e-pre-production-hardening-bundle
  • date_utc: 2026-05-21 · host: Contabo vmi3080463
  • gate covered: G3 — F4 backup readiness
  • result: G3 PASS · F4 PARTIAL — mechanism READY, key + runner MISSING (operator package below)

1. Backup readiness matrix

Component State Evidence
gpg binary READY GnuPG 2.4.4, libgcrypt 1.10.3
GPG encrypt/decrypt mechanism READY — PROVEN ephemeral-key round-trip, §2
BACKUP_GPG_FPR keypair MISSING gpg --list-keys → 0 public keys; BACKUP_GPG_FPR absent from /opt/incomex/docker/.env
pg_dump READY host 16.13 + postgres container 16.13
9 backup-target tables READY — EXIST §3
backup_runner concrete impl MISSING adapter default = _refuse_backup (refuses)
pre_write_backup seam READY phases/backup.py Mode.LIVE → adapter.pre_write_backup; Mode.DRYRUN simulates a deterministic backup_sha

2. GPG mechanism proof — non-production, no persistent key

To prove the encrypt envelope without provisioning the real (sensitive) BACKUP_GPG_FPR private key, an ephemeral throwaway key was generated in an isolated temporary GNUPGHOME, used for one encrypt+decrypt round-trip, then the entire keyring was destroyed:

ephemeral key:    generated in mktemp -d GNUPGHOME (chmod 700)
encrypt:          236 bytes ciphertext produced
decrypt:          round-trip sha256 == plaintext sha256   → faithful
GPG_MECHANISM_PROOF: PASS
cleanup:          temp GNUPGHOME rm -rf'd — host root keyring still 0 keys
secrets exposed:  NONE — ephemeral key never left the temp dir, never logged

This proves the host can run gpg --encrypt / --decrypt for the backup envelope. It does not provision the production backup key — that is a deliberate operator step (§5).

3. Backup scope — 9 mutation-surface tables (all exist)

ProductionLiveExecutionAdapter.pre_write_backup builds a narrow pg_dump spec over exactly the mutation-surface tables. Catalog-verified present:

public:             information_unit, unit_version, iu_lifecycle_log
cutter_governance:  cut_change_set, cut_change_set_affected_row,
                    manifest_envelope, review_decision, verify_result,
                    dot_pair_signature

(cutter_governance tables are catalog-confirmed via pg_class; the read-only query_pg role is privilege-filtered out of information_schema and the schema data itself — a known boundary, not a missing-table problem.)

4. What backup_runner must return

pre_write_backup refuses to advance unless the injected runner returns a complete envelope: {sha256, size_bytes, gpg_fpr, artifact_ref}. A missing field raises StopInvariantFailed — fail-closed.

5. Operator command package — provision F4 (NOT executed here)

Provisioning a private GPG key is a sensitive credential-creation step and is intentionally not performed by this macro (forbidden: write secrets). Exact operator steps:

# 1. Provision a dedicated, passphrase-protected backup keypair (operator host)
gpg --batch --gen-key <<'EOF'
  Key-Type: default
  Key-Length: 4096
  Subkey-Type: default
  Name-Real: dot-iu-cutter backup
  Name-Email: cutter-backup@incomexsaigoncorp.vn
  Expire-Date: 2y
  Passphrase: <OPERATOR-CHOSEN — store in a vault, never in .env plaintext>
EOF
# 2. Capture the fingerprint
gpg --list-keys --with-colons | awk -F: '/^fpr:/{print $10; exit}'
# 3. Publish (operator edits /opt/incomex/docker/.env — NOT this macro):
#      BACKUP_GPG_FPR=<fingerprint>
# 4. Author backup_runner: narrow `pg_dump --table=<9 tables>` | `gpg --encrypt
#    --recipient $BACKUP_GPG_FPR` → /var/lib/cutter/backups/<run_id>.sql.gpg ;
#    return {sha256,size_bytes,gpg_fpr,artifact_ref}.
# 5. Rollback-only-prove pre_write_backup (Mode.LIVE, post-GAP7 flip) — confirm
#    the encrypted artifact verifies and 0 production rows change.

backup_runner is a small VPS-side script — no Mac source patch required for the runner itself; it is injected into ProductionLiveExecutionAdapter by the first-run command package.

6. Verdict

backup_mechanism:    READY — gpg proven, pg_dump present, 9 tables exist
backup_key:          MISSING — BACKUP_GPG_FPR not provisioned (operator step §5)
backup_runner:       MISSING — concrete impl to be authored (§5 step 4)
f4_status:           PARTIAL — mechanism ready; key + runner packaged
secrets_exposed:     NONE
g3:                  PASS
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-o8e-pre-production-hardening-bundle/03-backup-readiness.md