Automation Orchestrator Design · 04 Artifact / Report / Idempotency Model
Automation Orchestrator Design · 04 Artifact / Report / Idempotency Model
doc 4 of 7 · 2026-05-20 · design-only macro
phase : G4 — KB artifact convention + idempotency keys outcome : G4 PASS — 1 KB folder per run, 12 docs per full run, deterministic idempotency keys for every phase production_mutation : NONE
1. KB folder convention
Each orchestrator run gets exactly one KB folder:
knowledge/dev/laws/<law-id>/v<version>-<run-slug>/
Where:
<law-id>is the doc_prefix lowercased (icx-const,icx-law-2026-001, …).<version>is the document's source_version_id short form (e.g.008a06ace23a96ea— first 16 hex of source_version digest).<run-slug>isautomation-cut-<YYYYMMDD>(one run/day/doc; if a second run exists the same day it becomes-<HHMM>suffixed).
Example for the Constitution:
knowledge/dev/laws/icx-const/v008a06ace23a96ea-automation-cut-20260520/
The folder contains exactly these docs (one per phase + final):
| # | Filename | When uploaded |
|---|---|---|
| 01 | 01-source-pin-<date>.md |
end of phase 1 |
| 02 | 02-mark-<date>.md |
end of phase 2 |
| 03 | 03-cutplan-<date>.md |
end of phase 3 |
| 04 | 04-pre-write-backup-<date>.md |
end of phase 4 |
| 05 | 05-grant-probe-<date>.md |
end of phase 5 |
| 06 | SG1-cut-authz-request-<date>.md |
start of SG_1 (request) |
| 07 | 06-cut-leg-a-<date>.md |
end of phase 6 |
| 08 | 07-structural-verify-<date>.md |
end of phase 7 |
| 09 | 08-leg-b-record-<date>.md |
end of phase 8 |
| 10 | 09-write-verify-<date>.md |
end of phase 9 |
| 11 | SG2-lifecycle-authz-request-<date>.md |
start of SG_2 (request) |
| 12 | 10-lifecycle-enact-<date>.md |
end of phase 10 |
| 13 | 11-final-orchestrator-report-<date>.md |
end of phase 11 |
Approval docs uploaded BY THE SOVEREIGN live alongside, named:
SG1-cut-authz-approval-<date>.mdSG2-lifecycle-authz-approval-<date>.md
The orchestrator never writes these two.
2. KB report content rules
Every phase report follows a fixed template (≤ 250 lines):
# <Phase Name> · <NN> of 11 · <run_id>
> ```yaml
> phase : <phase_name>
> outcome : <PASS | FAIL>
> production_mutation : <NONE | scope>
> run_id : <run_id>
> ```
## 1. Inputs (RunContext snapshot)
<YAML excerpt of relevant context_pins; never the full sidecar>
## 2. Work performed
<bulleted, one line per step>
## 3. Live snapshot diff (pre vs post)
<table of integers / sha256s changed>
## 4. Internal-gate invariants
<table of {invariant, expected, observed, ok}>
## 5. Artifacts produced
<table of {path, sha256, bytes}>
## 6. Verdict
<YAML block>
Forbidden in phase reports:
- Secrets, DSNs, GSM payloads (per hardcode-cleanliness policy).
- Full sidecar dump (truncate to ≤ 50 lines; full sidecar is on filesystem).
- Speculative next steps (those live in the final report only).
- Cross-run references (one folder = one run).
3. KB upload as the green light for IG advance
The orchestrator's evaluator (doc 03 §3) advances to the next phase only after the KB upload succeeds. If upload fails (timeout, 5xx, authentication), the gate is treated as failed and STOP_KB_UPLOAD is raised. This makes KB-as-SSOT load-bearing: a run that didn't upload its phase doc cannot have happened.
Retry policy: up to 3 retries with exponential backoff capped at 30 s. After 3 retries → STOP_KB_UPLOAD. No silent skip.
4. Idempotency keys — full table
Every phase has a deterministic idempotency key recorded in the
sidecar AND (where it represents a production write) recorded in
cutter_governance.cut_change_set or its sibling tables:
phase | key formula | persisted at
------------------|----------------------------------------------------------------|---------------
source_pin | sha256(document_id || source_version_id) | sidecar
mark | sha256(prev_key || region_sha) | sidecar
cutplan | writer_digest # already canonical | sidecar
backup | sha256(prev_key || backup_sha) | sidecar
grant_probe | sha256(prev_key || grant_probe_sha) | sidecar
cut_leg_a | change_set_id # UUID7, generated at this phase | cutter_governance.cut_change_set.change_set_id
structural_verify | sha256(change_set_id || structural_verify_payload_sha) | sidecar
leg_b_record | manifest_envelope_id # UUID7 from LegBRecorder | cutter_governance.manifest_envelope.id
write_verify | verify_result_id # UUID7 from VerifyRecorder | cutter_governance.verify_result.id
lifecycle_enact | phase7_review_decision_id # one per run | cutter_governance.review_decision.id
closeout | sha256(all prior keys || final_report_sha) | sidecar + KB runs-index
The orchestrator NEVER re-derives a key from scratch on resume; it reads the persisted key and re-validates against live state.
5. Replay safety — three layers
layer_1_sidecar:
rule : run_id is unique per (canonical_address_prefix, source_version_id).
attempting `cutter orchestrate cut --document-id X` when a non-voided
run already exists for (X, latest_version) → STOP_REPLAY_CONFLICT.
enforced: at orchestrate-cut entry, before any DB connect.
layer_2_database:
rule : cut_change_set.idempotency_key (UNIQUE) + Constraint
"change_set_id+phase" prevents duplicate cut_leg_a / leg_b /
write_verify rows for the same change_set_id.
enforced: by PG UNIQUE constraints (already in v0.5 ratified DDL).
layer_3_governance:
rule : a run cannot proceed past SG_2 with a re-used review_decision_id.
VerifyRecorder + LegBRecorder both refuse on existing row
("LegBAlreadyRecorded" / "VerifyAlreadyRecorded").
enforced: in module code (already in v0.5 ratified ledger_v2_*).
Replay = idempotent no-op (returns success with replayed_from=<run_id>)
ONLY when all three layers agree no new rows are needed.
6. Manifest / digest rules
manifest_digest:
scope : source_version envelope (text + region span + vocab snapshot)
algorithm : sha256 over canonical JSON serialization
computed_at : source_pin phase
pinned_at : RunContext.context_pins.manifest_digest
re_validated_at : every internal gate (drift check)
region_sha:
scope : the body-span text only (after MARK extracts it)
algorithm : sha256 over UTF-8 bytes
computed_at : mark phase
pinned_at : RunContext.context_pins.region_sha
re_validated_at : structural_verify (against body_hash_match per IU)
writer_digest:
scope : the cutplan rowset (canonical_address, unit_kind,
section_type, content_hash, idempotency_key)
algorithm : sha256 over canonical JSON over the rowset, sorted
by canonical_address ASC
computed_at : cutplan phase
pinned_at : RunContext.context_pins.writer_digest
re_validated_at : cut_leg_a (assert match before fn_iu_create loop)
change_set_id:
scope : the cut_leg_a transaction
algorithm : UUID7 (time-ordered, opaque)
computed_at : cut_leg_a phase (inside the txn)
pinned_at : RunContext.context_pins.change_set_id
re_validated_at : every subsequent phase (must match the row in cut_change_set)
The v0.5 Constitution had these values frozen as PIN_* module
constants in prod_iu_adapter.py (PIN_MANIFEST_FILE_SHA=7d56f3ce…,
PIN_MANIFEST_DIGEST=9d908a62…, PIN_REGION_SHA=17660443…,
PIN_WRITER_DIGEST=d99a31d4…). For v0.6+ these PINs are deleted from
module scope and live only inside per-run RunContext.context_pins.
The Constitution can be re-cut from the same digests by passing the
old run_id; new documents get fresh digests every time.
7. Rollback / backup references
forward_compensation_only:
- the orchestrator never executes a backward DELETE / TRUNCATE.
- on failure mid-cut_leg_a → in-txn ABORT (no compensation needed).
- on failure between cut_leg_a and leg_b → orchestrator emits
STOP_BETWEEN_LEG_A_AND_LEG_B with the change_set_id pinned ;
sovereign opens a fix-macro to either complete leg_b or
sovereign-void the cut_change_set.
- emergency revert (drop the IUs) is a SOVEREIGN-ONLY out-of-band
macro that uses the GPG backup as the restore source.
backup_sha_consumed_by:
- SG1-cut-authz-request (declares the backup exists)
- phase 11 final report (lists it as preserved artifact)
- never automatically used to restore (no auto-rollback)
emergency_revert_runbook:
- lives at sql/lifecycle/rollback_runbook.sql (v0.5 ratified)
- extended in v0.6 with a per-document scope filter (TBD-OWNED-DDL)
8. No-duplicate / no-clutter policy
rules:
- 1 phase = 1 KB doc (no "01a", "01b" splits)
- exactly 11 phase docs + 2 SG request docs + 1 final = 14 max per run
- sovereign approval docs live alongside but are sovereign-uploaded
- NO transient drafts uploaded
- NO file in the run folder is edited after upload (KB revisions
bump revision number; the orchestrator never rewrites)
- runs-index KB doc holds ONE line per run (≤ 200 chars)
- global runs-index folder: knowledge/dev/laws/_orchestrator-runs-index/
9. Per-run runs-index entry
After phase 11 the orchestrator appends one line to
knowledge/dev/laws/_orchestrator-runs-index/v0.6-runs.md:
- `<run_id>` · `<document_id>` · `<source_version_id>` · `<state>` · `<KB folder path>` · `<change_set_id>` · `<lifecycle_review_decision_id>` · `<actor>` · `<closeout_utc>`
Sovereign reads this file to see the history of all cuts in one place.
10. Verdict
g4_outcome : PASS
kb_docs_per_run : 12 orchestrator-uploaded + ≤ 2 sovereign-uploaded
sidecar_per_run : 1 JSON + 1 lock + 1 GPG backup
idempotency_layers : 3 (sidecar / DB UNIQUE / module-level recorders)
manifest_pin_lifetime : per-run, not module-level (RunContext)
forward_compensation_only : YES (no backward DELETE in orchestrator)
clutter_policy : ≤ 14 KB docs per run, no in-place rewrites