Skip to content

feat(proto): unify well-known endpoint across all RAMP roles - #2

Merged
legendko merged 5 commits into
mainfrom
well-known-ramp-json-unified
May 29, 2026
Merged

feat(proto): unify well-known endpoint across all RAMP roles#2
legendko merged 5 commits into
mainfrom
well-known-ramp-json-unified

Conversation

@legendko

Copy link
Copy Markdown
Collaborator

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.

@KonstantinMirin KonstantinMirin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 with kid + not_before/not_after. Verification is role-agnosticpublic_keys is always present; role only 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-valued role is correct under that model. ✅
  • Revocation (KeyInvalidationList :1859): kid-only snapshot, 300s poll. Leaner than carrying full keys and sufficient — you reject by kid. ✅
  • marketplace → exchange rename: 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_hash key 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_url short/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.

legendko added 4 commits May 28, 2026 11:40
  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).
@legendko
legendko force-pushed the well-known-ramp-json-unified branch from 2fe4e6d to f3be91d Compare May 28, 2026 09:54

@KonstantinMirin KonstantinMirin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requested changes

  1. Regenerate gen/. cd proto && buf generate produces a non-empty diff: RAMPResponse's descriptor in the embedded FileDescriptorProto is 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 regenerated gen/.

  2. Replace the removed /exchange/v1/keys endpoint with /.well-known/ramp.json:

    • website/src/content/docs/getting-started/poc-walkthrough.mdx:360 (live curl example)
    • website/src/content/docs/protocol/authentication.mdx:555 (prose)
  3. Replace Marketplace with Exchange in proto/comp/v1/comp.proto:53 and :126.

  4. Re-encode ext-news.mdx:400. The exchange_signature placeholder is bWFya2V0cGxhY2Utc2lnbmF0dXJl..., which base64-decodes to marketplace-signature. Encode exchange-signature (or any non-stale string) instead. (:469 decodes to npr-podcast-signature — looks intentional for the news extension; confirm.)

  5. Finish the orchestrator → broker rename. It's already half-done — the website currently has ~10 inbound links to /components/broker/* paths that 404 because the directory is still components/orchestrator/.

    • Proto: rename Orchestrator(s) in proto/ramp/v1/ramp.proto (lines 147, 261, 296, 434, 445, 743). Will mirror to gen/ on buf 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/.
  6. Grammar pass: a Exchangean Exchange, an Brokera Broker. ~30 occurrences across proto + website. Pattern is perfectly asymmetric — every a Exchange is wrong, every an Broker is wrong.

  7. Add protocol/exchange-manifest.mdx to the sidebar in website/astro.config.mjs.

  8. Convert camelCase → snake_case in website/src/content/docs/getting-started/poc-walkthrough.mdx:130, 142, 157 (exchangeSignatureexchange_signature, offerSignatureoffer_signature). Every other walkthrough uses snake_case.

  9. 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
@legendko

Copy link
Copy Markdown
Collaborator Author

On Claim 2:
Re: replacing /exchange/v1/keys with /.well-known/ramp.json at authentication.mdx:555 - not applying this one.
That current line isn't telling to call /exchange/v1/keys; it's prose documenting that the endpoint was removed:
"Every party publishes its keys in one place - {domain}/.well-known/ramp.json. The per-role files (ramp-agent.json, ramp-exchange.json, ramp-verifier.json) and the legacy /exchange/v1/keys endpoint are removed in v1.1.0; keys are inline JWKs."
The 2 sentences explain exactly what was changed. The suggestion to 'replace the removed /exchange/v1/keys endpoint with /.well-known/ramp.json' in that explanation would create confusion.

@KonstantinMirin KonstantinMirin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

All nine items from the prior request-changes review are addressed in f285934. Spot-check:

  • gen/ regenerates zero diff; buf lint and buf breaking clean.
  • /exchange/v1/keys curl gone from poc-walkthrough; comp.proto Marketplace mentions gone.
  • ext-news.mdx:400 placeholder now base64-decodes to exchange-signature (:469 confirmed as the news-theme npr-podcast-signature).
  • orchestrator → broker end-to-end: proto, website dir, index.mdx nav link.
  • 0 a Exchange / 0 an Broker left.
  • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants