KB-7BA2
O11 automation+agent-sandbox bundle — 03 Backup operator package
7 min read Revision 1
dieu44iu-cutterv0.6o11automation-agent-sandboxbackup-f4
O11 Report 03 — Backup operator package, finalized (BRANCH 2)
- macro:
v0.6-o11-automation-agent-sandbox-program-bundle - date_utc: 2026-05-21 · host: Contabo
vmi3080463· gate: BRANCH 2 - result: PASS — F4 operator package finalized; contains NO secret values; not executed by this macro
1. Scope
Finalize the operator steps that close F4 — BACKUP_GPG_FPR (blocker B3 in
Report 02). This is a secret-creation step; per the macro's forbidden list
it is not performed here. This report is a complete, copy-pasteable runbook
the operator executes on the VPS. No private key is created or logged here.
2. What is already done (no operator action)
backup_runner.py: /var/lib/cutter/backup_runner.py — present, fail-closed,
NON-SENSITIVE (credential NAMES only). O9 F4 artifact.
mechanism proven: gpg 2.4.4 + pg_dump 16 — ephemeral round-trip (O8E R03)
9 mutation tables: all exist (O9 R02 §3)
adapter wiring: ProductionLiveExecutionAdapter(backup_runner=…) accepts it
selftest entry: python3 /var/lib/cutter/backup_runner.py --selftest
3. What is missing (the entire operator package below)
BACKUP_GPG_FPR: absent from /opt/incomex/docker/.env (verified this macro)
host keyring: no backup public key imported
=> backup_runner --selftest currently FAILS CLOSED, by design:
"required env var BACKUP_GPG_FPR is unset"
4. Operator package — provision BACKUP_GPG_FPR
Run on the VPS as the operator. Never paste the passphrase into
.env, a shell history file, the KB, or any report. Store it in the operator vault.
# --- STEP 1. Generate a dedicated, passphrase-protected backup keypair --------
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 the vault, NEVER in .env / KB / logs>
EOF
# --- STEP 2. Capture the 40-char fingerprint ---------------------------------
gpg --list-keys --with-colons | awk -F: '/^fpr:/{print $10; exit}'
# -> copy this value; it is the ONLY thing that goes into .env (it is NOT a secret;
# a fingerprint is public — but the private key and passphrase ARE secret).
# --- STEP 3. Publish the fingerprint NAME=value into the env file ------------
# Operator edits /opt/incomex/docker/.env and appends ONE line:
# BACKUP_GPG_FPR=<fingerprint-from-step-2>
# (no other change; do not add the passphrase or the key)
# --- STEP 4. Ensure the backup PUBLIC key is in the keyring of the user that
# runs the first-run command package (so gpg --encrypt resolves it) -
gpg --export --armor <fingerprint> > /root/cutter-backup-public.asc # if needed
gpg --import /root/cutter-backup-public.asc
# --- STEP 5. Verify readiness — NO DB write, NO dump, NO secret printed ------
set -a; . /opt/incomex/docker/.env; set +a
python3 /var/lib/cutter/backup_runner.py --selftest
# expect: SELFTEST: PASS
# pg_dump / gpg paths, BACKUP_GPG_FPR present (…last-8 only), DB target,
# backups dir, 9 tables in scope.
5. Companion grant package (G1 — apply with the run, as workflow_admin)
backup_runner's pg_dump runs as least-privilege cutter_exec and must
SELECT all 9 tables. Two SELECTs are still missing — apply immediately before
the first run, then verify:
-- as workflow_admin on database `directus`
GRANT SELECT ON cutter_governance.verify_result TO cutter_exec;
GRANT SELECT ON cutter_governance.cut_change_set_affected_row TO cutter_exec;
SELECT has_table_privilege('cutter_exec','cutter_governance.verify_result','SELECT'),
has_table_privilege('cutter_exec','cutter_governance.cut_change_set_affected_row','SELECT');
-- both must return true.
6. Config-drift fix (do this in the same env step — non-secret)
file: /var/lib/cutter/orchestrator.config.json
field: deployed_source_commit : "cad989a7c7c37c1b042778f0b601a599a6d04ee3"
-> "f111d4abd098bfd6653b157ea45a83e086c0a2fe"
field: milestone_of_record : "O8B" -> "O10"
note: non-loading metadata file; this only keeps the record honest.
Supersedes O9 R06 §1 (which named the now-stale fdcf580).
7. Verification at the first run (operator confirms)
during Phase 4 (pre_write_backup): an encrypted dump is produced at
/var/lib/cutter/backups/<run_id>.sql.gpg
backup_runner returns a COMPLETE envelope {sha256,size_bytes,gpg_fpr,artifact_ref};
a missing field => adapter raises StopInvariantFailed (fail-closed, run aborts).
0 production rows change as a result of the backup step.
8. Rollback / disable
disable backup provisioning (pre-first-run, no data exists):
- remove the BACKUP_GPG_FPR line from /opt/incomex/docker/.env
- (optional) gpg --delete-secret-keys <fpr> ; gpg --delete-keys <fpr>
=> backup_runner --selftest returns to FAIL-CLOSED; no run can pass Phase 4.
restore from a backup (catastrophic path only — GAP6 soft-revert is preferred):
gpg --decrypt /var/lib/cutter/backups/<run_id>.sql.gpg > /tmp/<run_id>.sql
# inspect, then restore wanted tables into a SCRATCH db or via table-rename —
# never blind-restore over a live schema.
key rotation:
generate a new keypair (steps 1-2), update BACKUP_GPG_FPR, re-import the
public key. Old encrypted artifacts still decrypt with the old private key —
retain it in the vault until those artifacts are retired.
9. Secret-handling attestation
private key created by this macro: NO
passphrase chosen/written/logged: NO
BACKUP_GPG_FPR value written: NO (operator does, in step 3)
secrets in this report: NONE — names and procedure only
10. Verdict
operator package: FINALIZED — copy-pasteable, no secret values
verification: --selftest + has_table_privilege checks included
rollback/disable: included (§8)
executed here: NO — F4 (B3) remains an operator step
branch_2: PASS