Conversation
ACK-ID: a core RFC (identities spelled did:web, one-fetch key resolution, grants, RFC 9421 signed requests, verification checklist, revocation levers, crit-based extension mechanism) plus six extensions, with ext-controller drafted in full. ACK-Pay: a design language plus one normative profile adopting the x402 offer-receipt artifacts and binding receipts to the authorizing grant. The README records settled decisions and the document map.
EfeDurmaz16
left a comment
There was a problem hiding this comment.
Read the full RFC set and cross-checked the payment profile against the pinned x402 source at commit 59ac597. The core + extensions split and the plain-JOSE posture feel right; comments ordered by severity.
Open questions, non-blocking:
- ES256K (open decision 2): rejecting has a real cost (EVM sellers must provision a non-wallet key) but I still lean reject: section 3 already forbids the signing key being
payTo, and WebCrypto has no secp256k1, so accepting would make receipts unverifiable with native crypto on browser and edge runtimes. - did:jwks: the SDK's did:jwks resolver uses the fallback chain core and ext-web forbid, and the RFC never mentions did:jwks. The README should say: superseded, folded into ext-web discovery, or profiled separately?
- Standing authority: do we want ext-delegation to profile recurring/metered payment flows? The intermediate-plus-leaf model is promising, but caps, cadence, sibling-leaf accounting, and buyer countersignatures need their own normative treatment. I would keep it out of this profile and track it as a follow-up design issue; happy to open one.
- Stub drafting notes: require every non-leaf ancestor to be
grant-int+jwt; spell out the multi-level controller walk; name the revocation-key relationship; note that jti redemption and replay caches need coordination across an RP's acceptance domain.
Review assistance: Codex and Claude Code were used to cross-check the RFC against the pinned protocol sources and draft these notes. I reviewed the conclusions.
| - **Signature scheme.** ACK-Pay conformance requires the JWS scheme. An | ||
| EIP-712/did:pkh signature MAY additionally be present; it carries no | ||
| ACK-Pay semantics and is passed through unevaluated. | ||
| - **Signer identity.** The JWS signer MUST be a did:web identity per ACK-ID |
There was a problem hiding this comment.
Resolving the signer's keys proves who signed, not that they were entitled to. Nothing binds the signer DID to resourceUrl: anyone can host a did.json and sign offers for someone else's resource, and checks 1-4 pass. Needs an authorization rule: signer host matches the resourceUrl origin, or the resource names its accepted signer DIDs.
| offer's `validUntil` had not passed at `issuedAt`. | ||
| 4. **Freshness**: `issuedAt` is sane for the claimed transaction; where | ||
| `txHash` is present, it MAY be checked against the named network. | ||
| 5. **The trail**: when the `ack` member is present and the named grant is |
There was a problem hiding this comment.
Check 5 runs ACK-ID section 7 rules, but those are time-of-request checks. When a third party walks the trail later, the grant has expired and the seller may have rotated keys away, so every honest historical receipt fails. The profile needs a historical-verification mode: which checks run against the artifact's own timestamps, and the seller's key-publication obligation through the dispute horizon.
| endpoint (`https://raw.githubusercontent.com/<owner>/.ack-id/HEAD/ack-id.json`). | ||
| Profiles for other hosts pin their equivalents. The shape is the same as | ||
| the well-known file, with `anchor` = `github:<owner>` or the host's | ||
| equivalent. The proof record MUST store the host's stable numeric owner ID |
There was a problem hiding this comment.
Three lifecycle gaps in ownership proofs. The expected numeric owner ID has no defined home: in the committed file it belongs to the rename-squatter it defends against, so it only works as trust-on-first-use, which should be stated. Proof claims have no exp and "whoever publishes proof status" is an undefined actor; either verifiers re-check anchors each time (say so) or a callback is back. And the DNS jkt pin is not wired into the Section 6 checklist: a verifier holding the pin can still accept artifacts signed by any other assertion key, defeating the pin against a compromised origin.
SkalorAI
left a comment
There was a problem hiding this comment.
Design review from the Skalor side. We implement against ACK-ID as an attestation issuer and run a pre-transaction control layer above the rails, so these are the places the RFC touches us directly.
Nothing here overlaps @EfeDurmaz16's comments, which I agree with — particularly content-binding ack and the need for a historical-verification mode.
Three of the seven are ext-attestations (carriage, subject binding through delegation chains, revocation); four are ACK-Pay (one reference rule, where a per-transaction control decision lives, issuer key obligations, rail-neutrality). Happy to go deeper on any of these in the thread.
| offer's `validUntil` had not passed at `issuedAt`. | ||
| 4. **Freshness**: `issuedAt` is sane for the claimed transaction; where | ||
| `txHash` is present, it MAY be checked against the named network. | ||
| 5. **The trail**: when the `ack` member is present and the named grant is |
There was a problem hiding this comment.
Historical verification needs an issuer-side obligation, not just a verifier mode.
+1 to the historical-verification mode @EfeDurmaz16 raised. Adding the other half: whoever signs receipts, attestations or clearances that will be re-verified after key rotation needs a stated obligation to keep retired keys resolvable — or to be log-backed per ext-audit — through a declared dispute horizon.
We would commit to that as an attestation issuer and would rather it were a rule than a courtesy. Suggest the profile names the horizon explicitly, and makes ext-audit's event log the conformant way to satisfy it.
aadopii
left a comment
There was a problem hiding this comment.
Let a one-payment grant survive the 402 price challenge
There is an interaction between ACK-ID and ACK-Pay that I did not find in the existing reviews: when does checking a single-use payment grant become redemption of that grant?
A seller may authenticate an agent before showing a price. The agent sends a signed request, receives 402 Payment Required, then retries with payment. Core §8 says the grant is spent at first acceptance. If middleware redeems it during the initial check, the paid retry fails as already spent even though the purchase has not started.
I propose making the ordering explicit:
- Checking a payment grant and returning only a price challenge must not spend it.
- On the paid retry, recheck the request, grant, and payment payload, then atomically and durably redeem (
iss,jti) before settlement submission or the protected operation starts. - Only the winning request may start the authorized effects. A timeout after execution starts must not make the grant available for another operation.
This lets one purchase complete without asking the owner for another grant. It adds no token or header. A separately authorized paid quote can still consume its own grant.
The proposed changes are in #215, targeting this RFC branch: a short ACK-Pay section, an ACK-ID cross-reference, and nine executable lifecycle examples integrated into the repository checks. The examples model ordering; they do not implement cryptography, distributed storage, or settlement. The full build and check commands pass in the fork's GitHub Actions run.
This concerns the stage before payment begins and is separate from the existing comments on spent-record expiry and receipt reissuance. Follow-ups could cover a production adapter and recovery using the payment rail's idempotency support.
AI assistance: I used Claude Code, Hermes, and Codex for source review and proposal development. Codex prepared the RFC edits, executable examples, and validation workflow, and ran the checks.
|
A few additional thoughts. Keeping them in one comment to avoid crowding the review. These are design suggestions, not blockers; some build on the existing discussions.
|
The profile named @x402/extensions 2.22.0 next to commit 59ac597, but that commit ships 2.15.0, and the field names were copied from the extension's documentation page, which disagrees with types.ts. Pin the source at the commit (the last change to offer-receipt, unchanged through 2.25.0), make the code the source of truth, and use `scheme` and `transaction`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Platform HTTP clients follow redirects without exposing the hops, so per-hop SSRF checks never run unless the fetcher walks the chain itself. Make refusal the default and following an explicit opt-in for fetchers that implement the checks, matching the SDK resolver (#133). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three drafting requirements from review: a request field for attestations since Grant rejects non-grant typs, attestation subjects that name a verified chain ancestor so did:key leaves can inherit them, and the SD-JWT VC status claim as the revocation lever. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the open item with the intended shape: Signature-Agent absent, identity from the leaf sub, key decoded from the identifier, keyid equal to its thumbprint and to cnf.jkt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A colon inside a path segment made the URL-to-DID mapping lossy: https://acme.com/a:b and https://acme.com/a/b both spelled did:web:acme.com:a:b. Reject any segment character outside the did:web idchar set (percent-encoding is already rejected), which closes that hole and any other separator collision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Close open decision 2 as reject. WebCrypto has no secp256k1, so an ES256K receipt would not verify with native crypto on browser and edge runtimes, and key separation already forbids the signing key being payTo, so accepting it would buy no wallet-key reuse. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The SDK resolver's jwks.json-then-OIDC fallback is the chain core forbids. Name the disposition in the settled decisions so the RFC answers what happens to the method. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Covering content-digest binds the header value only; a verifier must recompute the digest over the received body or a middlebox can swap the body under a valid signature. created had a maximum age but no future bound; apply the Section 5 skew, the same bound iat carries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The receipt carries no amount, so the profile covers fixed-price flows only; say so in Section 5 and fold a settled-amount field into open decision 1 so attribution and outcome upstream together. Fold buyer side receipt recovery (pay, lose the 200, hold no proof) into open decision 2, since idempotent re-issue needs a request surface the profile does not define. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Acceptance runs to exp + skew but the single-use record was dropped at exp, leaving a skew-sized window in which a spent grant passes again. Retain through exp + skew in core Section 8 and for registration grants in ext-delegation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Name the receipt binding as a standalone attribution shape (agent, grant reference, settlement reference) so other rail mappings carry the same members; state that translation into rail-native authority never widens a grant; and say the profile's one-grant, one-request, one-receipt, one-settlement shape is not a permanent relationship. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jti is unique per issuer only and the ack member names no issuer, so a second owner could mint a grant with the same sub and jti and walk the trail to the wrong party. Define the artifact reference (base64url SHA-256 over the compact serialization) once in core Section 4.2, have chain reuse it, and replace ack.grantId with ack.grant carrying the reference of the presented grant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@EfeDurmaz16, on the open questions in your review:
On your follow-up list, two landed as one-liners in 9ade080. Section 1 says the profile describes one grant, one request, one receipt, one settlement, and doesn't fix that relationship (your 5). Section 6 says translation into rail-native authority never widens a grant, and an unenforceable constraint refuses the payment (your 1a). The rest (category limits, agreements, refund authority, fulfillment acks, discovery versions) are follow-up issues rather than profile text; happy to have you file them. |
Status: proposal. Nothing here has shipped. The documents live under
docs/ack-id/rfc/anddocs/ack-pay/rfc/, unlisted from the docs site. We're asking for review of the design, not the prose.Summary
ACK-ID lets an AI agent prove three things to a relying party in one HTTP request: which agent is calling, which person or company is accountable for it, and whether that owner authorized this action. The request signature proves the agent, the grant's signature proves the owner, and the grant's scope proves the authorization.
This RFC proposes v2 as one small core plus six optional extensions:
did.json), grants (short-lived JWTs carrying audience, scope, and a possession pin), signed requests (RFC 9421, verifiable by Web Bot Auth infrastructure as deployed), a verification checklist, and revocation levers.ACK-Pay becomes a design language plus one normative profile: adopt the x402 offer-receipt artifacts (pinned at
x402-foundation/x402@59ac597, the source shipped in@x402/extensions2.15.0 and unchanged through 2.25.0), sign them with ACK-ID identities, and bind each receipt to the grant that authorized the payment. The trail runs receipt → agent → grant → owner → legal entity, and a third party can walk it with no callback to any participant.A core-only deployment (an org authenticating its own agents to its own services) is implementable with a stock JOSE library and an HTTP client in an afternoon. The whole setup, verified against
joseas published:Key differences from v1
<identity URL>/did.json, or/.well-known/did.jsonfor a bare domain, read for itspublicKeyJwkentries only. No fallback on 404: failing closed is what makes key removal a revocation lever. The same document serves x402's key discovery.aud,scope,constraints,exp,jti, and a requiredcnf.jktpossession pin. A copied grant is useless without the agent's private key; core has no bearer artifacts.Grantheader field, and the same signatures are verifiable by Web Bot Auth infrastructure already deployed at CDNs.exp, single-usejti, agent key removal, owner key unpinning. List mechanisms move to ext-revocation, and an RP declares the longest grant lifetime it will accept.crit(the RFC 7515 pattern applied to payload claims). A verifier that cannot evaluate acritclaim rejects the grant, so an extension claim can never downgrade a verifier that lacks the extension. Extensions also give their artifacts a second rejection surface (a distincttyp, a reserved scope token, anaudshape core rejects). Each extension is adopted and versioned on its own.chainclaim carries one hash: the immediate parent. Ancestry is pinned transitively, decoy entries are structurally impossible, and the convention matches AP2's mandate chain, so grants slot into AP2/UCP flows. ext-delegation also names OAuth 2.0 Token Exchange (RFC 8693) as the intended mint carriage for issuance services, so token-exchange-shaped issuers (a Keycloak realm, a custody provider's signing API) can mint leaves through a standard interface while verification stays offline.ackmember (agent,grant) inside the seller-signed receipt binds the payment to the grant that authorized it. A receipt without the binding stays a valid x402 receipt; it proves payment, and RPs that need the trail reject unbound receipts by policy.What didn't change
did.jsonserves ACK-ID, ACK-Pay, and x402.Reading order
docs/ack-id/rfc/README.md: the split, settled decisions with rationale, document mapdocs/ack-id/rfc/core.md: identities, keys, grants, signed requests, verification, revocationdocs/ack-id/rfc/ext-controller.md: controllers, ownership proofs, the full checklistdocs/ack-pay/rfc/core.md: the x402 profile and the receipt bindingAI usage
Claude Code drafted the review-response revisions and the thread replies; I reviewed every change.