Skip to content

A service can be generated in every SDK and still be unreachable: TypeScript and Ruby have no accessor-roster test #755

Description

@jeremy

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:

  1. TypeScript client wiring — a missing import, BasecampClient property, or defineService call in typescript/src/client.ts.
  2. 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.
  3. 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.ktgenerated structurally immune, no hand-wiring to forget
Swift Generated/AccountClient+Services.swiftgenerated structurally immune
Go hand-written on AccountClient coveredcheck-service-inventory-parity reads Go's accessors as one of its eight renderings (#752 case 9 pins it)
Python hand-written coveredpython/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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions