KB-4048

Automation Orchestrator Design · 04 Artifact / Report / Idempotency Model

11 min read Revision 1
dot-iu-cutterv0.5automation-orchestrator-designartifact-report-idempotencykb-conventionmanifest-digest-rulesg4-passdieu442026-05-20

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> is automation-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>.md
  • SG2-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
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.5-automation-orchestrator-design/04-artifact-report-and-idempotency-model-2026-05-20.md