@openwop/spec-artifacts 2.42.5 → 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.
- package/CORPUS-STAMP.json +64 -63
- package/api/seams-v2.yaml +238 -316
- package/api/v2/asyncapi.yaml +83 -113
- package/api/v2/openapi.yaml +339 -724
- package/package.json +1 -1
- package/schemas/audit-verify-result.schema.json +2 -2
- package/schemas/v2/audit-verify-result.schema.json +2 -2
- package/schemas/v2/capabilities.schema.json +2 -5
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/gaps.json +171 -1
- package/spec/v2/README.md +34 -36
- package/spec/v2/core/capabilities.md +186 -119
- package/spec/v2/core/conformance.md +78 -27
- package/spec/v2/core/connection-packs.md +8 -3
- package/spec/v2/core/conversation.md +10 -4
- package/spec/v2/core/errors.md +30 -9
- package/spec/v2/core/events.md +116 -80
- package/spec/v2/core/form-content-packs.md +11 -13
- package/spec/v2/core/headers.md +22 -14
- package/spec/v2/core/host-services.md +22 -5
- package/spec/v2/core/idempotency.md +48 -36
- package/spec/v2/core/identity.md +110 -42
- package/spec/v2/core/interop.md +82 -21
- package/spec/v2/core/interrupt.md +79 -40
- package/spec/v2/core/node-pack-runtimes.md +18 -4
- package/spec/v2/core/oauth.md +40 -6
- package/spec/v2/core/overview.md +61 -33
- package/spec/v2/core/packs.md +53 -28
- package/spec/v2/core/persistence.md +61 -80
- package/spec/v2/core/replay.md +56 -42
- package/spec/v2/core/runs.md +198 -60
- package/spec/v2/core/security-defaults.md +57 -33
- package/spec/v2/core/tool-catalog.md +29 -6
- package/spec/v2/core/versioning.md +95 -49
- package/spec/v2/core/webhooks.md +59 -20
- package/spec/v2/core/workflow-chain-packs.md +16 -14
- package/spec/v2/corrections.json +17 -1
- package/spec/v2/corrections.schema.json +1 -1
- package/spec/v2/declaration.json +24 -7
- package/spec/v2/declaration.schema.json +10 -0
- package/spec/v2/errors.json +109 -2
- package/spec/v2/errors.schema.json +38 -0
- package/spec/v2/ext/README.md +36 -23
- package/spec/v2/ext/a2uiSurface/README.md +75 -89
- package/spec/v2/ext/brand/README.md +15 -15
- package/spec/v2/ext/canvas/README.md +15 -15
- package/spec/v2/ext/chat/README.md +15 -15
- package/spec/v2/ext/coordination/README.md +15 -15
- package/spec/v2/ext/dataIntegration/README.md +17 -20
- package/spec/v2/ext/entities/README.md +15 -15
- package/spec/v2/ext/grpc-transport/README.md +6 -8
- package/spec/v2/ext/kanban/README.md +15 -15
- package/spec/v2/ext/knowledge/README.md +15 -15
- package/spec/v2/ext/launchStudio/README.md +16 -16
- package/spec/v2/ext/messaging/README.md +15 -15
- package/spec/v2/ext/portability/README.md +3 -17
- package/spec/v2/ext/provider-idempotency/README.md +7 -10
- package/spec/v2/ext/restTransport/README.md +30 -19
- package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
- package/spec/v2/ext/webResearch/README.md +16 -16
- package/spec/v2/profiles.json +6 -2
- package/spec/v2/release.json +2 -2
- package/spec/v2/surface-baseline.json +542 -2
package/spec/v2/core/interop.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# Interop
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `a2a`, `mcp`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
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.
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
67
|
+
**Isolation.** On either interface:
|
|
55
68
|
|
|
56
|
-
|
|
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).**
|
|
73
|
+
**A2A multi-turn (A2A §3.4.3).**
|
|
59
74
|
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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:
|
|
127
|
+
`GetExtendedAgentCard` with `tenant: R` MUST return that agent's card:
|
|
91
128
|
|
|
92
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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) |
|
|
14
|
+
| Kind | `data` (required fields) | Snapshot status / gate |
|
|
15
15
|
| --- | --- | --- |
|
|
16
|
-
| `approval` | `artifactId`, `artifactType`, `title`, `actions` | 5-action vocabulary
|
|
17
|
-
| `clarification` | `questions[]` (`id`, `question`, optional `schema`) |
|
|
18
|
-
| `external-event` | `eventType`, `correlation` |
|
|
19
|
-
| `custom` | `customKind`, optional `payload` |
|
|
20
|
-
| `conversation.start` | `conversationId` | Gated on
|
|
21
|
-
| `conversation.exchange` | `conversationId`, `prompt` |
|
|
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` |
|
|
24
|
-
| `credential` | `provider`, `scopes`, `reason`, `connectUrl` |
|
|
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
|
-
|
|
26
|
+
Per-kind rules:
|
|
27
27
|
|
|
28
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
94
|
+
The token grammar and the `interrupt.tokenAlgs[]` / `kid` check are [identity.md](identity.md) §4 (`401 interrupt_token_invalid`).
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
host
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `nodePackRuntimes`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
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
|
-
|
|
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.*
|
package/spec/v2/core/oauth.md
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
# OAuth
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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.*
|