KB-3AD4

O9 first-automated-production-run readiness — 02-backup-readiness

5 min read Revision 1
dieu44iu-cutterv0.6o9first-automated-production-runreadiness

O9 Report 02 — Backup readiness (G2 / F4)

  • macro: v0.6-o9-first-automated-production-run-readiness
  • date_utc: 2026-05-21 · host: Contabo vmi3080463
  • gate covered: G2 — F4 backup readiness
  • result: G2 PASS · F4 advanced — mechanism READY + runner READY (authored); GPG key MISSING (operator package below)

1. Readiness matrix

Component State Evidence
gpg binary READY GnuPG 2.4.4 / libgcrypt 1.10.3
GPG mechanism READY proven by O8E Report 03 ephemeral round-trip
pg_dump READY host 16.13 (DB server postgres container 16.13)
9 mutation-surface tables READY all exist (§3)
pre_write_backup seam READY phases/backup.pyadapter.pre_write_backup (Mode.LIVE)
backup_runner concrete impl READY — NEW (this macro) /var/lib/cutter/backup_runner.py — §2
BACKUP_GPG_FPR keypair MISSING absent from /opt/incomex/docker/.env; host root keyring 0 public keys

F4 status: PARTIAL → only the GPG key remains. It is a secret-creation step and is intentionally NOT performed by this macro (forbidden: write/log secrets). Exact operator package in §4.

2. backup_runner — authored & validated

/var/lib/cutter/backup_runner.py (mode 0640, NON-SENSITIVE — credential names only, zero secret values) was authored this macro. It is the concrete backup_runner callable that ProductionLiveExecutionAdapter.pre_write_backup requires.

contract_in:   spec = {run_id, document_id, source_version_id, tables[9]}
contract_out:  {sha256, size_bytes, gpg_fpr, artifact_ref}   (complete envelope;
               a missing field => adapter raises StopInvariantFailed — fail-closed)
mechanism:     pg_dump --table=<9 tables>  (plain SQL, schema+data, --no-owner
               --no-privileges)  |  gpg --encrypt --recipient $BACKUP_GPG_FPR
               > /var/lib/cutter/backups/<run_id>.sql.gpg   (plaintext never on disk)
db_principal:  cutter_exec (least-privilege) — requires the O9 grant package
               (Report 03) so it can SELECT all 9 tables
fail_closed:   refuses if BACKUP_GPG_FPR unset / key not in keyring /
               pg_dump|gpg missing / run_id unsafe / artifact already exists
selftest:      python3 /var/lib/cutter/backup_runner.py --selftest

Validation performed (no DB write, no secret):

py_compile:                      OK
selftest (no key provisioned):   FAIL-CLOSED as designed
                                 ("required env var BACKUP_GPG_FPR is unset")
adapter wiring:                  ProductionLiveExecutionAdapter(backup_runner=…) accepts it
fail-closed on bad spec:         backup_runner({tables:…}) without run_id -> BackupRunnerError
EXPECTED_TABLES:                 9 — matches the adapter spec exactly

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

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

4. Operator package — provision BACKUP_GPG_FPR (NOT executed here)

# 1. Generate 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 the fingerprint (operator edits /opt/incomex/docker/.env):
#      BACKUP_GPG_FPR=<fingerprint>

# 4. Import the backup PUBLIC key into the keyring of the user that runs the
#    first-run command package (so backup_runner's gpg --encrypt can resolve
#    the recipient):   gpg --import <backup-public-key.asc>

# 5. Verify readiness (no DB write):
#      set -a; . /opt/incomex/docker/.env; set +a
#      python3 /var/lib/cutter/backup_runner.py --selftest      # expect: SELFTEST: PASS

# 6. At the first run, pre_write_backup (Phase 4) exercises it for real;
#    confirm /var/lib/cutter/backups/<run_id>.sql.gpg is produced and 0
#    production rows change.

5. Verdict

backup_mechanism:  READY      (gpg + pg_dump proven; 9 tables exist)
backup_runner:     READY      (authored this macro — /var/lib/cutter/backup_runner.py)
backup_key:        MISSING    (BACKUP_GPG_FPR — operator secret step §4)
f4_status:         PARTIAL — only the operator key step remains
secrets_exposed:   NONE
g2:                PASS
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-o9-first-automated-production-run-readiness/02-backup-readiness.md