docs: add L402 discovery specification#27
Conversation
The base L402 protocol is reactive: a client learns what a service offers, and what it costs, only by hitting a resource and getting a 402 back with a single invoice for a single thing. There is no way to see the catalog, the prices, the tier structure, or the caveat vocabulary ahead of time. In this commit, we add an optional discovery layer that closes that gap and gives the protocol a place for dynamic pricing and the start of negotiation. Discovery is built from two pieces that share one vocabulary. The first is a static manifest, served free at /.well-known/l402.json, that describes a provider's services, capabilities, tiers, constraints, prices, and the caveat conditions its satisfiers enforce. The second is an optional quote endpoint: the client proposes a bundle, and the provider answers with a price and a ready-to-pay challenge, plus counter-offers. The manifest comes in two interchangeable profiles, a compact bespoke JSON document and a fully self-describing OpenAPI 3.1 document that carries the same pricing inline through an x-l402-* extension vocabulary, so a single fetch can teach a client the whole API including its prices. The load-bearing property is that discovery adds no new payment or verification machinery. A quote response is an ordinary L402 challenge, a macaroon committing to a payment hash alongside an invoice, minted proactively for a bundle the client chose rather than for a fixed resource it happened to request. To make that reuse precise, the spec pins down the bundle-to-caveat mapping, requires the client to verify the returned macaroon binds to the invoice and encodes the requested bundle before paying, and ties the quote expiry to the BOLT 11 invoice expiry, since the credential itself carries no notion of a quote price or its expiry. It also defines a structured error model for the quote endpoint, a budget-aware negotiation request, and the security considerations for authenticity, price integrity, and quote-endpoint denial of service. The agent specification gains a token-efficient discovery section covering the same ground for autonomous clients. Discovery is fully optional and backwards compatible: a provider that implements none of it remains L402-compliant, and a discovery-unaware client falls back to the reactive 402 flow unchanged.
|
Coming over from the x402 side — rolznz's question in #22 is how I found this. Context so the comparisons below have some grounding: I operate an x402 facilitator (Sui) and wrote the spec text for x402's settlement-receipt-binding extension (x402-foundation/x402#2666, still in review), so discovery and "what can you prove you paid for" are the two surfaces I've spent the most time on over there. Read the spec end to end; four things, roughly in order of consequence. 1. The aggregator case is where this will actually live, which makes the OPTIONAL signature load-bearing. x402's bazaar extension went the opposite way structurally: discovery metadata rides on the
2. The design already yields an offline-verifiable proof of purchase — worth one informative paragraph making that explicit. This is the question I came in with, since it's the thing we had to bolt on in x402: after the fact, what can a client prove about what it bought? Here the answer falls out of existing structure: the BOLT 11 invoice is signed by the provider's node and carries the amount; the macaroon identifier commits to that invoice's payment hash; the preimage proves settlement; and §5.7's deterministic bundle-to-caveat mapping makes the purchased bundle legible from the macaroon's own caveats. So 3. Quote-endpoint cost: let the client signal intent on the wire. §10.3's price-only quote ("minting the challenge only when the client signals intent to pay") is the right escape valve for the mint-per-quote cost — unauthenticated requests that force priced work server-side is a griefing class I have to think about on the facilitator verify path too. But as written there's no wire field for that intent, so a provider has to guess from heuristics. A 4. Small one on §6.2, check 2. The client must confirm the caveats are "exactly the bundle-to-caveat mapping" of the requested bundle. Does "exactly" intend to preclude provider-internal bookkeeping caveats entirely (ops tags, deployment metadata)? §5.7 forbids widening and forbids omitting requested constraints, but a strict exact-match check also fails closed on any benign extra narrowing caveat. That's probably the right call — a client can't distinguish benign from not — but if it's deliberate it may deserve one explicit sentence, so an implementer doesn't "helpfully" relax the check later. None of this touches the core shape, which reads ready to implement against. And the manifest publishing the caveat vocabulary with attenuation rules (§5.5) has no x402 analog — for agent clients that want to sub-delegate credentials, that's quietly one of the most useful parts of the document. |
In this PR, we add a discovery layer for L402. Today the protocol is reactive:
you only find out what a service offers, or how it prices things, by hitting a
resource and getting a
402back with a single invoice for a single thing.There's no way to look at the catalog, the prices, the tier structure, or even
the caveat vocabulary a server enforces before you commit to paying. We close
that gap, and in doing so give L402 a natural home for dynamic pricing and the
start of in-protocol negotiation.
Discovery is built from two pieces that share one vocabulary. The first is a
static manifest, served free at
/.well-known/l402.json(RFC 8615), that describes a provider's
services, capabilities, tiers, constraints, prices, and the caveat conditions
its satisfiers actually understand. The second is an optional quote endpoint:
the client proposes a bundle (a service, a tier, some capabilities, some
constraint values) and the provider answers with a price and a ready-to-pay
challenge, plus counter-offers. The manifest comes in two interchangeable
profiles, a compact bespoke JSON document and a fully self-describing
OpenAPI 3.1 document that carries the same pricing
inline through an
x-l402-*extension vocabulary, so a single fetch can teach aclient the whole API including its prices.
No new verification machinery
The property that makes this fit cleanly is that discovery doesn't add a second
payment or verification path. A quote response is just an ordinary L402
challenge, a macaroon committing to a payment hash alongside an invoice, except
it's minted proactively for a bundle the client chose rather than for a fixed
resource it happened to request. Once the client holds it, the rest is the base
protocol unchanged: pay the invoice, present
Authorization: L402 <macaroon>:<preimage>, done. Discovery only changes howthe challenge gets selected, never how it's honored.
To make that reuse precise rather than hand-wavy, the spec pins down the
bundle-to-caveat mapping (how
{service, tier, capabilities, constraints}becomes
services=s:t,s_capabilities=..., and onekey=valueperconstraint), and requires the client to verify the returned macaroon binds to
the invoice and encodes the bundle it asked for before paying. We also tie the
quote expiry to the BOLT 11 invoice's own expiry, since the credential carries
no notion of a quote price or its expiry: the only way an expired quote actually
becomes unpayable is for the underlying invoice to expire with it.
What's in the spec
The bulk of the change is
discovery-spec.md, written in the same RFC style asthe existing protocol spec. It covers the manifest format, the quote endpoint
with a structured error model and budget-aware negotiation, the OpenAPI profile
and its
x-l402-*vocabulary, and the security considerations w.r.tauthenticity, price integrity, and quote-endpoint DoS.
agent-spec.mdgains atoken-efficient discovery section that covers the same ground for autonomous
clients.
Discovery is fully optional and backwards compatible. A provider that implements
none of it stays L402-compliant, and a discovery-unaware client just falls back
to the reactive
402flow.BOLT 12 is left in as experimental: a macaroon has to commit to the payment
hash, but a BOLT 12 offer doesn't hand you one until you fetch an invoice over
an onion message, so the deferred-binding flow needs its own treatment in a
later revision.
A few gaps are deliberately left open for a follow-up: client-side privacy of
token_idcorrelation, root-key storage growth from unpaid quotes, anidempotency key for quote retries, per-resource fiat pricing, and version-parsing
rules.