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.py → adapter.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