diff --git a/drafts/agent-surface.md b/drafts/agent-surface.md index bf4e2a4..2574c6f 100644 --- a/drafts/agent-surface.md +++ b/drafts/agent-surface.md @@ -595,24 +595,39 @@ indicators. Relevant standards: - OAuth 2.0: +- OAuth 2.0 Proof Key for Code Exchange: + - OAuth 2.0 Token Revocation: - OAuth 2.0 Token Introspection: - OAuth 2.0 Token Exchange: - OAuth 2.0 Resource Indicators: +- OAuth 2.0 Rich Authorization Requests: + +- OAuth 2.0 Pushed Authorization Requests: + +- Best Current Practice for OAuth 2.0 Security: + Agent Surface Protocol uses the term **grant** for the semantic object, even when an OAuth access token is the transport representation. -If OAuth is used, `agent_delegation` MAY be represented as an OAuth extension -grant type. `agent_delegation` is not a standard OAuth grant type in this draft. +The OAuth Grant Lifecycle Profile in this draft uses standard OAuth flows and +extension parameters; it does not define an `agent_delegation` OAuth grant type. +Implementations MAY use: -Implementations MAY also use existing OAuth flows, including: - -- Authorization Code with PKCE and additional agent delegation parameters. +- Authorization Code with PKCE and an Agent Grant + `authorization_details` object. - OAuth Token Exchange to exchange a user-authorized credential for an agent-scoped grant credential. - Resource Indicators to constrain the resource server or app surface. +The collision-resistant authorization-details type identifier defined by this +draft is: + +```text +https://github.com/0al-spec/agent-surface/authorization-details/agent-grant +``` + ### Agent Passport [Agent Passport](https://github.com/0al-spec/agent-passport) provides agent @@ -849,7 +864,13 @@ surface discoverable. "token_url": "https://example.com/oauth/token", "introspection_url": "https://example.com/oauth/introspect", "revocation_url": "https://example.com/oauth/revoke", - "grant_types": ["agent_delegation"], + "grant_types_supported": [ + "authorization_code", + "urn:ietf:params:oauth:grant-type:token-exchange" + ], + "authorization_details_types_supported": [ + "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant" + ], "token_binding": ["runtime", "agent_passport_hash"], "pkce_required": true }, @@ -1073,6 +1094,10 @@ Grant constraints filter events the same way they filter actions: a grant constrained to one repository SHOULD NOT receive events about other repositories, even when the event scope matches. +`grant.revoked` is an application control event rather than an event authorized +by the revoked grant. Its payload, authentication, and processing requirements +are defined in the OAuth Grant Revocation Profile. + Event delivery semantics — transport, ordering, acknowledgement, and replay — are not defined in this draft; see Open Questions. @@ -1188,6 +1213,8 @@ surface, scopes, and caveats. "issuer": "https://code.example.com", "surface_version": "2026-06-25" }, + "locations": ["https://code.example.com/agent-actions"], + "actions": ["pull_request.get", "comment.create"], "scopes": [ "pull_request.read", "pull_request.comment" @@ -1203,11 +1230,13 @@ surface, scopes, and caveats. "mode": "deny" } }, + "credential_profile": "proof_bound", "credential_binding": { "method": "dpop", "runtime_id": "application_runtime_456", "agent_id": "local_agent_789", - "passport_hash": "sha256:..." + "passport_hash": "sha256:...", + "jkt": "" }, "audit": { "local_receipt": "required", @@ -1216,6 +1245,16 @@ surface, scopes, and caveats. } ``` +This object is the authoritative Agent Grant wire shape. `locations` restricts +the action endpoints at which the grant may be used, and `actions`, when +present, is an authoritative allow-list of Agent Surface action identifiers. +OAuth `authorization_details` uses this same shape with the additional RFC 9396 +`type` discriminator; it does not define aliases for Grant Object fields. +`credential_binding` is authorization-server output and MUST repeat the bound +runtime, agent, and passport tuple. A DPoP binding MUST additionally contain +`jkt`; an mTLS binding MUST instead contain `x5t#S256`. Those values use the +same encoding and semantics as the corresponding standard `cnf` members. + Numeric caveats need defined accounting. In this draft, `max_actions` counts side-effecting action requests accepted by the application under the grant. Reads and denied requests do not consume the budget, and neither do idempotent @@ -1234,14 +1273,14 @@ discover surface -> verify manifest -> choose agent -> verify Agent Passport - -> request grant + -> request grant with authorization_details -> user consent - -> issue grant + -> issue or exchange Grant Credential -> store grant in runtime -> start session - -> mediate actions + -> introspect / verify and mediate actions -> issue receipts - -> expire / revoke / renew + -> expire / revoke / notify / renew ``` ### Grant Issuance Models @@ -1312,6 +1351,410 @@ Cons: semantics, and stronger interop work. - Too large for the first MVP. +### OAuth Grant Lifecycle Profile + +This profile maps an Agent Grant onto OAuth Rich Authorization Requests, Token +Exchange, Token Introspection, and Token Revocation. It applies when the Agent +Surface Manifest declares OAuth endpoints and the Agent Grant authorization +details type defined by this draft. + +The authorization server and resource server MAY be operated by the same +application, but they retain their OAuth roles. The authorization server issues +and manages Grant Credentials. The application action endpoint is the resource +server and continues to enforce the semantic Agent Grant for every action. + +#### Rich Authorization Request Profile + +An Agent Grant authorization request MUST use the RFC 9396 +`authorization_details` parameter encoded as a JSON array containing exactly +one object whose `type` is: + +```text +https://github.com/0al-spec/agent-surface/authorization-details/agent-grant +``` + +The authorization server metadata MUST list this value in +`authorization_details_types_supported`. The Agent Surface Manifest `auth` +object MUST mirror that value and the standard `grant_types_supported` values +used by the deployment; conflicting metadata makes the OAuth profile invalid. + +Example, shown decoded from its form-encoded authorization request parameter: + +```json +[ + { + "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant", + "locations": ["https://code.example.com/agent-actions"], + "actions": ["pull_request.get", "comment.create"], + "delegate": { + "runtime": "application_runtime_456", + "agent": "local_agent_789", + "passport_ref": "agent-passport://local-agent", + "passport_hash": "sha256:..." + }, + "resource_server": { + "app_id": "code.example.com", + "issuer": "https://code.example.com", + "surface_version": "2026-06-25" + }, + "scopes": ["pull_request.read", "pull_request.comment"], + "constraints": { + "repositories": ["example-org/example-repo"], + "pull_requests": [13], + "expires_at": "2026-06-25T20:00:00Z", + "write_approval": "required", + "max_actions": 20 + }, + "credential_profile": "proof_bound", + "audit": { + "local_receipt": "required", + "app_receipt": "required" + } + } +] +``` + +The Agent Grant authorization details type has the following contract. Except +for the required RFC 9396 `type` discriminator, its field names and semantics +are the authoritative Grant Object wire shape defined above: + +- `type`, `delegate`, `resource_server`, `scopes`, `constraints`, + `credential_profile`, and `audit` are REQUIRED. +- `delegate` MUST contain `runtime`, `agent`, and `passport_hash`; it MAY contain + `passport_ref`. +- `resource_server` MUST contain `app_id`, `issuer`, and `surface_version`. +- `constraints` MUST contain `expires_at`; other fields use the semantics of the + Agent Grant object. +- `credential_profile` MUST be `compatibility_bearer` or `proof_bound` and maps + to the credential profiles defined in this draft. +- RFC 9396 common fields `locations` and `actions` MAY restrict the request to + published action endpoints and Agent Surface action identifiers. When + `actions` is present, the granted value is an authoritative allow-list: every + invoked action MUST be a member. The authorization applies to the product of + the granted actions, locations, scopes, and resource filters; every allowed + combination MUST be published by the surface and semantically compatible. +- `grant_id`, `subject`, and `credential_binding` MUST NOT be supplied by the + client in an authorization request; they are authorization-server output. +- The request MUST NOT supply `subject.user` or another asserted user identity; + the authorization server derives the subject from its authenticated user + session or, at the token endpoint, from the validated `subject_token`. + +The authorization server MUST reject unknown fields, unknown action or scope +values, a mismatched `resource_server.app_id` or +`resource_server.surface_version`, an unverified passport hash, or constraints +that are invalid for the published surface. It MUST use the RFC 9396 +`invalid_authorization_details` error for malformed or unsupported Agent Grant +authorization details. + +Authorization Code use of this profile MUST use PKCE with the `S256` challenge +method. Deployments SHOULD use Pushed Authorization Requests when supported so +the rich grant request is integrity-protected and is not exposed in browser +URLs, history, or intermediary logs. + +The OAuth `scope` request parameter MUST NOT be used in an authorization or +token-exchange request that carries this Agent Grant `authorization_details` +type. The authorization server MUST reject such a request with `invalid_request`. +Independent OAuth authorization therefore requires a separate request and +credential; this profile never silently drops or unions independent scopes. + +In token and introspection responses, the standard OAuth `scope` member MAY be +an exact space-delimited projection of the granted Agent Grant `scopes` for +legacy resource-server integration. If present, it MUST contain exactly that +projection; the granted `authorization_details` remains authoritative. A +resource server MUST reject a credential when the two representations conflict. + +Consent MUST present the application, runtime, agent, passport evidence, +requested actions and scopes, resources, expiration and budgets, approval +requirements, credential profile, data exposure, and receipt requirements. The +user MAY approve a strict subset. The authorization server MUST compare the +requested and approved objects according to Agent Grant semantics rather than +using raw JSON equality, and MUST NOT enrich the result with additional +authority. + +The token response MUST return the granted `authorization_details` as required +by RFC 9396. For this type, the returned object MUST be enriched with the +authoritative `grant_id`, `subject`, delegate binding, effective constraints, +and `credential_binding` assigned by the authorization server. The +authorization server and resource server MUST retain or receive the same +granted object for later action verification and introspection. + +#### OAuth Token Exchange Profile + +A runtime MAY exchange a user-authorized subject token for an Agent Grant +Credential using RFC 8693. The request uses the standard token endpoint and +form-encoded parameters: + +```http +POST /oauth/token HTTP/1.1 +Host: code.example.com +Content-Type: application/x-www-form-urlencoded +DPoP: + +grant_type=urn:ietf:params:oauth:grant-type:token-exchange +&resource=https%3A%2F%2Fcode.example.com%2Fagent-actions +&requested_token_type=urn:ietf:params:oauth:token-type:access_token +&subject_token= +&subject_token_type=urn:ietf:params:oauth:token-type:access_token +&authorization_details= +``` + +The Token Exchange request has these additional ASP requirements: + +- The runtime MUST authenticate to the token endpoint. For a + `proof_bound` request, it MUST authenticate using the key or channel binding + that will identify the bound runtime. +- A DPoP-bound exchange MUST include a `DPoP` HTTP header containing a proof for + the token request, as required by RFC 9449. The authorization server MUST + validate that proof independently of OAuth client authentication and derive + the issued token's `cnf.jkt` and Agent Grant `credential_binding.jkt` from the + proof key. An mTLS-bound exchange instead derives `cnf["x5t#S256"]` and the + corresponding credential binding from the client certificate presented on + the token request; it does not use the example's DPoP header. +- `subject_token` MUST represent the authenticated user's authorization for the + requested application and MUST be valid at the time of exchange. +- `resource` MUST contain exactly the published Agent Surface action resource + URI. An `audience` value MAY additionally name the same logical resource + server but MUST NOT add another target. +- `requested_token_type` MUST be + `urn:ietf:params:oauth:token-type:access_token` for the OAuth Grant Credential + profile in this draft. +- `authorization_details` MUST contain exactly one Agent Grant object of the + type defined above and MUST be semantically equal to or narrower than the + authorization approved by the user. +- If OAuth client authentication does not establish the runtime identity, the + request MUST include an `actor_token` representing the runtime and the + corresponding `actor_token_type`. The authorization server MUST verify it and + bind the output credential to that runtime. For a `proof_bound` request, the + actor token MUST itself be sender-constrained or presented through the same + bound channel authentication. + +The authorization server MUST validate the subject token, runtime identity, +agent and passport binding, resource, requested scopes, constraints, and +credential profile. The exchange MUST NOT increase authority, widen resources, +relax approval or receipt requirements, extend beyond the approved expiration, +or replace `proof_bound` with `compatibility_bearer` without fresh user consent. + +RFC 8693 does not itself create lifecycle linkage between input and output +tokens. This ASP profile does: the authorization server MUST record the source +authorization or parent grant from which the Agent Grant was derived. Revoking +or invalidating that source authority MUST revoke or suspend every derived Agent +Grant unless an independently approved grant replaced it. + +Issuance also MUST preserve cumulative caveats across that derivation graph. +Every accepted action or cost charge MUST atomically consume both the derived +grant's local budget and the authoritative remaining budget of every ancestor +authorization from which it derives. Repeating an exchange therefore cannot +multiply `max_actions`, `max_cost_usd`, or another stateful budget. The +authorization server MUST treat semantically equivalent exchanges with the +same source authorization, client and delegate tuple, target resource, +normalized Agent Grant details, and proof-binding key as idempotent: it MUST +reuse the same `grant_id` and accounting state, although it MAY rotate the +access-token representation. + +Example successful response: + +```json +{ + "access_token": "", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "DPoP", + "expires_in": 1800, + "scope": "pull_request.read pull_request.comment", + "grant_id": "grant_123", + "authorization_details": [ + { + "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant", + "locations": ["https://code.example.com/agent-actions"], + "actions": ["pull_request.get", "comment.create"], + "subject": { + "user": "app-user-7f3a" + }, + "delegate": { + "runtime": "application_runtime_456", + "agent": "local_agent_789", + "passport_ref": "agent-passport://local-agent", + "passport_hash": "sha256:..." + }, + "resource_server": { + "app_id": "code.example.com", + "issuer": "https://code.example.com", + "surface_version": "2026-06-25" + }, + "scopes": ["pull_request.read", "pull_request.comment"], + "constraints": { + "repositories": ["example-org/example-repo"], + "pull_requests": [13], + "expires_at": "2026-06-25T20:00:00Z", + "write_approval": "required", + "max_actions": 20 + }, + "credential_profile": "proof_bound", + "credential_binding": { + "method": "dpop", + "runtime_id": "application_runtime_456", + "agent_id": "local_agent_789", + "passport_hash": "sha256:...", + "jkt": "" + }, + "audit": { + "local_receipt": "required", + "app_receipt": "required" + }, + "grant_id": "grant_123" + } + ] +} +``` + +The response MUST include `access_token`, `issued_token_type`, `token_type`, +`expires_in`, `grant_id`, the exact `scope` projection defined above, and the +granted `authorization_details`. This also satisfies the RFC 8693 requirement to +return `scope` when the issued scope differs from the request. The `token_type` +and method-specific credential confirmation data MUST match the selected +credential profile and the binding established at the token endpoint. A +refresh token SHOULD NOT be issued by default; if one is issued, it MUST +preserve the tuple binding, attenuation, and revocation linkage of the Agent +Grant and follow RFC 9700 refresh-token replay protections. + +Token responses containing a Grant Credential or its authorization details MUST +use `Cache-Control: no-store` and `Pragma: no-cache`. + +#### Grant Introspection Profile + +The manifest `agent_api.grant_introspection_url` MAY identify the same RFC 7662 +endpoint as `auth.introspection_url`. A protected resource or runtime +introspects a Grant Credential using an authenticated RFC 7662 request with the +required `token` parameter and optional `token_type_hint`. The endpoint MUST +authenticate and authorize the caller and disclose only grant data that caller +needs. + +For an inactive, unknown, or undisclosable credential, the response MUST be: + +```json +{ + "active": false +} +``` + +It MUST NOT reveal whether the credential was unknown, expired, revoked, or +outside the caller's authority. + +For an active Grant Credential, the response MUST include the RFC 7662 fields +`active`, `client_id`, `scope`, `token_type`, `exp`, `iat`, `sub`, `aud`, and +`iss`, plus the ASP fields `grant_id`, `resource_server`, `delegate`, +`constraints`, `credential_binding`, and `authorization_details`. An active +proof-bound credential MUST additionally include the method-specific standard +`cnf` confirmation member. The `sub` value SHOULD be a stable app-scoped +pseudonymous user identifier. `client_id` identifies the OAuth client; +`delegate.runtime` is the authoritative ASP runtime binding and MAY differ from +`client_id`. + +The `authorization_details` member MUST contain the granted Agent Grant object, +filtered only to data the authenticated caller may receive. Top-level `sub`, +`grant_id`, `resource_server`, `delegate`, `constraints`, and +`credential_binding` are projections of that object and MUST match it; `sub` +corresponds to `subject.user`. A resource server MUST treat a mismatch as an +invalid grant proof rather than selecting one representation. + +For DPoP, `cnf` MUST contain `jkt` as specified by RFC 9449; for mTLS, it MUST +contain `x5t#S256` as specified by RFC 8705. The confirmation value MUST match +the method-specific value in the Agent Grant `credential_binding`. The resource +server MUST reject a missing or mismatched confirmation member. A Compatibility +Bearer Credential MUST NOT fabricate a `cnf` member. + +```json +{ + "active": true, + "client_id": "application_runtime_456", + "scope": "pull_request.read pull_request.comment", + "token_type": "DPoP", + "exp": 1782417600, + "iat": 1782415800, + "sub": "app-user-7f3a", + "aud": "https://code.example.com/agent-actions", + "iss": "https://code.example.com", + "cnf": { + "jkt": "" + }, + "grant_id": "grant_123", + "resource_server": { + "app_id": "code.example.com", + "issuer": "https://code.example.com", + "surface_version": "2026-06-25" + }, + "delegate": { + "runtime": "application_runtime_456", + "agent": "local_agent_789", + "passport_ref": "agent-passport://local-agent", + "passport_hash": "sha256:..." + }, + "constraints": { + "repositories": ["example-org/example-repo"], + "pull_requests": [13], + "expires_at": "2026-06-25T20:00:00Z", + "write_approval": "required", + "max_actions": 20 + }, + "credential_binding": { + "method": "dpop", + "runtime_id": "application_runtime_456", + "agent_id": "local_agent_789", + "passport_hash": "sha256:...", + "jkt": "" + }, + "authorization_details": [ + { + "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant", + "locations": ["https://code.example.com/agent-actions"], + "actions": ["pull_request.get", "comment.create"], + "subject": { + "user": "app-user-7f3a" + }, + "delegate": { + "runtime": "application_runtime_456", + "agent": "local_agent_789", + "passport_ref": "agent-passport://local-agent", + "passport_hash": "sha256:..." + }, + "resource_server": { + "app_id": "code.example.com", + "issuer": "https://code.example.com", + "surface_version": "2026-06-25" + }, + "scopes": ["pull_request.read", "pull_request.comment"], + "constraints": { + "repositories": ["example-org/example-repo"], + "pull_requests": [13], + "expires_at": "2026-06-25T20:00:00Z", + "write_approval": "required", + "max_actions": 20 + }, + "credential_profile": "proof_bound", + "credential_binding": { + "method": "dpop", + "runtime_id": "application_runtime_456", + "agent_id": "local_agent_789", + "passport_hash": "sha256:...", + "jkt": "" + }, + "audit": { + "local_receipt": "required", + "app_receipt": "required" + }, + "grant_id": "grant_123" + } + ] +} +``` + +The response MUST describe current authoritative state and MUST use +`Cache-Control: no-store`. A resource-server enforcement point that does not +share the authorization server's authoritative grant state MUST introspect on +every action and MUST NOT positively cache `active: true`. This prohibition is +required by this profile's immediate revocation semantics; deployments that +need positive caching must define and advertise a different bounded stale-use +profile rather than claiming conformance to this one. + ### Grant Credentials and Proof An Agent Grant MAY be represented or proven by one of several mechanisms: @@ -1420,6 +1863,9 @@ Applications MUST verify every action against grant state: not rejected by itself - for a proof-bound server session, the session is active, bound to the grant and runtime, and authenticated with the bound key or channel credential +- when the grant contains `actions`, the requested action identifier is a + member of that authoritative allow-list, is served at a granted `location`, + and remains compatible with the granted scopes and resource constraints - scope permits the action - resource constraints permit the target object - expiration has not passed @@ -1719,6 +2165,88 @@ in-app view of active agent grants — comparable to OAuth application management pages — where a grant can be inspected and revoked without going through the runtime. +### OAuth Grant Revocation Profile + +The manifest `agent_api.grant_revocation_url` MAY identify the same endpoint as +`auth.revocation_url`. When it does, a runtime requests revocation using RFC +7009: an authenticated form-encoded `POST` containing the Grant Credential in +the required `token` parameter and, optionally, an `access_token` +`token_type_hint`. + +The endpoint MUST authenticate the runtime client and, for a credential it can +locate, verify that the credential was issued to that client. A successful +request and a request containing an unknown or already invalid credential both +return HTTP 200 with no response body, as required by RFC 7009. The runtime MUST +stop using the credential after that response. An HTTP 503 response means +revocation is not confirmed; the runtime MUST continue treating the credential +as sensitive, MUST NOT initiate new actions with it, and SHOULD retry according +to `Retry-After`. + +For the Agent Grant profile, revoking a Grant Credential through +`grant_revocation_url` revokes the semantic Agent Grant, not only that token. +The application MUST immediately mark the grant inactive, reject every +credential derived from it, invalidate refresh tokens and proof-bound sessions, +and cascade revocation to child or exchanged grants whose authority derives +from it. Revocation initiated through the application's user-facing grant view +MUST produce the same state transition. + +When an active grant changes to revoked and the manifest declares an event +subscription endpoint, the application MUST emit a `grant.revoked` control event +with this minimum envelope: + +```json +{ + "id": "event_01J2ABCDEF", + "type": "grant.revoked", + "occurred_at": "2026-06-25T18:30:00Z", + "issuer": "https://code.example.com", + "audience": "application_runtime_456", + "payload": { + "grant_id": "grant_123", + "app_id": "code.example.com", + "runtime_id": "application_runtime_456", + "agent_id": "local_agent_789", + "passport_hash": "sha256:...", + "revoked_at": "2026-06-25T18:30:00Z", + "effective_at": "2026-06-25T18:30:00Z", + "reason": "user_revoked", + "parent_grant_id": null, + "cascade": true + } +} +``` + +Required event fields are `id`, `type`, `occurred_at`, `issuer`, `audience`, and +`payload`. The payload MUST contain `grant_id`, `app_id`, `runtime_id`, +`agent_id`, `passport_hash`, `revoked_at`, `effective_at`, `reason`, and +`cascade`; `parent_grant_id` is REQUIRED for a child grant and otherwise MAY be +null. Defined reason values are `user_revoked`, `application_revoked`, +`runtime_revoked`, `credential_compromise`, `parent_revoked`, `policy_changed`, +and `superseded`. A runtime MUST still enforce revocation when it receives an +unknown future reason value and MAY preserve that value as opaque audit data. + +The event MUST be delivered over an application-authenticated event channel +bound to the manifest issuer and target runtime. The runtime MUST verify +`issuer`, `audience`, tuple binding, and channel authenticity before acting on +it. Delivery of this control event MUST use event-channel authority independent +of the revoked grant and MUST disclose no more grant data than the target +runtime already possessed. A future signing profile MAY additionally define an +application signature for portable event verification. + +After accepting the event, the runtime MUST atomically mark the grant inactive, +discard cached active introspection state, stop new actions and credential use, +cancel or downgrade affected sessions according to app policy, cascade the +state to locally tracked child grants, and record a runtime receipt. Event +processing is idempotent by `id`; a duplicate event MUST NOT create duplicate +receipts or repeat external side effects. + +The event is notification, not the enforcement mechanism. The application MUST +reject the revoked grant immediately even if delivery is delayed or lost. A +runtime that misses the event learns the inactive state from introspection or a +rejected action. General event ordering, acknowledgement, replay cursor, +retention, and backpressure remain outside this profile and are defined by the +future Event Delivery Semantics work. + ### Grant Revoked If a grant is revoked: @@ -1728,10 +2256,9 @@ If a grant is revoked: - active sessions SHOULD be cancelled or downgraded to read-only according to app policy - receipt generation SHOULD record the revocation event -- the event channel SHOULD deliver `grant.revoked` as a final event before the - app closes the subscription; delivery of this one event MUST NOT itself - require an active grant, or the runtime could never learn about the - revocation through the event channel +- when the manifest declares an event subscription endpoint, the app MUST emit + `grant.revoked` according to the OAuth Grant Revocation Profile before it + closes the subscription ### Runtime Disconnected @@ -2024,6 +2551,21 @@ An application conforms to the Grant-Enforcing profile when it: - supports idempotency for side-effecting actions - supports grant revocation +### OAuth Grant Lifecycle Application + +An application conforms to the OAuth Grant Lifecycle Application profile when +it: + +- satisfies the Grant-Enforcing Application profile +- advertises the Agent Grant authorization-details type and supported standard + OAuth grant types +- validates and returns Agent Grant `authorization_details` according to the + Rich Authorization Request Profile +- implements the OAuth Token Exchange Profile without privilege amplification +- returns the active and inactive Grant Introspection Profile contracts +- implements RFC 7009 semantic grant revocation, derivation cascade, and the + authenticated `grant.revoked` control event when an event endpoint is declared + ### Receipt-Producing Application An application conforms to the Receipt-Producing profile when it: @@ -2061,6 +2603,8 @@ An application runtime conforms to this profile when it: models, and ungranted secondary runtimes - treats a separately granted child runtime as its own controlling runtime and preserves parent linkage, attenuation, and cascade revocation +- implements RAR, Token Exchange, introspection, and revocation processing when + using the OAuth Grant Lifecycle Application profile - implements the Proof-Bound Credential Profile when the application requires the Proof-Bound Grant-Enforcing Application profile - enforces local policy and approval rules @@ -2112,24 +2656,26 @@ To support Agent Surface Protocol, the next slices are: 2. Application runtime discovers and validates the surface. 3. User chooses "Connect my local agent". 4. Runtime verifies the selected agent's Agent Passport. -5. Runtime shows consent: +5. Runtime sends an Agent Grant `authorization_details` request. +6. The app authorization server shows consent: - app: code.example.com - agent: local-agent - scopes: pull_request.read, pull_request.comment - repository: example-org/example-repo - duration: 2 hours - writes: require approval -6. User approves. -7. App issues grant_123 and a grant credential. -8. Runtime stores grant_123 and the grant credential. -9. App starts a pull-request review session. -10. Agent reads typed PR context through runtime-mediated resources. -11. Agent proposes a review comment. -12. User or app approves the write. -13. Runtime sends comment.create with an idempotency key and grant credential. -14. App verifies grant and writes the comment. -15. Runtime and app issue linked receipts. -16. User revokes grant or grant expires. +7. User approves a subset or the complete request. +8. App issues or token-exchanges grant_123 and its bound Grant Credential. +9. Runtime stores the authoritative granted details and credential. +10. App starts a pull-request review session. +11. Agent reads typed PR context through runtime-mediated resources. +12. Agent proposes a review comment. +13. User or app approves the write. +14. Runtime sends comment.create with an idempotency key and Grant Credential. +15. App verifies or introspects current grant state and writes the comment. +16. Runtime and app issue linked receipts. +17. User revokes grant_123; the app rejects it immediately. +18. App emits authenticated `grant.revoked`; runtime stops affected work. ``` ## Open Questions @@ -2168,6 +2714,8 @@ To support Agent Surface Protocol, the next slices are: - OAuth 2.0: +- OAuth 2.0 Proof Key for Code Exchange: + - OAuth 2.0 Token Revocation: - OAuth 2.0 Token Introspection: @@ -2176,6 +2724,12 @@ To support Agent Surface Protocol, the next slices are: - OAuth 2.0 Resource Indicators: +- OAuth 2.0 Rich Authorization Requests: + +- OAuth 2.0 Pushed Authorization Requests: + +- Best Current Practice for OAuth 2.0 Security: + - OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens: diff --git a/review/agent-surface-rfc-review.html b/review/agent-surface-rfc-review.html index 07e80c0..1be17ae 100644 --- a/review/agent-surface-rfc-review.html +++ b/review/agent-surface-rfc-review.html @@ -633,22 +633,35 @@

OAuth

Relevant standards:

Agent Surface Protocol uses the term grant for the semantic object, even when an OAuth access token is the transport representation.

-

If OAuth is used, agent_delegation MAY be represented as an OAuth extension -grant type. agent_delegation is not a standard OAuth grant type in this draft.

-

Implementations MAY also use existing OAuth flows, including:

+

The OAuth Grant Lifecycle Profile in this draft uses standard OAuth flows and +extension parameters; it does not define an agent_delegation OAuth grant type. +Implementations MAY use:

    -
  • Authorization Code with PKCE and additional agent delegation parameters.
  • +
  • Authorization Code with PKCE and an Agent Grant +authorization_details object.
  • OAuth Token Exchange to exchange a user-authorized credential for an agent-scoped grant credential.
  • Resource Indicators to constrain the resource server or app surface.
+

The collision-resistant authorization-details type identifier defined by this +draft is:

+
https://github.com/0al-spec/agent-surface/authorization-details/agent-grant
+

Agent Passport

Agent Passport provides agent identity, capability, policy, lifecycle, signature, and integrity evidence.

@@ -844,7 +857,13 @@

Example ManifestEvents

Grant constraints filter events the same way they filter actions: a grant constrained to one repository SHOULD NOT receive events about other repositories, even when the event scope matches.

+

grant.revoked is an application control event rather than an event authorized +by the revoked grant. Its payload, authentication, and processing requirements +are defined in the OAuth Grant Revocation Profile.

Event delivery semantics — transport, ordering, acknowledgement, and replay — are not defined in this draft; see Open Questions.

Risk Taxonomy

@@ -1129,6 +1151,8 @@

Grant Object

"issuer": "https://code.example.com", "surface_version": "2026-06-25" }, + "locations": ["https://code.example.com/agent-actions"], + "actions": ["pull_request.get", "comment.create"], "scopes": [ "pull_request.read", "pull_request.comment" @@ -1144,11 +1168,13 @@

Grant Object

"mode": "deny" } }, + "credential_profile": "proof_bound", "credential_binding": { "method": "dpop", "runtime_id": "application_runtime_456", "agent_id": "local_agent_789", - "passport_hash": "sha256:..." + "passport_hash": "sha256:...", + "jkt": "<base64url-thumbprint>" }, "audit": { "local_receipt": "required", @@ -1156,6 +1182,15 @@

Grant Object

} } +

This object is the authoritative Agent Grant wire shape. locations restricts +the action endpoints at which the grant may be used, and actions, when +present, is an authoritative allow-list of Agent Surface action identifiers. +OAuth authorization_details uses this same shape with the additional RFC 9396 +type discriminator; it does not define aliases for Grant Object fields. +credential_binding is authorization-server output and MUST repeat the bound +runtime, agent, and passport tuple. A DPoP binding MUST additionally contain +jkt; an mTLS binding MUST instead contain x5t#S256. Those values use the +same encoding and semantics as the corresponding standard cnf members.

Numeric caveats need defined accounting. In this draft, max_actions counts side-effecting action requests accepted by the application under the grant. Reads and denied requests do not consume the budget, and neither do idempotent @@ -1171,14 +1206,14 @@

Grant Lifecycle

-> verify manifest -> choose agent -> verify Agent Passport - -> request grant + -> request grant with authorization_details -> user consent - -> issue grant + -> issue or exchange Grant Credential -> store grant in runtime -> start session - -> mediate actions + -> introspect / verify and mediate actions -> issue receipts - -> expire / revoke / renew + -> expire / revoke / notify / renew

Grant Issuance Models

Model A: App-Issued Grant

@@ -1237,6 +1272,369 @@

OAuth Grant Lifecycle Profile

+

This profile maps an Agent Grant onto OAuth Rich Authorization Requests, Token +Exchange, Token Introspection, and Token Revocation. It applies when the Agent +Surface Manifest declares OAuth endpoints and the Agent Grant authorization +details type defined by this draft.

+

The authorization server and resource server MAY be operated by the same +application, but they retain their OAuth roles. The authorization server issues +and manages Grant Credentials. The application action endpoint is the resource +server and continues to enforce the semantic Agent Grant for every action.

+

Rich Authorization Request Profile

+

An Agent Grant authorization request MUST use the RFC 9396 +authorization_details parameter encoded as a JSON array containing exactly +one object whose type is:

+
https://github.com/0al-spec/agent-surface/authorization-details/agent-grant
+
+

The authorization server metadata MUST list this value in +authorization_details_types_supported. The Agent Surface Manifest auth +object MUST mirror that value and the standard grant_types_supported values +used by the deployment; conflicting metadata makes the OAuth profile invalid.

+

Example, shown decoded from its form-encoded authorization request parameter:

+
[
+  {
+    "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+    "locations": ["https://code.example.com/agent-actions"],
+    "actions": ["pull_request.get", "comment.create"],
+    "delegate": {
+      "runtime": "application_runtime_456",
+      "agent": "local_agent_789",
+      "passport_ref": "agent-passport://local-agent",
+      "passport_hash": "sha256:..."
+    },
+    "resource_server": {
+      "app_id": "code.example.com",
+      "issuer": "https://code.example.com",
+      "surface_version": "2026-06-25"
+    },
+    "scopes": ["pull_request.read", "pull_request.comment"],
+    "constraints": {
+      "repositories": ["example-org/example-repo"],
+      "pull_requests": [13],
+      "expires_at": "2026-06-25T20:00:00Z",
+      "write_approval": "required",
+      "max_actions": 20
+    },
+    "credential_profile": "proof_bound",
+    "audit": {
+      "local_receipt": "required",
+      "app_receipt": "required"
+    }
+  }
+]
+
+

The Agent Grant authorization details type has the following contract. Except +for the required RFC 9396 type discriminator, its field names and semantics +are the authoritative Grant Object wire shape defined above:

+
    +
  • type, delegate, resource_server, scopes, constraints, +credential_profile, and audit are REQUIRED.
  • +
  • delegate MUST contain runtime, agent, and passport_hash; it MAY contain +passport_ref.
  • +
  • resource_server MUST contain app_id, issuer, and surface_version.
  • +
  • constraints MUST contain expires_at; other fields use the semantics of the +Agent Grant object.
  • +
  • credential_profile MUST be compatibility_bearer or proof_bound and maps +to the credential profiles defined in this draft.
  • +
  • RFC 9396 common fields locations and actions MAY restrict the request to +published action endpoints and Agent Surface action identifiers. When +actions is present, the granted value is an authoritative allow-list: every +invoked action MUST be a member. The authorization applies to the product of +the granted actions, locations, scopes, and resource filters; every allowed +combination MUST be published by the surface and semantically compatible.
  • +
  • grant_id, subject, and credential_binding MUST NOT be supplied by the +client in an authorization request; they are authorization-server output.
  • +
  • The request MUST NOT supply subject.user or another asserted user identity; +the authorization server derives the subject from its authenticated user +session or, at the token endpoint, from the validated subject_token.
  • +
+

The authorization server MUST reject unknown fields, unknown action or scope +values, a mismatched resource_server.app_id or +resource_server.surface_version, an unverified passport hash, or constraints +that are invalid for the published surface. It MUST use the RFC 9396 +invalid_authorization_details error for malformed or unsupported Agent Grant +authorization details.

+

Authorization Code use of this profile MUST use PKCE with the S256 challenge +method. Deployments SHOULD use Pushed Authorization Requests when supported so +the rich grant request is integrity-protected and is not exposed in browser +URLs, history, or intermediary logs.

+

The OAuth scope request parameter MUST NOT be used in an authorization or +token-exchange request that carries this Agent Grant authorization_details +type. The authorization server MUST reject such a request with invalid_request. +Independent OAuth authorization therefore requires a separate request and +credential; this profile never silently drops or unions independent scopes.

+

In token and introspection responses, the standard OAuth scope member MAY be +an exact space-delimited projection of the granted Agent Grant scopes for +legacy resource-server integration. If present, it MUST contain exactly that +projection; the granted authorization_details remains authoritative. A +resource server MUST reject a credential when the two representations conflict.

+

Consent MUST present the application, runtime, agent, passport evidence, +requested actions and scopes, resources, expiration and budgets, approval +requirements, credential profile, data exposure, and receipt requirements. The +user MAY approve a strict subset. The authorization server MUST compare the +requested and approved objects according to Agent Grant semantics rather than +using raw JSON equality, and MUST NOT enrich the result with additional +authority.

+

The token response MUST return the granted authorization_details as required +by RFC 9396. For this type, the returned object MUST be enriched with the +authoritative grant_id, subject, delegate binding, effective constraints, +and credential_binding assigned by the authorization server. The +authorization server and resource server MUST retain or receive the same +granted object for later action verification and introspection.

+

OAuth Token Exchange Profile

+

A runtime MAY exchange a user-authorized subject token for an Agent Grant +Credential using RFC 8693. The request uses the standard token endpoint and +form-encoded parameters:

+
POST /oauth/token HTTP/1.1
+Host: code.example.com
+Content-Type: application/x-www-form-urlencoded
+DPoP: <proof-jwt>
+
+grant_type=urn:ietf:params:oauth:grant-type:token-exchange
+&resource=https%3A%2F%2Fcode.example.com%2Fagent-actions
+&requested_token_type=urn:ietf:params:oauth:token-type:access_token
+&subject_token=<user-authorized-token>
+&subject_token_type=urn:ietf:params:oauth:token-type:access_token
+&authorization_details=<percent-encoded-agent-grant-details>
+
+

The Token Exchange request has these additional ASP requirements:

+
    +
  • The runtime MUST authenticate to the token endpoint. For a +proof_bound request, it MUST authenticate using the key or channel binding +that will identify the bound runtime.
  • +
  • A DPoP-bound exchange MUST include a DPoP HTTP header containing a proof for +the token request, as required by RFC 9449. The authorization server MUST +validate that proof independently of OAuth client authentication and derive +the issued token's cnf.jkt and Agent Grant credential_binding.jkt from the +proof key. An mTLS-bound exchange instead derives cnf["x5t#S256"] and the +corresponding credential binding from the client certificate presented on +the token request; it does not use the example's DPoP header.
  • +
  • subject_token MUST represent the authenticated user's authorization for the +requested application and MUST be valid at the time of exchange.
  • +
  • resource MUST contain exactly the published Agent Surface action resource +URI. An audience value MAY additionally name the same logical resource +server but MUST NOT add another target.
  • +
  • requested_token_type MUST be +urn:ietf:params:oauth:token-type:access_token for the OAuth Grant Credential +profile in this draft.
  • +
  • authorization_details MUST contain exactly one Agent Grant object of the +type defined above and MUST be semantically equal to or narrower than the +authorization approved by the user.
  • +
  • If OAuth client authentication does not establish the runtime identity, the +request MUST include an actor_token representing the runtime and the +corresponding actor_token_type. The authorization server MUST verify it and +bind the output credential to that runtime. For a proof_bound request, the +actor token MUST itself be sender-constrained or presented through the same +bound channel authentication.
  • +
+

The authorization server MUST validate the subject token, runtime identity, +agent and passport binding, resource, requested scopes, constraints, and +credential profile. The exchange MUST NOT increase authority, widen resources, +relax approval or receipt requirements, extend beyond the approved expiration, +or replace proof_bound with compatibility_bearer without fresh user consent.

+

RFC 8693 does not itself create lifecycle linkage between input and output +tokens. This ASP profile does: the authorization server MUST record the source +authorization or parent grant from which the Agent Grant was derived. Revoking +or invalidating that source authority MUST revoke or suspend every derived Agent +Grant unless an independently approved grant replaced it.

+

Issuance also MUST preserve cumulative caveats across that derivation graph. +Every accepted action or cost charge MUST atomically consume both the derived +grant's local budget and the authoritative remaining budget of every ancestor +authorization from which it derives. Repeating an exchange therefore cannot +multiply max_actions, max_cost_usd, or another stateful budget. The +authorization server MUST treat semantically equivalent exchanges with the +same source authorization, client and delegate tuple, target resource, +normalized Agent Grant details, and proof-binding key as idempotent: it MUST +reuse the same grant_id and accounting state, although it MAY rotate the +access-token representation.

+

Example successful response:

+
{
+  "access_token": "<opaque-grant-credential>",
+  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
+  "token_type": "DPoP",
+  "expires_in": 1800,
+  "scope": "pull_request.read pull_request.comment",
+  "grant_id": "grant_123",
+  "authorization_details": [
+    {
+      "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+      "locations": ["https://code.example.com/agent-actions"],
+      "actions": ["pull_request.get", "comment.create"],
+      "subject": {
+        "user": "app-user-7f3a"
+      },
+      "delegate": {
+        "runtime": "application_runtime_456",
+        "agent": "local_agent_789",
+        "passport_ref": "agent-passport://local-agent",
+        "passport_hash": "sha256:..."
+      },
+      "resource_server": {
+        "app_id": "code.example.com",
+        "issuer": "https://code.example.com",
+        "surface_version": "2026-06-25"
+      },
+      "scopes": ["pull_request.read", "pull_request.comment"],
+      "constraints": {
+        "repositories": ["example-org/example-repo"],
+        "pull_requests": [13],
+        "expires_at": "2026-06-25T20:00:00Z",
+        "write_approval": "required",
+        "max_actions": 20
+      },
+      "credential_profile": "proof_bound",
+      "credential_binding": {
+        "method": "dpop",
+        "runtime_id": "application_runtime_456",
+        "agent_id": "local_agent_789",
+        "passport_hash": "sha256:...",
+        "jkt": "<base64url-thumbprint>"
+      },
+      "audit": {
+        "local_receipt": "required",
+        "app_receipt": "required"
+      },
+      "grant_id": "grant_123"
+    }
+  ]
+}
+
+

The response MUST include access_token, issued_token_type, token_type, +expires_in, grant_id, the exact scope projection defined above, and the +granted authorization_details. This also satisfies the RFC 8693 requirement to +return scope when the issued scope differs from the request. The token_type +and method-specific credential confirmation data MUST match the selected +credential profile and the binding established at the token endpoint. A +refresh token SHOULD NOT be issued by default; if one is issued, it MUST +preserve the tuple binding, attenuation, and revocation linkage of the Agent +Grant and follow RFC 9700 refresh-token replay protections.

+

Token responses containing a Grant Credential or its authorization details MUST +use Cache-Control: no-store and Pragma: no-cache.

+

Grant Introspection Profile

+

The manifest agent_api.grant_introspection_url MAY identify the same RFC 7662 +endpoint as auth.introspection_url. A protected resource or runtime +introspects a Grant Credential using an authenticated RFC 7662 request with the +required token parameter and optional token_type_hint. The endpoint MUST +authenticate and authorize the caller and disclose only grant data that caller +needs.

+

For an inactive, unknown, or undisclosable credential, the response MUST be:

+
{
+  "active": false
+}
+
+

It MUST NOT reveal whether the credential was unknown, expired, revoked, or +outside the caller's authority.

+

For an active Grant Credential, the response MUST include the RFC 7662 fields +active, client_id, scope, token_type, exp, iat, sub, aud, and +iss, plus the ASP fields grant_id, resource_server, delegate, +constraints, credential_binding, and authorization_details. An active +proof-bound credential MUST additionally include the method-specific standard +cnf confirmation member. The sub value SHOULD be a stable app-scoped +pseudonymous user identifier. client_id identifies the OAuth client; +delegate.runtime is the authoritative ASP runtime binding and MAY differ from +client_id.

+

The authorization_details member MUST contain the granted Agent Grant object, +filtered only to data the authenticated caller may receive. Top-level sub, +grant_id, resource_server, delegate, constraints, and +credential_binding are projections of that object and MUST match it; sub +corresponds to subject.user. A resource server MUST treat a mismatch as an +invalid grant proof rather than selecting one representation.

+

For DPoP, cnf MUST contain jkt as specified by RFC 9449; for mTLS, it MUST +contain x5t#S256 as specified by RFC 8705. The confirmation value MUST match +the method-specific value in the Agent Grant credential_binding. The resource +server MUST reject a missing or mismatched confirmation member. A Compatibility +Bearer Credential MUST NOT fabricate a cnf member.

+
{
+  "active": true,
+  "client_id": "application_runtime_456",
+  "scope": "pull_request.read pull_request.comment",
+  "token_type": "DPoP",
+  "exp": 1782417600,
+  "iat": 1782415800,
+  "sub": "app-user-7f3a",
+  "aud": "https://code.example.com/agent-actions",
+  "iss": "https://code.example.com",
+  "cnf": {
+    "jkt": "<base64url-thumbprint>"
+  },
+  "grant_id": "grant_123",
+  "resource_server": {
+    "app_id": "code.example.com",
+    "issuer": "https://code.example.com",
+    "surface_version": "2026-06-25"
+  },
+  "delegate": {
+    "runtime": "application_runtime_456",
+    "agent": "local_agent_789",
+    "passport_ref": "agent-passport://local-agent",
+    "passport_hash": "sha256:..."
+  },
+  "constraints": {
+    "repositories": ["example-org/example-repo"],
+    "pull_requests": [13],
+    "expires_at": "2026-06-25T20:00:00Z",
+    "write_approval": "required",
+    "max_actions": 20
+  },
+  "credential_binding": {
+    "method": "dpop",
+    "runtime_id": "application_runtime_456",
+    "agent_id": "local_agent_789",
+    "passport_hash": "sha256:...",
+    "jkt": "<base64url-thumbprint>"
+  },
+  "authorization_details": [
+    {
+      "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+      "locations": ["https://code.example.com/agent-actions"],
+      "actions": ["pull_request.get", "comment.create"],
+      "subject": {
+        "user": "app-user-7f3a"
+      },
+      "delegate": {
+        "runtime": "application_runtime_456",
+        "agent": "local_agent_789",
+        "passport_ref": "agent-passport://local-agent",
+        "passport_hash": "sha256:..."
+      },
+      "resource_server": {
+        "app_id": "code.example.com",
+        "issuer": "https://code.example.com",
+        "surface_version": "2026-06-25"
+      },
+      "scopes": ["pull_request.read", "pull_request.comment"],
+      "constraints": {
+        "repositories": ["example-org/example-repo"],
+        "pull_requests": [13],
+        "expires_at": "2026-06-25T20:00:00Z",
+        "write_approval": "required",
+        "max_actions": 20
+      },
+      "credential_profile": "proof_bound",
+      "credential_binding": {
+        "method": "dpop",
+        "runtime_id": "application_runtime_456",
+        "agent_id": "local_agent_789",
+        "passport_hash": "sha256:...",
+        "jkt": "<base64url-thumbprint>"
+      },
+      "audit": {
+        "local_receipt": "required",
+        "app_receipt": "required"
+      },
+      "grant_id": "grant_123"
+    }
+  ]
+}
+
+

The response MUST describe current authoritative state and MUST use +Cache-Control: no-store. A resource-server enforcement point that does not +share the authorization server's authoritative grant state MUST introspect on +every action and MUST NOT positively cache active: true. This prohibition is +required by this profile's immediate revocation semantics; deployments that +need positive caching must define and advertise a different bounded stale-use +profile rather than claiming conformance to this one.

Grant Credentials and Proof

An Agent Grant MAY be represented or proven by one of several mechanisms:

    @@ -1334,6 +1732,9 @@

    Grant Verifica not rejected by itself
  • for a proof-bound server session, the session is active, bound to the grant and runtime, and authenticated with the bound key or channel credential
  • +
  • when the grant contains actions, the requested action identifier is a +member of that authoritative allow-list, is served at a granted location, +and remains compatible with the granted scopes and resource constraints
  • scope permits the action
  • resource constraints permit the target object
  • expiration has not passed
  • @@ -1585,6 +1986,77 @@

    Revocation in-app view of active agent grants — comparable to OAuth application management pages — where a grant can be inspected and revoked without going through the runtime.

    +

    OAuth Grant Revocation Profile

    +

    The manifest agent_api.grant_revocation_url MAY identify the same endpoint as +auth.revocation_url. When it does, a runtime requests revocation using RFC +7009: an authenticated form-encoded POST containing the Grant Credential in +the required token parameter and, optionally, an access_token +token_type_hint.

    +

    The endpoint MUST authenticate the runtime client and, for a credential it can +locate, verify that the credential was issued to that client. A successful +request and a request containing an unknown or already invalid credential both +return HTTP 200 with no response body, as required by RFC 7009. The runtime MUST +stop using the credential after that response. An HTTP 503 response means +revocation is not confirmed; the runtime MUST continue treating the credential +as sensitive, MUST NOT initiate new actions with it, and SHOULD retry according +to Retry-After.

    +

    For the Agent Grant profile, revoking a Grant Credential through +grant_revocation_url revokes the semantic Agent Grant, not only that token. +The application MUST immediately mark the grant inactive, reject every +credential derived from it, invalidate refresh tokens and proof-bound sessions, +and cascade revocation to child or exchanged grants whose authority derives +from it. Revocation initiated through the application's user-facing grant view +MUST produce the same state transition.

    +

    When an active grant changes to revoked and the manifest declares an event +subscription endpoint, the application MUST emit a grant.revoked control event +with this minimum envelope:

    +
    {
    +  "id": "event_01J2ABCDEF",
    +  "type": "grant.revoked",
    +  "occurred_at": "2026-06-25T18:30:00Z",
    +  "issuer": "https://code.example.com",
    +  "audience": "application_runtime_456",
    +  "payload": {
    +    "grant_id": "grant_123",
    +    "app_id": "code.example.com",
    +    "runtime_id": "application_runtime_456",
    +    "agent_id": "local_agent_789",
    +    "passport_hash": "sha256:...",
    +    "revoked_at": "2026-06-25T18:30:00Z",
    +    "effective_at": "2026-06-25T18:30:00Z",
    +    "reason": "user_revoked",
    +    "parent_grant_id": null,
    +    "cascade": true
    +  }
    +}
    +
    +

    Required event fields are id, type, occurred_at, issuer, audience, and +payload. The payload MUST contain grant_id, app_id, runtime_id, +agent_id, passport_hash, revoked_at, effective_at, reason, and +cascade; parent_grant_id is REQUIRED for a child grant and otherwise MAY be +null. Defined reason values are user_revoked, application_revoked, +runtime_revoked, credential_compromise, parent_revoked, policy_changed, +and superseded. A runtime MUST still enforce revocation when it receives an +unknown future reason value and MAY preserve that value as opaque audit data.

    +

    The event MUST be delivered over an application-authenticated event channel +bound to the manifest issuer and target runtime. The runtime MUST verify +issuer, audience, tuple binding, and channel authenticity before acting on +it. Delivery of this control event MUST use event-channel authority independent +of the revoked grant and MUST disclose no more grant data than the target +runtime already possessed. A future signing profile MAY additionally define an +application signature for portable event verification.

    +

    After accepting the event, the runtime MUST atomically mark the grant inactive, +discard cached active introspection state, stop new actions and credential use, +cancel or downgrade affected sessions according to app policy, cascade the +state to locally tracked child grants, and record a runtime receipt. Event +processing is idempotent by id; a duplicate event MUST NOT create duplicate +receipts or repeat external side effects.

    +

    The event is notification, not the enforcement mechanism. The application MUST +reject the revoked grant immediately even if delivery is delayed or lost. A +runtime that misses the event learns the inactive state from introspection or a +rejected action. General event ordering, acknowledgement, replay cursor, +retention, and backpressure remain outside this profile and are defined by the +future Event Delivery Semantics work.

    Grant Revoked

    If a grant is revoked:

      @@ -1593,10 +2065,9 @@

      Grant Revoked

    • active sessions SHOULD be cancelled or downgraded to read-only according to app policy
    • receipt generation SHOULD record the revocation event
    • -
    • the event channel SHOULD deliver grant.revoked as a final event before the -app closes the subscription; delivery of this one event MUST NOT itself -require an active grant, or the runtime could never learn about the -revocation through the event channel
    • +
    • when the manifest declares an event subscription endpoint, the app MUST emit +grant.revoked according to the OAuth Grant Revocation Profile before it +closes the subscription

    Runtime Disconnected

    If the runtime disconnects:

    @@ -1836,6 +2307,20 @@

    OAuth Grant Lifecycle Application

    +

    An application conforms to the OAuth Grant Lifecycle Application profile when +it:

    +
      +
    • satisfies the Grant-Enforcing Application profile
    • +
    • advertises the Agent Grant authorization-details type and supported standard +OAuth grant types
    • +
    • validates and returns Agent Grant authorization_details according to the +Rich Authorization Request Profile
    • +
    • implements the OAuth Token Exchange Profile without privilege amplification
    • +
    • returns the active and inactive Grant Introspection Profile contracts
    • +
    • implements RFC 7009 semantic grant revocation, derivation cascade, and the +authenticated grant.revoked control event when an event endpoint is declared
    • +

    Receipt-Producing Application

    An application conforms to the Receipt-Producing profile when it:

      @@ -1870,6 +2355,8 @@

      Exam 2. Application runtime discovers and validates the surface. 3. User chooses "Connect my local agent". 4. Runtime verifies the selected agent's Agent Passport. -5. Runtime shows consent: +5. Runtime sends an Agent Grant `authorization_details` request. +6. The app authorization server shows consent: - app: code.example.com - agent: local-agent - scopes: pull_request.read, pull_request.comment - repository: example-org/example-repo - duration: 2 hours - writes: require approval -6. User approves. -7. App issues grant_123 and a grant credential. -8. Runtime stores grant_123 and the grant credential. -9. App starts a pull-request review session. -10. Agent reads typed PR context through runtime-mediated resources. -11. Agent proposes a review comment. -12. User or app approves the write. -13. Runtime sends comment.create with an idempotency key and grant credential. -14. App verifies grant and writes the comment. -15. Runtime and app issue linked receipts. -16. User revokes grant or grant expires. +7. User approves a subset or the complete request. +8. App issues or token-exchanges grant_123 and its bound Grant Credential. +9. Runtime stores the authoritative granted details and credential. +10. App starts a pull-request review session. +11. Agent reads typed PR context through runtime-mediated resources. +12. Agent proposes a review comment. +13. User or app approves the write. +14. Runtime sends comment.create with an idempotency key and Grant Credential. +15. App verifies or introspects current grant state and writes the comment. +16. Runtime and app issue linked receipts. +17. User revokes grant_123; the app rejects it immediately. +18. App emits authenticated `grant.revoked`; runtime stops affected work.

      Open Questions