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.