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 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:] 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/.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.