One Language on Both Ends of the Wire

Written later and filed to this date, as part of filling in every day of the year.

The status service was supposed to be Python. The planning document I started the loop from had a web framework filled in for it, the sensible default for a small JSON API, and the first day’s third decision crossed it out. The reason given is one line: one language across the HTTP surface, because the two things that would read the service, a dashboard and a heads-up display for a pair of glasses, were already TypeScript; and shared wire types.

The scaffold landed the next day: three routes, a health probe, a summary and an event intake, and one file called types.ts with the wire format in it as schemas, declared in its header to be the single source of truth shared between the service and both consumers. Five tests, all of them against the schemas. Everything the service has grown since, and it has grown a great deal, validates against that one file.

Here is what the rule actually bought, and it is not an import. The dashboard is a bundled web app, and a bundle cannot reach across to a sibling repository at run time, so its copy of the wire types is a hand-written mirror. The workspace rule says import, don’t redefine; the mirror redefines. What one language makes possible is the test that holds the mirror to the source. It imports the real schemas from the service’s repository, in development only, never shipped, and checks the mirror three ways. Every schema’s keys must equal the mirror’s keys, so an optional field added upstream fails loudly even though two types that differ by one optional field still assign to each other. Fixtures typed as the mirror must round-trip through the upstream parser unchanged, which catches a changed type and any key the mirror has that the service does not. And the two types must be assignable in both directions, which fails the type check even on a day the tests do not run. The glasses app holds its own mirror to a sample fixture the service publishes.

None of that can exist across a language boundary. A Python service and a TypeScript dashboard share a contract on paper, in a document both sides promise to keep, and the first time one of them forgets, the other renders a blank where a number was. A shared language turns the promise into a compile error. That is the whole content of a decision that reads, in the file, like a preference about frameworks.

The rule, as recorded: “status-api is Hono (TypeScript), Node 22.”


← all writing