ADR-014: Landing the settlement envelope on the wire is a transport change, not a wiring change
Status: Accepted Implementation: not-started Force: heuristic Date: 2026-08-31 Related: ADR-007 (Settlement Response Envelope), ADR-011 (Transport Abstraction), ADR-006 (Version Negotiation), ADR-013 (x402 Intake Compatibility)
Context
Danny ruled on 2026-08-31: wire the gate to the envelope. He was told the decision was a one-line wiring choice, on the strength of ADR-007 reading Implementation: shipped while gate.ts:186 emitted the legacy flat s402SettleResponse. That framing was wrong, and this ADR exists so the correction outlives the conversation it was made in.
What is actually true. The envelope's types shipped in April. The envelope has never been on the wire. buildSettledEnvelope, buildPendingEnvelope and encodeEnvelopeBody appear nowhere in typescript/src/ except their own module and a re-export line in index.ts. Their only callers are in test/envelope.test.ts. ADR-007's Supersedes: line describes an intention that was never executed.
So the gap is not a loose wire. It is a complete, tested component that was never connected to anything, and a status field that said shipped because a symbol was exported.
Four reasons this is bigger than a swap
It is a breaking wire change on a public repo.
encodeSettleResponseanddecodeSettleResponseare public API (index.ts:75-76), the joke-api example client decodes thepayment-responseheader directly, and the flat shape is baked into the published conformance vectors (test/conformance/generate-vectors.ts). Any existing client breaks.The envelope has no header encoder. It ships
encodeEnvelopeBody— plain JSON, with aMAX_BODY_BYTESguard. The gate writes a base64 header. Header-versus-body is ADR-011's ground, and choosing one is a protocol decision, not an implementation detail.There is no version negotiation to hide behind.
S402_VERSIONis the constant'1', and the gate deliberately treatss402Versionas optional so x402 clients pass through (gate.ts:218). There is currently no mechanism to emit the new shape only to clients that understand it — which is the thing that would make this safe.Nothing is broken today.
s402SettleResponsecannot expresspending, so no s402 server can emit one, so the double-pay this would prevent is latent, not live. ADR-013 already closed the live half on intake. Urgency is low; blast radius is high. That is the worst possible ratio for a change made while nobody can review it.
Decision
The ruling stands — the envelope should land on the wire — and it is scoped as a versioned transport change, not a wiring fix. Three things must be decided before code, and none of them were part of the question as originally put:
- Body or header? The envelope is body-shaped and the receipt path is header-shaped.
- How does a client opt in? Version negotiation, a second header, or a major-version break.
- What happens to the conformance vectors? They encode the flat shape as correct behavior.
Until those are answered, the honest state is not-started, and ADR-007 has been corrected from shipped to in-progress so it stops asserting otherwise.
Alternatives Considered
- Swap
gate.ts:186to emit an envelope. Rejected: silently breaks every existing client and the published vectors, to fix a bug that cannot currently occur. - Emit the envelope in an additional header, leaving
payment-responseuntouched. Genuinely attractive — additive and non-breaking. Rejected for now because inventing a second receipt header is itself a protocol design decision, and it is the kind that gets made once. - Close ADR-007's supersession as abandoned. Rejected: Danny ruled the other way, and the design is good. The envelope's chain-agnostic shape and
txBindingare the reasons ADR-007 exists, and they have not stopped being right. - Chosen: record the true scope, correct the lying status field, and leave the wire alone until the three questions above have answers.
Consequences
- Positive. ADR-007 no longer claims a state it never reached. The next agent starts from what is true rather than re-deriving it, and the three real questions are written down.
- Negative. The latent gap stays open: an s402 server fronting an x402 facilitator still cannot relay
pendingto its own clients. That is the same open item ADR-013 named, unchanged. - Risk to watch.
Implementation:fields that track whether a symbol exists rather than whether a behavior reaches the wire. This ADR is the second instance found in one night. A useful check would assert that every ADR markedshippednames a call site outside its own module and its tests.