Skip to content

Feature/random unique value - #46

Open
EugeneDymo wants to merge 15 commits into
mainfrom
feature/random-unique-value
Open

Feature/random unique value#46
EugeneDymo wants to merge 15 commits into
mainfrom
feature/random-unique-value

Conversation

@EugeneDymo

Copy link
Copy Markdown
Collaborator

No description provided.

EugeneDymo and others added 15 commits September 1, 2026 13:45
…the resource

The comment already said the id is assigned by the Exchange, but an
implementation historically derived it from the resource, so two offers
for the same resource collided. The comment now states the id is opaque
and unique per offer. No wire change; changelog mirrored on the website.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KAjPGWneSodefKfA3EauY
…the resource

The comment already said the id is assigned by the Exchange, but an
implementation historically derived it from the resource, so two offers
for the same resource collided. The comment now states the id is opaque
and unique per offer. No wire change; changelog mirrored on the website.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KAjPGWneSodefKfA3EauY
…e test

Two example ids survived the sweep (offer-001 in extension-profiles,
fake in the poc-walkthrough denial demo); both are now UUID v4.
TestDocOfferIDsAreOpaque scans every offer_id value in the docs and
rejects anything that is not an opaque UUID v4, so a structured example
id cannot reappear silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
…-value

# Conflicts:
#	docs/sdk-parity-matrix.md
#	gen/descriptor.binpb
#	gen/ts/wire/schemas.ts
#	proto/CHANGELOG.md
#	sdk/python/ramp_sdk/__init__.py
#	sdk/ts/client/index.ts
#	website/src/content/docs/reference/changelog.mdx
… its guards

The Execute tests in all three SDKs now read agent_request_acceptance back
off the recorded wire body and verify it the way a receiving Exchange would
(Go through the projection check, Python and TypeScript through the verify
wrappers), pinning the exact wire spelling of the payload. Go test offers
now carry an exchange, so the signing branch actually runs under test; each
runtime also asserts the field is omitted for an offer that names no
exchange, since a request-acceptance item requires a recipient.

The Go verifier's guards get negatives: an acceptance replayed under a
request with a different requester or idempotency key is refused, a
non-EdDSA algorithm is refused, and the Signer face round-trips and refuses
a non-Ed25519 signer before asking it to sign. Removing the envelope-binding
block now fails the suite instead of passing silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
… projections

AgentRequestAcceptancePayload.items is capped at 256 (repeated.max_items,
the same ceiling a discovery query's uris list carries). The Go helper
enforces the same bound itself before its item walk and JCS rendering, and
decodes and size-checks the Ed25519 signature before canonicalization — a
verifier may run with wire validation off, and the canonical rendering of
an unbounded caller-controlled list is the expensive step a caller whose
signature cannot possibly verify must not be able to buy. A test pins the
helper's bound to the wire rule so the two cannot drift, and an ordering
probe pairs an over-cap payload with a wrong-size signature to prove the
cheap check runs first.

VerifyRequestAcceptanceProjection now refuses an empty subrequest outright:
for an Exchange the signed set never names, the projection is also empty,
zero compared equal to zero, and the helper reported a verified projection
for a request addressed to nobody.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
…fication

VerifyRequestAcceptanceProjection filtered the signed set and validated
forwarded offers with raw string equality, while the SDK's Exchange
identity rule (CheckAudience) folds case and treats an explicit :443 as
the omitted HTTPS-default port. With a signed set holding one.example
and one.example:443, a relay could drop the differently spelled item
and still pass, and an honest complete forward was refused.

Both comparisons now go through CheckAudience. The projection exchange
must be a bare domain; a value in any other shape names nobody and
never matches, so malformed values fail closed. Port 80 stays a
distinct identity.

Tests: an honest mixed-spelling forward verifies under either accepted
spelling of the verifier's identity; dropping any equivalent item, or
forwarding only the raw-equal subset, is refused; port-80 and malformed
values are refused. Both new tests fail if the comparison reverts to
raw equality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
…and order set verification before idempotency

The request-acceptance contract said a Broker forwards the envelope
unchanged while projecting a mixed-Exchange request, but the documented
RFC 9421 model only covered byte-for-byte forwarding, where the agent's
signature spans the body through Content-Digest. Projecting the body
invalidates that signature, and nothing said who authenticates a
projected subrequest or how an Exchange resolves the acceptance
verification key.

The model is now written down in the proto comment, the authentication
page, and the Broker overview. A projected subrequest is a new HTTP
request its sender authors and RFC 9421-signs - a Broker, or the agent
itself when it splits its own set. The agent's authorization travels in
the body as the two detached Ed25519 signatures, which is why they
exist. The Exchange resolves the acceptance verification key from the
WBA directory of the requester domain the signed payload names, which
must equal the request's requester.domain; when the requester signed
the arriving request, that is the already-resolved request-signing key.
Delegation holder binding still matches the wire signer, so a Broker
may project a delegated request only when the agent delegated to the
Broker's key. Fan-out stays on the existing Resolve plus one
ExecuteTransaction per Exchange; no new RPC.

The threat model gains T-BRK-1: item-level removal, append, reorder,
and valid-subset-first mutation of the request set during fan-out,
countered by the request acceptance and explicitly not by the hop
stack, which proves nothing about a body the Broker authored. The
Exchange step lists now verify a present request acceptance and its
exact projection before any idempotency lookup, matching the proto
requirement - idempotency state is never created or served for a
request whose set claim has not been proven. Optional-field behavior
for older clients is stated consistently everywhere.

Doc conformance is clean and the site builds with all internal links
valid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
…bsite

proto/CHANGELOG.md records the additive
TransactionRequest.agent_request_acceptance wire change, but the
website changelog had no matching Unreleased entry despite the rule
that meaningful proto changes are mirrored there. The new entry covers
the complete ordered request-set proof, the Broker projection purpose,
the optional field's wire compatibility, and the 256-item work bound,
and states that the RFC 9421 forwarding stack does not provide the
item-level property because a projected subrequest is a new request its
sender signs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
The canonical-signing section said two protobuf-message payloads are
signed, that the acceptance payload is the one place Python and
TypeScript hand-build canonical JSON, and listed two accessor triples.
AgentRequestAcceptance is a third signed payload and a second
hand-built acceptance object in both ports, down to each entry of its
nested items list, with the omission invariant pinned by the shared
request-acceptance vectors. The accessor list now carries
CanonicalRequestAcceptanceBytes, jcs_request_acceptance_payload, and
requestAcceptancePayload beside the existing pairs.

The recipient-addressing section now distinguishes
AgentRequestAcceptanceItem.exchange - a signed projection index that
lets a recipient of a projected subrequest derive its own projection -
from the redundant top-level TransactionRequest audience field that
section rejects. The projection check itself polices the copy, so the
top-level-versus-items mismatch objection does not apply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STq1uLaq6mVSKAn73xQnwC
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.

1 participant