docs(learnings): capture BC ports wire-up arc (cycle 122)

26-cycle structural milestone deserves a durable learning so future
/evolve cycles can find it via `ao lookup` / docs/learnings/
browsing and not re-discover the same shape patterns.

New file: docs/learnings/2026-05-13-bc-ports-wire-up-arc.md.
(First attempt wrote to .agents/learnings/ which is gitignored —
moved to the tracked docs/learnings/ home that sibling learnings
2026-05-11-* and 2026-05-12-* live in.)

Captures:

1. What the arc proved — repeatable file triplet (~250 LOC + ~10
   tests per cycle), 8 distinct adapter shapes that all worked
   under the same triplet, reader+writer pair tests prove on-disk
   format compatibility, two valid subprocess-test shapes (fake-
   script vs pluggable func field).

2. Anti-patterns observed — fileExists collision caught by go vet
   (cycle 115), cap shadowing (cycle 117), assuming wrong adapter
   shape until re-reading port contract (cycle 116). All three
   have actionable preventions ("grep before util", "rename when
   shadowing builtins", "read port doc-comment first").

3. What the arc did NOT do — no call-site migration, no cross-
   process EventBus. Names the per-BC follow-up bds tracking that
   work and the operator transport-choice still deferred.

4. WHY the arc stopped at 12 adapters instead of migrating callers
   — intra-package routing (cli/cmd/ao → cli/cmd/ao) adds layers
   without semantic gain because the port surface only matters at
   a package boundary. The genuine value comes from cross-package
   callers or test substitution. Documents the next-phase target
   explicitly so cycle 123+ doesn't pick a manufactured migration.

5. Cycle-history pointers — scaffold arc 78-106, wire-up arc
   83+108-118, doc reconciliation 119-121, total 26 cycles across
   the BC ports surface.

Why this is a cycle-worth-doing: the cycle 122 selection ladder had
no fresh signal post-wire-up. Capturing the structural milestone
preserves the lessons for future operators and gives docs/learnings/
something concrete to surface when "ports", "wire-up", or
"hexagonal" comes up in a future session.

Cycle 122 / soc-2klg knowledge-capture mode.
This commit is contained in:
Boden Fuller
2026-05-12 20:26:52 -04:00
parent bd38ff228a
commit 4226b9b395
@@ -0,0 +1,97 @@
---
title: BC ports wire-up arc — 12 production adapters in 11 consecutive cycles
date: 2026-05-13
tags: [hexagonal-architecture, ddd-bounded-context, ports, wire-up, evolve-loop]
source: .agents/evolve/cycle-history.jsonl cycles 83+108-118
maturity: lessons-learned
---
# BC ports wire-up arc
11 consecutive `/evolve` cycles (108-118), preceded by cycle 83's
first-mover `productionCitationAdapter`, delivered 12 production
adapters covering all 5 DDD bounded contexts (BC1-BC5). The arc is
the canonical compound-by-repetition demonstration in the cycle
history. Cycles 119-121 reconciled doc-narrative drift after the
arc completed.
## What the arc proved
- **A repeatable file shape ships fast.** Every adapter followed the
same triplet: `<port>_adapter.go` in `cli/cmd/ao/`, paired
`<port>_adapter_test.go`, compile-time
`var _ XPort = (*productionX)(nil)` assertion. Average cycle:
~250 LOC + ~10 tests in ~10 minutes wall-clock.
- **8 distinct adapter shapes** all worked under the same triplet:
CLI wrapper (Citation), JSONL append/read (Loop R/W, Operator,
ClaimEvidenceBinder w/ upgrade-only rule), tree-walk + hash join
(Harness), tree-walk + ranker (CorpusReader), idempotent file
write + frontmatter (CorpusWriter), pure-Go transform
(FindingCompiler), subprocess bash wrapper (GateRunner), external
CLI + pluggable runner (CIStatus), sync in-memory pubsub
(EventBus).
- **Reader+writer pair tests work.** Both BC1 (CorpusReader+Writer
cycles 112-113) and BC3 (LoopReader+Writer cycles 108-109)
shipped round-trip tests in the writer's suite — proves the
on-disk format is shared end-to-end.
- **Subprocess testing has two valid shapes.** Cycle 115's
GateRunner used a fake-bash-script-in-temp-repo harness;
cycle 117's CIStatus refined to a pluggable `runGH` func field
that lets tests substitute canned bytes without writing a script.
Both are valid; the func-field shape is faster and platform-
independent (use it when the adapter's interesting logic is the
parser, not the subprocess invocation).
## Anti-patterns observed during the arc
- **Naming collisions caught by `go vet`.** Cycle 115 introduced a
local `fileExists` helper that collided with `doctor.go`'s
existing one. `go vet` caught it; fix was to reuse doctor's
helper rather than rename. Lesson: before writing a small util
in an adapter, `grep -n "func <name>" cli/cmd/ao/` first.
- **Shadowing builtins.** Cycle 117 wrote `cap := limit` which
shadowed `cap()`. `go vet` didn't fail (it's legal) but it's a
readability trap. Renamed to `pageSize`.
- **Assuming the wrong adapter shape.** Cycle 116
(ClaimEvidenceBinder) initially looked like a bd-CLI wrapper
(cycle 83 Citation precedent), but re-reading the port contract
showed it's a JSONL adapter (the binding ledger lives on disk,
not in bd). Lesson: always read the port doc-comment first; the
adapter shape is constrained by the port's persistence model.
## What the arc did NOT do
- **No call-site migration.** Production callers in `cli/cmd/ao`
still invoke the leaf helpers (e.g. `beads.go:227` still calls
`verifyCitationInPlace` directly, not via the
`productionCitationAdapter`). The adapters are reachable but
latent. The per-BC follow-up bds (`soc-pm5t` for BC1.1, etc.)
track the migration work.
- **No cross-process EventBus.** `productionEventBus` is sync
in-process; the adapter doc explicitly names the swap path
(`event_bus_nats.go` or similar) for when the factory goes
distributed. Operator transport-choice is deferred.
## Why I stopped at 12 instead of migrating callers
A migration cycle inside `cli/cmd/ao` adds a layer
(`verifyCitationInPlace` → `productionCitationAdapter` →
`verifyFileCitation`) without semantic gain because both endpoints
are in the same package — the port surface only matters at a
package boundary. The genuine call-site value comes when:
- a non-`cli/cmd/ao` package consumes one of these adapters, or
- a test substitutes the InMemoryX adapter to exercise a code path
without real I/O.
Future cycle should pursue the former (create or migrate a
cross-package caller) rather than manufacture intra-package
routing.
## Cycle-history pointers
- Scaffold arc: cycles 78-106 (12 port interfaces + InMemoryX
adapters, captured in `docs/contracts/bc-ports-inventory.md`)
- Wire-up arc: cycle 83 + cycles 108-118 (12 production adapters)
- Doc reconciliation arc: cycles 119-121 (3 doc files updated to
reflect actual state)
- Total: 26 cycles across the BC ports surface