Automation Orchestrator Design · 03 Phase Orchestration & Gates
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_idis consumed exactly once. - Resume validates the approval AGAIN at resume time (re-read the KB doc; assert sovereign signature/timestamp ≤ 24 h old).
voiddoes 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