Raised by Codex on #752 as three separate findings (:81, :77, :85). They are three instances of one class, so they are filed once.
The failure
A service is generated in all six SDKs, every generator agrees, make check-service-inventory-parity passes — and an external developer still cannot reach it, because the hand-written client wiring or barrel export was omitted. Nothing fails: compilation is fine (nothing references the missing accessor), the parity gate is fine (it compares generator output), and the per-SDK drift checks are fine (each SDK matches its own generator).
This is exactly what happened to Python: GaugesService shipped unreachable from #221 until #732/#748, roughly a year, with every gate green.
Three concrete doors:
- TypeScript client wiring — a missing
import, BasecampClient property, or defineService call in typescript/src/client.ts.
- TypeScript root exports — the service is wired into
client.ts but its export block is missing from typescript/src/index.ts. package.json exports only the package root and ./oauth, so consumers cannot import the service or its request/response types through any supported path. index.ts carries 54 hand-written generated/services export lines today.
- Ruby client accessors — a missing
AccountClient accessor in ruby/lib/basecamp/client.rb.
Who is actually exposed
Not all six. Measured:
| SDK |
Accessors |
Covered today? |
| Kotlin |
generated/ServiceAccessors.kt — generated |
structurally immune, no hand-wiring to forget |
| Swift |
Generated/AccountClient+Services.swift — generated |
structurally immune |
| Go |
hand-written on AccountClient |
covered — check-service-inventory-parity reads Go's accessors as one of its eight renderings (#752 case 9 pins it) |
| Python |
hand-written |
covered — python/tests/test_client.py::TestGroupedClientAccessorInventory (#748) |
| TypeScript |
hand-written ×2 (client.ts, index.ts) |
exposed |
| Ruby |
hand-written |
exposed |
So the gap is two SDKs and three doors, not five SDKs.
Why not add them to the parity gate
That was the literal suggestion in all three threads, and it is the wrong instrument. check-service-inventory-parity asserts generator-output parity — did the five split tables produce the same service set. Reachability is a different invariant: is the generated thing wired into the hand-written client. They happen to be comparable sets, which is what makes bolting them together tempting, but one instrument answering two questions reports a failure of either as a failure of both, and per AGENTS.md three near-identical findings is the signal to reassess the instrument rather than add a third, fourth and fifth rendering to it.
Reachability is also per-SDK by nature — the thing being checked is that SDK's own hand-written file — so it wants a per-SDK test, not a cross-SDK gate.
The remedy
Replicate the shape of python/tests/test_client.py::TestGroupedClientAccessorInventory (#748), which is already proven:
- derive the expected roster from the generated services directory (or barrel), never a hand-copied literal — a literal has to be edited by the same person who forgot the accessor
- assert every generated service has an accessor
- assert the accessor resolves to that service, not merely that a property exists
- assert the reverse: no accessor outlives its generated service
For TypeScript that means two rosters (client properties and index.ts exports). For Ruby, one.
Worth checking as part of the work: whether any service is unreachable in TypeScript or Ruby right now. Nothing has ever asked.
Raised by Codex on #752 as three separate findings (
:81,:77,:85). They are three instances of one class, so they are filed once.The failure
A service is generated in all six SDKs, every generator agrees,
make check-service-inventory-paritypasses — and an external developer still cannot reach it, because the hand-written client wiring or barrel export was omitted. Nothing fails: compilation is fine (nothing references the missing accessor), the parity gate is fine (it compares generator output), and the per-SDK drift checks are fine (each SDK matches its own generator).This is exactly what happened to Python:
GaugesServiceshipped unreachable from #221 until #732/#748, roughly a year, with every gate green.Three concrete doors:
import,BasecampClientproperty, ordefineServicecall intypescript/src/client.ts.client.tsbut its export block is missing fromtypescript/src/index.ts.package.jsonexports only the package root and./oauth, so consumers cannot import the service or its request/response types through any supported path.index.tscarries 54 hand-writtengenerated/servicesexport lines today.AccountClientaccessor inruby/lib/basecamp/client.rb.Who is actually exposed
Not all six. Measured:
generated/ServiceAccessors.kt— generatedGenerated/AccountClient+Services.swift— generatedAccountClientcheck-service-inventory-parityreads Go's accessors as one of its eight renderings (#752 case 9 pins it)python/tests/test_client.py::TestGroupedClientAccessorInventory(#748)client.ts,index.ts)So the gap is two SDKs and three doors, not five SDKs.
Why not add them to the parity gate
That was the literal suggestion in all three threads, and it is the wrong instrument.
check-service-inventory-parityasserts generator-output parity — did the five split tables produce the same service set. Reachability is a different invariant: is the generated thing wired into the hand-written client. They happen to be comparable sets, which is what makes bolting them together tempting, but one instrument answering two questions reports a failure of either as a failure of both, and per AGENTS.md three near-identical findings is the signal to reassess the instrument rather than add a third, fourth and fifth rendering to it.Reachability is also per-SDK by nature — the thing being checked is that SDK's own hand-written file — so it wants a per-SDK test, not a cross-SDK gate.
The remedy
Replicate the shape of
python/tests/test_client.py::TestGroupedClientAccessorInventory(#748), which is already proven:For TypeScript that means two rosters (client properties and
index.tsexports). For Ruby, one.Worth checking as part of the work: whether any service is unreachable in TypeScript or Ruby right now. Nothing has ever asked.