step.21.diff.txt -- narrative retro for step.21 (U3.8: THE M1 ORACLE
-- the one-complete-UoW arc, mock AND real provider; the frozen DAG
passes the substrate's round-trip oracle. P3 CLOSED.)

Executed: 2026-07-21, same session as the draft (single pass, the
step.7/9/10/11/14-20 convention -- arc.py, the ArcError addition, the
`run` CLI subcommand, the additive M1 mock scenarios, the 16 test
additions, scripts/m1_oracle.sh, the committed m1_exec corpus +
registration, the end_to_end.md/testing.md/state-machine-doc edits,
and the step file all built together). Companion artifact:
step.21.gdiff.txt (automated, scripts/step_gdiff). Per the operator's
explicit per-UoW checkpoint rule (HARD), this session STOPS after
U3.8 -- P3 is CLOSED; P4/P5/P6 are not started.

## Plan vs actual

```
+---+----------------------------------+------------------------------------------+
| # | Planned (brief)                  | Actual                                   |
+---+----------------------------------+------------------------------------------+
| 1 | Decide the continuation shape    | STATE-DISPATCH loop (the brief's LEAN    |
|   |   (`b3ubot run` or justified     | sharpened): each iteration dispatches    |
|   |   alternative)                   | the one stage the engine state admits,   |
|   |                                  | so resumption falls out FREE (halted     |
|   |                                  | execute, escalated verify, amend stop    |
|   |                                  | all re-enter the same command) and the   |
|   |                                  | arc composes U3.3-U3.7 unchanged -- no   |
|   |                                  | new edge, no reimplementation. Stops     |
|   |                                  | only at design section 4's two gates.    |
| 2 | The M1 oracle's form (pytest +   | BOTH, permanent (the U2.2 rule):         |
|   |   script LEAN)                   | tests/test_m1_arc.py (16, hermetic,      |
|   |                                  | mock, form assertions mechanical) +      |
|   |                                  | scripts/m1_oracle.sh (p1_gates shape:    |
|   |                                  | real CLI, mock leg always 19/19, keyed   |
|   |                                  | leg standing). test_synthetic_uow_arc    |
|   |                                  | read and left as-is: it honestly proves  |
|   |                                  | the U2.4 LEDGER machinery, so the M1     |
|   |                                  | test is a SIBLING, not an edit.          |
| 3 | Substrate round-trip decision    | BOTH legs (the brief's LEAN): per-run    |
|   |   ((i) in-oracle and/or (ii)     | roundtrip_verify on both frozen          |
|   |   committed corpus)              | instances in test AND script; PLUS the   |
|   |                                  | live smoke's own frozen execution DAG    |
|   |                                  | committed VERBATIM as                    |
|   |                                  | dag/instances/m1_execution_example.dag,  |
|   |                                  | registered as m1_exec (corpora 7->8);    |
|   |                                  | full dag regression green with it: 8/8   |
|   |                                  | 4-way byte-identity + npass + quirks.    |
| 4 | Keyed leg: three-outcome honest  | Implemented IN the oracle ((a) skip /    |
|   |   handling; check key without    | (b) live battery / (c) BLOCKED(env)      |
|   |   printing it                    | counted separately from FAIL). Actual    |
|   |                                  | outcome here: (a) -- and sharper than    |
|   |                                  | the brief expected: ~/.anthropic_api.env |
|   |                                  | exists but is an UNFILLED template       |
|   |                                  | (value length 0, checked via awk         |
|   |                                  | length() + a subshell sourcing probe;    |
|   |                                  | no key byte printed, no call attempted   |
|   |                                  | against an empty credential). M1's       |
|   |                                  | real-provider clause recorded as         |
|   |                                  | pending-on-environment, held by the      |
|   |                                  | standing keyed leg.                      |
| 5 | Form assertions ("indistinguish- | Mechanical, all green: closure step      |
|   |   able in form")                 | file parses (parser.py) with all 4       |
|   |                                  | engine stamps; ledger table byte-        |
|   |                                  | round-trips (U2.3) with the REAL hash;   |
|   |                                  | both frozen DAGs byte-round-trip via     |
|   |                                  | bin/dagModel; retro pair = the corpus    |
|   |                                  | MACHINE-DRAFTED shape; 3 commits +       |
|   |                                  | clean porcelain.                         |
| 6 | Tests + testing.md same commit   | 16 new tests; testing.md same-commit     |
|   |                                  | (new section, phase overview, totals     |
|   |                                  | 27 files/402 funcs/565 collected,        |
|   |                                  | guard + 8-corpora notes) -- its own      |
|   |                                  | maintenance rule honored.                |
| 7 | Docs: section 5 row DONE + P3    | Row DONE in the U3.2-U3.7 style with     |
|   |   banner/exit note; ledger via   | the caveat stated; NEW "P3 exit state"   |
|   |   CLI; design.md if drifted      | note under the exit condition; ledger    |
|   |                                  | via the ledger CLI (dogfooded);          |
|   |                                  | design.md checked -- NO drift, left      |
|   |                                  | untouched (named, not skipped).          |
+---+----------------------------------+------------------------------------------+
```

## Surprises / genuine findings

1. **The arc composed clean on the first run** -- the 5-stage mock
   arc test passed on its first execution, and so did the live CLI
   smoke. Steps 14-20's insistence on per-stage legality-first,
   injectable I/O, and files-as-truth meant the continuation had
   NOTHING to patch around: the M1 arc is 200 lines of dispatch, not
   an integration battle. The composition discipline's payout,
   measured twice now (step.20 recorded the same for closure).
2. **The key file is an unfilled template** -- the brief prepared for
   outcome (c) (key present, billing-blocked); the truth was sharper:
   `~/.anthropic_api.env` exists (0600, correctly commented) but its
   `export ANTHROPIC_API_KEY=` line carries a ZERO-LENGTH value --
   the operator scaffolded the file (step.3's own instructions) and
   never filled it, consistent with the blocked credit purchase
   making a key pointless to install. Established without printing
   anything (length-only probes); recorded as outcome (a). The
   BLOCKED(environment) branch is built and standing but has never
   yet fired.
3. **The engine's own instance IS corpus-grade** -- the frozen
   execution DAG from the live smoke ran the full 4-language
   byte-identity matrix (C++/Py/JS/Rust), N-pass, and quirk legs
   with ZERO adjustment: the step.18/19 kv-sanitization and ASCII
   lessons (learned against exactly these substrates) held. The
   corpus upgrade from hand-authored exemplar to engine-produced
   evidence cost one `cp` and one registration line.
4. **The state-dispatch shape made `--title/--intent` optional-by-
   state** -- a fixed five-call script would have required them
   always; dispatch means a resumed arc doesn't need them at all,
   which fell out as a small honest UX win (and its own refusal
   message when a FRESH arc lacks them).
5. **The synthetic-arc test survived unedited** -- the brief flagged
   it for extend-or-sibling; reading it settled the question in one
   docstring: it proves the U2.4 ledger MACHINERY and says so
   explicitly, so the M1 test is its P3 sibling, and both now
   bracket the arc from below (machinery) and above (orchestrator).

## Scope discipline

P3 CLOSES with this step. P4 (Track B), P5 (CCS plane -- T3 still
refuses naming U5.2), P6 (second adapter/agnosticism/T4), the
PAL-narrated retro (now eligible, Q-21-C records where it could
land), Q-17-A, d9's final policy, pushing anything anywhere: none
started (G7). The operator's explicit P3-closes-here boundary is
honored -- control returns after the retro pair.

## Numbers

536 passed / 11 skipped before -> 552 passed / 11 skipped after the
build (+16 tests/test_m1_arc.py) -> 554 passed / 11 skipped with this
step's files in the corpus (+2 self-inclusive pickups). Zero
regressions; zero existing tests edited (the fixture builder's new
`gates=` param is default-byte-identical -- DRAFT_SKELETON_UT1's
bytes unchanged, proven by the untouched step.16-20 suites). Live:
one `b3ubot run --yes` arc rc=0 (the exemplar's source run);
m1_oracle.sh mock leg 19/19 PASS + keyed leg honest SKIP, exit 0;
dag regression 10 subprojects PASS with the new 8th corpus 4-way
byte-identical.

End of retro.
