step.19.diff.txt -- narrative retro for step.19 (U3.6: VERIFYING --
T0/T1 tiers wired; the gate graph as a `verification` DAG; bounded
repair loop (d9 V0 default recorded) + escalation)

Executed: 2026-07-21, same session as the draft (single pass, the
step.7/9/10/11/14-18 convention -- verifying.py, the two new error
classes, the executing.py repair/halt_note growth, the drafting
template extension, the `verify` CLI subcommand, the 34 test
additions, the design.md/end_to_end.md/orchestrator_state_machine.md
edits, and the step file all built together). Companion artifact:
step.19.gdiff.txt (automated, scripts/step_gdiff). Per the operator's
explicit per-UoW checkpoint rule (HARD), this session STOPS after
U3.6 -- U3.7 is not started.

## Plan vs actual

```
+---+----------------------------------+------------------------------------------+
| # | Planned (brief)                  | Actual                                   |
+---+----------------------------------+------------------------------------------+
| 1 | Ground the gate-naming           | Surveyed both corpora: G<n> tokens +     |
|   |   convention in the real corpus  | free prose everywhere, ZERO tier         |
|   |   §(3) sections + the drafting   | markers anywhere. Kept the G-block       |
|   |   template; extend the template  | split (extract_leans precedent) + ONE    |
|   |   if needed                      | bracketed [T<n>:<check>] marker per      |
|   |                                  | machine gate; extended                   |
|   |                                  | _format_instructions with the one-line   |
|   |                                  | instruction (the anticipated finding --  |
|   |                                  | without it the convention is             |
|   |                                  | unreachable from the drafting flow).     |
| 2 | Decide d9: resolve, or V0        | V0 DEFAULT, row stays OPEN: fixed        |
|   |   default with the row open      | attempts, DEFAULT_MAX_REPAIR_ATTEMPTS    |
|   |                                  | = 2, per-invocation (re-run after        |
|   |                                  | escalation IS the human override).       |
|   |                                  | Cost-budgeting lacks a cost model;       |
|   |                                  | signal-driven lacks a verdict lattice    |
|   |                                  | richer than pass/fail. Recorded          |
|   |                                  | IN-TABLE in design.md §5.                |
| 3 | T0/T1 on ONLY what exists;       | REAL: py_compile (applied files from     |
|   |   decide real vs named-future    | the execution DAG's own applied kv;      |
|   |                                  | vacuous pass NAMED), ledger_roundtrip    |
|   |                                  | (§2.5's own T0 example, in-process),     |
|   |                                  | pytest (the engine interpreter, exit-    |
|   |                                  | as-data, exit-5 red), T2 via run_gate    |
|   |                                  | as-is. NAMED-FUTURE: lint/type gates,    |
|   |                                  | non-pytest suite adapters, per-          |
|   |                                  | workspace interpreters.                  |
| 4 | The verification-kind DAG:       | Nodes = gates (t0_static/t1_unit/        |
|   |   shape, location, ordering      | t2_gate + tier/check/verdict kvs);       |
|   |                                  | linear precedes chain in tier-ladder     |
|   |                                  | order (cost/determinism + fail-fast);    |
|   |                                  | steps/<step>.verification.dag beside     |
|   |                                  | the skeleton; version = pass number;     |
|   |                                  | run through U3.5's OWN frontier engine   |
|   |                                  | (halt_note param added so its message    |
|   |                                  | cannot lie inside a VERIFYING pass).     |
| 5 | VerificationRecords precursor:   | BOTH, deliberately: node kvs = the live  |
|   |   kv and/or jsonl                | pass surface (rewritten per pass);       |
|   |                                  | ~/.b3ubot/verifications.jsonl = the      |
|   |                                  | durable ordered cross-pass record        |
|   |                                  | (trail.py's JSONL shape), one row per    |
|   |                                  | gate RUN, pass AND fail.                 |
| 6 | Repair re-entry through the SAME | execute_uow grew ONE optional param      |
|   |   machinery, diagnostics egress- | (repair=RepairContext); diagnostics are  |
|   |   recorded; a repair marker      | one more context entry (egress-recorded  |
|   |   mirroring RETRY_MARKER         | by construction); REPAIR_MARKER_TEMPLATE |
|   |                                  | rides the first entry (Q-4-B distinct    |
|   |                                  | scenarios). Exec-instance nodes reset    |
|   |                                  | to pending; U3.5's resume machinery      |
|   |                                  | re-runs exactly the reset set.           |
| 7 | Escalation: report + halt,       | Loud multi-line report + EscalationError |
|   |   state honest                   | (state STAYS VERIFYING); provider calls  |
|   |                                  | bounded by the budget (counted in the    |
|   |                                  | test); no interactive UI (no V0          |
|   |                                  | consumer -- "stops and reports" IS the   |
|   |                                  | contract). Verdict-vs-defect split:      |
|   |                                  | only VERDICTS feed the repair loop.      |
| 8 | CLI: separate verify vs folded   | Separate `b3ubot verify` (the dispatch's |
|   |   into execute                   | LEAN, taken): automatic execute->verify  |
|   |                                  | continuation is U3.8's M1 arc; V0 keeps  |
|   |                                  | the human at the wheel between phases,   |
|   |                                  | symmetric with draft/ratify/execute.     |
|   |                                  | --max-repairs overrides d9's default     |
|   |                                  | per invocation.                          |
+---+----------------------------------+------------------------------------------+
```

## Surprises / genuine findings

1. **Real tool output broke the instance byte round-trip** -- the one
   real mid-build defect, and step.18's encoding lesson in a new
   costume: a verdict `detail` kv carried pytest's multi-line tail
   (newlines + quote characters), which the latin-1 .dag string
   rendering cannot re-produce byte-identically. Fixed at the kv
   boundary: node kv strings are single-line, double-quote-free,
   truncated (`_safe_desc`); the FULL detail still lands in the
   verifications.jsonl row, where JSON escapes anything. The
   green-path roundtrip_verify caught it immediately -- the oracle
   paying rent on its own step.
2. **The node description doubled its gate prefix** -- caught in the
   live CLI smoke, not the tests: the compiled description prepended
   "G1 [T0:py_compile]" to a block that already starts with exactly
   that (it is the corpus gate line verbatim). Fixed on sight;
   worth recording because it shows the live-smoke leg catches what
   assertion-shaped tests do not think to pin.
3. **The verdict-vs-defect split fell out of the kv shape for free.**
   Distinguishing "a red gate" (repairable) from "the engine could
   not run the gate" (never repairable) needed no new channel: a
   gate that RAN always carries `verdict = fail`; a defect halt
   carries only `error`. The evidence discipline again doubling as
   control-flow truth (step.18's finding 2, rhymed).
4. NOT a surprise, verified as hoped: the generic frontier engine ran
   the verification kind unchanged except for one honesty parameter
   (`halt_note` -- its hardcoded halt message named EXECUTING, which
   would have been a lie inside a VERIFYING pass). One engine, two
   workflow kinds, exactly why U3.5 built it generic.

## Scope discipline

U3.7 (RETRO closure/freeze), U3.8 (M1 / automatic continuation), T2
beyond run_gate, T3 (P5), T4 (U6.4), Track B, Q-17-A: none started
(G7). A green UoW lands in RETRO and sits; no RETRO->CLOSED call site
exists anywhere. The operator's explicit stop-after-U3.6 boundary is
honored -- control returns after the retro pair.

## Numbers

481 passed / 11 skipped before -> 515 passed / 11 skipped after the
build (+34 tests/test_orchestrator_verifying.py) -> 517 passed / 11
skipped with this step's files in the corpus (+2 self-inclusive
pickups). Zero regressions; zero existing tests edited (the
stale-docstring sweep touched no pinned strings -- checked by grep
before editing).

End of retro.
