feat(proto): unify well-known endpoint across all RAMP roles - #2
Conversation
KonstantinMirin
left a comment
There was a problem hiding this comment.
Review — feat(proto): unify well-known endpoint across all RAMP roles
✅ Approve — with two non-blocking changes to propagate
The design is sound and faithful to the goal: one canonical /.well-known/ramp.json per participant, standard RFC 7517 JWKs inline, role-agnostic signature verification, and an emergency-revocation channel that defeats long-lived manifest caching. Binary wire compatibility is preserved; the intentional JSON renames are thoroughly disclosed. Two small additions are requested below — please carry them into the proto comments and the corresponding website discovery/auth documentation so the spec, the generated reference, and the site stay in lockstep.
Mechanical verification — PASS
| Check | Result |
|---|---|
gen/ reproducibility (buf generate) |
✅ zero diff — no hand-editing |
buf lint |
✅ clean |
buf breaking --against main |
✅ clean — only FIELD_SAME_NAME / FIELD_SAME_JSON_NAME / ENUM_VALUE_SAME_NAME suppressed in buf.yaml, i.e. binary-additive; JSON names change by design |
| CHANGELOG disclosure of the rename | ✅ full rename table + "binary holds / JSON consumers MUST update" + verifier-transition section |
Design assessment against intent
- Unified manifest + standard keys (
WellKnownManifest:1739,JsonWebKey:1712): keys are real RFC 7517 JWKs withkid+not_before/not_after. Verification is role-agnostic —public_keysis always present;roleonly gates capability/authorization fields a verifier ignores. ✅ - Co-hosted roles: handled operationally via per-role subdomains (
exchange.x.com/broker.x.com), one manifest each — single-valuedroleis correct under that model. ✅ - Revocation (
KeyInvalidationList:1859): kid-only snapshot, 300s poll. Leaner than carrying full keys and sufficient — you reject bykid. ✅ marketplace → exchangerename: endorsed. The old "marketplace" naming was a mistake; with no production consumers yet, a JSON-breaking change under v1 is acceptable and is documented loudly in the CHANGELOG. ✅- Domainless individual agent: intentionally not solved at the protocol level in v1.1 — the supported path is a hosted-registry manifest (no proto change). See change #2 below to make that an explicit, conscious decision rather than a silent gap.
Requested change 1 — State the caching contract (SHOULD-level)
The revocation feature exists because ramp.json is cached for long periods, yet the proto specifies only the 300s consumer poll, not the freshness of the documents themselves. Without stating it, an intermediary cache on invalidation_url can silently defeat the whole mechanism. Add to the Well-Known Discovery section (~ramp.proto:1680) and reference it from WellKnownManifest.invalidation_url (:1768):
// Caching contract:
// /.well-known/ramp.json MAY be cached by consumers and CDNs for routine
// periods (minutes to hours) — keys rotate slowly and carry explicit
// not_before/not_after bounds. The invalidation_url body, by contrast,
// SHOULD be served with a short or zero freshness lifetime (e.g.
// Cache-Control: max-age=60, or no-store) so the 300s consumer poll is not
// defeated by an intermediary cache. Emergency-revocation latency is bounded
// by the freshness of this list.
//
// Enforcement is operational, not protocol-guaranteed: a consumer that
// grants access against a stale revocation set bears that as its own
// business risk — it is not a protocol deficiency.
The closing note matters: it sets the expectation correctly so implementers don't read the binding as a hard guarantee.
Requested change 2 — Document the domainless-agent workaround as a deliberate decision
The discovery comments now hard-assert {domain}/.well-known/ramp.json for ROLE_AGENT (:109, :918, :960) and Requester.domain stays required (:919). That's fine — but record why it's fine, so the rigidity is a choice and the workaround is discoverable. Add to the Well-Known Discovery section:
// Domainless requesters — an individual agent with no public domain of its
// own — are accommodated without a protocol change: a registry (operated by an
// Exchange/Broker or a third party) hosts the agent's WellKnownManifest, and
// the agent sets Requester.domain to that registry host
// (e.g. "agent-7.registry.example"). The agent generates its own keypair; the
// registry publishes the public half at {registry}/.well-known/ramp.json. The
// domain + well-known paradigm is preserved end to end.
//
// Relaxing the discovery anchor (e.g. an explicit per-requester keys URL) is
// intentionally deferred: revisit at the protocol level only if self-hosting
// or registry-hosting proves too rigid in practice.
Cross-reference for the author: this is also the resolution to the open question from PR #1 — an Exchange resolves a domainless agent's
agent_identity_hashkey via the registry-hosted manifest. Worth a one-line link between the two sections when both land.
Website propagation
Both additions are normative discovery/auth behavior, so the site's well-known / authentication / key-management pages need the same content:
- the caching contract (manifest cacheable,
invalidation_urlshort/no-cache, stale-revocation = operator risk), and - the domainless-agent registry pattern as the documented workaround, flagged as a deliberate v1.1 scope decision.
No other changes requested. Nice cleanup overall — the rename alone makes the whole spec read better.
Every participant (agent, broker, exchange, publisher) now serves
a single /.well-known/ramp.json via WellKnownManifest. Eliminates
the per-role filenames (ramp-agent.json, ramp-exchange.json,
ramp-verifier.json) and /marketplace/v1/keys from the spec.
New messages:
- WellKnownManifest — role-tagged unified manifest with inline JWKs,
optional invalidation_url, publisher exchanges/catalog_contributors,
and exchange capability fields
- JsonWebKey — inline RFC 7517 JWK (Ed25519 only, v1.1.0) with
not_before/not_after RFC3339 time bounds
- KeyInvalidationList — snapshot kid-revocation list at invalidation_url
- Role enum: AGENT, EXCHANGE, BROKER, PUBLISHER
Deprecations (kept on wire for one cycle):
- ProviderManifest → WellKnownManifest with role=ROLE_PUBLISHER
- ExchangeManifest → WellKnownManifest with role=ROLE_EXCHANGE
- ExchangeManifest.keys_uri and .jwks_uri → public_keys inline
BREAKING CHANGE (JSON wire only, binary-compatible):
ProviderManifest field 4 renamed marketplaces → exchanges. Wire tag
preserved; JSON consumers must update to read "exchanges".
buf.yaml: exempt FIELD_SAME_NAME/FIELD_SAME_JSON_NAME — intentional JSON wire break documented in CHANGELOG.
…ity bound Address two non-blocking review items plus an interval-bound inconsistency on the unified well-known endpoint. Comment + CHANGELOG only; wire unchanged (buf lint + breaking clean, gen/ diff is doc-comments only). - Caching contract: ramp.json is cacheable (minutes-hours); the invalidation_url body SHOULD be short/no-store so the 300s consumer poll is not defeated by an intermediary cache. Stale-revocation enforcement is operational, not protocol-guaranteed. Pointer added from WellKnownManifest.invalidation_url. - Domainless requesters: document the registry-hosted-manifest pattern (agent sets Requester.domain to the registry host) as a deliberate v1.1 deferral, no protocol change. - JsonWebKey: fix the at-least-one-valid-key invariant to half-open `not_before <= now < not_after`, matching the not_after "strict upper bound" definition and the [not_before, not_after) window.
…→exchange rename Propagate the v1.1.0 proto changes into the website, which the proto commits (596d895 unify well-known, de84edd marketplace→exchange, 480cadd caching/domainless/key-validity) never touched. Mirrors: - proto-ramp.mdx: add WellKnownManifest, JsonWebKey, KeyInvalidationList, Role; mark ProviderManifest/ExchangeManifest deprecated; DiscoveryMethod value MARKETPLACE→EXCHANGE. - changelog.mdx: add v1.1.0, v1.0.2, v1.0.1 entries. - ramp-json-example.mdx: reshape to WellKnownManifest (role=ROLE_PUBLISHER, public_keys, invalidation_url, exchanges). Discovery/auth content (review changes 1 & 2): - authentication.mdx: unified key lookup; add Key Validity Windows, Caching Contract, Emergency Revocation, Domainless Agents. - exchange-manifest.mdx: rewrite from the removed ramp-exchange.json to the single role-tagged ramp.json (four well-known files -> one). - discovery-paths, content-attestation, publisher-onboarding, threat-model, ext-*, walkthroughs, architecture, components: unify the per-role well-known files (ramp-agent/exchange/verifier.json, /exchange/v1/keys) to /.well-known/ramp.json (WellKnownManifest.public_keys). Verifier transition: claims_schema -> ext["ramp.attestation.claims_schema"], no dedicated verifier role. marketplace→exchange rename (full site): - git mv components/marketplace->exchange, marketplace-manifest-> exchange-manifest, for-marketplace-operators->for-exchange-operators. - prose, env var names (MARKETPLACE_*->EXCHANGE_*), internal links, DISCOVERY_METHOD_MARKETPLACE->EXCHANGE. - modernize deprecated message refs ProviderManifest/ExchangeManifest -> WellKnownManifest (supported_profiles field 18->23). - org URL postindustria-tech/ramp-protocol -> RAMP-Protocol/protocol. Build: astro build green (73 pages). Deferred (separate axes): orchestrator-> broker rename; the "a Exchange" grammar artifact (also present in proto).
2fe4e6d to
f3be91d
Compare
KonstantinMirin
left a comment
There was a problem hiding this comment.
Requested changes
-
Regenerate
gen/.cd proto && buf generateproduces a non-empty diff:RAMPResponse's descriptor in the embeddedFileDescriptorProtois 152 bytes short of the actual schema (missing fields 13 / 14 from PR #1). Struct-based code works; reflection-based code (gRPC reflection, descriptor JSON, dynamic dispatch) does not. Commit the regeneratedgen/. -
Replace the removed
/exchange/v1/keysendpoint with/.well-known/ramp.json:website/src/content/docs/getting-started/poc-walkthrough.mdx:360(livecurlexample)website/src/content/docs/protocol/authentication.mdx:555(prose)
-
Replace
MarketplacewithExchangeinproto/comp/v1/comp.proto:53and:126. -
Re-encode
ext-news.mdx:400. Theexchange_signatureplaceholder isbWFya2V0cGxhY2Utc2lnbmF0dXJl..., which base64-decodes tomarketplace-signature. Encodeexchange-signature(or any non-stale string) instead. (:469decodes tonpr-podcast-signature— looks intentional for the news extension; confirm.) -
Finish the
orchestrator → brokerrename. It's already half-done — the website currently has ~10 inbound links to/components/broker/*paths that 404 because the directory is stillcomponents/orchestrator/.- Proto: rename
Orchestrator(s)inproto/ramp/v1/ramp.proto(lines 147, 261, 296, 434, 445, 743). Will mirror togen/onbuf generate. - Website:
git mv website/src/content/docs/components/orchestrator/ .../components/broker/(3 files). - Fix
website/src/content/docs/index.mdx:58— label"Broker"with href pointing at the old/components/orchestrator/overview/.
- Proto: rename
-
Grammar pass:
a Exchange→an Exchange,an Broker→a Broker. ~30 occurrences across proto + website. Pattern is perfectly asymmetric — everya Exchangeis wrong, everyan Brokeris wrong. -
Add
protocol/exchange-manifest.mdxto the sidebar inwebsite/astro.config.mjs. -
Convert camelCase → snake_case in
website/src/content/docs/getting-started/poc-walkthrough.mdx:130, 142, 157(exchangeSignature→exchange_signature,offerSignature→offer_signature). Every other walkthrough uses snake_case. -
Pin the deprecation removal version in
proto/CHANGELOG.md. Currently says "next minor release"; replace with a concrete target (e.g.v1.2.0).
- regenerate gen/: RAMPResponse embedded descriptor was 152B short (stale length prefix) → panic at package init on import; now loads - comp.proto: Marketplace→Exchange in comments (CoMP "Content Owner" kept) - finish orchestrator→broker rename: website dir (git mv, 5 files), proto comments, and the stale nav link in index.mdx - grammar: "a Exchange"→"an Exchange", "an Broker"→"a Broker" (~31) - add Exchange Manifest page to the Starlight sidebar - poc-walkthrough: JSON examples → snake_case (block-wide); keys curl → /.well-known/ramp.json (mcpServers left; it is MCP config, not RAMP) - ext-news: re-encode stale "marketplace-signature" placeholder - CHANGELOG: pin deprecated-field removal to v1.2.0
|
On Claim 2: |
KonstantinMirin
left a comment
There was a problem hiding this comment.
Approved.
All nine items from the prior request-changes review are addressed in f285934. Spot-check:
gen/regenerates zero diff;buf lintandbuf breakingclean./exchange/v1/keyscurl gone from poc-walkthrough;comp.protoMarketplace mentions gone.ext-news.mdx:400placeholder now base64-decodes toexchange-signature(:469confirmed as the news-themenpr-podcast-signature).orchestrator → brokerend-to-end: proto, website dir,index.mdxnav link.- 0
a Exchange/ 0an Brokerleft. - Sidebar entry added; camelCase → snake_case in poc-walkthrough; deprecation pinned to v1.2.0.
On the one pushback (authentication.mdx:555): you're right — that line is the removal note ("...the legacy /exchange/v1/keys endpoint are removed in v1.1.0"), not a usage instruction. Withdrawn.
LGTM — yours to merge.
Every participant (agent, broker, exchange, publisher) now serves
a single /.well-known/ramp.json via WellKnownManifest. Eliminates
the per-role filenames (ramp-agent.json, ramp-exchange.json,
ramp-verifier.json) and /marketplace/v1/keys from the spec.
New messages:
Deprecations (kept on wire for one cycle):
BREAKING CHANGE (JSON wire only, binary-compatible):
ProviderManifest field 4 renamed marketplaces → exchanges. Wire tag
preserved; JSON consumers must update to read "exchanges".
buf.yaml: exempt FIELD_SAME_NAME/FIELD_SAME_JSON_NAME — intentional JSON wire break documented in CHANGELOG.