Skip to content

The connection ceremony - #13

Merged
dickhardt merged 1 commit into
mainfrom
feat/connection-ceremony
Sep 9, 2026
Merged

dickhardt merged 1 commit into
mainfrom
feat/connection-ceremony

Conversation

@dickhardt

Copy link
Copy Markdown
Member

Found while building the MCP end-to-end suite in aauth-proxy (hellocoop/aauth-proxy test/e2e-compose): connect_resource reported connected against mockin while no upstream OAuth exchange had happened at all.

mockin has no notion of the connection ceremony. A connection-only resource token — no scope, no r3_*, an interaction_code naming the pending record the resource is holding — was read as scope: '' and got an auth token. Hellō's Wallet shipped the ceremony in Wallet#4269; the reference PS never learned it, which left the entire feature testable only against beta Wallet.

This is Q7 in ONBOARDING-PLAN-2.md ("mockin learns the connection-only ceremony — yes if cheap"), deferred in session 2. It was not cheap, but it is the thing standing between us and an e2e that means anything.

What the ceremony is

The PS issues nothing. It holds a pending record, sends the person to the resource's own published interaction_endpoint with the code the resource is holding, and the record terminates when the resource bounces the browser back — connection_established, no auth token. The resource finishing is the event, not the person arriving at the PS.

Changes

File
verify-resource-token.js absent scope (distinct from '' — an existing R3 test mints an empty one) plus interaction_code is a connection; r3 without scope rejected; the retired nested interaction object rejected
token.js a connection creates a kind: 'connection' pending and answers 202 requirement=interaction; code="…" — code only, no url=, because the recipient composes it from interaction_endpoint (which must be published, and https)
consent.js a connection is not the PS's to approve — redirects to {interaction_endpoint}?code={interaction_code}&callback={ISSUER}/aauth/bounce/{code}
consent.js new GET /aauth/bounce/:code — the resource returning the browser, which terminates the record. ?error= fails it instead
pending.js an approved connection polls 200 { status: 'connection_established' }

The callback contract matches what the fleet already implements: core/oauth.ts reads ?callback= on /oauth/start and redirects there when the upstream OAuth completes.

Eight tests in test/aauth/connection.spec.js cover the 202 shape, the redirect target, termination on the bounce rather than on consent, the error bounce, the two rejections, empty-vs-absent scope, and the capability gate.

241 passing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CRdau7tgZa1tPVzUdyNrHc

A resource that fronts an upstream the person must link mints a
CONNECTION-ONLY resource token: no `scope`, no `r3_*`, and an
`interaction_code` naming the pending record it is holding. The PS issues
nothing for it. It puts the person in front of the resource's own published
`interaction_endpoint` with that code and a callback, and the record
terminates when the resource bounces the browser back —
`connection_established`, and no auth token.

mockin had none of it. A scope-less token was read as `scope: ''` and got an
auth token, so an agent driving a connect against mockin was told it had
connected while no upstream OAuth had happened at all. Hellō's Wallet shipped
the ceremony in #4269; the reference PS never learned it, which left the whole
feature testable only against beta.

- `verify-resource-token.js`: absent `scope` (distinct from `''` — an existing
  R3 test mints an empty one) plus `interaction_code` is a connection; `r3`
  without scope is rejected; the retired nested `interaction` object is
  rejected.
- `token.js`: a connection creates a `kind: 'connection'` pending and answers
  202 `requirement=interaction; code="…"` — code only, no `url=`, because the
  recipient composes it from the resource's `interaction_endpoint` (which must
  be published, and https).
- `consent.js`: a connection is not the PS's to approve — it redirects to
  `{interaction_endpoint}?code={interaction_code}&callback={ISSUER}/aauth/bounce/{code}`.
- `GET /aauth/bounce/:code`: the resource returning the browser, which is what
  terminates the record. `?error=` fails it instead.
- `pending.js`: an approved connection polls `200 { status:
  'connection_established' }`.

Eight tests in `test/aauth/connection.spec.js`. 241 passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRdau7tgZa1tPVzUdyNrHc
@dickhardt
dickhardt merged commit 4f309d3 into main Sep 9, 2026
2 checks passed
@dickhardt
dickhardt deleted the feat/connection-ceremony branch September 9, 2026 15:46
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