AAuth -11: @aauth/proxy 1.0.0 — person tokens, three-way access_mode, R3 -02 - #2
Merged
Conversation
Person tokens (WP-9 task 1)
---------------------------
-11 makes the person token load-bearing: a resource MUST have verified
one before it issues a resource token, and the agent MUST present one on
every authorization endpoint request. `obtainPersonToken` POSTs the PS's
`person_token_endpoint` with `{resource, mission_s256?}`, signed, agent
token via Signature-Key, and handles the `202` + `requirement=interaction`
deferred path by surfacing the interaction like any other.
Cached in store.ts keyed (resource, mission_s256). Every person token
binds the same key through `cnf`, so the store records the RFC 7638
thumbprint it was populated under and flushes the WHOLE cache the moment
a different one is presented. No partial invalidation, no migration.
PS metadata (task 2)
--------------------
`token_endpoint` -> `auth_token_endpoint`; `person_token_endpoint` added.
Requests carrying a body to a PS or AS now cover `content-digest` and
`content-type`, which httpsig only generates when the component list
names them.
Gateway vocabulary deleted (task 3)
-----------------------------------
R3 -02 removed `urn:aauth:vocabulary:openapi-gateway` (issue #72).
Operation identifiers are scoped to the one discovery endpoint a resource
advertises per vocabulary, so the composite `service:operationId` identity
and the `{service, operationId}` entry shape are gone with it —
`formatOperationEntry` off the adapter contract, the `Record<string,string>`
discovery-value union off `load()`, `L1Entry.picked_vocabs[].docUrl`
narrowed to `string`.
session-token (task 4)
----------------------
`aauth-access-token` -> `session-token` throughout. -11 names this the one
credential a resource issues for its own consumption. Implemented rather
than only renamed: AAuth-Access is captured off any response and presented
as `Authorization: AAuth ...` with `authorization` in the covered
components, per the header's anti-replay requirement.
Three-way access_mode (task 5)
------------------------------
`access_mode` is an IANA registry, not a closed list, so resource.ts no
longer rejects unrecognized values. planAccessMode returns exactly three
outcomes: undeclared (absent or unrecognized — call and read the runtime
requirement, never an error), satisfiable (plan against it), unsatisfiable
(skip, with the reason). The third is the point: an agent whose agent token
carries no `ps` claim cannot complete person-token, auth-token or per-call,
and now learns that while planning instead of at a 401. `invoke` returns
`kind: 'skipped'`; find_resources and list_resources carry `skip_reason`.
Mirrors the @aauth/protocol surface the package contract pins. Kept local
because @aauth/protocol is unpublished; swap for an import when it ships.
Operation access annotations (task 6)
-------------------------------------
Read `x-aauth-access-mode` / `x-aauth-budget` off the OpenAPI Operation
Object and `_meta["aauth.dev/access-mode"]` / `_meta["aauth.dev/budget"]`
off an MCP Tool, from the vocabulary document the proxy already fetches
for L3. Flattened onto every list_operations / get_operations result as
`access_mode` (always — the mode that actually applies to that operation)
and `budget: true`. Sparse; replacing rather than intersecting, so an
annotation may LOWER the requirement; advisory, never enforced.
`session-token` in an annotation is dropped; `budget: true` raises the
operation to auth-token.
Per-call proposals (task 7)
---------------------------
The resource builds the proposal from the call's concrete parameters and
the token carries only `r3_uri`/`r3_s256`; the agent exchanges and retries.
The request init is fixed for the whole invoke flow, so the retry is
byte-identical by construction — the resource recovers the proposal by
hash and rejects any parameter that differs.
Missions (task 8)
-----------------
`mission_s256` on ProxyConfig and per-invoke, forwarded to the person
token endpoint. It appears in no auth-token request body: the claim
travels inside the tokens. Tests carry a mission throughout.
Also: identity.peek() so a cheap listing tool can annotate what the agent
can complete without provoking an enclave signature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FmiCqDjRUSx6zb1N4gZPXE
OpenAPIGatewayAdapter removed, InvokeResult gains a skipped variant, AccessMode widened to an open string type, routeOperation and invokeAtResource signatures changed, IdentityProvider gains peek(). Every other package in the wave took a major bump; a 0.x minor understated this one.
This was referenced Aug 12, 2026
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@aauth/proxy1.0.0 — the agent-proxy core moves to AAuth -11 and R3 -02. Two commits: the feature work as 0.6.0, then a re-version to 1.0.0 because a 0.x minor understated a breaking surface change.This is a breaking release.
OpenAPIGatewayAdapterremoved,InvokeResultgains askippedvariant,AccessModewidened to an open string type,routeOperationandinvokeAtResourcesignatures changed,L1Entry.picked_vocabs[].docUrlnarrowed tostring,IdentityProvidergains an optionalpeek(). Every other package in the wave took a major bump; this one should too.What changed
Person tokens are now load-bearing. -11 says a resource MUST have verified a person token before it issues a resource token, and the agent MUST present one on every authorization endpoint request.
obtainPersonTokenPOSTs the PS'sperson_token_endpointwith{resource, mission_s256?}, signed, agent token viaSignature-Key, and handles the202 requirement=interactiondeferred path by surfacing the interaction like any other.Cached in
store.tskeyed(resource, mission_s256). Every person token binds the same key throughcnf, so the store records the RFC 7638 thumbprint it was populated under and flushes the whole cache the moment a different one is presented. No partial invalidation, no migration.PS metadata:
token_endpoint→auth_token_endpoint,person_token_endpointadded. Requests carrying a body to a PS or AS now covercontent-digestandcontent-type— httpsig only generates those when the component list names them.Gateway vocabulary deleted. R3 -02 removed
urn:aauth:vocabulary:openapi-gateway(AAuth#72). Operation identifiers are scoped to the one discovery endpoint a resource advertises per vocabulary, so the compositeservice:operationIdidentity and the{service, operationId}entry shape go with it.aauth-access-token→session-tokenthroughout, and implemented rather than only renamed:AAuth-Accessis captured off any response and presented asAuthorization: AAuth …withauthorizationin the covered components, per the header's anti-replay requirement.Three-way
access_modeplanning — the substantive behaviour change.access_modeis an IANA registry, not a closed list, soresource.tsno longer rejects unrecognized values.planAccessModereturns exactly three outcomes:undeclared— absent, or a value this version does not know. Call the resource and read the runtimeAAuth-Requirement. Never an error.satisfiable— plan against it.unsatisfiable— skip, with the reason.The third is the point. An agent whose agent token carries no
psclaim cannot completeperson-token,auth-tokenorper-call, and now learns that while planning instead of at a 401.invokereturnskind: 'skipped';find_resourcesandlist_resourcescarryskip_reason.Operation access annotations. Read
x-aauth-access-mode/x-aauth-budgetoff the OpenAPI Operation Object and_meta["aauth.dev/access-mode"]/_meta["aauth.dev/budget"]off an MCP Tool, from the vocabulary document the proxy already fetches for L3. Flattened onto everylist_operations/get_operationsresult asaccess_mode(always — the mode that actually applies) andbudget: true. Sparse; replacing rather than intersecting, so an annotation may LOWER the requirement; advisory, never enforced.session-tokenin an annotation is dropped;budget: trueraises the operation toauth-token.Per-call proposals in R3 -02's shape: the resource builds the proposal from the call's concrete parameters, the token carries only
r3_uri/r3_s256, the agent exchanges and retries. The request init is fixed for the whole invoke flow, so the retry is byte-identical by construction — the resource recovers the proposal by hash and rejects any parameter that differs.Missions:
mission_s256onProxyConfigand per-invoke, forwarded to the person token endpoint. It appears in no auth-token request body — the claim travels inside the tokens.Also
identity.peek(), so a cheap listing tool can annotate what the agent can complete without provoking an enclave signature.This is one coordinated wave
Twelve PRs across twelve repositories implement AAuth -11 and R3 -02, built in parallel worktrees that could not see each other and reconciled in one integration pass.
Merging any one alone breaks the others. This package sits in the middle of the ordering:
hellocoop/proxy-mcpalready tracks@aauth/proxy ^1.0.0on its wave branch. That dependency is unpublished, so proxy-mcp cannot build until this merges and releases.person-tokenlocks agents out. 0.4.0 hard-fails on an unrecognizedaccess_modewithinvalid access_mode person-token.hellocoop/aauth-proxyflips the fleet default toperson-tokenin this same wave. 1.0.0 must publish first.HelloCoop/WalletshipsEd25519means every person token this package obtains is signedEdDSAand rejected by every resource in the wave.The twelve PRs
hellocoop/mockindickhardt/AAuthaauth-dev/packages-js@aauth/protocol1.0.0 (new),@aauth/agent3.0.0,@aauth/resource2.0.0, five more.aauth-dev/proxy@aauth/proxy1.0.0 — the agent-proxy core.hellocoop/aauth-proxyhellocoop/proxy-mcp@aauth/proxy^1.0.0; AP metadata cleanup.aauth-dev/notesaauth-dev/whoamiaauth-dev/web-agent-demo(playground)202deferred path.aauth-dev/playground-popupaauth-dev/registryaauth-dev/wwwRelated and already open:
aauth-dev/explorer#5 reworks the explorer for -11 / R3 -02.Background:
AAUTH-11-INTEGRATION.md(the integration ledger) andAAUTH-11-PACKAGE-CONTRACT.md(the pinned interface contract the parallel work packages were built against).Ordering constraints
@aauth/protocol1.0.0 needs a manual first publish — npm will not register a trusted publisher for a name that does not yet exist in the registry.@aauth/proxy1.0.0 must publish before the fleet servesperson-token. See above.HelloCoop/Walletsvr/issuer/sign.js:32must shipEd25519in the same window. It readsconst alg = useEdDSA ? 'EdDSA' : 'RS256'withuseEdDSA = isAAuthType(typ), so everyaa-auth+jwtandaa-person+jwtperson.hello.coopissues today carriesalg: EdDSA. RS256 stays for OIDC.Release is
publish.yml, triggered on av*.*.*tag orworkflow_dispatch. No tag is pushed by this PR.Not covered by tests
expires_atclamp.mission_endpointis unimplemented by agreement across the wave. The reference PS accepts any value as amission_s256, so there is no mission to look up and no clamp is ever exercised. Tests carry a mission throughout, which proves it is plumbed, not that it is enforced.upstream_token/ call chaining — the latter is rejected at both PS endpoints and deferred fleet-wide.revocation_endpoint.agent.tsandidentity.tsdeliberately do not import@aauth/local-keysso the core entry point stays bundleable for workerd; that constraint is not verified by CI.What a reviewer should check
src/access-mode.tsis a deliberate local copy of@aauth/protocol'splanAccessMode, kept because@aauth/protocolis unpublished. It should become an import once that ships — but not inagent.tsoridentity.ts, which must stay free oflocal-keysfor workerd bundling.alg-strip beforecrypto.subtle.importKeyis intact. EmitEd25519, strip before import.One item from the integration ledger that is not fixed here
src/identity-local.ts's privatewithFullySpecifiedAlgstill opens with:That early return means a JWK whose
kty/crvdisagrees with itsalg— sayalg: 'ES256'on anOKP/Ed25519key — passes through unchecked, which -10 makes a verifier MUST reject. The ledger's plan was to swap this for@aauth/local-keys's stricter 2.0.0 export;identity-local.tsalready importslocal-keys, so the swap is available and does not touch the workerd-bundled path. Left for a follow-up rather than changed silently in a release PR.