step.21.txt -- b3ubot U3.8: THE M1 ORACLE -- the one-complete-UoW arc, mock AND real provider; the frozen DAG passes the substrate's round-trip oracle. end_to_end.md §5 P3's FINAL item -- the phase's exit milestone, "the engine's first light": ONE invocation drives intent -> skeleton -> human ratify -> execute -> verify -> retro closure, leaving a ledger AND a frozen execution DAG indistinguishable in form from hand-run b3u artifacts. Stops here -- P3 is CLOSED; does NOT continue into P4/P5/P6 or any other phase. Step: 21 Parent: end_to_end.md §5 U3.8 row verbatim ("M1 oracle: the one-complete-UoW arc, mock AND real provider; the frozen DAG passes the substrate's round-trip oracle") + §5's exit condition ("M1, the engine's first light"). design.md §4 ("Everything between the gates is machine" -- the sentence this step finally takes literally; ratify + escalation are the ONLY gates). app/orchestrator/ U3.1-U3.7 (steps 14-20) are the five delivered stages this step COMPOSES -- U3.6 (step.19) and U3.7 (step.20) both explicitly deferred "automatic continuation" to exactly this UoW. The U2.2 "oracle is a regression suite" rule and the U1.4 p1_gates.sh live-gates pattern shape the oracle's form; the step.6/U3.9a corpus-registration precedent shapes the substrate-oracle decision. UoW: U3.8 Date: 2026-07-21 Origin: Operator directive 2026-07-21 (same dispatch shape as step.14-20's): draft AND execute steps/step.21.txt for U3.8 in a single pass (the step.7/9/10/11/14-20 convention, NOT a skeleton-split), with the SAME explicit HARD scope boundary -- U3.8 closes P3; stop after it, do not continue into P4/P5/P6 or any later phase; the operator directs one UoW at a time. ## (0) Status EXECUTED in this pass. Not a skeleton-then-later-execute split -- app/orchestrator/arc.py, the ArcError addition, the `b3ubot run` CLI subcommand, the M1 mock-fixture scenarios, tests/test_m1_arc.py, scripts/m1_oracle.sh, the committed engine-produced regression corpus dag/instances/m1_execution_example.dag (+ corpora.sh registration + instances README), the end_to_end.md §5 row + P3-exit-state note + §11 ledger row, tests/testing.md (same commit, its own maintenance rule), the stale "U3.8 pending" docstring sweep, and this step file were all built together, per the operator's explicit "draft AND execute (single pass)" direction. Per the operator's explicit scope boundary (Origin, above), this session STOPS after U3.8 -- **P3 is CLOSED**; P4 (Track B), P5 (CCS plane), P6 (agnosticism) are NOT started: the engine returns control to the human between phases. M1's honest achieved state, stated plainly (the row and the §5 exit note carry the same words): the arc is FULLY proven on the mock provider -- hermetically (pytest) and live (the real CLI, whose frozen execution DAG is committed as a permanent corpus) -- and the "with a real provider" clause is held by a STANDING keyed oracle leg that runs the identical arc live the moment the environment provides a usable $ANTHROPIC_API_KEY. In THIS environment the key is genuinely absent (outcome (a) of the three the operator's brief named): the live real-provider confirmation is recorded as PENDING-ON-ENVIRONMENT, never fake-passed. See §(1) CONFIRMED for the exact environment fact. ## (1) Ground truth probed at drafting (outputs -> named writers) READ end_to_end.md §5 in full: the exit condition verbatim ("M1, the engine's first light: ... with a real provider, leaving a ledger AND a frozen execution DAG indistinguishable in form from hand-run b3u artifacts") and the U3.8 row ("mock AND real provider; the frozen DAG passes the substrate's round-trip oracle"). READ app/orchestrator/ in full (steps 14-20's five stages + state machine + persistence + compiler): every stage's signature, result dataclass, legality-first rule, and resume semantics -- the arc COMPOSES these, so their exact contracts are the design input. app/cli.py in full: the five arc subcommands, the Q-8-B guard per-command reasoning (the `run` guard is their union), _make_adapter (§2.11 -- provider names stay out of the orchestrator). READ step.19/step.20's explicit deferrals ("automatic execute->verify continuation is U3.8's M1 arc"; "automatic verify->close continuation is U3.8's") -- the wiring this step owns. READ tests/test_synthetic_uow_arc.py in full (the U2.4-era arc test the brief flagged): it walks the U2.4 LEDGER MACHINERY (draft-step template/table ops/step_gdiff) through a synthetic UoW with ratify/execute/verify as explicit stand-ins -- P3's orchestrator is named out of its scope in its own docstring. Decision: EXTEND-AS- SIBLING, not edit -- it stays exactly what it honestly is (the U2.4 machinery proof); tests/test_m1_arc.py is the REAL-orchestrator arc oracle. Zero edits to it. READ tests/test_orchestrator_closing.py (the toy-workspace + git fixture + ledger-doc conventions, reused); adapters/mock/fixtures.py (the UT.1 skeleton builder + EXECUTE routing key -- the M1 scenarios are additive siblings); scripts/p1_gates.sh in full (the live-gates shape m1_oracle.sh mirrors); dag/regression/lib/ corpora.sh + bin/run_regression (corpus registration + what the matrix runs); tests/testing.md (the live doc's own same-commit maintenance rule). CONFIRMED baseline: full suite 536 passed / 11 skipped before any edit (matches step.20's reported final). CONFIRMED the keyed environment, WITHOUT ever printing key material (the operator's brief demanded exactly this): `claude` binary IS on PATH; $ANTHROPIC_API_KEY is NOT in this process's env; ~/.anthropic_api.env EXISTS but is an UNFILLED TEMPLATE -- its one variable line is `export ANTHROPIC_API_KEY=` with a ZERO-LENGTH value (checked via awk length(), names/lengths only; a sourcing probe in a throwaway subshell confirmed the var stays empty). So the keyed leg's outcome here is (a) key absent -> the established honest skip -- NOT (c) billing-blocked: no live call was possible at all, and none was attempted against an empty credential. CONFIRMED live CLI smoke BEFORE committing (scratch workspace + isolated $B3UBOT_HOME): ONE `b3ubot run UM.1 --title ... --intent ... --yes` invocation drove draft -> ratify -> execute -> verify -> close end to end, rc=0: 3 commits (founding + work 61d818d + gdiff ef9982b in the scratch repo), porcelain clean, real hash in §(7) AND the ledger row, both instances frozen, "arc complete ... engine state CLOSED". THIS run's frozen step.1.execution.dag is the committed exemplar (2.5) -- captured verbatim, not re-generated. CONFIRMED `git log --oneline -30` for the 2-commit phrasing (step.20's precedent, 7cbee41/8c72bc4). ## (2) What U3.8 delivers 2.1 **app/orchestrator/arc.py** (new; the package's capstone): `run_uow_arc(workspace_root, uow, *, adapter, provider, output_fn, input_fn, uow_title=, intent=, assume_yes=, home=, date=, max_repair_attempts=, scripts_home=)` -> ArcResult. **The continuation shape (THE core decision, made and owned): a STATE-DISPATCH loop, not a fixed five-call script.** Each iteration reads the engine state and dispatches the one stage that state admits (untracked/INTENT->draft, SKELETON->ratify, RATIFIED/EXECUTING->execute -- EXECUTING resumes, executing.py's own rule -- VERIFYING->verify, RETRO->close), looping to CLOSED. Why dispatch: design §4 IS a state machine, so the honest continuation advances whatever state the record is in; RESUMPTION FALLS OUT FOR FREE (a halted execution, an escalated verify, or an arc stopped at amend all re-enter with the same `b3ubot run`); and every per-stage behavior (bounded repair, freeze walls, egress, trails, legality-first) is exercised UNCHANGED -- the arc composes U3.3-U3.7, it adds NO transition and reimplements NOTHING. A bounded dispatch cap (7) turns a state-not-advancing bug into a loud ArcError instead of a spin. 2.2 **Where the arc stops -- the gates, and ONLY the gates (design §4's own set):** ratify (interactive per-LEAN, or --yes ratify-as-is; a non-ratified decision -- amended/discarded/ aborted -- returns closed=False with a named stop_reason: a DECISION, not an error, CLI exit 0) and escalation (EscalationError propagates unchanged; state stays VERIFYING; a re-run re-enters with a fresh budget). The in-loop proposal review inside EXECUTING keeps its own gate (or --yes), exactly as `b3ubot execute` runs it. `uow_title`/`intent` are required ONLY when the arc actually starts at draft (ArcError names --title/--intent otherwise); a terminal UoW refuses loudly (never revived -- the U3.7 rule). Plus ArcError in errors.py; arc exports through app/orchestrator/__init__. 2.3 **CLI: top-level `b3ubot run`** (the draft/ratify/execute/ verify/close sibling row's capstone -- ALL five per-stage commands remain first-class; `run` composes them). Q-8-B as the UNION of the stages' rules: --provider human needs a TTY regardless of --yes (the relay is interactive); a non-TTY stdin refuses unless --yes (--yes = ratify-as-is + auto-approved in-loop gate, each stage's established semantics). Presentation- layer errors only; an honest gate stop prints "arc stopped: " and exits 0. 2.4 **The M1 oracle -- a PERMANENT regression in two halves (the U2.2 rule; LEAN taken):** (a) tests/test_m1_arc.py (16 new tests; pytestmark on git AND the built bin/dagModel -- the arc genuinely needs both): the full mock arc on a git toy workspace with the M1 clause "indistinguishable in form from hand-run b3u artifacts" made MECHANICAL -- the closure-produced step file PARSES under app/ledger/parser.py with all FOUR engine stamps in §(0) (ratification/execution/verification/closure); the workspace ledger table byte-round-trips through the U2.3 machinery with the REAL work-commit hash in the row; BOTH frozen instances byte-round-trip through the REAL bin/dagModel; the retro pair is the MACHINE-DRAFTED corpus shape with a real gdiff; 3 commits + clean porcelain; plus the honest-stop (amend), mid-arc resume (SKELETON entry, no re-draft), missing-title/ intent, terminal, own-repo-untouched, and 4 CLI legs. tests/test_synthetic_uow_arc.py deliberately NOT edited -- extend-as-sibling (§(1)'s reading: it proves the U2.4 ledger machinery, and still does). (b) scripts/m1_oracle.sh (the p1_gates.sh live half: pass/fail counters, scratch WORK dir, real CLI invocations): boot (venv + import + git + bin/dagModel, each refusal naming the fix); the MOCK ARC LEG always -- one `b3ubot run --yes`, then 15 named checks incl. the in-oracle substrate round-trips and the egress-trail count; the KEYED LEG (below); B-4 hygiene (the key value appears nowhere under $WORK -- and is never echoed by the script itself). Live this session: mock leg 19/19 PASS, keyed leg honest SKIP, exit 0. 2.5 **The substrate round-trip oracle -- decision: BOTH legs (the brief's LEAN, taken):** (i) per-run, in-oracle: both frozen instances re-verified through executing.roundtrip_verify (the REAL bin/dagModel parse + byte-compare) inside the pytest arc AND the live script; (ii) permanent, in-corpus: dag/instances/m1_execution_example.dag -- the frozen evidence graph of THIS session's real `b3ubot run` mock arc, captured VERBATIM from the closed scratch workspace (4 nodes/3 edges, the execution kind, every node carrying the live evidence kvs the engine stamped + the dag-level frozen_at) -- registered as the `m1_exec` corpus in dag/regression/lib/corpora.sh (7 -> 8; header comment updated; instances README row + scope section added). The step.6/U3.9a hand-authored exemplar thereby UPGRADED: the regression now re-proves on every run that what the ENGINE WRITES round-trips -- and the full dag suite ran green with it THIS session: matrix 8/8 corpora 4-way byte-identical (C++/Py/JS/Rust), npass 8/8, quirks 8/8 baseline, 10 subprojects PASS. tests/test_m1_arc.py pins the exemplar permanently (exists + frozen + round-trips + registered). 2.6 **The keyed/real-provider leg -- the three-outcome honest handling (the operator's brief, implemented verbatim):** the leg gates on ClaudeCodeAdapter.is_available() (the p1_gates.sh faithful-mirror pattern -- never a bash re-derivation; the key VALUE never echoed): (a) unavailable -> named SKIP; the leg STANDS and auto-runs when the environment allows (the step.3/9 keyless discipline); (b) available + arc completes -> the M1 real-provider clause proven live (the same 15-check battery runs on the keyed workspace; the keyed intent adds "keep the change minimal" -- smallest possible spend); (c) available but the call fails on a billing/credit/auth condition -> BLOCKED (environment): the named evidence line is printed, counted SEPARATELY from FAIL (an environment fact is not an engine defect), and the oracle exits 0 with a loud "PASS with N BLOCKED(environment) leg(s)" -- never fake-passed as live, never misreported as an engine failure. THIS session's actual outcome: (a) -- §(1) CONFIRMED: the key file is an unfilled template (zero-length value), so no live call was possible; M1's "with a real provider" clause is recorded in the §5 row and exit note as PENDING-ON-ENVIRONMENT, held by the standing leg. 2.7 **Mock-fixture scenarios (additive; zero edited fixtures):** adapters/mock/fixtures.py's `_skeleton()` gained a `gates=` param (default byte-identical to the pre-step fixture -- DRAFT_SKELETON_UT1 unchanged); DRAFT_SKELETON_M1 = the UM.1 skeleton whose §(3) carries the U3.6 tier markers ([T0:py_compile] + [T0:ledger_roundtrip] -- both hermetic, deterministic, and §2.5's own named T0 pair; T1 pytest deliberately not in the M1 toy: the tier machinery is U3.6's already-proven ground, and the M1 arc's job is the CONTINUATION, not re-proving tiers). Exactly TWO scripted scenarios cover the whole arc (the draft intent key + the deterministic "EXECUTE UM.1: ..." key) -- the green verify pass and the deterministic closure touch no provider at all. 2.8 **Docs (same commit):** end_to_end.md §5 U3.8 row -> DONE in the U3.2-U3.7 row style (with the keyed-leg caveat stated) + the NEW "P3 exit state" note under §5's exit condition (M1 ACHIEVED with one named caveat -- the actual achieved state, not the aspiration); §11 ledger THROUGH the ledger CLI (transition U3.8 active --step step.21; close U3.8 PENDING; hash backfilled in the gdiff commit -- the step.13-20 shape). tests/testing.md updated in the SAME commit as the tests it inventories (its own maintenance rule, operator-requested): new test_m1_arc.py section (16 rows), phase-overview and totals reconciled to 27 files / 402 functions / 565 collected, the pytestmark-guard note, the 8-corpora + m1_oracle.sh pointer. docs/orchestrator_state_machine.md's does-NOT-do list annotated (U3.8 delivered). Stale "is U3.8's M1" claims in app/cli.py + app/orchestrator/closing.py -> "DELIVERED at step.21"; app/orchestrator/__init__ docstring + exports. design.md deliberately UNTOUCHED -- checked for drift, found none: §4 already describes exactly what the arc now does, and naming U3.8 per-step delivery there would duplicate the ledger's job (the "only where genuinely drifted" rule). EXPLICITLY OUT OF SCOPE (named here per the operator's brief, not silently built -- and NOT continued into after this step, per the operator's explicit P3-closes-here scope boundary): - P4 (Track B: B3UBOT.sgr -> SQLite -> OO API -> CLI). -> next phase(s), - P5 (CCS plane / b3u client; T3 stays refusing). operator- - P6 (second adapter / agnosticism gate / T4). directed - PAL-narrated retro (step.20's named post-M1 upgrade -- M1 is now closed, so it is ELIGIBLE, but not begun). -> later - Auto re-draft on amendments (Q-17-A stays open). -> later - Resolving d9 beyond its recorded V0 default. -> later - Pushing anything anywhere (git_push still refuses; B-3). -> never (without explicit operator direction) ## (3) Verification gates G1 tests/test_m1_arc.py 16/16 PASS live: the full mock arc (one invocation, five stages, INTENT..CLOSED history), the mechanical form assertions (parser + all four §(0) stamps; ledger-table byte round-trip with the real hash; both frozen instances through the REAL bin/dagModel), the committed exemplar pin, honest stop/resume/terminal/refusal legs, CLI legs -- scripted adapters, injected I/O, zero network. G2 scripts/m1_oracle.sh live this session: boot 4/4, mock arc 19/19 PASS (one real `b3ubot run --yes` through the real CLI on a scratch git workspace; in-oracle substrate round-trips both instances; egress-trail count), keyed leg honest SKIP naming the adapter's own is_available() reason, exit 0. The script is a PERMANENT standing oracle -- re-run in a keyed environment, the keyed leg goes live unchanged. G3 The three-outcome keyed handling exists IN THE ORACLE (not prose): (a) skip on unavailable; (b) the full check battery on a live completion; (c) BLOCKED(environment) on billing/credit/auth failure -- named evidence line, counted separately from FAIL, loud in the summary. The environment fact behind this session's (a) established WITHOUT printing key material: ~/.anthropic_api.env is an unfilled template (zero-length value -- awk length() + subshell sourcing probe, §(1) CONFIRMED). G4 The substrate round-trip oracle, both halves: (i) per-run roundtrip_verify on both frozen instances in test + script; (ii) dag/instances/m1_execution_example.dag committed (captured verbatim from the live smoke's closed workspace), registered as m1_exec, and the FULL dag regression suite run green with it this session: matrix 8/8 4-way (C++/Py/JS/Rust), npass 8/8, quirks 8/8 baseline, 10 subproject PASS / 0 FAIL / 0 SKIP. G5 The continuation composes, never reimplements: arc.py calls the five delivered stage functions with their own signatures, adds NO state-machine edge, and the per-stage tests (steps 14-20's 220) all still pass unchanged -- zero regressions, zero edited tests (the fixture builder's gates= param is default-byte-identical, asserted by the untouched drafting/ratify/executing/verifying/closing suites passing). G6 Full suite before this step's edits: 536 passed / 11 skipped (confirmed live, matching step.20's reported final). After: 552p/11s pre-step-file (+16 m1_arc); 554p/11s once this step.21.txt lands in the corpus (+2 self-inclusive pickups -- the steps 10-20 growth pattern). G7 Scope boundary held: P3 CLOSED and NOTHING beyond it -- no P4/P5/P6 artifact, no PAL retro, no Q-17-A, no d9 re-resolution, nothing pushed anywhere; this repo's own steps/ + dag/instances/ untouched by every test (asserted in-suite; the ONE deliberate dag/instances addition is the committed exemplar, made by THIS session's own hand, not by a test); this session stops after U3.8. G8 §11 ledger row updated THROUGH the existing `ledger` CLI (transition -> active --step step.21, close -> done/PENDING), against the REAL end_to_end.md -- dogfooding, the step.13-20 precedent. tests/testing.md updated in the SAME commit as the tests (its own rule). G9 M1's "indistinguishable in form" bar checked artifact by artifact MECHANICALLY (G1's form assertions), and the engine-produced closure's artifact set matches what steps 13-20 left by hand: step.N.txt with §(0) stamps + §(7) real hash, step.N.diff.txt, step.N.gdiff.txt, done ledger row with real hash, 2 machine commits, frozen evidence graphs. G10 b3ubot porcelain clean after each commit; retro pair via the SHARED v3_claude/scripts/step_gdiff (steps/step.21.gdiff.txt); exactly 2 commits (work + gdiff-with-hash-backfill-folded-in), the step.7-20 precedent. ## (4) LOCKs C-21-A b3ubot porcelain clean after each commit. C-21-B THE ARC COMPOSES, NEVER REIMPLEMENTS: run_uow_arc adds no state-machine edge, no stage logic, no gate of its own -- it dispatches the five delivered stage functions and stops exactly at design §4's two human gates. Any future stage change flows through to the arc for free; arc.py must never grow stage-local behavior. C-21-C THE M1 CLAIM STAYS HONEST: anywhere M1 is stated (the §5 row, the exit note, this file), the real-provider clause carries its actual status. Until the keyed leg has run live, that status is pending-on-environment -- held by scripts/m1_oracle.sh's standing keyed leg -- and a future live run updates the record THEN, not before. Never fake-passed, never quietly dropped. C-21-D KEY MATERIAL IS NEVER PRINTED: the environment probe reported presence/absence and value LENGTH only; the oracle script never echoes the key and greps its own logs for leakage (B-4). This session printed no key byte and attempted no call against an empty credential. C-21-E THE ENGINE-PRODUCED EXEMPLAR IS EVIDENCE, NOT A BUILD ARTIFACT: dag/instances/m1_execution_example.dag is the verbatim frozen instance of a real arc run -- it is never regenerated in place, never hand-edited; a future better exemplar is a NEW file + corpus registration (the monotonic-id spirit applied to corpora). C-21-F THE PER-STAGE COMMANDS REMAIN FIRST-CLASS: `run` is a composition, not a replacement -- draft/ratify/execute/ verify/close stay documented, tested, and usable one stage at a time (the operator's own working style). C-21-G NO SILENT SCOPE CREEP: P4/P5/P6, the PAL retro, Q-17-A, d9's final policy all named out of scope in (2) and confirmed not started (G7); the operator's explicit "P3 closes here" direction is honored. C-21-H standing locks inherited: B-3 (no disclosure/no push); B-4/B-5 (keys env-only + never logged; every provider exchange egress-recorded -- the arc's stages record exactly as they did standalone); C-15-B (the tool trail); C-20-B (frozen evidence never rewritten -- the arc re-runs REFUSE over frozen instances via the existing walls); C-20-C (the machine retro transcribes, never invents). ## (5) Open questions Q-21-A Should `run` re-verify a resumed arc's ALREADY-green stages (e.g. re-run verify when resuming at RETRO)? DECIDED HERE for V0: no -- the engine record + frozen evidence are the proof the stages ran (Q-20-B's own reasoning, inherited); a paranoid re-check would double-spend the oracle on every resume. Revisit only if a real staleness incident surfaces. Q-21-B Should the keyed leg's outcome (b), when it eventually runs live, ALSO capture a real-provider exemplar corpus (a keyed sibling of m1_execution_example.dag)? LEAN: yes, as a one-time act in whatever step witnesses the first live run -- the corpus would then carry both a mock-provider and a real-provider engine-produced instance. Left for that step; not pre-built. Q-21-C Where does the PAL-narrated retro upgrade (step.20's named post-M1 item, now eligible) get scheduled? LEAN: as a P3-numbered follow-up UoW (U3.10-style, §12's next-free-number rule) whenever the operator promotes it -- it is a quality upgrade inside a closed phase, not a new phase's entry. Operator's call; recorded only so eligibility is not lost. ## (6) Acceptance - [x] app/orchestrator/arc.py: the state-dispatch continuation -- one invocation, five composed stages, gates-only stops, resume-for-free, bounded dispatch (2.1, 2.2, G1, G5). - [x] `b3ubot run` CLI: the sibling row's capstone; Q-8-B as the union rule; honest gate-stop exit 0 (2.3, G1). - [x] The M1 oracle, both halves, both PERMANENT: 16-test pytest arc + scripts/m1_oracle.sh live (2.4, G1, G2). - [x] Substrate round-trip decided BOTH ways and proven: per-run in-oracle + the committed engine-produced m1_exec corpus, dag regression suite green 8/8 4-way (2.5, G4). - [x] Keyed leg: three-outcome handling in the oracle; THIS session's outcome (a) recorded with the exact environment fact, key material never printed (2.6, G3, C-21-C/D). - [x] Mock scenarios additive; DRAFT_SKELETON_UT1 byte-identical; zero edited tests/fixtures (2.7, G5). - [x] 16 new tests, zero regressions (536p/11s -> 552p/11s -> 554p/11s with this file) (G1, G6). - [x] end_to_end.md §5 row + P3 exit-state note + §11 via the ledger CLI; tests/testing.md same-commit; state-machine doc; stale-docstring sweep; design.md checked, no drift (2.8, G8). - [x] Scope boundary held: P3 CLOSED, nothing beyond started (G7, C-21-G) -- this session stops after U3.8. - [x] Retro pair: this file + step.21.diff.txt + step.21.gdiff.txt via the SHARED scripts/step_gdiff (G10). - [x] Exactly 2 commits (work + gdiff-with-backfill), not 3 (G10). ## (7) Hash backfill WORK commit (app/orchestrator/arc.py + errors + __init__ + cli + mock fixtures + tests/test_m1_arc.py + scripts/m1_oracle.sh + dag/instances/m1_execution_example.dag + corpora.sh + READMEs + end_to_end.md/testing.md/docs + this file): 69c2df9 GDIFF commit (step.21.gdiff.txt via scripts/step_gdiff): (HEAD) End of step.