Skip to content

fix: draft-11 live test, person-bound demo roles, spec-compliant interaction chaining - #55

Merged
dasiths merged 5 commits into
mainfrom
fix/live-whoami-draft11
Oct 1, 2026
Merged

dasiths merged 5 commits into
mainfrom
fix/live-whoami-draft11

Conversation

@dasiths

@dasiths dasiths commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

make live (LiveWhoAmITest against whoami.aauth.dev + person.hello.coop) had drifted from draft-11 and never reached the Person Server.

Changes

  • Mode 2b expects requirement=person-token. Under draft-11 a resource answers a scoped request carrying only an agent token with person-token. It issues auth-token + resource token only after it sees a person token. The old check expected auth-token directly, so 2b failed and Mode 3 was skipped. IsAuthTokenChallenge is replaced by IsPersonTokenChallenge, with tests updated to match.
  • Live egress policy. person.hello.coop publishes jwks_uri on issuer.hello.coop. The sample now uses its own AAuthEgressPolicy with crossOriginJwks: [(person.hello.coop, issuer.hello.coop)] instead of the shared localhost-only SampleEgress.
  • Mode 3 failure reporting. TokenVerificationException is caught and reported, so the run prints the PASS/FAIL summary instead of crashing with an unhandled exception.
  • samples/README.md and the Mode 3 flow text now describe the draft-11 sequence.
  • Regenerated DocumentationInventory.snapshot.md for the README change, and added AGENTS.md describing when and how to regenerate it.

Live result

Mode 1:  PASS
Mode 2a: PASS
Mode 2b: PASS
Mode 3:  PASS  — whoami returns identity claims from person.hello.coop

Mode 3 first failed because whoami's resource token lacked the draft-11 presented_jti claim (aauth-dev/whoami#5). That is now fixed upstream, and the full three-party flow passes against the live deployments with no SDK change.

Validation

  • dotnet test tests/AAuth.Tests --filter LiveInteropValidationTests: 21 passed
  • make live against whoami.aauth.dev + person.hello.coop: all four modes pass

Console samples: role binding and consent display

Running every console sample against make demo showed that AgentConsole's RBAC (/events/admin) and payment (/wallet/charge) calls always failed. The PS and the stub AS granted demo roles only to the hard-coded agent aauth:demo@ap.example, but MockAgentProvider now assigns IDs like aauth:agent-<hash>@localhost, so a real agent could never match.

Roles now belong to the person (spec-aligned). roles and groups are identity claims about the user (RFC 9068/SCIM).

  • MockPersonServer asserts calendar.owner, wallet.payer and demo-users for its demo person whichever agent asks. MockPersonServer:GuestPerson=true acts for a person with no roles, to show role-based denial.
  • The Federated stub AS no longer derives roles from the agent ID. For wallet.charge it asks the PS for the person's roles (requirement=claims) and denies if wallet.payer is missing.
  • Keycloak tests decide by the logged-in user, like the real realm.
  • A denied deferred request keeps the server's reason end to end (AS → PS → agent), so the agent sees "scope 'wallet.charge' requires the 'wallet.payer' role" rather than a generic "user denied".

Display fixes

  • The PS dashboard and consent pages showed calendar.read (the PS default scope) for R3 requests, which carry r3_uri instead of scope. They now show the R3 request.
  • EventAgent printed an extra empty Consent: line.

Verified live against make demo with an AP-assigned agent ID:

  • /events/admin → 200, with roles in the token.
  • /wallet/charge → 200: PS consent → AS claims push → AS consent.
  • With GuestPerson=true: /events/admin → 403, and /wallet/charge is denied with the policy reason.
  • MissionAgent and EventAgent (public and protected) still pass.

All suites pass: 3,695 tests.

Interaction chaining: poll the downstream instead of re-sending it

Concierge aborted its downstream token exchange on the first 202 and re-ran the whole chain on every caller poll. Each re-run sent a new PS token request, so one AgentConsole run left ~80 duplicate consent requests on the PS dashboard. That was the SDK's documented pattern.

Draft-11 §Interaction Chaining says the intermediary "completes the original request and returns the result at its pending URL". §Polling with GET says that after a 202 the agent "switches to GET … and does not resend the original request". So the intermediary must keep its downstream request and poll it.

  • SDK: AAuthChainedOperation<TResult> keeps the downstream exchange alive in the background and lets the SDK poll the downstream Location with GET. Also adds AAuthRequestOptions.UpstreamToken, AAuthChainedInteractions.Rekey/PollingFailure, and a Park(Interaction) overload. A request with its own interaction handler no longer joins another request's in-flight token acquisition.
  • Concierge: polls read the operation's state and never re-run the chain. Entries re-key when the downstream moves to a new interaction (PS → AS); earlier codes stay valid; DELETE cancels.
  • Docs: docs/advanced/interaction-chaining.md, the SampleApp CallChain snippet and GuidedTour text.

Verified live: Agent → Concierge → Calendar left exactly one pending PS request per hop through ~30 s of agent polling, then returned 200. Agent → Concierge → Wallet (PS + AS + chained consent) returned 200 with no pending requests left. A denied downstream hop returned 403 denied with the PS's reason. A code review pass found three issues (same-token concurrent requests hanging, a snapshot race, far-future expiry throwing); all are fixed with tests.

Tests: 3,711 pass.

dasiths and others added 3 commits October 1, 2026 16:31
- Mode 2b now expects requirement=person-token for a scoped request carrying
  only an agent token (draft-11 issues the resource token after a person
  token), so Mode 3 is no longer skipped.
- Use a live egress policy admitting person.hello.coop's cross-origin
  jwks_uri (issuer.hello.coop) instead of the localhost-only SampleEgress.
- Report token verification failures in Mode 3 instead of crashing.

Mode 3 remains blocked upstream: whoami.aauth.dev still emits
person_token_jti instead of the required presented_jti
(aauth-dev/whoami#5).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
samples/README.md changed in the previous commit, so the frozen docs
inventory hash was stale. AGENTS.md records which files the inventory
covers and how to regenerate the snapshot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Roles are identity claims about the person (RFC 9068/SCIM), so the demo
roles no longer depend on a hard-coded agent ID that the Agent Provider
can no longer assign (IDs are now aauth:agent-<hash>@host):

- MockPersonServer asserts calendar.owner, wallet.payer and demo-users
  for its demo person whichever agent asks.
  MockPersonServer:GuestPerson=true acts for a person with no roles, to
  exercise role-based denial.
- The Federated stub AS no longer derives roles from the agent ID. For
  wallet.charge it asks the PS for the person's roles via
  requirement=claims, and denies when the pushed claims lack
  wallet.payer.
- Keycloak tests now decide by the logged-in user, like the real realm.

A denied deferred request now keeps the server's reason: the PS->AS
client keeps the AS problem detail, the PS relays it on its own
`denied`, and agents include it in AAuthInteractionDeniedException.

Display fixes:
- PS dashboard and consent pages show an R3 request's r3_uri instead
  of the PS default scope (calendar.read), which R3 tokens don't carry.
- EventAgent prints each consent URL once instead of an extra empty
  "Consent:" line.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dasiths dasiths changed the title fix(samples): align LiveWhoAmITest with draft-11 person-token flow fix(samples): draft-11 live test, person-bound demo roles, consent display fixes Oct 1, 2026
dasiths and others added 2 commits October 1, 2026 18:08
- AgentConsole prints its AP-assigned agent ID. /admin/consent keys
  consent on that ID plus the agent's key thumbprint, so the docs now pass
  both instead of the old aauth:demo@ap.example label.
- An explicit trailing "/" now targets the resource root. Before, the
  documented http://localhost:5200 Concierge call was sent to /events and
  returned 404.
- PS flows now follow a resource's 202 + requirement=interaction, which
  the Concierge uses to relay downstream consent.
- The Concierge README documents the two consent hops. Pre-granting the
  second hop isn't practical because the Concierge generates a new key on
  every start.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…g them

Interaction chaining aborted the intermediary's downstream token exchange on
the first 202 and re-ran the whole chain on every caller poll. Each re-run
sent a new token request to the PS, so one AgentConsole run through the
Concierge left ~80 duplicate consent requests.

Draft-11 §Interaction Chaining: the intermediary "completes the original
request and returns the result at its pending URL" once it obtains the
downstream token. §Polling with GET: after a 202 the agent "switches to GET
for all subsequent requests to the Location URL and does not resend the
original request".

- AAuthChainedOperation<TResult> runs the downstream work in the background.
  Its per-request interaction handler records the downstream interaction and
  returns normally, so the SDK keeps polling the downstream Location with GET.
  It publishes versioned interaction snapshots, cancels on expiry, host
  shutdown or Cancel(), and observes failures nobody awaits.
- AAuthRequestOptions.UpstreamToken passes the upstream token per request, so
  downstream work no longer needs the inbound HttpContext.
- AAuthChainedInteractions: Park(Interaction), Rekey for a new downstream step
  (new code, same id/Location), and PollingFailure mapping downstream
  outcomes to §Polling Error Codes.
- A request with its own interaction handler no longer joins another
  request's in-flight token acquisition, which would never call its handler.
- Concierge (/, /mission, /wallet): polls read the operation's state and never
  re-run the chain. Entries re-key atomically, earlier codes stay valid, and
  DELETE cancels the downstream work.
- Docs, SampleApp CallChain snippet and GuidedTour text describe the new flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dasiths dasiths changed the title fix(samples): draft-11 live test, person-bound demo roles, consent display fixes fix: draft-11 live test, person-bound demo roles, spec-compliant interaction chaining Oct 1, 2026
@dasiths
dasiths merged commit 2900019 into main Oct 1, 2026
2 checks passed
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