KB-7E06

O8A live-execution wiring authoring (Contabo) — 03-live-adapter-design-lock

5 min read Revision 1
dieu44iu-cutterv0.6o8alive-execution-wiringauthoringsandbox-proofcontabo

O8A Report 03 — Live-adapter design lock

  • macro: v0.6-o8a-live-execution-wiring-authoring
  • date_utc: 2026-05-21 · host: vmi3080463 (Contabo)
  • gate covered: G2 live-adapter design lock

1. Adapter interface — the seam

New module cutter_agent/orchestrator/live_execution.py defines LiveExecutionAdapter (ABC) — one method per mutating phase:

class LiveExecutionAdapter(abc.ABC):
    name: str
    def pre_write_backup(ctx) -> BackupResult
    def cut_leg_a(ctx, *, change_set_id, cutplan_rows) -> list[IUCreateResult]
    def leg_b_record(ctx, *, change_set_id) -> LegBResult
    def verify_result_count_for(ctx, *, change_set_id) -> int
    def write_verify(ctx, *, change_set_id) -> VerifyResult
    def lifecycle_enact(ctx, *, addresses, review_decision_id,
                        change_set_id) -> EnactBatchResult

Result value objects (BackupResult, LegBResult, VerifyResult, EnactBatchResult) are frozen dataclasses; cut_leg_a returns the same IUCreateResult type the simulator returns, so phase post-processing is mode-agnostic.

2. Transaction boundary

  • One transaction per phase. Each adapter method opens exactly one txn (conn.execute("BEGIN")), does all its work, then COMMIT.
  • Atomic, all-or-nothing. Any error inside the txn ⇒ ROLLBACK + re-raise (_safe_rollback); never a partial write (v0.5 G6 doctrine).
  • Caller-owned. The adapter — not the v0.5 recorder classes — owns BEGIN/COMMIT/ROLLBACK, exactly as prod_iu_adapter_canonical requires.
  • cut_leg_a / lifecycle_enact fan out N SELECT fn_iu_create(...) / fn_iu_enact(...) calls inside the single txn; one bad status ⇒ whole batch rolls back.

3. Dry-run vs live separation

mutating phase body:
  refuse_if_killswitch_off(ctx, phase)        # 1. kill-switch gate
  ... approval / pin checks ...
  if ctx.mode == Mode.LIVE:
      adapter = require_live_adapter(db_provider, phase)   # 2. adapter or refuse
      result  = adapter.<phase>(ctx, ...)                  # 3. LIVE → adapter
  else:  # Mode.DRYRUN
      result  = discoverer.simulate_<phase>(...)           #    DRYRUN → simulator

The simulator is reachable only on the Mode.DRYRUN branch. Mode.LIVE reaches the adapter or fails closed — it can never fall through to the simulator.

4. Kill-switch enforcement

Three independent locks, all reading the switch dynamically:

  1. execution_enabled() — a function in orchestrator/__init__.py (replaces value-import of __execution_enabled__), evaluated at call time. Backing constant stays False.
  2. refuse_if_killswitch_off(ctx, phase) — every mutating phase calls it first; Mode.LIVE + switch OFF ⇒ ProductionExecutionNotAuthorized.
  3. ProductionLiveExecutionAdapter._assert_live_allowed — defence in depth: the adapter re-checks the switch even if a caller bypassed the phase guard.

The runner's cut() also gates Mode.LIVE: kill-switch first, then a mandatory LiveExecutionAdapter wired as db_provider.

5. Role / credential expectation (no secret logging)

  • ProductionLiveExecutionAdapter construction never connects.
  • DB access via an injected connection_provider(role) -> conn; roles are cutter_exec (cut/leg-B/enact) and cutter_verify (verify).
  • Backup via an injected backup_runner(spec) -> dict.
  • The default collaborators all REFUSE (ProductionExecutionNotAuthorized) — no DSN, no .env, no GPG key, no secret at module scope (mirrors prod_iu_adapter_canonical._default_provider).
  • Env-var / role names may appear; values never are.

6. Rollback / compensation hooks

  • Per-phase txn rollback on any error (_safe_rollback).
  • cut_leg_a / lifecycle_enact: a single non-created / non-enacted status raises → whole batch ROLLBACK.
  • pre_write_backup runs before any DB write — the fresh backup is the cross-phase compensation anchor.
  • A torn live run escalates via SG_3 to a separate sovereign compensation macro (unchanged orchestrator doctrine).

7. Design-lock verdict

G2_live_adapter_design_lock: PASS
interfaces:        LiveExecutionAdapter ABC (6 methods) + 4 result types
txn_boundary:      one atomic txn per phase; rollback on any error
dryrun_vs_live:    simulator on Mode.DRYRUN only; never on Mode.LIVE
kill_switch:       execution_enabled() fn + 3 enforcement locks
credentials:       injected collaborators; defaults refuse; no secrets
production_enabled: NO — execution_enabled stays False
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-o8a-live-execution-wiring-authoring/03-live-adapter-design-lock.md