step.9.diff.txt -- narrative retro for step.9 (U1.4: P1 gates + retro)

Executed: 2026-07-20, same session as the skeleton (no separate
skeleton-then-execute split -- gates script, docs, and this step file
were built together per the operator's explicit "step file and its
execution" direction). Companion artifact: step.9.gdiff.txt (automated,
scripts/step_gdiff). This step closes the LAST still-`todo` item in
P1.M1 (end_to_end.md §3) -- U1.1 through U1.3, U1.5, U1.6 were already
`done` going in.

## Plan vs actual

```
+---+----------------------------------+------------------------------------------+
| # | Planned (§)                      | Actual                                   |
+---+----------------------------------+------------------------------------------+
| 1 | §2.1: keyed-arc availability      | Built as LEANED (Q-9-A): a `python -c`   |
|   | check -- Q-9-A left open in the  | one-liner calling                        |
|   |   skeleton-drafting pass         | `ClaudeCodeAdapter().is_available()`     |
|   |                                  | directly, not a bash re-derivation.      |
|   |                                  | Found live while grounding: this machine |
|   |                                  | has `claude` on PATH (`which claude`     |
|   |                                  | succeeds) but no $ANTHROPIC_API_KEY --   |
|   |                                  | on THIS machine a bash-only env-var      |
|   |                                  | check would have produced the identical  |
|   |                                  | verdict to is_available(), which would   |
|   |                                  | have hidden the divergence risk (a       |
|   |                                  | machine with the binary absent but a key |
|   |                                  | set, or the reverse) until it silently   |
|   |                                  | produced a wrong SKIP/RUN decision        |
|   |                                  | somewhere else. Called the adapter's own |
|   |                                  | method instead -- zero duplication-drift |
|   |                                  | risk, and the reason string printed in   |
|   |                                  | the SKIP line is the adapter's own, not  |
|   |                                  | a hand-written approximation of it.      |
|   |                                  | See steps/step.9.txt §1/§5 Q-9-A.        |
| 2 | §2.1: boot section, "lighter than| Built exactly as planned: no cppcc probe |
|   |   a full smoke check" per        | at all, venv-present + CLI-importable    |
|   |   end_to_end.md's own U1.4       | only. Confirmed correct by re-reading    |
|   |   objective wording              | app/cli.py/app/egress.py in full -- P1's |
|   |                                  | `ask` round trip genuinely never touches |
|   |                                  | cppcc or the b3u API anywhere in its     |
|   |                                  | call graph (that's P5's job); repeating  |
|   |                                  | smoke.sh's cppcc probe here would have   |
|   |                                  | been redundant coverage of a dependency  |
|   |                                  | this UoW doesn't actually have.          |
+---+----------------------------------+------------------------------------------+
```

Every other part of the skeleton (§2.1's mock-arc/keyed-arc/hygiene-leg
shapes, §4's LOCKs) held exactly as drafted -- no other surprises.

## Gate evidence

```
+------+---------------------------------------------------------------------+
| G1   | bash scripts/p1_gates.sh with a present venv reaches the mock-arc   |
|      | section: `PASS venv present (.../.venv/bin/python)` then           |
|      | `PASS CLI importable (python -c 'import app.cli')`, live output.    |
| G2   | Mock arc: 6/6 PASS -- dry-run exit 0, dry-run diff contains         |
|      | print('goodbye'), dry-run leaves hello.py == print('hello'), apply  |
|      | exit 0, apply leg rewrites hello.py to contain print('goodbye'),    |
|      | egress-trail JSON exists under the scratch $B3UBOT_HOME/runs/ (1    |
|      | file). All confirmed by a live run, not by inspection.              |
| G3   | Keyed arc: `ClaudeCodeAdapter().is_available()` returned False with  |
|      | reason "$ANTHROPIC_API_KEY is not set (required for --bare          |
|      | headless auth; C-3-B...)" -- printed verbatim in one SKIP line,      |
|      | zero contribution to the fails counter, script's own exit code       |
|      | (checked separately, boot+mock legs alone) unaffected.              |
| G4   | B-4 hygiene: no key available this run -> explicit SKIP line         |
|      | printed ("nothing to check for leakage this run"), NOT a fabricated |
|      | PASS -- confirmed by reading the live script output directly.       |
| G5   | B-5 hygiene: PASS -- grep against the mock egress-trail JSON found   |
|      | the literal intent string "add a greeting line", the literal        |
|      | original content "print('hello')", and the literal proposed content |
|      | "print('goodbye')" all present verbatim in the recorded             |
|      | sent_context/response_summary fields (not a hash/summary),          |
|      | confirmed live.                                                      |
| G6   | `.venv/bin/python -m pytest -q` -> "55 passed, 11 skipped", run      |
|      | before writing the gates script and again after -- byte-identical   |
|      | to the step.8 baseline; this step adds no file under app/,           |
|      | adapters/, or tests/, so zero delta was expected and zero delta was |
|      | observed.                                                            |
| G7   | b3ubot porcelain clean confirmed before committing; retro pair via  |
|      | step_gdiff (steps/step.9.gdiff.txt).                                 |
+------+---------------------------------------------------------------------+
```

## Findings for later steps

1. **Re-deriving an adapter's own availability logic in a second
   language is a live duplication-drift risk, not a hypothetical one.**
   This dev box happens to have the `claude` binary on PATH with no key
   set -- a naive bash `[ -z "$ANTHROPIC_API_KEY" ]` check would have
   produced the exact same SKIP/RUN verdict as
   `ClaudeCodeAdapter().is_available()` on THIS machine, purely by
   coincidence of which precondition happened to be the one that was
   missing. Calling the adapter's own method directly (a `python -c`
   one-liner) instead of re-deriving its two-precondition logic in
   bash removes that coincidence dependency entirely, and means any
   FUTURE widening of `is_available()` (e.g. a version check, a
   reachability probe) is automatically picked up by this gate with no
   edit required here. Worth the same lens on any future gates script
   that needs to ask "is provider X usable right now" -- ask the
   adapter, don't re-derive its answer.
2. **A gates script's "boot" section should match the UoW's ACTUAL
   dependency graph, not the sibling repo's.** b3u's own p1_gates.sh
   boots a live server because b3u's P1 already has a web layer to
   stand up. b3ubot's P1 has no server, no CCS dependency, and no b3u
   API call anywhere in its call graph yet (confirmed by re-reading
   app/cli.py + app/egress.py end to end before writing a single line
   of this script) -- so the "adapt the SHAPE, not the CONTENT"
   instruction this step was given up front turned out to be load-
   bearing in practice, not just a style note: a boot section that
   blindly imitated b3u's server-health-check shape would have been
   testing a dependency this UoW genuinely doesn't have yet.
3. **Honest SKIP lines are more informative than a green checkmark
   would have been.** Both the keyed arc and the B-4 hygiene leg
   printed the REAL reason (the adapter's own unavailability string,
   "nothing to check for leakage this run") rather than either
   silently passing or silently omitting the section -- a future
   operator re-running this script with a key exported gets the same
   two legs actually exercised, with no script edit needed, and can
   trust that a PASS in either leg, when it eventually appears, means
   the check genuinely ran. Consistent with step.7's finding #2 and
   step.1's C-1-B discipline: the pattern generalizes to gates
   scripts, not just adapters.

## Numbers

```
+---------------------------------------+----------------------+
| new files                              | 3 (scripts/          |
|                                         | p1_gates.sh,          |
|                                         | steps/step.9.txt,     |
|                                         | this file)            |
| gates script lines                     | 188 (bash)            |
| gate checks asserted live              | 9 (6 mock-arc PASS +  |
|                                         | 1 B-5 PASS + 2 honest |
|                                         | SKIP: keyed arc, B-4) |
| live claude-code API calls made        | 0 (honest SKIP,       |
|                                         | $ANTHROPIC_API_KEY    |
|                                         | genuinely unset)      |
| suite before -> after                  | 55p/11s -> 55p/11s    |
|                                         | (unchanged, no test   |
|                                         | file touched)         |
| regressions                            | 0                     |
| P1.M1 UoWs remaining after this step   | 0 (U1.1-U1.6 all done)|
+---------------------------------------+----------------------+
```

U1.4 done -- closes P1.M1 (end_to_end.md §3): every UoW in "The PAL and
one real provider" is now `done`. Ledger updated in the work commit per
the §12 execution contract; that commit's own hash could not be known
at the moment its tree was written (the same self-reference problem
step.7's own retro named), so both end_to_end.md §11 and this step's
own steps/step.9.txt §7 carried a placeholder in the work commit and
were backfilled in the immediately following gdiff commit -- the exact
2-part resolution step.7 used, confirmed by inspecting 4f24b22's own
diff (`git show --stat 4f24b22`: it touches end_to_end.md and
steps/step.7.txt in addition to adding step.7.gdiff.txt) before
repeating the pattern here rather than assuming it.

End of retro.
