ADR 0074 — the wire is specified; an SDK is what passes the suite
ADR 0039 settles what belongs in an SDK. It does not say what happens when there is more than one, and there already is: the contracts are expressed as Go types in the control plane and host and as TypeScript types in the SDK, and nobody has felt it because both live in one repository. They already disagree. The provision's field is "resource" in one and "Provision" in the other; "consumer" means the module in one and the node in the other; the envelope declares six headers on one side and emits four on both — the missing two being x-causation-id and x-schema, the second of which is exactly what a body needs in order to change shape without silent misreads. That class of failure does not announce itself. Two implementations disagreeing about an envelope do not fail to compile — they ignore each other's messages, and a mesh where a module stops reacting looks like a mesh where nothing happened. So the decision is to specify the wire rather than share the types, because the shapes are the easy half. What two implementations actually disagree about is behaviour: queue naming and durability, which headers are required and what an unknown one means, taking identity from the sealed credential rather than the environment, dedup on an id only the emitter can make, pinning a fingerprint rather than trusting an authority. And the suite is executable rather than prose, because a specification nobody can run is a document two implementations drift from while both believe they conform. The two existing implementations are the first made to pass it — a suite only new SDKs must satisfy would certify every future language against a disagreement that is already here. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
---
|
||||||
|
topic: the tiers
|
||||||
|
status: proposed
|
||||||
|
date: 2026-09-15
|
||||||
|
deciders: jochen
|
||||||
|
reconstructed: false
|
||||||
|
extends: 0039-what-the-sdk-holds-and-refuses.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 74. The wire is specified; an SDK is whatever passes the conformance suite
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
[ADR 0039](0039-what-the-sdk-holds-and-refuses.md) says what the SDK holds: the tool-serving
|
||||||
|
harness, the messaging and event framework, the contracts, and core primitives. It settles what
|
||||||
|
belongs in *an* SDK. It does not say what happens when there is more than one.
|
||||||
|
|
||||||
|
There is already more than one. **The contracts are expressed twice** — as Go types in the control
|
||||||
|
plane and the host, and as TypeScript types in the SDK — and nobody has felt it because both live
|
||||||
|
in one repository and one head.
|
||||||
|
|
||||||
|
**They already disagree.** Not in some future where a second language is added; today:
|
||||||
|
|
||||||
|
| | TypeScript | Go |
|
||||||
|
|---|---|---|
|
||||||
|
| the provision's field | `resource` | `Provision` |
|
||||||
|
| what `consumer` means | **the module** | **the node**; the module is `From` |
|
||||||
|
| event headers | six, including `x-causation-id` and `x-schema` | four — the other two are never written |
|
||||||
|
|
||||||
|
So one word means two things in the two halves of one mesh, and the header that exists so a body's
|
||||||
|
shape can change without silent misreads is declared on one side and emitted by neither.
|
||||||
|
|
||||||
|
A failure of this kind does not announce itself. Two implementations that disagree about an
|
||||||
|
envelope do not fail to compile — they ignore each other's messages, and a mesh where a module
|
||||||
|
stops reacting looks exactly like a mesh where nothing happened.
|
||||||
|
|
||||||
|
## The question this settles
|
||||||
|
|
||||||
|
A module may be written in any language the mesh can build
|
||||||
|
([`18-building-a-module`](../03-DESIGN/01-to-be/18-building-a-module.md)). Every language needs an
|
||||||
|
SDK. What is an SDK *of*?
|
||||||
|
|
||||||
|
Two answers were available, and the obvious one is wrong.
|
||||||
|
|
||||||
|
**Shared types, generated.** Write the shapes once — a schema, an IDL — and generate Go, TypeScript,
|
||||||
|
Rust. It is the familiar answer and it solves the smaller half of the problem. The shapes are not
|
||||||
|
where the difficulty is.
|
||||||
|
|
||||||
|
**A specified wire, with a conformance suite.** The shapes are a consequence; what an SDK must get
|
||||||
|
right is *behaviour*.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**The wire is specified, and an SDK is any implementation that passes the conformance suite.**
|
||||||
|
|
||||||
|
What the specification covers is what two implementations can disagree about:
|
||||||
|
|
||||||
|
- **the exchanges and queues** — which exchanges exist, that a consumer's queue is durable and
|
||||||
|
named `<node>.<module>.events`, that a tool is served from a shared durable `serve.<key>`
|
||||||
|
- **the envelope** — every header, which are required, what an unknown `x-` header means, and that
|
||||||
|
ignoring one is correct rather than lax
|
||||||
|
- **identity** — that a module's node and module name come from its sealed credential and not from
|
||||||
|
its environment, so what it emits matches what the mesh authorised
|
||||||
|
- **delivery** — at-least-once, and that dedup is on `x-event-id`, which only the emitter can make
|
||||||
|
- **the credential** — the sealed document's fields, and that a connection pins a certificate
|
||||||
|
fingerprint rather than trusting an authority
|
||||||
|
- **provisioning** — a grant in, a credential out, and what each carries
|
||||||
|
- **the vocabulary** — that `consumer` is one thing, named once
|
||||||
|
|
||||||
|
**And the suite is executable, not prose.** A specification nobody can run is a document two
|
||||||
|
implementations drift from while both believe they conform. Conformance is a set of fixtures — an
|
||||||
|
emitted event, a served tool call, a grant and its answer — that every SDK must produce and consume
|
||||||
|
byte-for-byte.
|
||||||
|
|
||||||
|
**The existing two implementations are the first two to be made to pass it.** Not a future language:
|
||||||
|
the drift above is present, and a suite that only new SDKs must satisfy would leave the disagreement
|
||||||
|
that already exists in place while certifying everything added afterwards against it.
|
||||||
|
|
||||||
|
## Why not generated types
|
||||||
|
|
||||||
|
Generation makes the shapes agree and leaves everything that matters unspecified. Two SDKs
|
||||||
|
generated from one schema can still name their queues differently, take identity from the
|
||||||
|
environment, dedup on the wrong field, or omit a header the other requires — and every one of those
|
||||||
|
is a mesh that runs and quietly does not work.
|
||||||
|
|
||||||
|
It also makes the contract into whatever the generator supports, which is a decision nobody made
|
||||||
|
about a boundary everything else depends on.
|
||||||
|
|
||||||
|
**The shapes are worth generating once the wire is specified.** That is a convenience, and it comes
|
||||||
|
second.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**A language is a commitment, and now a measurable one.** Adding one means implementing the wire
|
||||||
|
and passing the suite. That is more work than transliterating types, and it is the work that was
|
||||||
|
always there — the difference is that it can now be finished rather than believed.
|
||||||
|
|
||||||
|
**Versioning becomes possible.** `x-schema` exists for it and is never written. A specified envelope
|
||||||
|
with a version on the body is what lets a mesh hold a module built against an older SDK, which is
|
||||||
|
the ordinary state of any mesh that has been running for a while.
|
||||||
|
|
||||||
|
**The two current implementations will be found wrong.** They disagree, so at least one is. Fixing
|
||||||
|
that is the point rather than a cost, but it is not free: something is emitting or expecting
|
||||||
|
something it should not.
|
||||||
|
|
||||||
|
**This does not make the mesh polyglot by itself**, and should not be reported as though it does. It
|
||||||
|
makes polyglot possible to do correctly. A Rust SDK is still a Rust SDK.
|
||||||
|
|
||||||
|
## How this is checked
|
||||||
|
|
||||||
|
| Rule | Checked by |
|
||||||
|
|---|---|
|
||||||
|
| One vocabulary | A word means one thing across implementations, checked by the fixtures using it. |
|
||||||
|
| The wire is what is specified | Both existing SDKs run the conformance suite in their own test suites, and a change to one that breaks a fixture fails there rather than in a mesh. |
|
||||||
|
| A new SDK is a passing SDK | A language is not listed as buildable until its SDK passes; the toolchain list and the conformance results name the same set. |
|
||||||
|
| An unknown header is ignored | A fixture carries one, and every implementation accepts it. |
|
||||||
|
| Identity comes from the credential | A fixture sets an environment that disagrees with the credential, and the emitted event carries the credential's. |
|
||||||
@@ -106,6 +106,7 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0071** — [Genesis clones from a mesh, and checks what it got](0071-where-genesis-gets-its-source.md)
|
- **0071** — [Genesis clones from a mesh, and checks what it got](0071-where-genesis-gets-its-source.md)
|
||||||
- **0072** — [Two graphs, and a build chain that orders itself](0072-two-graphs-and-the-build-chain.md)
|
- **0072** — [Two graphs, and a build chain that orders itself](0072-two-graphs-and-the-build-chain.md)
|
||||||
- **0073** — [The installer carries a builder, and the registry stays where it is](0073-the-installer-carries-a-builder.md)
|
- **0073** — [The installer carries a builder, and the registry stays where it is](0073-the-installer-carries-a-builder.md)
|
||||||
|
- **0074** — [The wire is specified; an SDK is whatever passes the conformance suite](0074-the-wire-is-specified-not-the-types.md) *(proposed)*
|
||||||
|
|
||||||
### What runs on them, and how it gets there
|
### What runs on them, and how it gets there
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user