@openwop/spec-artifacts 2.42.6 → 2.42.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CORPUS-STAMP.json +64 -63
  2. package/api/seams-v2.yaml +187 -322
  3. package/api/v2/asyncapi.yaml +83 -113
  4. package/api/v2/openapi.yaml +339 -724
  5. package/package.json +1 -1
  6. package/schemas/audit-verify-result.schema.json +2 -2
  7. package/schemas/v2/audit-verify-result.schema.json +2 -2
  8. package/schemas/v2/capabilities.schema.json +2 -5
  9. package/spec/v1/core-standard-manifest.json +2 -2
  10. package/spec/v1/gaps.json +171 -1
  11. package/spec/v2/README.md +34 -36
  12. package/spec/v2/core/capabilities.md +186 -119
  13. package/spec/v2/core/conformance.md +78 -27
  14. package/spec/v2/core/connection-packs.md +8 -3
  15. package/spec/v2/core/conversation.md +10 -4
  16. package/spec/v2/core/errors.md +30 -9
  17. package/spec/v2/core/events.md +116 -80
  18. package/spec/v2/core/form-content-packs.md +11 -13
  19. package/spec/v2/core/headers.md +22 -14
  20. package/spec/v2/core/host-services.md +22 -5
  21. package/spec/v2/core/idempotency.md +48 -36
  22. package/spec/v2/core/identity.md +110 -42
  23. package/spec/v2/core/interop.md +82 -21
  24. package/spec/v2/core/interrupt.md +79 -40
  25. package/spec/v2/core/node-pack-runtimes.md +18 -4
  26. package/spec/v2/core/oauth.md +40 -6
  27. package/spec/v2/core/overview.md +61 -33
  28. package/spec/v2/core/packs.md +53 -28
  29. package/spec/v2/core/persistence.md +61 -80
  30. package/spec/v2/core/replay.md +56 -42
  31. package/spec/v2/core/runs.md +198 -60
  32. package/spec/v2/core/security-defaults.md +57 -33
  33. package/spec/v2/core/tool-catalog.md +29 -6
  34. package/spec/v2/core/versioning.md +95 -49
  35. package/spec/v2/core/webhooks.md +59 -20
  36. package/spec/v2/core/workflow-chain-packs.md +16 -14
  37. package/spec/v2/corrections.json +17 -1
  38. package/spec/v2/corrections.schema.json +1 -1
  39. package/spec/v2/declaration.json +24 -7
  40. package/spec/v2/declaration.schema.json +10 -0
  41. package/spec/v2/errors.json +109 -2
  42. package/spec/v2/errors.schema.json +38 -0
  43. package/spec/v2/ext/README.md +36 -23
  44. package/spec/v2/ext/a2uiSurface/README.md +75 -89
  45. package/spec/v2/ext/brand/README.md +15 -15
  46. package/spec/v2/ext/canvas/README.md +15 -15
  47. package/spec/v2/ext/chat/README.md +15 -15
  48. package/spec/v2/ext/coordination/README.md +15 -15
  49. package/spec/v2/ext/dataIntegration/README.md +17 -20
  50. package/spec/v2/ext/entities/README.md +15 -15
  51. package/spec/v2/ext/grpc-transport/README.md +6 -8
  52. package/spec/v2/ext/kanban/README.md +15 -15
  53. package/spec/v2/ext/knowledge/README.md +15 -15
  54. package/spec/v2/ext/launchStudio/README.md +16 -16
  55. package/spec/v2/ext/messaging/README.md +15 -15
  56. package/spec/v2/ext/portability/README.md +3 -17
  57. package/spec/v2/ext/provider-idempotency/README.md +7 -10
  58. package/spec/v2/ext/restTransport/README.md +30 -19
  59. package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
  60. package/spec/v2/ext/webResearch/README.md +16 -16
  61. package/spec/v2/profiles.json +6 -2
  62. package/spec/v2/release.json +2 -2
  63. package/spec/v2/surface-baseline.json +542 -2
@@ -1,15 +1,18 @@
1
1
  # Interop
2
2
 
3
- > **Status: Stable · RFC 0175.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `a2a`, `mcp`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- The v2 contract for the two embedded protocols (A2A and MCP): how a host advertises them, how a version is negotiated, and what every negotiation leaves behind. Capability shapes are in capabilities.md; the peer identity is the Subject of identity.md.
8
+ This document covers the two embedded protocols, A2A and MCP: how a host advertises them, how a version is negotiated, and what every negotiation leaves behind. Capability shapes are in [capabilities.md](capabilities.md); the peer identity is the Subject of [identity.md](identity.md).
9
9
 
10
10
  ## REST is the wire
11
11
 
12
- REST and SSE are the wire. A host MUST NOT advertise a transport list; `supportedTransports` does not exist in `schemas/v2/capabilities.schema.json`, and a discovery document carrying it MUST fail validation. A2A and MCP are **compositions** over the wire, advertised by their own facets and nothing else.
12
+ REST and SSE are the wire. A2A and MCP are compositions over it, advertised by their own facets and nothing else.
13
+
14
+ - A host MUST NOT advertise a transport list.
15
+ - A discovery document carrying `supportedTransports` MUST fail validation against `schemas/v2/capabilities.schema.json`.
13
16
 
14
17
  ## The facets
15
18
 
@@ -21,14 +24,16 @@ A host that speaks either protocol MUST advertise the corresponding facet — `a
21
24
  | Default | `preferredVersion` | `preferredVersion` | REQUIRED; served when the peer names none |
22
25
  | Floor | `minimumVersion` | `minimumRevision` | REQUIRED; below it negotiation fails closed |
23
26
  | Freshness | `refreshedAt` | `refreshedAt` | REQUIRED; see the refresh SLA |
24
- | Profiles | `profiles[]` `a2a-<major.minor>` | `profiles[]` `mcp-<date>` | no `-legacy` alternative exists |
27
+ | Profiles | `profiles[]` `a2a-<major.minor>` | `profiles[]` `mcp-<date>` | no `-legacy` suffix |
25
28
  | Protocol-specific | `agentCardUrl`, `streaming`, `pushNotifications`, `durableTasks` | `features[]`, `serverUrls[]`, `serverMount.transports[]` (`stdio` \| `streamable-http`), `mrtr.maxRounds` | optional |
26
29
 
27
30
  `mcp.serverMount.transports[]` is the MCP server's own transport enum; it is not a host transport advertisement.
28
31
 
29
32
  ## Legacy profiles are absent
30
33
 
31
- The profile ids `a2a-0.3-legacy` and `mcp-2025-06-18-legacy` do not exist in v2. The `profiles[]` item patterns admit no `-legacy` suffix, and the legacy code paths (the A2A 0.3 mapping and the MCP live-callback bridges) are not part of this corpus. A host that still speaks a legacy version does so as a private, non-advertised behavior. When no `A2A-Version` header is present, a host MUST serve the agent card of `preferredVersion`.
34
+ The `profiles[]` item patterns admit no `-legacy` suffix, so `a2a-0.3-legacy` and `mcp-2025-06-18-legacy` are not valid profile ids. A host that still speaks a legacy version does so as a private, non-advertised behavior.
35
+
36
+ When no `A2A-Version` header is present, a host MUST serve the agent card of `preferredVersion`.
32
37
 
33
38
  ## Negotiation is a protocol
34
39
 
@@ -43,23 +48,45 @@ The profile ids `a2a-0.3-legacy` and `mcp-2025-06-18-legacy` do not exist in v2.
43
48
  "negotiated": "…" | null, "outcome": "accepted" | "downgraded" | "refused", "reason": "…" }
44
49
  ```
45
50
 
46
- The event is content-free: `peer` MUST be a digest of the peer origin, never the origin in clear. The event on the host's own log is the normative witness of the two silent-downgrade invariants (`a2a-version-no-silent-downgrade`, `mcp-version-no-silent-downgrade`); the conformance seams profile (conformance.md) drives the exchange and captures the wire leg.
51
+ The event is content-free: `peer` MUST be a digest of the peer origin, never the origin in clear. The event is the normative witness of the invariants `a2a-version-no-silent-downgrade` and `mcp-version-no-silent-downgrade`; the seams profile ([conformance.md](conformance.md)) drives the exchange and captures the wire leg.
47
52
 
48
53
  **The refresh SLA.** A host MUST re-evaluate its advertised `versions[]` / `revisions[]` against the upstream registry within the window its `refreshedAt` declares, and that window MUST NOT exceed 90 days. An advertisement older than its window is non-conformant.
49
54
 
50
55
  **Downgrade above the floor.** A host MAY accept an authenticated request for a version between the floor and `preferredVersion`; the event then reports `outcome: downgraded`.
51
56
 
52
- ## The operation mappings (RFC 0208)
57
+ ## The operation mappings
58
+
59
+ `spec/v2/interop-map.json` (schema `interop-map.schema.json`) maps each profile's upstream operations, states, fields and errors to the v2 wire, pinned to an upstream release. A host advertising a profile:
60
+
61
+ - MUST serve every row it implements as the row states, under the caller's Subject with the authorization, tenant scoping and state of the v2 operation the row names;
62
+ - MUST refuse a row whose `requires` facet it does not advertise with the row's error;
63
+ - MUST list every feature the map requires for that profile.
64
+
65
+ What the map does not name is opaque: it MUST round-trip where upstream requires it and MUST NOT become authority, a prompt segment, a tool call or a workflow variable. A patch release that re-maps a row is a map edit; patch numbers are never negotiated.
53
66
 
54
- `spec/v2/interop-map.json` (schema `interop-map.schema.json`) maps each profile's upstream operations, states, fields and errors to the v2 wire, pinned to an upstream release. A host advertising a profile MUST serve every row it implements as the row states, under the caller's Subject with the authorization, tenant scoping and state of the v2 operation the row names; MUST refuse a row whose `requires` facet it does not advertise with the row's error; and MUST list every feature the map requires for that profile. What the map does not name is opaque: it MUST round-trip where upstream requires it and MUST NOT become authority, a prompt segment, a tool call or a workflow variable. A patch release that re-maps a row is a map edit; patch numbers are never negotiated.
67
+ **Isolation.** On either interface:
55
68
 
56
- **Isolation.** On either interface, a task the caller could not read through `getRun` MUST be answered exactly as a nonexistent one, including a tenant mismatch REST refuses `403`. `ListTasks` MUST return only runs `listRuns` would return to the same Subject, whether or not `runList` is advertised. `contextId`, `tenant` and `_meta` never select a tenant, workspace or principal.
69
+ - A task the caller could not read through `getRun` MUST be answered exactly as a nonexistent one, including a tenant mismatch REST refuses `403`.
70
+ - `ListTasks` MUST return only runs `listRuns` would return to the same Subject, whether or not `runList` is advertised.
71
+ - `contextId`, `tenant` and `_meta` never select a tenant, workspace or principal.
57
72
 
58
- **A2A multi-turn (A2A §3.4.3).** A message carrying `taskId` without `contextId` MUST be answered with the task's `contextId`. A message whose `contextId` is not its task's MUST be refused with its binding's invalid-parameters error and MUST NOT change the run. A message to a retained terminal task MUST be refused `UnsupportedOperationError`; `TaskNotFoundError` is for unknown, purged and unreadable tasks.
73
+ **A2A multi-turn (A2A §3.4.3).**
59
74
 
60
- **A2A error details (RFC 0211).** On a 1.0 `JSONRPC` interface, an A2A error's `error.data` MUST be an array of objects each carrying `@type`, including exactly one `type.googleapis.com/google.rpc.ErrorInfo` whose `reason` is the map row's `reason` and whose `domain` is `a2a-protocol.org` (a tightening of A2A §9.5's SHOULD). An `HTTP+JSON` interface MUST answer A2A §11.6's `google.rpc.Status`. A response on an interface URL the card lists, including a refusal before dispatch, MUST NOT be the OpenWOP error envelope. `TaskNotFoundError` details MUST NOT differ between an unknown task and an unreadable one, apart from an echo of the requested id. On `VersionNotSupportedError` a host SHOULD set `metadata.supportedVersions` to a comma-joined list; a client falls back to the card's `supportedInterfaces[].protocolVersion`. A client MUST identify an error by code or by the ErrorInfo `reason`, MUST accept `data` as an array, and SHOULD accept a `data` object carrying `reason` through 2.x.
75
+ - A message carrying `taskId` without `contextId` MUST be answered with the task's `contextId`.
76
+ - A message whose `contextId` is not its task's MUST be refused with its binding's invalid-parameters error and MUST NOT change the run.
77
+ - A message to a retained terminal task MUST be refused `UnsupportedOperationError`; `TaskNotFoundError` is for unknown, purged and unreadable tasks.
61
78
 
62
- ## MCP tasks and cancellation (RFC 0198)
79
+ **A2A error details.** A host:
80
+
81
+ - on a 1.0 `JSONRPC` interface, MUST make an A2A error's `error.data` an array of objects each carrying `@type`, including exactly one `type.googleapis.com/google.rpc.ErrorInfo` whose `reason` is the map row's `reason` and whose `domain` is `a2a-protocol.org` (a tightening of A2A §9.5's SHOULD);
82
+ - on an `HTTP+JSON` interface, MUST answer A2A §11.6's `google.rpc.Status`;
83
+ - MUST NOT answer with the OpenWOP error envelope on an interface URL the card lists, including a refusal before dispatch;
84
+ - MUST NOT vary `TaskNotFoundError` details between an unknown task and an unreadable one, apart from an echo of the requested id;
85
+ - on `VersionNotSupportedError`, SHOULD set `metadata.supportedVersions` to a comma-joined list; a client falls back to the card's `supportedInterfaces[].protocolVersion`.
86
+
87
+ A client MUST identify an error by code or by the ErrorInfo `reason`, MUST accept `data` as an array, and SHOULD accept a `data` object carrying `reason` through 2.x.
88
+
89
+ ## MCP tasks and cancellation
63
90
 
64
91
  A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revision `2026-07-28`) on its server mount. It advertises it in its `server/discover` `capabilities.extensions` and by listing `extensions` in `mcp.features[]`, and nowhere else. A host that advertises it MUST implement the extension as published and the map's `mcp.tasks` rows, and:
65
92
 
@@ -67,7 +94,11 @@ A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revisi
67
94
  - MUST use the run's projected `runId` (identity.md §5) as `taskId`, with an opaque segment of at least 128 bits of entropy. A `taskId` is never a credential;
68
95
  - MUST NOT append to a run's log to answer `tasks/get`.
69
96
 
70
- **Cancellation.** Until the host has sent its whole response to a request that starts or continues a run, the run belongs to that request: a client disconnect on streamable HTTP, or a stdio `notifications/cancelled` naming the request, MUST cancel the run as `cancelRun` would, with `run.cancelled.reason` `mcp-request-cancelled`. Once the response is sent, a disconnect MUST NOT affect the run; a task ends through `tasks/cancel`, `cancelRun`, or its own terminal state. A host MUST NOT send `notifications/cancelled` except to end a `subscriptions/listen` stream.
97
+ **Cancellation.** Until the host has sent its whole response to a request that starts or continues a run, the run belongs to that request.
98
+
99
+ - Before the response is sent, a client disconnect on streamable HTTP, or a stdio `notifications/cancelled` naming the request, MUST cancel the run as `cancelRun` would, with `run.cancelled.reason` `mcp-request-cancelled`.
100
+ - Once the response is sent, a disconnect MUST NOT affect the run; a task ends through `tasks/cancel`, `cancelRun`, or its own terminal state.
101
+ - A host MUST NOT send `notifications/cancelled` except to end a `subscriptions/listen` stream.
71
102
 
72
103
  ## The MCP round ceiling
73
104
 
@@ -77,28 +108,58 @@ A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revisi
77
108
 
78
109
  `auth-required` remains a member of the persisted A2A task state enum (`schemas/v2/a2a-task-state.schema.json`) for the reverse direction (consuming an external A2A agent). The forward projection MUST emit it, with `interruptKind: credential` and a status message carrying `connectUrl`, for a run suspended on a `credential` interrupt (interrupt.md), and MUST NOT emit it otherwise.
79
110
 
80
- ## A2A push delivery (RFC 0214)
111
+ ## A2A push delivery
81
112
 
82
- A host advertising `a2a.pushNotifications` treats each push as a webhook egress: webhooks.md §Egress binds at delivery time as well as registration, a `3xx` is a failed delivery, and the push credential is bound as security-defaults.md §"Onward hops" states. It MUST attempt each push at least once; a host that retries follows `webhooks.md` `retryPolicy` semantics. The body is an A2A 1.0 `StreamResponse` sent as `application/a2a+json`, carrying `Authorization: {scheme} {credentials}` from the config; when only `token` is set a host SHOULD send it as `Authorization: Bearer <token>` (A2A v1.0.1 leaves its carriage undefined). A host MUST NOT add an OpenWOP signature. Push dead-letters are not visible to A2A clients; a client recovers with `GetTask`.
113
+ A host advertising `a2a.pushNotifications` treats each push as a webhook egress: webhooks.md §Egress binds at delivery time as well as registration, a `3xx` is a failed delivery, and the push credential is bound as security-defaults.md §"Onward hops" states.
83
114
 
84
- A `replay` fork MUST NOT push re-emitted history, and no fork inherits a source run's push configs. A push-config read or delete on a task the caller cannot read, or naming a `configId` that is not that task's, MUST answer exactly as for an unknown id, apart from the JSON-RPC `id`; a `configId` MUST NOT encode a tenant, workspace or principal, and delete is idempotent.
115
+ - A host MUST attempt each push at least once; a host that retries follows `webhooks.md` `retryPolicy` semantics.
116
+ - The body is an A2A 1.0 `StreamResponse` sent as `application/a2a+json`, carrying `Authorization: {scheme} {credentials}` from the config. When only `token` is set, a host SHOULD send it as `Authorization: Bearer <token>` (A2A v1.0.1 leaves its carriage undefined).
117
+ - A host MUST NOT add an OpenWOP signature.
118
+ - Push dead-letters are not visible to A2A clients; a client recovers with `GetTask`.
119
+ - A `replay` fork MUST NOT push re-emitted history, and no fork inherits a source run's push configs.
120
+ - A push-config read or delete on a task the caller cannot read, or naming a `configId` that is not that task's, MUST answer exactly as for an unknown id, apart from the JSON-RPC `id`.
121
+ - A `configId` MUST NOT encode a tenant, workspace or principal. Delete is idempotent.
85
122
 
86
123
  ## Per-agent cards
87
124
 
88
125
  A host advertising `a2a.agentCards` MUST also offer the `a2a-1.0` profile and `agents.manifestRuntime`, and MUST declare `capabilities.extendedAgentCard: true` on its public card. It publishes each entry of a caller's agent inventory (`GET /agents`) as an A2A `AgentCard`, reached through the entry's `a2aTenant`: an opaque routing value `R` the host mints, stable for the agent and host version, that MUST NOT encode a tenant, workspace, or principal.
89
126
 
90
- `GetExtendedAgentCard` with `tenant: R` MUST return that agent's card: `name` is the entry's `persona`, `version` its `packVersion`, `description` its `description` or else `label`; `supportedInterfaces[]` are the host card's interfaces, each carrying `tenant: R`; `capabilities` and `securitySchemes` equal the host card's; `skills[]` holds one skill per workflow the host routes to the agent for this caller. The card MUST NOT carry anything the inventory entry may not, and does not replace it: `degraded[]` and `memoryDegraded` stay on the entry.
127
+ `GetExtendedAgentCard` with `tenant: R` MUST return that agent's card:
91
128
 
92
- **Non-disclosure.** A request carrying `R` MUST be authenticated and authorized as `GET /agents/{agentId}` is, before `R` is resolved. For an `R` naming an agent outside the caller's inventory, every A2A operation MUST return what it returns for an `R` the host never minted, apart from the JSON-RPC `id`. The public card at `agentCardUrl` MUST NOT list any `R`. `R` is a `tenant` value under §"The operation mappings" **Isolation**.
129
+ - `name` is the entry's `persona`, `version` its `packVersion`, `description` its `description` or else `label`;
130
+ - `supportedInterfaces[]` are the host card's interfaces, each carrying `tenant: R`;
131
+ - `capabilities` and `securitySchemes` equal the host card's;
132
+ - `skills[]` holds one skill per workflow the host routes to the agent for this caller.
133
+
134
+ The card MUST NOT carry anything the inventory entry may not, and does not replace it: `degraded[]` and `memoryDegraded` stay on the entry.
135
+
136
+ **Non-disclosure.**
137
+
138
+ - A request carrying `R` MUST be authenticated and authorized as `GET /agents/{agentId}` is, before `R` is resolved.
139
+ - For an `R` naming an agent outside the caller's inventory, every A2A operation MUST return what it returns for an `R` the host never minted, apart from the JSON-RPC `id`.
140
+ - The public card at `agentCardUrl` MUST NOT list any `R`.
141
+ - `R` is a `tenant` value under §"The operation mappings" **Isolation**.
93
142
 
94
143
  ## gRPC
95
144
 
96
- gRPC is not part of the core wire. Its document lives at `spec/v2/ext/grpc-transport/` with `witness: unwitnessable` and `adoption: none`; its requirements are SHOULDs of that extension. A host MUST NOT advertise a `grpc` capability block — an unwitnessable family is not advertisable — and `api/v2/openapi.yaml` and the AsyncAPI document are the only canonical API descriptions. The extension re-enters core only by a v2.x additive RFC that generates the proto from `spec/v2/declaration.json` and lands a suite client.
145
+ gRPC is not part of the core wire. Its document lives at `spec/v2/ext/grpc-transport/` with `witness: unwitnessable` and `adoption: none`; its requirements are SHOULDs of that extension.
146
+
147
+ - A host MUST NOT advertise a `grpc` capability block — an unwitnessable family is not advertisable.
148
+ - `api/v2/openapi.yaml` and the AsyncAPI document are the only canonical API descriptions.
97
149
 
98
- ## Trace context (RFC 0207)
150
+ The extension re-enters core only by an additive RFC that generates the proto from `spec/v2/declaration.json` and lands a suite client.
99
151
 
100
- A host that propagates W3C Trace Context into an MCP request MUST carry it in that request's `params._meta` (unprefixed `traceparent`, and `tracestate` when present; MCP 2026-07-28 `_meta`, SEP-414) or in the HTTP `traceparent` header, and SHOULD use `_meta`, the only carrier on stdio. Into an A2A message it MUST carry it in `Message.metadata.openwop.traceparent` and `.tracestate` or in the HTTP header, and SHOULD use the metadata. A receiver prefers the in-message value, ignores a malformed one, and MUST NOT derive tenant, principal or scope from either.
152
+ ## Trace context
153
+
154
+ A host that propagates W3C Trace Context:
155
+
156
+ - into an MCP request MUST carry it in that request's `params._meta` (unprefixed `traceparent`, and `tracestate` when present; MCP 2026-07-28 `_meta`, SEP-414) or in the HTTP `traceparent` header, and SHOULD use `_meta`, the only carrier on stdio;
157
+ - into an A2A message MUST carry it in `Message.metadata.openwop.traceparent` and `.tracestate` or in the HTTP header, and SHOULD use the metadata.
158
+
159
+ A receiver prefers the in-message value, ignores a malformed one, and MUST NOT derive tenant, principal or scope from either.
101
160
 
102
161
  ## Threat model
103
162
 
104
163
  `SECURITY/threat-model-interop.md` is the threat model for this document; its invariants are rows of `SECURITY/invariants.yaml`. Peer identity and authorization at the boundary are governed by security-defaults.md; a peer MUST NOT gain authority the caller's Subject does not hold.
164
+
165
+ *Sources: RFC 0175, RFC 0198, RFC 0207, RFC 0208, RFC 0211, RFC 0214.*
@@ -1,6 +1,6 @@
1
1
  # Interrupt
2
2
 
3
- > **Status: Stable · RFC 0170 §E.1, RFC 0171 §A.4, RFC 0173 §B.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `interrupt`.
5
5
 
6
6
  ## Why this exists
@@ -11,25 +11,49 @@
11
11
 
12
12
  `schemas/v2/suspend-request.schema.json` (`InterruptPayload`) is closed and discriminated by `kind`; `kind`, `key` and `data` are REQUIRED.
13
13
 
14
- | Kind | `data` (required fields) | Notes |
14
+ | Kind | `data` (required fields) | Snapshot status / gate |
15
15
  | --- | --- | --- |
16
- | `approval` | `artifactId`, `artifactType`, `title`, `actions` | 5-action vocabulary below; quorum and eligibility fields |
17
- | `clarification` | `questions[]` (`id`, `question`, optional `schema`) | Snapshot status `waiting-input` |
18
- | `external-event` | `eventType`, `correlation` | Snapshot status MUST be `waiting-external` |
19
- | `custom` | `customKind`, optional `payload` | A host MUST accept and persist it; rendering is best-effort |
20
- | `conversation.start` | `conversationId` | Gated on the `conversation` capability (capabilities.md); `conversationId` MUST be tenant-unique and MUST NOT be assumed resolvable on another host |
21
- | `conversation.exchange` | `conversationId`, `prompt` | The resume value MUST validate against `outcomeSchema` when supplied |
16
+ | `approval` | `artifactId`, `artifactType`, `title`, `actions` | 5-action vocabulary, quorum and eligibility (§Approval) |
17
+ | `clarification` | `questions[]` (`id`, `question`, optional `schema`) | `waiting-input` |
18
+ | `external-event` | `eventType`, `correlation` | `waiting-external` |
19
+ | `custom` | `customKind`, optional `payload` | — |
20
+ | `conversation.start` | `conversationId` | Gated on `conversation` ([capabilities.md](capabilities.md)) |
21
+ | `conversation.exchange` | `conversationId`, `prompt` | — |
22
22
  | `conversation.close` | `conversationId` | Gated as above |
23
- | `low-confidence` | `agentId`, `threshold`, `observed` | An `agent.decided` with `confidence` below the threshold MUST be followed by `node.suspended { reason: 'low-confidence' }`; the per-run threshold is `configurable.run.escalationThreshold` (runs.md) |
24
- | `credential` | `provider`, `scopes`, `reason`, `connectUrl` | Gated on `oauth.credentialInterrupt` (oauth.md); snapshot status `waiting-input`; the resume value is `{ outcome }` and carries no credential |
23
+ | `low-confidence` | `agentId`, `threshold`, `observed` | — |
24
+ | `credential` | `provider`, `scopes`, `reason`, `connectUrl` | `waiting-input`; gated on `oauth.credentialInterrupt` ([oauth.md](oauth.md)) |
25
25
 
26
- An interrupt of any kind MUST NOT solicit credential material; a credential is acquired through `credential`.
26
+ Per-kind rules:
27
27
 
28
- `key` is the deterministic re-entry key: a host MUST invoke an interrupt with key `K` at most once for the lifetime of the run. On recovery the engine MUST consult the event log, find the prior `interrupt.resolved`, and return the persisted `resumeValue` without emitting a second `interrupt.requested`; an in-memory cache MAY serve in-process replays but MUST NOT replace the event log for cross-process replays. A host MUST validate the resume value against `resumeSchema` when one is declared and MUST refuse a failing value with `400 validation_error`. `timeoutMs`, when set, is the interrupt's own deadline.
28
+ - `external-event`: the snapshot status MUST be `waiting-external`.
29
+ - `custom`: a host MUST accept and persist it; rendering is best-effort.
30
+ - `conversation.start`: `conversationId` MUST be tenant-unique and MUST NOT be assumed resolvable on another host.
31
+ - `conversation.exchange`: the resume value MUST validate against `outcomeSchema` when supplied.
32
+ - `low-confidence`: an `agent.decided` with `confidence` below the threshold MUST be followed by `node.suspended { reason: 'low-confidence' }`. The per-run threshold is `configurable.run.escalationThreshold` ([runs.md](runs.md)).
33
+ - `credential`: the resume value is `{ outcome }` and carries no credential.
34
+ - An interrupt of any kind MUST NOT solicit credential material; a credential is acquired through `credential`.
35
+
36
+ ### Re-entry and resume values
37
+
38
+ `key` is the deterministic re-entry key.
39
+
40
+ - A host MUST invoke an interrupt with key `K` at most once for the lifetime of the run.
41
+ - On recovery the engine MUST consult the event log, find the prior `interrupt.resolved`, and return the persisted `resumeValue` without emitting a second `interrupt.requested`.
42
+ - An in-memory cache MAY serve in-process replays but MUST NOT replace the event log for cross-process replays.
43
+ - A host MUST validate the resume value against `resumeSchema` when one is declared, and MUST refuse a failing value with `400 validation_error`.
44
+
45
+ `timeoutMs`, when set, is the interrupt's own deadline.
29
46
 
30
47
  ## Events
31
48
 
32
- Every kind uses two registered types (events.md): `interrupt.requested`, whose payload is the `InterruptPayload` verbatim, and `interrupt.resolved`, whose closed payload is `interruptResolved`; resolving an approval-kind interrupt MUST record the applied `action` there, with the field §Approval requires. The legacy `approval.*` and `clarification.*` types remain registered; their payload definitions in `schemas/v2/run-event-payloads.schema.json` are `$ref` aliases of `interruptRequested` and `interruptResolved` (RFC 0171 §A.4 E4), so there is one shape per direction. A host emitting `interrupt.requested` SHOULD also emit the legacy kind-specific type until its consumers migrate. Both events are durable and appear in the `updates` and `debug` stream modes. While suspended, `RunSnapshot.currentNodeId` names the node and `status` is `waiting-approval`, `waiting-input` or `waiting-external`.
49
+ Every kind uses two registered types ([events.md](events.md)):
50
+
51
+ - `interrupt.requested` — the payload is the `InterruptPayload` verbatim.
52
+ - `interrupt.resolved` — the closed payload is `interruptResolved`. Resolving an approval-kind interrupt MUST record the applied `action` there, with the field §Approval requires.
53
+
54
+ The kind-specific `approval.*` and `clarification.*` types remain registered. Their payload definitions in `schemas/v2/run-event-payloads.schema.json` are `$ref` aliases of `interruptRequested` and `interruptResolved`, so there is one shape per direction. A host emitting `interrupt.requested` SHOULD also emit the kind-specific type until its consumers migrate.
55
+
56
+ Both events are durable and appear in the `updates` and `debug` stream modes. While suspended, `RunSnapshot.currentNodeId` names the node and `status` is `waiting-approval`, `waiting-input` or `waiting-external`.
33
57
 
34
58
  ## Resolve surfaces
35
59
 
@@ -39,28 +63,40 @@ Every kind uses two registered types (events.md): `interrupt.requested`, whose p
39
63
  | `inspectInterruptByToken` | `GET /interrupts/{token}` | the token | — (returns the `InterruptPayload`) |
40
64
  | `resolveInterruptByToken` | `POST /interrupts/{token}` | the token | `{ resumeValue }` (closed) |
41
65
 
42
- A host MUST expose the run-scoped surface and SHOULD expose the signed-token surface for callers not authenticated to the protocol. Every resolve MUST honor `Idempotency-Key` (idempotency.md). Exactly one of two concurrent resolves MUST succeed; the other MUST receive `409 interrupt_already_resolved`.
66
+ - A host MUST expose the run-scoped surface, and SHOULD expose the signed-token surface for callers not authenticated to the protocol.
67
+ - Every resolve MUST honor `Idempotency-Key` ([idempotency.md](idempotency.md)).
68
+ - Of two concurrent resolves, exactly one MUST succeed; the other MUST receive `409 interrupt_already_resolved`.
69
+
70
+ ### Callback delivery
71
+
72
+ `createRun.callbackUrl` names where a host that advertises `interrupt.callbackDelivery: true` delivers notice of an interrupt, so its holder can resolve it through the token surface. The payload, timing and signing are host-defined.
73
+
74
+ A host advertising the facet:
43
75
 
44
- **Callback delivery (RFC 0196).** `createRun.callbackUrl` names where a host that advertises `interrupt.callbackDelivery: true` delivers notice of an interrupt, so its holder can resolve it through the token surface. The payload, timing and signing are host-defined in this revision. A host advertising the facet MUST refuse at `createRun`, with `400 validation_error` and `details.field: "callbackUrl"`, a URL the `webhooks.md` §Egress registration guard would refuse, MUST re-validate every resolved address at delivery, and MUST NOT follow a redirect. A host that does not advertise it SHOULD refuse the member and MUST NOT claim delivery it does not perform.
76
+ - MUST refuse at `createRun`, with `400 validation_error` and `details.field: "callbackUrl"`, a URL the `webhooks.md` §Egress registration guard would refuse;
77
+ - MUST re-validate every resolved address at delivery;
78
+ - MUST NOT follow a redirect.
79
+
80
+ A host that does not advertise it SHOULD refuse the member and MUST NOT claim delivery it does not perform.
81
+
82
+ ### Errors
45
83
 
46
84
  | Status | Code | Condition |
47
85
  | --- | --- | --- |
48
- | `400` | `validation_error` | `resumeValue` fails `resumeSchema` or the approval action is not in `actions` |
86
+ | `400` | `validation_error` | `resumeValue` fails `resumeSchema`, or the approval action is not in `actions` |
49
87
  | `401` | `interrupt_token_invalid` | MAC, `alg` or `kid` not accepted |
50
88
  | `404` | `not_found` | No such run or node |
51
- | `409` | `interrupt_already_resolved` | Already resolved, or the run is cancelled or completed (both surfaces); or a token invalidated by resolution, cancellation or completion |
89
+ | `409` | `interrupt_already_resolved` | Already resolved; the run is cancelled or completed (both surfaces); or the token was invalidated |
52
90
  | `410` | `interrupt_expired` | Token past `expiresAt` (token surface only) |
53
91
 
54
92
  ## Tokens
55
93
 
56
- The token grammar, the `interrupt.tokenAlgs[]` / `kid` check and the v1 drain are identity.md §4 (`401 interrupt_token_invalid`).
94
+ The token grammar and the `interrupt.tokenAlgs[]` / `kid` check are [identity.md](identity.md) §4 (`401 interrupt_token_invalid`).
57
95
 
58
- | Rule | Requirement |
59
- | --- | --- |
60
- | Expiry | Every token MUST carry `expiresAt`. The default SHOULD be 30 minutes; a host MUST cap the lifetime at the interrupt's `timeoutMs` when one exists. A token MUST NOT outlive the interrupt it resolves; past `expiresAt` the host MUST answer `410 interrupt_expired`. |
61
- | Invalidation | A token MUST be invalidated when its interrupt is resolved or its run is cancelled or completed; later use MUST answer `409 interrupt_already_resolved`. |
62
- | Verification | MAC comparison MUST be constant-time. `kid` selects the verification secret so secrets rotate without orphaning outstanding tokens. |
63
- | Intent | A token minted with `intent: resolve` authorizes both operations; a host MAY mint `intent: inspect` tokens, and a resolve with one MUST be refused with `403`. |
96
+ - **Expiry.** Every token MUST carry `expiresAt`. The default SHOULD be 30 minutes, and a host MUST cap the lifetime at the interrupt's `timeoutMs` when one exists. A token MUST NOT outlive the interrupt it resolves; past `expiresAt` the host MUST answer `410 interrupt_expired`.
97
+ - **Invalidation.** A token MUST be invalidated when its interrupt is resolved or its run is cancelled or completed. Later use MUST answer `409 interrupt_already_resolved`.
98
+ - **Verification.** MAC comparison MUST be constant-time. `kid` selects the verification secret, so secrets rotate without orphaning outstanding tokens.
99
+ - **Intent.** A token minted with `intent: resolve` authorizes both operations. A host MAY mint `intent: inspect` tokens; a resolve with one MUST be refused with `403`.
64
100
 
65
101
  ## Approval
66
102
 
@@ -73,23 +109,26 @@ The token grammar, the `interrupt.tokenAlgs[]` / `kid` check and the v1 drain ar
73
109
  | `refine` | `refineFeedback { scope: whole \| section \| items, sectionPath?, itemIds?, tags?, text? }` |
74
110
  | `edit-accept` | `editedArtifactData` |
75
111
 
76
- Every resume carries `decidedAt`; `decidedBy` MAY be omitted by an authenticated caller, and every consumer MUST treat it as an opaque string. `requiredApprovals` sets the quorum (default 1) and `rejectionPolicy` is `single-veto` (default) or `majority`; when `overrideBypassesQuorum` is `true` a configured override principal MAY release the gate alone, otherwise its vote counts once.
112
+ - Every resume carries `decidedAt`. `decidedBy` MAY be omitted by an authenticated caller, and every consumer MUST treat it as an opaque string.
113
+ - `requiredApprovals` sets the quorum (default 1). `rejectionPolicy` is `single-veto` (default) or `majority`.
114
+ - When `overrideBypassesQuorum` is `true`, a configured override principal MAY release the gate alone; otherwise its vote counts once.
77
115
 
78
116
  ## Approver enforcement
79
117
 
80
- Enforcement is an obligation of the fields, not a discovery flag (RFC 0173 §B). The facet `spec/v2/facets/interrupt.schema.json` carries `tokenAlgs[]` (REQUIRED) and `refKinds[]` ⊆ `principal`, `group`, `role`.
118
+ Enforcement is an obligation of the fields, not a discovery flag. The facet `spec/v2/facets/interrupt.schema.json` carries `tokenAlgs[]` (REQUIRED) and `refKinds[]` ⊆ `principal`, `group`, `role`.
81
119
 
82
- | Field | Binds |
83
- | --- | --- |
84
- | `approversList` (explicit principals) | Everywhere: a host advertising `interrupt` MUST refuse a resolver not in the list |
85
- | `approverGroupRefs` | Only where `refKinds` includes `group`: the host MUST surface the field unchanged and MUST resolve and enforce its members as eligible approvers |
86
- | `approverRoleRefs` | Only where `refKinds` includes `role`: as for groups, with holders |
87
- | `audience` | A notification hint, never eligibility; omitted ⇒ the host SHOULD notify the union of the eligibility refs |
88
-
89
- Eligibility binds every WRITER of the suspension record, not every route. A
90
- host whose durable store is writable by a principal other than the engine MUST
91
- enforce the same eligibility at the store, or MUST NOT expose the record to
92
- that principal for write: a rule enforced per route is only as complete as the
93
- census of writers (RFC 0187 §C.1).
94
-
95
- Refs are opaque to the engine; the host resolves them against its own identity model. Membership MUST be resolved at decision time and MUST NOT be re-resolved during replay or `forkRun`: the recorded eligibility decision is fixed history (replay.md). A host that does not advertise a ref kind MUST ignore that field.
120
+ - **`approversList`** (explicit principals) binds everywhere: a host advertising `interrupt` MUST refuse a resolver not in the list.
121
+ - **`approverGroupRefs`** binds only where `refKinds` includes `group`: the host MUST surface the field unchanged and MUST resolve and enforce its members as eligible approvers.
122
+ - **`approverRoleRefs`** binds only where `refKinds` includes `role`: as for groups, with holders.
123
+ - **`audience`** is a notification hint, never eligibility. When omitted, the host SHOULD notify the union of the eligibility refs.
124
+ - A host that does not advertise a ref kind MUST ignore that field.
125
+
126
+ Eligibility binds every writer of the suspension record, not every route. A host whose durable store is writable by a principal other than the engine MUST enforce the same eligibility at the store, or MUST NOT expose the record to that principal for write.
127
+
128
+ Refs are opaque to the engine; the host resolves them against its own identity model. Membership MUST be resolved at decision time and MUST NOT be re-resolved during replay or `forkRun`: the recorded eligibility decision is fixed history ([replay.md](replay.md)).
129
+
130
+ ## During the v1 overlap
131
+
132
+ The v1 interrupt-token drain is [identity.md](identity.md) §4.
133
+
134
+ *Sources: RFC 0170, RFC 0171, RFC 0173, RFC 0187, RFC 0196.*
@@ -1,11 +1,11 @@
1
1
  # Node-pack runtimes
2
2
 
3
- > **Status: Stable · RFC 0008.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `nodePackRuntimes`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- v2 carried this family only by pointing at `spec/v1/node-packs.md`. This is its v2 contract, and it lets a `remote` pack name its MCP server by an inline MCP Registry record instead of a bare URL. The shape is `$defs/Runtime` in `schemas/v2/node-pack-manifest.schema.json`.
8
+ This is the v2 contract for the language a node pack runs in. It also lets a `remote` pack name its MCP server by an inline MCP Registry record instead of a bare URL. The shape is `$defs/Runtime` in `schemas/v2/node-pack-manifest.schema.json`.
9
9
 
10
10
  ## Languages
11
11
 
@@ -13,8 +13,22 @@ v2 carried this family only by pointing at `spec/v1/node-packs.md`. This is its
13
13
 
14
14
  ## WASM
15
15
 
16
- A host that loads `wasm` packs MUST advertise `nodePackRuntimes.wasm` with at least one `abiVersions[]` entry (RFC 0008), and MUST reject at load a pack whose `openwop_abi_version()` is not listed. When it advertises `maxMemoryBytes` it MUST enforce it and emit `cap.breached` with `kind: "wasm-memory"` on a breach. `nodePackRuntimes.wasmComponent` advertises `wasm-component` loading; its interfaces are reserved for a later RFC.
16
+ - A host that loads `wasm` packs MUST advertise `nodePackRuntimes.wasm` with at least one `abiVersions[]` entry, and MUST reject at load a pack whose `openwop_abi_version()` is not listed.
17
+ - When it advertises `maxMemoryBytes` it MUST enforce it and emit `cap.breached` with `kind: "wasm-memory"` on a breach.
18
+ - `nodePackRuntimes.wasmComponent` advertises `wasm-component` loading; its interfaces are reserved for a later RFC.
17
19
 
18
20
  ## The MCP registry record
19
21
 
20
- RFC 0203 (`Accepted`): a `remote` runtime MAY carry `mcpServer`, an inline subset of an MCP Registry `server.json` (schema `2025-12-11`): `name`, `description`, `version`, optional `title`, `websiteUrl` and `repository`, and exactly one `remotes[]` entry of `type: "streamable-http"` with an `https://` URL. It has no `packages[]`, `headers`, `variables` or `_meta`, so it carries no install instruction and no credential. `mcpServer` under any other language, or an `entry` that differs from `remotes[0].url`, makes the manifest invalid (`pack_validation_failed`). The record is by value; this contract defines no registry lookup. A host SHOULD NOT treat `name` as a verified identity: an inline record carries no proof of namespace ownership. A host that authenticates to the server does so through the node's `requiredCredentials` or `auth`.
22
+ A `remote` runtime MAY carry `mcpServer`, an inline subset of an MCP Registry `server.json` (schema `2025-12-11`):
23
+
24
+ - `name`, `description`, `version`;
25
+ - optional `title`, `websiteUrl` and `repository`;
26
+ - exactly one `remotes[]` entry of `type: "streamable-http"` with an `https://` URL.
27
+
28
+ It has no `packages[]`, `headers`, `variables` or `_meta`, so it carries no install instruction and no credential. The record is by value; this contract defines no registry lookup.
29
+
30
+ - `mcpServer` under any other language, or an `entry` that differs from `remotes[0].url`, makes the manifest invalid (`pack_validation_failed`).
31
+ - A host SHOULD NOT treat `name` as a verified identity: an inline record carries no proof of namespace ownership.
32
+ - A host that authenticates to the server does so through the node's `requiredCredentials` or `auth`.
33
+
34
+ *Sources: RFC 0008, RFC 0203.*
@@ -1,19 +1,31 @@
1
1
  # OAuth
2
2
 
3
- > **Status: Stable · RFC 0046, RFC 0047.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `oauth`, `credentials`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A connector node needs a token a user granted to a third party. The host obtains it, stores it, refreshes it and hands it to the node's sandbox, so a pack names a provider and scopes and never touches the grant. Here the host is an OAuth client; identity.md covers the host as a protected resource. The client rules, the MCP-reach paragraph and the credential interrupt below are RFC 0199 (`Active`).
8
+ A connector node needs a token a user granted to a third party. The host obtains, stores and refreshes it and hands it to the node's sandbox, so a pack names a provider and scopes and never touches the grant. Here the host is an OAuth client; [identity.md](identity.md) covers the host as a protected resource.
9
9
 
10
10
  ## Credentials
11
11
 
12
- A host advertising `credentials` MUST resolve a `{ ref, scope }` reference (`schemas/v2/credential-reference.schema.json`) at node execution and inject the material into the node sandbox only; it MUST NOT appear in inputs, variables, events, the debug bundle or replay state (invariant `credential-payload-redaction`). A failed resolution is `credential_not_found`, `credential_forbidden` (outside the caller's scope; fail closed) or `credential_scope_unsupported` (a scope not in `credentials.scopes`). With `credentials.sharing`, every reference within a scope resolves one stored credential. With `credentials.rotation` `two-key-overlap`, old and new material both resolve during the grace window and the old fails `credential_not_found` after it. `credentials.encryptionAtRest` is a claim about storage; it gates nothing.
12
+ A host advertising `credentials` MUST resolve a `{ ref, scope }` reference (`schemas/v2/credential-reference.schema.json`) at node execution and inject the material into the node sandbox only.
13
+
14
+ - The material MUST NOT appear in inputs, variables, events, the debug bundle or replay state (invariant `credential-payload-redaction`).
15
+ - A failed resolution is `credential_not_found`, `credential_forbidden` (outside the caller's scope; fail closed) or `credential_scope_unsupported` (a scope not in `credentials.scopes`).
16
+ - With `credentials.sharing`, every reference within a scope resolves one stored credential.
17
+ - With `credentials.rotation` `two-key-overlap`, old and new material both resolve during the grace window; after it the old fails `credential_not_found`.
18
+ - `credentials.encryptionAtRest` is a claim about storage; it gates nothing.
13
19
 
14
20
  ## Token lifecycle
15
21
 
16
- A host advertising `oauth` MUST perform only the grants in `oauth.grants` and MUST refuse to register a node whose `auth.provider` or scope is not in `oauth.providers` (`oauth_provider_unsupported`, `oauth_scope_unsupported`). It drives the redirect and callback host-side; the code, redirect URI, `state` and PKCE verifier MUST NOT enter a run-visible surface. Tokens persist as a `credentials` entry at scope `user` or `workspace` and are refreshed host-side. On terminal refresh failure the host MUST emit `connector.auth-expired` and fail the node with `connector_auth_expired`, unless it advertises `oauth.credentialInterrupt`.
22
+ A host advertising `oauth`:
23
+
24
+ - MUST perform only the grants in `oauth.grants`.
25
+ - MUST refuse to register a node whose `auth.provider` or scope is not in `oauth.providers` (`oauth_provider_unsupported`, `oauth_scope_unsupported`).
26
+ - Drives the redirect and callback host-side. The code, redirect URI, `state` and PKCE verifier MUST NOT enter a run-visible surface.
27
+ - Persists tokens as a `credentials` entry at scope `user` or `workspace`, and refreshes them host-side.
28
+ - On terminal refresh failure, MUST emit `connector.auth-expired` and fail the node with `connector_auth_expired`, unless it advertises `oauth.credentialInterrupt`.
17
29
 
18
30
  ## The authorization-code client
19
31
 
@@ -25,8 +37,30 @@ On every `authorization_code` grant the host MUST:
25
37
  4. validate `iss` per RFC 9207 §2.4 where the provider's issuer is known (`oauth.providers[].issuer`), and otherwise give the provider a redirect URI no other provider shares (RFC 9700 §4.4.2);
26
38
  5. use one fixed, registered redirect URI per provider.
27
39
 
28
- Where the provider is reached as an MCP server, the host MUST also send `resource` (RFC 8707), the server's canonical URI, in both requests, and MUST refuse a provider whose authorization-server metadata omits `S256`. It fetches the server's Protected Resource Metadata (RFC 9728) only from URLs derived from the manifest's server URL. Discovery verifies and never selects: a discovered issuer or endpoint that differs from the manifest's, or from the tuple pinned at registration, MUST be refused `connection_auth_metadata_mismatch`, as MUST a grant for such a provider whose manifest declares no `issuer`. This binds a provider the host itself configures as well as one a connection pack defines: the configured server URL and issuer stand in for the manifest's, and the tuple is pinned when the host loads that configuration.
40
+ ### MCP-reach providers
41
+
42
+ Where the provider is reached as an MCP server:
43
+
44
+ - The host MUST also send `resource` (RFC 8707), the server's canonical URI, in both requests.
45
+ - The host MUST refuse a provider whose authorization-server metadata omits `S256`.
46
+ - It fetches the server's Protected Resource Metadata (RFC 9728) only from URLs derived from the manifest's server URL.
47
+ - Discovery verifies and never selects. A discovered issuer or endpoint that differs from the manifest's, or from the tuple pinned at registration, MUST be refused `connection_auth_metadata_mismatch`. A grant for such a provider whose manifest declares no `issuer` MUST be refused the same way.
48
+
49
+ This binds a provider the host configures itself as well as one a connection pack defines: the configured server URL and issuer stand in for the manifest's, and the tuple is pinned when the host loads that configuration.
29
50
 
30
51
  ## The credential interrupt
31
52
 
32
- A host advertising `oauth.credentialInterrupt` MUST suspend the node with a `credential` interrupt (interrupt.md) instead of failing it when a node declaring `auth: { type: "oauth2", provider, scopes }` is about to run and no credential resolves for the Subject, provider and scopes (`reason: "missing"`), one resolves with fewer scopes (`"insufficient_scope"`), or refresh failed terminally (`"expired"`). `connectUrl` MUST be host-owned, MUST NOT be pre-authenticated, and MUST complete only for the initiating Subject. The host resolves the interrupt when the grant completes; a resolve of `authorized` MUST be refused `400 validation_error` unless a credential now resolves, and `declined` fails the node with `connector_auth_declined`. A host that binds a node to one credential reference (for example, a connection) reads "resolves" as that reference resolving with the node's scopes, carries it as `credentialRef`, and still completes `connectUrl` only for the initiating Subject.
53
+ A host advertising `oauth.credentialInterrupt` MUST suspend the node with a `credential` interrupt ([interrupt.md](interrupt.md)) instead of failing it when a node declaring `auth: { type: "oauth2", provider, scopes }` is about to run and:
54
+
55
+ - no credential resolves for the Subject, provider and scopes (`reason: "missing"`);
56
+ - one resolves with fewer scopes (`"insufficient_scope"`); or
57
+ - refresh failed terminally (`"expired"`).
58
+
59
+ Then:
60
+
61
+ - `connectUrl` MUST be host-owned, MUST NOT be pre-authenticated, and MUST complete only for the initiating Subject.
62
+ - The host resolves the interrupt when the grant completes. A resolve of `authorized` MUST be refused `400 validation_error` unless a credential now resolves.
63
+ - `declined` fails the node with `connector_auth_declined`.
64
+ - A host that binds a node to one credential reference (for example, a connection) reads "resolves" as that reference resolving with the node's scopes, carries it as `credentialRef`, and still completes `connectUrl` only for the initiating Subject.
65
+
66
+ *Sources: RFC 0046, RFC 0047, RFC 0199.*