step.19.txt -- b3ubot U3.6: VERIFYING -- T0/T1 tiers wired; the gate
               graph as a `verification` DAG; bounded repair loop
               (d9) + escalation. design.md §2.5's oracle harness
               ("the orchestrator refuses to enter VERIFYING with an
               empty gate set"; "Verdicts are recorded as
               VerificationRecords") and §4's VERIFYING span
               ("--gates green--> RETRO"; "--fail--> EXECUTING
               (bounded repair loop; then human escalation)") -- as
               code. Records d9's V0 default. Stops here -- does NOT
               continue into U3.7 or any later P3 UoW.

Step:     19
Parent:   end_to_end.md §5 U3.6 bullet verbatim ("VERIFYING: T0/T1
          tiers wired; the gate graph as a `verification` DAG;
          bounded repair loop (d9) + escalation"). design.md §2.5
          (the tier table + the empty-gate-set rule + the
          VerificationRecords sentence), §4 (the VERIFYING edges and
          the two load-bearing human gates -- escalation is the
          SECOND), §2.13 (the `verification` kind), §5 d9 (OPEN going
          in). app/orchestrator/ (U3.1-U3.5) is the package this
          grows; executing.py's generic frontier engine + resume
          machinery are what the pass and the repair re-entry run
          through; app/tools/shell.py's scrubbed allowlisted
          run_shell/run_gate are what the checks execute under.
UoW:      U3.6
Date:     2026-07-21
Origin:   Operator directive 2026-07-21 (same dispatch shape as
          step.14-18's): draft AND execute steps/step.19.txt for
          U3.6 in a single pass (the step.7/9/10/11/14-18
          convention, NOT a skeleton-split), with the SAME explicit
          HARD scope boundary -- stop after U3.6, do not continue
          into U3.7 or any later UoW; the operator directs P3 one
          UoW at a time with strict per-UoW checkpoints.

## (0) Status

EXECUTED in this pass. Not a skeleton-then-later-execute split --
app/orchestrator/verifying.py, the VerifyError/EscalationError
additions, the RepairContext + REPAIR_MARKER + halt_note growth in
executing.py, the drafting-template gate-marker extension, the
`b3ubot verify` CLI subcommand, tests/test_orchestrator_verifying.py,
the design.md (§2.5/§2.13/§5 d9) / end_to_end.md /
docs/orchestrator_state_machine.md edits, 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.6 -- U3.7 (RETRO
closure/freeze) is the next UoW but is NOT started here: a verified
UoW lands in RETRO and just sits there; nothing drafts the retro
pair or freezes the instance.

## (1) Ground truth probed at drafting (outputs -> named writers)

    READ    design.md §2.5 in full (the tier table; the load-bearing
            sentence "the orchestrator refuses to enter VERIFYING
            with an empty gate set"; "ledger round-trip" named IN the
            T0 row itself -- so it became a V0 check, not an
            invention), §4 (the VERIFYING edges; "a repair loop that
            exhausts its budget stops and reports; it never grinds
            silently"; the oracle_feedback paragraph the diagnostics
            feed-back realizes), §5 d9 (OPEN: fixed vs cost-budgeted
            vs oracle-signal-driven), §2.13 (the `verification` kind
            = "the gate graph of a VERIFYING pass").
    READ    app/orchestrator/ in full: executing.py (the generic
            run_ready_frontier this step REUSES rather than
            duplicating; the resume machinery whose _RUNNABLE set
            makes a pending-reset re-run exactly the reset nodes; the
            evidence-stamp pattern; _h_provider_run's context shape
            the repair diagnostics had to enter THROUGH, not around);
            state_machine.py (the actual 9 edges: VERIFYING->RETRO
            and VERIFYING->EXECUTING both exist with U3.6-naming
            rationale strings -- NO state-machine change needed, a
            checked finding); drafting.py (RETRY_MARKER's Q-4-B
            distinct-scenario precedent, mirrored as
            REPAIR_MARKER_TEMPLATE; _format_instructions, the
            template the gate-marker extension lands in); ratify.py
            (extract_leans' title-PREFIX + marker-line-split shape,
            mirrored for gate extraction; the legality-first lesson).
    SURVEYED the gate-naming ground truth before designing the
            convention: every §(3) section in this repo's own
            steps/step.2.txt-step.18.txt uses leading `G<n>` tokens
            + free prose; U2.1's 50-file b3u corpus survey documents
            the same shape (one file titles the section "Oracle
            (gates G1-G3)" -- which is why extraction matches the
            title by PREFIX and an off-convention title refuses
            honestly); NO tier markers exist anywhere in either
            corpus; U3.3's template emits "named gates (G1, G2, ...),
            each a checkable claim". Conclusion: keep G-blocks, add
            ONE bracketed [T<n>:<check>] marker per machine gate, and
            extend the drafting template to say so (2.2).
    READ    app/tools/shell.py (run_shell's allowlist matches
            argv[0]'s BASENAME -- so the engine's own sys.executable,
            basename python/python3, is the one interpreter
            guaranteed both present and allowlisted; run_gate;
            scrubbed_env; exit-as-data), trail.py + app/egress.py
            (the two file-based precursor patterns the
            VerificationRecord precursor had to choose between --
            JSONL chosen, 2.5), tests/conftest.py (hermetic
            $B3UBOT_HOME), app/cli.py (the draft/ratify/execute
            sibling row + Q-8-B guards `verify` joins).
    CONFIRMED bin/dagModel is built and working here (step.18's
            check re-verified by the live suite: roundtrip_verify
            returns "verified" on every written instance).
    CONFIRMED `git log --oneline -30` for the 2-commit phrasing
            (step.18's precedent, ae78fe1/8a3718d).
    CONFIRMED baseline: full suite 481 passed / 11 skipped before any
            edit (matches step.18's reported final).
    CONFIRMED live CLI smoke before committing (scratch workspace +
            isolated $B3UBOT_HOME), THREE legs: (a) mock draft +
            ratify --yes + execute --yes then `b3ubot verify --yes`
            -> the EMPTY-gate-set refusal verbatim, naming the marker
            convention and the next action (the drafted fixture
            skeleton's G1 is narrative -- the refusal is the correct
            live outcome); (b) markers added to the skeleton's §(3)
            by hand (the refusal's own named next action) -> verify
            green: 3 gates pass, verification instance beside the
            skeleton, verifications.jsonl rows, ENGINE VERIFICATION
            RECORD in §(0), engine state RETRO, rc=0; (c) the REAL
            repair loop: failing toy test -> pass 1 red at the pytest
            gate -> VERIFYING -> EXECUTING with diagnostics fed back
            -> HUMAN GATE diff of the repair proposal -> apply ->
            pass 2 green -> RETRO (passes=2 repairs=1).

## (2) What U3.6 delivers

  2.1 **app/orchestrator/verifying.py** (new; the package's
      per-concern peer of drafting.py/ratify.py/executing.py):
      `verify_uow(workspace_root, uow, *, adapter, provider,
      output_fn, input_fn, assume_yes=, home=,
      max_repair_attempts=)` -- the full arc: state legality FIRST
      (validate_transition(state, "RETRO") admits exactly VERIFYING
      -- the ratify.py indirection precedent; zero reads on an
      illegal move), gate extraction (refuse loudly on an EMPTY
      machine-runnable set -- §2.5's own rule), per pass: compile the
      verification DAG, write it beside the skeleton, run the tier
      chain, record verdicts; green -> stamp + VERIFYING -> RETRO;
      red verdict -> one bounded repair re-entry; budget spent ->
      loud escalation. Plus VerifyError + EscalationError in
      errors.py, all re-exported through app/orchestrator/__init__.
  2.2 **The gate-naming convention (decision, grounded -- see §(1)
      SURVEYED):** gate blocks split on leading `G<n>` marker lines
      (the extract_leans precedent; gates section found by title
      PREFIX "Verification gates"); a gate is MACHINE-RUNNABLE iff
      its block carries exactly ONE bracketed tier marker:
      [T0:py_compile], [T0:ledger_roundtrip], [T1:pytest], or
      [T2:<script>.sh]. Marker-less gates stay what the corpus always
      made them -- narrative claims for the human retro (counted,
      never run). Unknown checks/tiers refuse loudly naming the
      vocabulary; [T3:...] refuses naming P5/U5.2 (the plane is not
      available; a T3-NAMING skeleton already gets its oracle_gate
      node in the EXECUTION DAG, compiler.py -- that is where T3
      rides today); [T4:...] refuses naming LOCK B-6 + U6.4 (a
      blocking T4 gate is a contradiction); two markers in one gate
      and duplicate gate ids refuse (ambiguity is never guessed at).
      THE TEMPLATE EXTENSION (the honest finding the dispatch
      anticipated): U3.3's `_format_instructions` §(3) line gained
      the one-sentence marker instruction so PAL-drafted skeletons
      can name machine gates -- without it the convention would be
      unreachable from the drafting flow.
  2.3 **T0/T1 V0 implementations on ONLY what exists (which checks
      are real, named honestly):** REAL -- `py_compile` (the files
      the execution ACTUALLY applied, read from the execution DAG's
      own tool_call `applied` kv -- engine fact, never provider
      prose; `<sys.executable> -m py_compile <file>` through the
      scrubbed allowlisted run_shell; a non-.py applied set = a
      NAMED vacuous pass, asserted in-suite, never a silent skip),
      `ledger_roundtrip` (§2.5's own named T0 example:
      emit(parse(step file)) byte-identical, in-process via
      app/ledger -- the ledger IS the engine's own library, a shell
      boundary would add nothing), `pytest` (the workspace's REAL
      suite: `<sys.executable> -m pytest -q`, cwd = workspace root,
      scrubbed env, 600s bound, exit-as-data -- pytest exit 5
      "no tests collected" fails the gate honestly), and
      [T2:<script>.sh] via U3.2's run_gate EXACTLY as far as it
      already goes. NAMED-FUTURE (not built, no fake flows): T0
      lint/type gates (ruff/mypy are not dependencies of anything
      here), non-pytest T1 suite adapters (ctest/... -- §2.5's own
      "adapters" plural), per-workspace interpreter selection (V0
      runs the suite under the ENGINE's interpreter, the one
      guaranteed present AND allowlisted by basename).
  2.4 **The `verification` DAG (§2.13's kind, live):** one instance
      per pass at `steps/<step>.verification.dag` BESIDE the skeleton
      (the step.18 .execution.dag precedent verbatim); nodes = the
      machine gates (kind per tier: t0_static/t1_unit/t2_gate; kvs:
      tier, check, status, then verdict/exit_code/detail +
      started_at/finished_at as the pass runs); edges = a linear
      `precedes` chain in (tier, declaration) order -- T0 before T1
      before T2, justified: §2.5's ladder is a cost/determinism
      ordering (deterministic-first; a syntax error makes running the
      suite pointless -- fail-fast is the chain's point) and the V0
      runner is sequential anyway (the U3.5 decision, unchanged).
      `version` = the pass number (an honest use of the substrate's
      existing field); the file holds the CURRENT pass, the JSONL
      (2.5) holds the cross-pass history. Rewritten through the
      trail-recorded tool surface on every node status change and
      round-trip verified through the REAL bin/dagModel -- via
      executing.py's own roundtrip_verify and run_ready_frontier
      (ONE frontier engine and ONE verifier for both workflow kinds;
      run_ready_frontier grew a `halt_note` parameter so its halt
      message cannot claim "state stays EXECUTING" inside a
      VERIFYING pass -- reuse made honest, not assumed).
  2.5 **VerificationRecords -- the file-based precursor (decision,
      justified):** design §3's entity is Track B (P4, not built);
      the precursor is `<data_home>/verifications.jsonl`
      (record_verification/read_verifications) -- the trail.py
      append-only JSONL shape, NOT egress.py's file-per-id, because
      verdicts are an ORDERED cross-pass history whose value is the
      sequence. One row per gate RUN, pass AND fail (the C-4-C/
      C-15-B unconditional-recording posture): ts / uow / step /
      attempt / gate / tier / check / verdict / exit_code / detail /
      instance -- §3's "one oracle verdict: tier, gate name,
      pass/fail, evidence pointer", literally. Verdicts ALSO land in
      the DAG node kvs (the live pass surface); both, deliberately:
      the instance file is rewritten per pass, the JSONL is durable.
  2.6 **d9: V0 DEFAULT recorded, row stays OPEN (the decision the
      dispatch demanded, made and recorded in design.md §5's table
      in this same commit):** FIXED attempts,
      DEFAULT_MAX_REPAIR_ATTEMPTS = 2, scoped per verify_uow()
      invocation -- a re-run after escalation gets a fresh budget
      because re-running IS the human override (exactly the
      escalation contract). CLI override: --max-repairs. Why a
      default and NOT a resolution: cost-budgeting needs a cost
      model the PAL does not carry (usage tokens exist, prices do
      not); oracle-signal-driven needs a verdict lattice richer than
      per-gate pass/fail; both are real future policies with real
      prerequisites, and picking one against zero operational data
      would be speculation. The LEAN the dispatch named, taken --
      and owned.
  2.7 **The repair loop (VERIFYING -> EXECUTING, diagnostics through
      the SAME machinery):** a red gate VERDICT (and ONLY a verdict
      -- a halt whose node carries no `verdict = fail`, e.g. a
      tool-surface refusal or a missing execution instance, is an
      engine/environment defect: VerifyError, zero provider calls, a
      provider cannot fix the engine) drives: advance_state
      VERIFYING -> EXECUTING (design §4's fail edge, already in the
      machine -- no state-machine change), reset the execution
      instance's provider_run/human_gate/tool_call(/oracle_gate)
      nodes to `pending` + a repair_attempt kv (context_assembly
      stays done; U3.5's OWN resume machinery then re-runs exactly
      the reset set), and re-enter through execute_uow(...,
      repair=RepairContext(attempt, diagnostics)) -- a new optional
      parameter, the only executing.py signature change. The
      diagnostics (gate id, tier/check, exit code, output tail --
      engine-built mechanical fact) ride INSIDE the provider context
      as one more system entry, so they are egress-recorded
      unconditionally with everything else (C-4-C/B-5 -- asserted
      in-suite against the real runs/ record), and the FIRST entry
      carries REPAIR_MARKER_TEMPLATE (" [repair N: verification
      failed]") -- RETRY_MARKER's Q-4-B precedent: each repair
      attempt is a distinct, separately-scriptable mock scenario.
      The repair execution runs the FULL human-gated apply arc (diff
      review, --yes bypass, C-4-B at the write) and completes back
      into VERIFYING; the next pass runs. A repair execution that
      itself halts (decline/failure) raises out of verify_uow
      loudly -- state stays EXECUTING, the human resumes with
      `b3ubot execute` then re-runs verify; deliberately not
      swallowed.
  2.8 **Escalation (design §4's SECOND load-bearing human gate):**
      budget exhausted with gates still red => a LOUD multi-line
      report through output_fn (what failed on EVERY pass, what was
      tried, where the evidence lives -- instance, JSONL, egress +
      tool trails -- and the named next actions) + EscalationError;
      the engine state STAYS VERIFYING (honest: verification has not
      succeeded, and design §4 has no other failure edge out of
      VERIFYING). V0 escalation is REPORT + HALT -- the human acts by
      hand; an interactive escalation UI has no V0 consumer ("stops
      and reports" IS the contract; richer surfaces are P6+). Never
      a silent grind: the provider-call count is bounded by the
      budget, asserted in-suite.
  2.9 **The empty-gate-set refusal (§2.5's own rule, placed
      honestly):** the STATE arrives at VERIFYING on U3.5's
      completion (that was step.18's honest endpoint); what U3.6
      owns is the VERIFYING PASS, and verify_uow refuses to run one
      when no machine-runnable gate exists -- naming what IS there
      (no section / N narrative gates / empty section) and the
      marker convention as the fix. Whether the check should ALSO
      fire earlier (ratify/execute time, before tokens are spent) is
      Q-19-A, recorded, not silently decided.
  2.10 **CLI: top-level `b3ubot verify`** (draft/ratify/execute's
      sibling): workspace_root + uow + --provider + --yes +
      --max-repairs. A SEPARATE command, deliberately (the dispatch's
      V0 LEAN, taken): design §4's "everything between the gates is
      machine" ultimately wants execute -> verify to chain
      automatically, but that continuous arc is U3.8's M1 oracle --
      V0 keeps the human at the wheel between phases, symmetric with
      the existing sibling row. Q-8-B non-TTY refusal unless --yes
      (the repair loop re-runs the in-loop human gate); --provider
      human needs a TTY regardless (the relay is interactive).
      Presentation-layer error handling only; verify_uow stays pure
      and raises.
  2.11 **tests/test_orchestrator_verifying.py** (34 new): gate
      extraction (markers parsed with verbatim block text;
      narrative-only counted; no-section honest; unknown T0/T1
      checks, unknown tier, T3-naming-P5, T4-naming-B-6, two-markers,
      duplicate-id all refuse; the template extension pinned);
      verification-DAG shape (tier ladder beats declaration order;
      node kinds/kvs/version; t2 kind; linear chain); the empty-set
      refusal (state unmoved, no instance written); the all-green
      path (RETRO + full history; instance beside the skeleton with
      verdicts IN THE FILE; JSONL rows in order with real details;
      §(0) stamp once with the execution record preserved; round-trip
      "verified"; re-verify refuses from RETRO); REAL checks
      (py_compile catches an actually-broken .py; pytest red on a
      really-failing suite with T0 rows preceding the T1 row; pytest
      exit-5 red; the vacuous pass NAMED in its verdict row); the
      repair loop (fail -> repair -> green lands RETRO with the
      EXECUTING/VERIFYING history and farewell applied; the
      REPAIR_MARKER on the first context entry and the diagnostics
      entry asserted; the repair run's egress record carries the
      diagnostics); escalation (budget=1 -> exactly 1 provider call,
      EscalationError naming every pass's failure, ESCALATION report
      through output_fn, state VERIFYING, no verification stamp);
      the defect-is-not-repaired guard (missing execution instance ->
      VerifyError, zero provider calls); legality (RATIFIED-state
      verify refuses with ZERO read_file trail entries); this repo's
      own steps/ AND dag/instances/ untouched; CLI (non-TTY refusal,
      human-needs-TTY-even-with---yes, unattended --yes green to
      RETRO, error presentation).
  2.12 **Docs (same commit):** end_to_end.md §5 U3.6 row -> DONE in
      the U3.2-U3.5 row style; §11 ledger THROUGH the ledger CLI
      (transition U3.6 active --step step.19; close U3.6 PENDING;
      hash backfilled in the gdiff commit -- the step.13-18 shape).
      design.md §2.5 delivered-state paragraph, §2.13 delivered-state
      note (the `verification` kind is live), §5 d9 row updated
      IN-TABLE (V0 default recorded, status "OPEN (V0 default set
      2026-07-21, step.19)"). docs/orchestrator_state_machine.md
      (U3.6 delivered; edge-table annotations). Every stale "U3.6
      pending"/"d9 stays U3.6's" claim updated (executing.py's
      completion messages + stamp line now name `b3ubot verify`;
      errors.py; drafting.py; shell.py's run_gate docstring;
      app/orchestrator/__init__.py). No new docs/*.md file -- this
      file is the writeup.

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 stop-here scope boundary):
  - U3.7 RETRO closure/freeze: a green UoW lands in RETRO and
    SITS; nothing drafts the retro pair, closes the ledger row,
    or freezes the instance as evidence.                      -> next
  - U3.8 M1 (the one-complete-UoW arc; automatic
    execute->verify continuation lives there).                -> later
  - T2 beyond what run_gate already does; T3 (needs b3u --
    P5/U5.2); T4 (U6.4, advisory-only, B-6).                  -> later
  - Track B / P4 (VerificationRecord stays the JSONL
    precursor; no DB row).                                    -> later
  - Auto re-draft on amendments (Q-17-A stays open).          -> later

## (3) Verification gates

    G1  tests/test_orchestrator_verifying.py 34/34 PASS live: gate
        extraction, DAG shape, empty-set refusal, all-green to
        RETRO, real T0/T1 checks (broken .py caught; red AND green
        real pytest runs), repair arc, escalation, defect guard,
        legality, CLI -- mock/scripted providers, injected I/O,
        zero network.
    G2  The repair loop proven end to end: pass 1 red at the pytest
        gate -> VERIFYING -> EXECUTING -> the SAME provider/human-
        gate/apply machinery re-runs with REPAIR_MARKER on the first
        context entry and the diagnostics entry present (asserted) ->
        pass 2 green -> RETRO; the repair run's egress record carries
        the diagnostics (B-5).
    G3  Escalation proven bounded and loud: budget=1 => exactly 1
        provider call (counted), EscalationError + the ESCALATION
        report naming both passes' failures, state stays VERIFYING,
        no verification stamp -- no silent grind.
    G4  Substrate round-trip: every written verification instance
        parses under the REAL dag/cppcc/bin/dagModel and re-renders
        byte-identically (the "verified" outcome asserted); steps/
        holds exactly skeleton + execution + verification instances.
    G5  Live CLI smoke (not just tests), three legs -- §(1)
        CONFIRMED: the empty-gate refusal on the drafted narrative
        skeleton (the correct live outcome, naming the fix); markers
        added by hand -> verify --yes green to RETRO rc=0; the real
        red -> repair -> green arc (passes=2 repairs=1). Confirmed
        live before committing.
    G6  Full suite before this step's edits: 481 passed / 11 skipped
        (confirmed live, matching step.18's reported final). After:
        515p/11s pre-step-file (+34 verifying); 517p/11s once this
        step.19.txt lands in the corpus (+2 self-inclusive pickups --
        the steps 10-18 growth pattern). Zero regressions; zero
        existing tests edited.
    G7  Scope boundary held: no RETRO->CLOSED call site, no retro
        drafting, no freeze, no Track B row, no T3/T4 flow; this
        repo's own steps/ and dag/instances/ untouched by every test
        (asserted in-suite); this session stops after U3.6.
    G8  §11 ledger row updated THROUGH the existing `ledger` CLI
        (transition -> active --step step.19, close -> done/PENDING),
        against the REAL end_to_end.md -- dogfooding, the step.13-18
        precedent.
    G9  design.md §5 d9 updated IN THE TABLE in this same commit (V0
        default + why-not-a-resolution + row stays OPEN), per §6's
        resolve-rows-in-place rule.
    G10 b3ubot porcelain clean after each commit; retro pair via the
        SHARED v3_claude/scripts/step_gdiff (steps/step.19.gdiff.txt);
        exactly 2 commits (work + gdiff-with-hash-backfill-folded-in),
        the step.7-18 precedent.

## (4) LOCKs

    C-19-A  b3ubot porcelain clean after each commit.
    C-19-B  A GATE THE ENGINE CANNOT RUN IS REFUSED, NEVER SKIPPED:
            unknown checks/tiers, T3-before-P5, T4-ever (B-6),
            ambiguous and duplicate gates all refuse loudly at
            extraction; the vacuous py_compile pass is NAMED in its
            verdict row. §2.5's machine-checked-facts posture --
            no gate silently becomes a no-op.
    C-19-C  VERDICTS ONLY FEED THE REPAIR LOOP: a halt without a
            `verdict = fail` kv (tool refusal, engine defect) raises
            VerifyError with ZERO provider calls -- a provider cannot
            fix the engine, and burning tokens on it would be the
            silent grind design §4 forbids.
    C-19-D  THE BUDGET BOUNDS THE GRIND, THE HUMAN OWNS THE OVERRIDE
            (d9 V0): at most max_repair_attempts repair re-entries
            per verify_uow() invocation; exhaustion = report + halt
            with state honestly VERIFYING; a fresh run = the human's
            explicit override. No hidden lifetime counters.
    C-19-E  EVERY VERDICT IS RECORDED TWICE, DELIBERATELY (C-18-D
            carried): the DAG node kvs (the live pass surface,
            single-line sanitized for the latin-1 substrate) AND the
            verifications.jsonl row (the durable cross-pass
            VerificationRecord precursor, full detail) -- pass AND
            fail, unconditionally.
    C-19-F  REPAIR DIAGNOSTICS TRAVEL INSIDE THE PROVIDER CONTEXT,
            NEVER A SIDE CHANNEL: they enter through
            _assemble_execution_context like every other entry, so
            C-4-C/B-5 egress recording covers them by construction;
            the REPAIR_MARKER rides the first entry (Q-4-B).
    C-19-G  NO SILENT SCOPE CREEP: U3.7/U3.8, T3/T4, Track B and
            Q-17-A named out of scope in (2) and confirmed not
            started (G7); the operator's explicit "stop after U3.6"
            direction is honored.
    C-19-H  standing locks inherited: B-3 (no disclosure/no push);
            B-6 (deterministic gates block, T4 refused as blocking);
            B-4 (checks run under scrubbed_env); B-5 (repair context
            egress-recorded unconditionally); C-15-B (every check
            runs through the trail-recorded tool surface); C-18-B
            (the instance file is the live progress surface --
            extended to the verification kind).

## (5) Open questions

    Q-19-A  Should the empty-gate-set check ALSO fire at ratify (or
            execute) time, before any provider tokens are spent on a
            UoW that can never verify? LEAN: yes eventually, at
            U3.8's M1 (the continuous arc is where early refusal
            pays); not retrofitted into U3.4/U3.5 here -- that would
            be scope creep into ratify's contract mid-step. The
            V0 pass-level refusal is §2.5's rule enforced at the
            moment U3.6 owns.
    Q-19-B  Should repair re-entries re-present the FULL human gate
            diff every time, or should --yes-less repair runs offer
            a compact "re-approve the repaired file" prompt? LEAN:
            full diff every time -- the gate's value IS the human
            seeing exactly what changed, and repair proposals are
            precisely the risky ones; revisit only if real operation
            shows gate fatigue.
    Q-19-C  Should the verification DAG keep one instance PER PASS
            (step.1.verification.pass2.dag ...) instead of
            rewriting one file with version=pass? LEAN: one file --
            files-as-truth wants the CURRENT gate graph findable at
            a fixed name (the execution-instance precedent), the
            JSONL already preserves the cross-pass history, and
            per-pass files would multiply unboundedly under repair
            loops. Revisit at U3.7 if the freeze wants pass-level
            graph snapshots as evidence.

## (6) Acceptance

    - [x] app/orchestrator/verifying.py: legality-first verify_uow,
          extraction -> per-pass DAG -> tier chain -> verdicts ->
          green/repair/escalate (2.1, 2.7, 2.8, G1, G5).
    - [x] Gate convention grounded in the corpus survey; the
          drafting-template extension landed and pinned (2.2, G1,
          C-19-B).
    - [x] Real T0 checks: py_compile catches a real syntax error;
          ledger_roundtrip runs §2.5's own named example; vacuous
          pass NAMED. Real T1: red AND green pytest runs on the toy
          workspace; exit-5 red (2.3, G1).
    - [x] verification-kind DAG beside the skeleton, tier-ladder
          chain, version=pass, round-trip verified through the real
          bin/dagModel, run through the SHARED frontier engine
          (2.4, G4).
    - [x] VerificationRecord precursor: verifications.jsonl rows for
          every gate run, pass AND fail, plus node kvs (2.5,
          C-19-E).
    - [x] d9 V0 default recorded in design.md §5 IN-TABLE, row
          stays OPEN, reasoning owned (2.6, G9).
    - [x] Repair loop: diagnostics through the SAME provider
          machinery, REPAIR_MARKER + egress asserted; defects never
          repaired (2.7, G2, C-19-C/F).
    - [x] Escalation: loud report + EscalationError, state stays
          VERIFYING, provider calls bounded by the budget (2.8, G3,
          C-19-D).
    - [x] Empty-gate-set refusal live and in-suite; placement
          honesty recorded as Q-19-A (2.9, G1, G5).
    - [x] `b3ubot verify` CLI beside draft/ratify/execute: separate
          command justified; Q-8-B guards; --max-repairs (2.10, G5).
    - [x] 34 new tests, zero regressions, zero edited tests
          (481p/11s -> 515p/11s -> 517p/11s with this file) (2.11,
          G6).
    - [x] design.md §2.5/§2.13/§5-d9; docs/orchestrator_state_
          machine.md; every stale "U3.6 pending" docstring;
          end_to_end.md §5 row + §11 ledger via the `ledger` CLI
          (2.12, G8, G9).
    - [x] Scope boundary held: U3.7/U3.8 + T3/T4 + Track B +
          Q-17-A not started (G7, C-19-G) -- this session stops
          after U3.6.
    - [x] Retro pair: this file + step.19.diff.txt + step.19.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/verifying.py + errors + executing
repair/halt_note growth + drafting template extension + __init__ +
shell.py docstring + cli + tests + design.md/end_to_end.md/docs +
this file):  529834f
GDIFF commit (step.19.gdiff.txt via scripts/step_gdiff):  (HEAD)

End of step.
