# Portable authorization and fragments — implementation foundation

Status: local reversible implementation, 2026-10-11. Human review and production sign-off remain pending. This service does not expose owner-authorized demonstration routes as authenticated federation routes.

`packages/federation/index.mjs` exports `FederationService`, `FederationFault`, and an in-memory reference transaction store. Runtime integrations supply a serializable, rollback-capable `store.transaction(operation)` with transaction `get`, `set`, `delete`, and `list(prefix)` returning `{key,value}` records. Schema, resource, request, code, token, preference, and grant records share that transaction domain. A disconnected KV adapter is insufficient. Code consumption and bearer issuance occur atomically. Bearer and code records are indexed by domain-separated SHA-256 hashes, never plaintext bearer/code values.

The constructor accepts `{store, crypto, clock, issuer, clients, schemaFetcher, schemaHosts, authenticateOwner, dispatchCapability}`. Issuer is an HTTPS origin. Static operator-reviewed clients have `{id,name,origin,redirectUris}`: ID is HTTPS, origin is exact HTTPS, redirects are an exact preregistered allowlist on that origin. This implementation does not claim to verify application domain control or implement dynamic registration. Configuration provenance requires operator review.

`authenticateOwner(authority,transaction)` must verify the current authenticated, active provider account and return `{subject}` on the configured issuer. The argument is an injected runtime authority such as a live server session, never a caller-supplied subject string. Missing authentication fails closed. HTTP integration separately performs exact Origin, CSRF, framing protection, secure cookie handling, and session verification. Authentication alone creates no grants.

`schemaFetcher(url)` is a trusted injected runtime capability, not an ambient network fetch. `schemaHosts` is an exact host allowlist. The fetcher must impose response/time limits, reject redirects, validate public addresses after every DNS resolution, and return parsed JSON. An operator-local immutable schema registry is also valid. The service bounds canonical JSON to 64 KiB, accepts at most 32 fields, rejects unknown/executable properties, restricts primitive/cardinality/range values, and pins canonical SHA-256 digests. String/URL fields require explicit `maxLength`; numeric fields require finite `minimum`/`maximum`. Existing URLs cannot change. No schema URL triggers network access unless the runtime deliberately supplies a trusted fetcher.

## Runtime API

- `beginAuthorization({clientId,redirectUri,challenge,challengeMethod:'S256',state,requests})` returns `{requestId,view}`. At most eight fragment requests carry `{fragment,required,purpose,fields,methods?}`. `methods` is `{resourceField:{contractURL:[method]}}`. The request expires in ten minutes.
- `inspect(requestId,authority?)` returns `{requestId,issuer,client,requests,authenticated,expiresAt}`. Without owner authority it includes public schema/request information but no preferences, resources, or other approved apps. Authenticated requests add `values`, `revision`, `resources`, and `affectedApps` to each request.
- `registerResource(authority,{id,label,contracts,provider?,alias?,limits?})` stores owner-scoped resource metadata. `contracts` maps exact contract URLs to implemented method names. The optional `@profile` alias resolves only after bearer authorization. This method records capabilities; a working dispatcher/adaptor must verify actual conformance before registering them.
- `configure(authority,{fragment,values})` validates owner values and resource ownership/contracts, increments an opaque revision, and permanently narrows existing grants when resource assignments are removed. Adding or restoring assignments never broadens existing grants.
- `approve(authority,{requestId,approved})` accepts selections `{fragment,revision,fields,resources?,methods?}` and returns `{redirectUri}` with a sixty-second one-use code, original state, and issuer. Resource selections are `{field:[resourceURL]}`. Selected methods use the request shape above. Required requests cannot silently omit requested fields. The displayed preference revision must still match. `{requestId,denied:true}` cancels with `access_denied` on the registered callback.
- `exchange({code,verifier,clientId,redirectUri})` returns `{access_token,token_type:'Bearer',expires_in:300,issuer,subject,client_id,audience,grants}`. Exact client, redirect, S256, code expiry and atomic one-use checks apply. No refresh token or unsigned ID-token claim is provided.
- `revoke(authority,grantId)` immediately blocks the grant.
- `rpc(bearer,{version:'0',tx,resource,contract,method,args})` resolves owner aliases and checks token audience/issuer/expiry, client/subject, live grants, current assignments, permanently approved resources, exact contract, and implemented/requested/approved methods. Request transaction IDs must be UUIDs. Limits are 128 KiB for RPC requests/results. `@preferences` supports only an approved fragment's `read`; descriptors expose approved implemented methods. Unapproved assignments produce missing resource values and `requiresConsent` rather than identifiers or credentials.

`dispatchCapability({transaction,subject,clientId,resource,contract,method,args,tx})` receives only previously authorized requests. It must perform contract argument/result validation, resource isolation, durable idempotency, atomic object preconditions, and indeterminate-outcome handling. This foundation is an authorization gateway, not a claimed object-store implementation. It does not manufacture successful profile/object-storage results. Runtime integration must provide actual contract implementations and independent adapter conformance before claiming the baseline release gate. Holding a local store transaction does not itself make an external provider write atomic.

## Consent integration

Serve `apps/web/authorize.html` for `/oauth/authorize?request_id=...`. Its dodo UI uses genuine ngin `Action`/`Query` resources. GET `/api/session` returns `csrfToken`; GET `/api/oauth/requests/:id` returns `inspect`'s owner-specific view. POST `/api/oauth/requests/:id/decision` forwards the CSRF-protected decision. POST `/api/preferences` forwards an authenticated preference configuration. The HTTP layer must validate the request ID against the path and supply authority itself.

The UI preserves the request ID during sign-in, grants nothing automatically, shows registered app origin, developer purpose as unverified plain text, schema URL/publisher, every shared field value, selected resources/providers/methods, destructive-method labels, and already affected apps. Optional fragments can be declined before configuration. Saving preferences explains effects on approved apps and requires a subsequent explicit decision. The owner can cancel every connection. A stale revision requires fresh review. Resource provisioning and comprehensive persistent account settings remain runtime/account-UI integration work.

## Verification and remaining gates

Independent relying-party fixture computes PKCE with Node's separate SHA-256 implementation. Tests cover atomic parallel exchanges, wrong verifier, redirect substitution, expiry, state/issuer, anonymous inspection, unauthenticated approval, stale consent, live revoke, additions/removals/restoration, and immutable schema substitution. A happy-dom fixture exercises real dodo/ngin, sign-in gate, optional decline, plain-text metadata, affected apps and CSRF decisions. These are local conformance foundations, not two deployed independent provider implementations or a reviewed production authorization server.

Open release gates include trusted network-fetch adapter, authenticated HTTP routing, production client registration provenance, independent end-to-end provider/RP interoperability, complete object/profile dispatch conformance, durable journal, browser security headers, quota/audit/retention policy, accessible full-browser verification, and human security review. Reviewer: ___ Date: ___ Outcome: ___ .

## Shared runtime and hosted adapter integration

`FederationController` composes the service from `{metadata,auth,issuer,clients,schemaFetcher,schemaHosts,dispatcher,crypto?,clock?,allowInsecureLocalhost?,enabled?}`. Authentication is `auth.owner(context,{mutation})`; it must call the real `IdentityService.authorized(context,mutation)` to verify active account, current hashed session, expiry, exact origin and mutation CSRF. It returns the canonical entity subject. Controller owner methods take `(context,input)`; `inspect(context,requestId)` permits anonymous public metadata only. `federationMetadata(native)` adapts native `get/put/delete/list(Map)` transactions; `federationRemote(stub)` preserves typed errors through an explicit `{ok,result|error}` RPC envelope.

`handleFederation(request,{controller,issuer,clients,allowInsecureLocalhost,assets,enabled})` returns a response or null for unrelated paths. It implements configured-client CORS, bounded UTF-8/JSON and standard form token exchange, owner cookie extraction, exact provider Origin and CSRF on consent/configuration/revoke, path-bound decisions, authorization assets with denial of framing, and no-cache responses. A disabled/unconfigured controller returns 503 for discovery and authorization; a manifest never advertises a working service before configuration. JWKS contains no keys because tokens are opaque; no unsigned JWT identity claims are supplied. Explicit localhost HTTP configuration is restricted to literal `localhost`; remote HTTP remains rejected.

The controller journal is keyed by entity/client/UUID and fingerprints the canonical resolved resource, contract, method and args. Live grant authorization occurs before every cached outcome. Completed outcomes persist for 24 hours; ambiguous write failures persist as `indeterminate` and cannot automatically execute again. Adapters must declare verified `transactional:true` for local transactional mutations or `durableJournal:true` for external operations that reserve in their own durable account journal before side effects. An ordinary dispatcher function may serve reads but cannot execute mutations. A local metadata transaction cannot preserve a reservation across a process crash while an external effect is outstanding; the external/account journal is therefore mandatory. Runtime configuration must enable this assertion only after actual adapter tests.

`apps/worker/federation.mjs` is the only Cloudflare-specific module. `FederationObject` uses native storage transactions and validates owner contexts through the actual shared `IdentityService.authorized` method and the identity directory. Its operator configuration uses `FEDERATION_ENABLED`, JSON `FEDERATION_CLIENTS`, JSON immutable `FEDERATION_SCHEMAS`, optional `FEDERATION_SCHEMA_HOSTS`, and an explicitly enabled `FEDERATION_DISPATCHER_MODE=account-journal-v1` to invoke `AccountObject.federationRPC({subject,clientId,resource,contract,method,args,tx})`. The account dispatcher must return a raw contract result. Credentials and caller-supplied subject shortcuts never replace owner verification. Registry-only schemas trigger no network requests.

Runtime HTTP tests use a separate relying-party SHA-256 implementation and real IdentityService session hashes/CSRF checks. They exercise the complete configured request/consent/code/token/RPC flow, replay/collision, false Origin, invalid CSRF, suspended-owner inspection, duplicate token parameters, disabled discovery, explicit localhost issuer restrictions, and durable ambiguous-outcome suppression. Production publishing remains separately gated.
