KB-7A6C

Automation Orchestrator Design · 03 Phase Orchestration & Gates

16 min read Revision 1
dot-iu-cutterv0.5automation-orchestrator-designphase-orchestrationgatesapproval-modelg3-passdieu442026-05-20

Automation Orchestrator Design · 03 Phase Orchestration & Gates

doc 3 of 7 · 2026-05-20 · design-only macro

phase                : G3 — per-phase orchestration + gate model
outcome              : G3 PASS — 11 phases, 11 internal + 3 sovereign gates,
                       each phase reuses v0.5-proven modules
production_mutation  : NONE

1. Phase catalogue — mapping to existing v0.5 modules

# Phase Reuses (existing module) New code surface
1 source_pin (none — orchestrator/phases/source_pin.py from scratch) source_pin.py
2 mark cutter_agent.dryrun.D (region extraction) mark.py
3 cutplan cutter_agent.cutplan + cutter_agent.cutwrite (dry-run) cutplan.py
4 pre_write_backup new (calls pg_dump --table narrow + GPG wrap) backup.py
5 grant_probe cutter_agent.prod_iu_adapter (SECDEF probe) grant_probe.py
6 cut_leg_a cutter_agent.prod_iu_adapter_canonical + cutprod_canonical cut_leg_a.py
7 structural_verify cutter_agent.prod_iu_adapter (read-only post-CUT probe) structural_verify.py
8 leg_b_record cutter_agent.ledger_v2_canonical_cut.LegBRecorder leg_b_record.py
9 write_verify cutter_agent.ledger_v2_canonical_verify.VerifyRecorder write_verify.py
10 lifecycle_enact calls public.fn_iu_enact in single txn (Phase 7 pattern) lifecycle_enact.py
11 closeout new (uploads final KB doc + appends runs-index) closeout.py

Every new module is ≤200 LOC orchestration; the heavy lifting lives in the v0.5-proven cutter_agent.* modules already on main at 0a64a61. The orchestrator is a thin shell over already-ratified machinery.

2. Phase-by-phase orchestration

2.1 source_pin

inputs  : document_id, source_uri (optional)
work    :
  - SELECT id FROM source_document WHERE doc_code = $document_id
  - if missing AND source_uri given: refuse (insertion is sovereign-only)
  - SELECT id, version_string, manifest_digest FROM source_version
    WHERE source_document_id = $1 ORDER BY created_at DESC LIMIT 1
  - recompute manifest_digest live; assert == column
  - record into RunContext.context_pins
internal_gate : IG_source_pin
outputs : context_pins.{source_document_id, source_version_id,
                       version_string, manifest_digest}
production_mutation : NONE (read-only)

2.2 mark

inputs  : context_pins.source_version_id, document_id
work    :
  - load pinned region from source_version (binary text blob)
  - call cutter_agent.dryrun.MARK (region extraction, deterministic)
  - compute region_sha = sha256(region_text)
  - assert region_sha == source_version.region_sha if present
  - record candidate count + per-candidate (anchor_path, sort_order)
internal_gate : IG_mark
outputs : context_pins.{region_sha, candidate_count, mark_rowset_sha}
production_mutation : NONE

2.3 cutplan

inputs  : mark_rowset, vocab snapshot
work    :
  - run cutplan deterministic rebuild (D-isolated, no DB)
  - compute writer_digest over (canonical_address, unit_kind,
    section_type, content_hash, idempotency_key)
  - run cutplan rebuild a SECOND time with a different
    iteration order to assert determinism
  - if writer_digest_1 != writer_digest_2 → IG_cutplan FAIL
  - persist row set in RunContext (do not mutate DB)
internal_gate : IG_cutplan
outputs : context_pins.{writer_digest, candidate_count_confirmed}
production_mutation : NONE

2.4 pre_write_backup

inputs  : document_id, backup_policy (dot_config)
work    :
  - launch pg_dump narrow scope:
      tables          : public.information_unit, public.unit_version,
                        cutter_governance.cut_change_set,
                        cutter_governance.review_decision
      where_clauses   : doc_code-prefix-scoped via dot_config policy
      format          : custom (-Fc)
  - pipe through `gpg --encrypt --recipient ${BACKUP_GPG_FPR}`
  - write to sidecar dir; record sha256 + size_bytes
  - destroy intermediate plaintext (no plaintext residue)
internal_gate : IG_backup
outputs : context_pins.{backup_sha, backup_size_bytes, backup_gpg_fpr}
production_mutation : NONE (read-only pg_dump)
constitution_lesson : pattern matches v0.5 backup sha 076213737cac…
                      and ba0ef355e7… (pre-CUT)

2.5 grant_probe

inputs  : (from deployment dot_config) expected grant matrix
work    :
  - read pg_proc.proacl for fn_iu_create / fn_iu_enact / fn_iu_apply_edit_draft
  - read pg_class.relacl for cutter_governance.* tables
  - cross-check against expected matrix
internal_gate : IG_grant_probe (refuses if any deviation)
outputs : context_pins.grant_probe_sha
production_mutation : NONE
remediation_policy : GRANT/REVOKE is PRE-PROVISIONED at deployment time;
                     orchestrator never applies grants per-run
                     (v0.5 applied them via directus principal under
                      a separate sovereign gate; v0.6+ assumes the
                      grant matrix is stable)

2.6 cut_leg_a — POST sovereign gate

sovereign_gate_required : SG_1_cut_authz  (must precede this phase)
inputs  : approval_kb_id (validated), writer_digest, candidate rows
work    :
  - mint change_set_id (UUID7) → context_pins
  - open ONE explicit txn (autocommit=False — v0.5 G6 attempt-1 lesson)
  - call fn_iu_create for each candidate (60-IU case → 60 calls)
  - assert each return status == 'created'
  - COMMIT
  - if any failure mid-loop → ROLLBACK → IG_cut_leg_a FAIL
internal_gate : IG_cut_leg_a_execute (post-commit re-check)
outputs : 60 IU rows + 60 UV rows + 60 anchor UPDATEs (per Constitution scale)
          context_pins.{change_set_id, cut_committed_at}
production_mutation : YES (the only mutating phase besides 8/9/10)
constitution_lesson : v0.5 doc 04 — autocommit bug in attempt-1 silently
                      rolled back everything ; attempt-2 patched provider
                      to autocommit=False and COMMITTED clean

2.7 structural_verify

inputs  : context_pins.{change_set_id, writer_digest, candidate_count}
work    :
  - 11-bool probe (canonical_address count, distinct content_hash,
    anchored_exact, lifecycle_status=='draft' uniform, dieu_44_intrusion=0,
    section_type_cardinality, body_hash_match, etc.)
  - assert == expected from cutplan output
internal_gate : IG_structural_verify
outputs : context_pins.structural_verify_payload_sha
production_mutation : NONE
constitution_lesson : reproduces v0.5 first-controlled-cut doc 05 §3

2.8 leg_b_record

inputs  : context_pins.{change_set_id, writer_digest, manifest_digest, …}
work    :
  - build manifest_envelope payload (canonical JSON)
  - build executor_signature row (StubSigning today; provider-injected)
  - call LegBRecorder.record(live_state=…)
  - one atomic txn → 126 rows for Constitution scale
    (cut_change_set + manifest_envelope + executor_signature +
     dot_pair_signature + verify_result_pending +
     decision_backlog_entry + cut_iu_link[60] + cut_uv_link[60])
internal_gate : IG_leg_b_record
outputs : context_pins.{manifest_envelope_id, executor_signature_id,
                        leg_b_committed_at}
production_mutation : YES (cutter_governance only; +126 rows scale)
constitution_lesson : reproduces v0.5 leg-B execution log

2.9 write_verify

inputs  : context_pins.{change_set_id, verify payload pins}
work    :
  - re-derive live_state from a fresh survey
  - call VerifyRecorder.record(live_state=…) under cutter_verify principal
  - one atomic txn → +2 rows (verify_result + verifier dot_pair_signature)
internal_gate : IG_write_verify
outputs : context_pins.{verify_result_id, verifier_signature_id}
production_mutation : YES (+2 rows in cutter_governance.*)
constitution_lesson : reproduces v0.5 write-verify dot992 execution

2.10 lifecycle_enact — POST sovereign gate

sovereign_gate_required : SG_2_lifecycle_authz
inputs  : context_pins.review_decision_id (from approval doc)
work    :
  - BEGIN
  - CREATE TEMP TABLE target_addresses AS SELECT canonical_address FROM
    information_unit WHERE doc_code = $document_id AND lifecycle_status='draft'
  - assert COUNT == candidate_count (no drift)
  - FOR each row: SELECT public.fn_iu_enact(canonical_address, actor,
                                             review_decision_id, 'enacted',
                                             change_set_id, reason,
                                             tool_revision, FALSE)
  - assert each returns status='enacted'; collect into temp table
  - assert COUNT == candidate_count
  - COMMIT
internal_gate : IG_lifecycle_enact_execute
outputs : N enacted IUs + N UV.enacted_at + N iu_lifecycle_log rows
production_mutation : YES (lifecycle table mutations)
mvp_restriction : only target='enacted' supported (DQ_3) ;
                  supersede/retire/restore = explicit STOP_NOT_IMPLEMENTED
constitution_lesson : reproduces v0.5 Phase 7 rerun (60 fn_iu_enact in 1 txn,
                      review_decision af323ae3-…, performed_at single timestamp)

2.11 closeout

inputs  : entire RunContext
work    :
  - generate one consolidated final KB report
  - upload per-phase docs (already uploaded incrementally; ensure none missing)
  - append a one-line entry to the global runs index KB doc
  - finalize sidecar (state='closeout_reported', success=true)
internal_gate : IG_closeout
outputs : final KB report id, runs-index updated
production_mutation : NONE (KB writes only; KB is SSOT, not production DB)

3. Auto-pass internal-gate evaluator

Pseudocode for gates.evaluate_internal(gate_name, run_context) -> Result:

def evaluate_internal(gate: GateName, ctx: RunContext) -> GateResult:
    invariants = GATE_INVARIANTS[gate]      # static checklist (doc 02 §4)
    failures = []
    for name, predicate in invariants.items():
        ok = predicate(ctx)
        ctx.record_invariant(gate, name, ok)
        if not ok:
            failures.append(name)
    if failures:
        ctx.kb_upload(phase_doc(gate, FAILED, failures))
        raise OrchestratorGateFail(gate, failures)
    ctx.kb_upload(phase_doc(gate, PASSED))
    return GateResult.PASS

OrchestratorGateFail is terminal — never caught for retry. Sovereign must inspect the sidecar and either void the run, amend the input, or open a separate macro to fix the underlying drift.

4. Sovereign gate handshake

SG_1_cut_authz_handshake:
  step_a (orchestrator) : upload "<run>/SG1-request.md" to KB with:
                          - doc_id
                          - manifest_digest
                          - region_sha
                          - writer_digest
                          - candidate_count
                          - backup_sha
                          - grants_probe_snapshot
                          - sidecar_path
                          - run_id
                          - phase_soft_cap_remaining
  step_b (orchestrator) : set state=awaiting_cut_authorization;
                          flush sidecar; release fcntl lock; exit 0
  step_c (sovereign)    : reviews KB request; uploads sibling KB
                          "<run>/SG1-approval.md" with:
                          - explicit allowance line
                          - principal authorized
                          - max_writes (== candidate_count)
                          - approval_id (sha256 over request fields)
  step_d (operator)     : cutter orchestrate resume --run-id <id>
                                            --approval-kb-id <SG1-approval path>
  step_e (orchestrator) : re-acquire lock; validate approval;
                          re-survey drift; if clean → advance

SG_2_lifecycle_authz_handshake:
  same shape, with the addition that step_c MUST create a fresh
  cutter_governance.review_decision row and embed its UUID into the
  approval doc body. Orchestrator validates the UUID in step_e.

SG_3_failure_escalation:
  step_a (orchestrator) : upload "<run>/STOP-<reason>.md" containing
                          - failed gate name
                          - invariant diff
                          - sidecar dump
                          - reproduce command line (sanitised)
                          - drift evidence if any
  step_b (orchestrator) : set state=stopped_<reason> (or failed_<gate>)
                          flush sidecar; release lock; exit non-zero
  step_c (sovereign)    : decide one of:
                          - void: cutter orchestrate void --run-id <id>
                                              --reason <KB-doc-id>
                          - amend & resume: cutter orchestrate resume
                                              --run-id <id>
                                              --amendment-kb-id <path>
                          - open a fix-macro (out-of-band repair)
  step_d (operator)     : execute chosen option

5. Gate batching policy (NOT YET enabled)

DQ_9 considered "gate batching" — i.e. one approval doc covering N documents in a queue. The design forbids this in MVP because each (doc, run) needs an independent review_decision_id (Phase 7 doctrine). Batch mode (doc 05) collects N requests into a single KB folder but still requires N approval docs.

This is the deliberate operational trade-off: marginally more sovereign work in exchange for keeping the per-document audit trail clean.

6. STOP conditions — exhaustive enumeration

stop_routes:
  STOP_DOCUMENT_UNKNOWN              # doc 01 §3.2 (1)
  STOP_APPROVAL_REQUIRED             # SG_1 / SG_2 reached
  STOP_DRIFT_<dim>                   # doc 02 §8
  STOP_REPLAY_CONFLICT               # doc 01 §3.2 (4)
  STOP_REFUSED_INPUT                 # doc 01 §4
  STOP_NOT_IMPLEMENTED               # e.g. supersede/retire requested
  STOP_OVER_SOFT_CAP                 # doc 02 §6
  STOP_OVER_HARD_CAP                 # doc 02 §6
  STOP_INVARIANT_FAILED              # any internal gate fail
  STOP_LOCK_BUSY                     # state.lock held by another process
  STOP_BACKUP_GPG_PUBKEY_MISSING     # GSM not provisioned
  STOP_GRANT_DELTA                   # IG_grant_probe deviation

Every STOP route uploads a KB doc per §4-SG_3. Sovereign sees exactly one KB id to read.

7. Authority preservation rule

The orchestrator MUST NOT carry sovereign authority across runs:

  • Each approval_kb_id is consumed exactly once.
  • Resume validates the approval AGAIN at resume time (re-read the KB doc; assert sovereign signature/timestamp ≤ 24 h old).
  • void does NOT consume the approval; it just marks the run terminal.
  • A new run for the same (document, source_version) requires a NEW pair of (SG_1, SG_2) approvals.

This is the v0.5 doctrine "no silent retry that changes authority" made enforceable in code.

8. Verdict

g3_outcome              : PASS
phases_total            : 11
internal_gates_total    : 11 (one per phase; failure terminal)
sovereign_gates_total   : 3 (SG_1, SG_2, SG_3)
new_code_modules        : 11 (≤ 200 LOC each)
reused_modules          : 8 v0.5-proven cutter_agent.* modules
gate_batching           : forbidden in MVP (per Phase 7 doctrine)
authority_carry_across_runs : forbidden
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.5-automation-orchestrator-design/03-phase-orchestration-and-gates-2026-05-20.md