@openwop/spec-artifacts 2.35.1 → 2.36.0
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 +103 -89
- package/api/openapi.yaml +325 -4
- package/api/seams-v2.yaml +342 -1
- package/api/v2/asyncapi.yaml +1 -1
- package/api/v2/openapi.yaml +564 -8
- package/package.json +1 -1
- package/schemas/a2a-task-state.schema.json +3 -3
- package/schemas/artifact-type-pack-manifest.schema.json +3 -3
- package/schemas/capabilities.schema.json +34 -1
- package/schemas/connection-pack-manifest.schema.json +14 -0
- package/schemas/debug-bundle.schema.json +20 -0
- package/schemas/dispatch-config.schema.json +1 -1
- package/schemas/localized-content-language-settings.schema.json +3 -3
- package/schemas/localized-content-page.schema.json +2 -2
- package/schemas/localized-content-section.schema.json +3 -3
- package/schemas/run-event-payloads.schema.json +4 -2
- package/schemas/suspend-request.schema.json +61 -4
- package/schemas/v2/a2a-task-state.schema.json +10 -9
- package/schemas/v2/agent-deployment-transition.schema.json +4 -4
- package/schemas/v2/agent-deployment.schema.json +7 -7
- package/schemas/v2/agent-eval-suite.schema.json +10 -10
- package/schemas/v2/agent-inventory-response.schema.json +8 -2
- package/schemas/v2/artifact-type-pack-manifest.schema.json +3 -3
- package/schemas/v2/artifact.schema.json +84 -0
- package/schemas/v2/capabilities.schema.json +82 -8
- package/schemas/v2/certification-bundle.schema.json +1 -1
- package/schemas/v2/channel-presence-payload.schema.json +3 -3
- package/schemas/v2/connection-pack-manifest.schema.json +14 -0
- package/schemas/v2/conversation-event.schema.json +9 -2
- package/schemas/v2/conversation-turn.schema.json +19 -12
- package/schemas/v2/dispatch-config.schema.json +1 -1
- package/schemas/v2/envelopes/ui.a2ui-surface.schema.json +920 -22
- package/schemas/v2/error-envelope.schema.json +11 -1
- package/schemas/v2/eval-summary.schema.json +1 -2
- package/schemas/v2/goal.schema.json +4 -4
- package/schemas/v2/localized-content-language-settings.schema.json +3 -3
- package/schemas/v2/localized-content-page.schema.json +2 -2
- package/schemas/v2/localized-content-section.schema.json +3 -3
- package/schemas/v2/node-pack-manifest.schema.json +105 -0
- package/schemas/v2/part.schema.json +81 -0
- package/schemas/v2/proposal.schema.json +6 -6
- package/schemas/v2/run-event-payloads.schema.json +5 -3
- package/schemas/v2/self-hosted-runner-dispatch-frame.schema.json +2 -2
- package/schemas/v2/self-hosted-runner-result-frame.schema.json +1 -1
- package/schemas/v2/suspend-request.schema.json +251 -6
- package/schemas/v2/tool-descriptor.schema.json +246 -2
- package/schemas/v2/trigger-event.schema.json +1 -2
- package/schemas/v2/trigger-subscription.schema.json +1 -2
- package/schemas/v2/webhook-verification.schema.json +22 -0
- package/schemas/v2/workflow-definition.schema.json +25 -25
- package/spec/v1/alias-detectors.json +13 -1
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +45 -1
- package/spec/v1/deprecations.schema.json +119 -4
- package/spec/v1/gaps.json +1925 -4
- package/spec/v2/README.md +5 -2
- package/spec/v2/core/capabilities.md +13 -26
- package/spec/v2/core/conformance.md +5 -8
- package/spec/v2/core/connection-packs.md +1 -1
- package/spec/v2/core/conversation.md +1 -1
- package/spec/v2/core/errors.md +8 -3
- package/spec/v2/core/events.md +8 -20
- package/spec/v2/core/form-content-packs.md +2 -2
- package/spec/v2/core/headers.md +5 -4
- package/spec/v2/core/host-services.md +6 -2
- package/spec/v2/core/idempotency.md +1 -1
- package/spec/v2/core/identity.md +23 -12
- package/spec/v2/core/interop.md +34 -4
- package/spec/v2/core/interrupt.md +6 -3
- package/spec/v2/core/node-pack-runtimes.md +20 -0
- package/spec/v2/core/oauth.md +32 -0
- package/spec/v2/core/overview.md +19 -6
- package/spec/v2/core/packs.md +4 -6
- package/spec/v2/core/persistence.md +20 -55
- package/spec/v2/core/replay.md +5 -11
- package/spec/v2/core/runs.md +9 -8
- package/spec/v2/core/security-defaults.md +18 -20
- package/spec/v2/core/tool-catalog.md +22 -0
- package/spec/v2/core/versioning.md +10 -28
- package/spec/v2/core/webhooks.md +16 -4
- package/spec/v2/core/workflow-chain-packs.md +1 -1
- package/spec/v2/corrections.json +32 -0
- package/spec/v2/corrections.schema.json +49 -0
- package/spec/v2/declaration.json +18 -13
- package/spec/v2/declaration.schema.json +3 -1
- package/spec/v2/errors.json +46 -1
- package/spec/v2/ext/a2uiSurface/README.md +131 -17
- package/spec/v2/ext/dataIntegration/README.md +5 -0
- package/spec/v2/facets/a2a.schema.json +4 -0
- package/spec/v2/facets/auth.schema.json +24 -0
- package/spec/v2/facets/mcp.schema.json +4 -0
- package/spec/v2/facets/oauth.schema.json +70 -0
- package/spec/v2/facets/webhooks.schema.json +23 -2
- package/spec/v2/id-field-bindings.json +2 -0
- package/spec/v2/interop-map.json +639 -0
- package/spec/v2/interop-map.schema.json +458 -0
- package/spec/v2/migrations.json +7 -0
- package/spec/v2/migrations.schema.json +58 -0
- package/spec/v2/path-manifest.json +6 -1
- package/spec/v2/release.json +3 -3
- package/spec/v2/surface-baseline.json +7981 -0
package/spec/v2/core/interop.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
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.
|
|
9
9
|
|
|
10
10
|
## REST is the wire
|
|
11
11
|
|
|
@@ -49,18 +49,48 @@ The event is content-free: `peer` MUST be a digest of the peer origin, never the
|
|
|
49
49
|
|
|
50
50
|
**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
51
|
|
|
52
|
+
## The operation mappings (RFC 0208)
|
|
53
|
+
|
|
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.
|
|
55
|
+
|
|
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.
|
|
57
|
+
|
|
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.
|
|
59
|
+
|
|
60
|
+
## MCP tasks and cancellation (RFC 0198)
|
|
61
|
+
|
|
62
|
+
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:
|
|
63
|
+
|
|
64
|
+
- MUST answer a `tools/call` that declared the extension with `CreateTaskResult` whenever the run is not terminal when the host answers, never with `InputRequiredResult`;
|
|
65
|
+
- 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;
|
|
66
|
+
- MUST NOT append to a run's log to answer `tasks/get`.
|
|
67
|
+
|
|
68
|
+
**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.
|
|
69
|
+
|
|
52
70
|
## The MCP round ceiling
|
|
53
71
|
|
|
54
|
-
`mcp.mrtr.maxRounds` (integer, 1–16) is the advertised ceiling on multi-round tool-result rounds. A host MUST refuse an `input_required` round beyond `maxRounds` with `mcp_mrtr_rounds_exceeded` (`spec/v2/errors.json`). The
|
|
72
|
+
`mcp.mrtr.maxRounds` (integer, 1–16) is the advertised ceiling on multi-round tool-result rounds. A host MUST refuse an `input_required` round beyond `maxRounds` with `mcp_mrtr_rounds_exceeded` (`spec/v2/errors.json`). The `requestState` rules are the map's `mcp.mrtr` rows.
|
|
55
73
|
|
|
56
74
|
## The durable-task projection
|
|
57
75
|
|
|
58
|
-
`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
|
|
76
|
+
`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.
|
|
77
|
+
|
|
78
|
+
## Per-agent cards
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
`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.
|
|
83
|
+
|
|
84
|
+
**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**.
|
|
59
85
|
|
|
60
86
|
## gRPC
|
|
61
87
|
|
|
62
88
|
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.
|
|
63
89
|
|
|
90
|
+
## Trace context (RFC 0207)
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
64
94
|
## Threat model
|
|
65
95
|
|
|
66
|
-
`SECURITY/threat-model-interop.md` is the threat model for this document
|
|
96
|
+
`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.
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
| `conversation.exchange` | `conversationId`, `prompt` | The resume value MUST validate against `outcomeSchema` when supplied |
|
|
22
22
|
| `conversation.close` | `conversationId` | Gated as above |
|
|
23
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 |
|
|
25
|
+
|
|
26
|
+
An interrupt of any kind MUST NOT solicit credential material; a credential is acquired through `credential`.
|
|
24
27
|
|
|
25
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.
|
|
26
29
|
|
|
@@ -38,7 +41,7 @@ Every kind uses two registered types (events.md): `interrupt.requested`, whose p
|
|
|
38
41
|
|
|
39
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`.
|
|
40
43
|
|
|
41
|
-
**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` §
|
|
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.
|
|
42
45
|
|
|
43
46
|
| Status | Code | Condition |
|
|
44
47
|
| --- | --- | --- |
|
|
@@ -50,7 +53,7 @@ A host MUST expose the run-scoped surface and SHOULD expose the signed-token sur
|
|
|
50
53
|
|
|
51
54
|
## Tokens
|
|
52
55
|
|
|
53
|
-
The token grammar
|
|
56
|
+
The token grammar, the `interrupt.tokenAlgs[]` / `kid` check and the v1 drain are identity.md §4 (`401 interrupt_token_invalid`).
|
|
54
57
|
|
|
55
58
|
| Rule | Requirement |
|
|
56
59
|
| --- | --- |
|
|
@@ -89,4 +92,4 @@ enforce the same eligibility at the store, or MUST NOT expose the record to
|
|
|
89
92
|
that principal for write: a rule enforced per route is only as complete as the
|
|
90
93
|
census of writers (RFC 0187 §C.1).
|
|
91
94
|
|
|
92
|
-
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.
|
|
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.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Node-pack runtimes
|
|
2
|
+
|
|
3
|
+
> **Status: Stable · RFC 0008.**
|
|
4
|
+
> **Normative home:** `nodePackRuntimes`.
|
|
5
|
+
|
|
6
|
+
## Why this exists
|
|
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`.
|
|
9
|
+
|
|
10
|
+
## Languages
|
|
11
|
+
|
|
12
|
+
`runtime.language` is one of `javascript`, `python`, `go`, `wasm`, `wasm-component`, `remote`. `entry` is a path inside the tarball, or, for `remote`, the URL of an MCP server the host calls as an MCP client. A host MAY refuse a language it cannot execute, at workflow registration and with `unsupported_runtime`.
|
|
13
|
+
|
|
14
|
+
## WASM
|
|
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.
|
|
17
|
+
|
|
18
|
+
## The MCP registry record
|
|
19
|
+
|
|
20
|
+
RFC 0203 (`Active`): 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`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# OAuth
|
|
2
|
+
|
|
3
|
+
> **Status: Stable · RFC 0046, RFC 0047.**
|
|
4
|
+
> **Normative home:** `oauth`, `credentials`.
|
|
5
|
+
|
|
6
|
+
## Why this exists
|
|
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`).
|
|
9
|
+
|
|
10
|
+
## Credentials
|
|
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.
|
|
13
|
+
|
|
14
|
+
## Token lifecycle
|
|
15
|
+
|
|
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`.
|
|
17
|
+
|
|
18
|
+
## The authorization-code client
|
|
19
|
+
|
|
20
|
+
On every `authorization_code` grant the host MUST:
|
|
21
|
+
|
|
22
|
+
1. send PKCE with `S256` and never `plain`, omitting PKCE only for a provider advertised with `pkce: "unsupported"`;
|
|
23
|
+
2. send a fresh, unguessable `state` bound to the initiating Subject, and refuse a callback whose `state` is absent, unknown, reused or expired, making no token request for it;
|
|
24
|
+
3. complete the callback only for the initiating Subject (invariant `oauth-same-user-binding`);
|
|
25
|
+
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
|
+
5. use one fixed, registered redirect URI per provider.
|
|
27
|
+
|
|
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`.
|
|
29
|
+
|
|
30
|
+
## The credential interrupt
|
|
31
|
+
|
|
32
|
+
A host advertising `oauth.credentialInterrupt` MUST suspend the node with a `credential` interrupt (interrupt.md) instead of failing it when no credential resolves for the Subject or refresh failed terminally. `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`.
|
package/spec/v2/core/overview.md
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
## Reading order
|
|
10
10
|
|
|
11
|
-
1. `overview.md` — axioms, §0, claim vocabulary, `ext/` rule
|
|
11
|
+
1. `overview.md` — axioms, §0, §0a, claim vocabulary, `ext/` rule
|
|
12
12
|
2. `versioning.md` — major negotiation, `OpenWOP-Version`, the 18 axes, release identity
|
|
13
13
|
3. `capabilities.md` — one well-known resource, record type, closed root, derived profiles
|
|
14
14
|
4. `identity.md` — Subject, lanes, `SubjectLink`, id grammars, resume tokens
|
|
15
15
|
5. `runs.md` — create / get / cancel / fork, `configurable`, snapshot, owner
|
|
16
16
|
6. `events.md`, `errors.md`, `headers.md` — event `oneOf`, payload and error registries, `OpenWOP-*`
|
|
17
|
-
7. `
|
|
17
|
+
7. `interrupt.md`, `idempotency.md`, `replay.md`, `conversation.md` — run-side surfaces
|
|
18
18
|
8. `persistence.md` — era key, v1 reader rule, pinned runs
|
|
19
|
-
9. `security-defaults.md`, `webhooks.md`, `interop.md` — obligations of a surface, signatures, A2A / MCP
|
|
19
|
+
9. `security-defaults.md`, `webhooks.md`, `interop.md`, `host-services.md` — obligations of a surface, signatures, A2A / MCP, host services
|
|
20
20
|
10. `packs.md`, `connection-packs.md`, `form-content-packs.md`, `workflow-chain-packs.md` — pack identity, engines ceiling
|
|
21
21
|
11. `conformance.md` — requirement ids, witness classes, bundle v3, seams profile
|
|
22
22
|
|
|
@@ -31,7 +31,20 @@
|
|
|
31
31
|
|
|
32
32
|
## §0 Closed-enum growth rule (RFC 0171 §A.5)
|
|
33
33
|
|
|
34
|
-
A registry-backed enum (event types, error codes, envelope kinds, reason vocabularies, lanes) grows by adding a row to its registry and regenerating. Consumers MUST accept an unknown member of a registry-backed enum and MUST NOT act on it. Producers MUST NOT emit an unregistered member. Adding a member is additive in v2.x;
|
|
34
|
+
A registry-backed enum (event types, error codes, envelope kinds, reason vocabularies, lanes) grows by adding a row to its registry and regenerating. Consumers MUST accept an unknown member of a registry-backed enum and MUST NOT act on it. Producers MUST NOT emit an unregistered member. Adding a member is additive in v2.x; renaming one is a major, and removing one is a major except under §0a.
|
|
35
|
+
|
|
36
|
+
## §0a Retiring a v2 surface (RFC 0197)
|
|
37
|
+
|
|
38
|
+
A 2.x minor MUST NOT change the shape of an existing v2 surface; a new shape is a new surface added beside the old one. A 2.x minor MAY remove a surface only when `scripts/check-v2-retirement.mjs` proves all of the following; otherwise the removal waits for 3.0.
|
|
39
|
+
|
|
40
|
+
1. Its replacement shipped in an earlier 2.x minor.
|
|
41
|
+
2. A `v2-minor` row in `spec/v1/deprecations.json` named the removal minor at least two minors and 30 days earlier.
|
|
42
|
+
3. No committed v2 host bundle and no published registry manifest carries it.
|
|
43
|
+
4. It is an optional family, facet, enum member or envelope kind, so its absence is already a 2.0 state.
|
|
44
|
+
5. Its family is `experimental` in `spec/v2/declaration.json`.
|
|
45
|
+
6. No independent host is in the INTEROP-MATRIX v2 table.
|
|
46
|
+
|
|
47
|
+
Readers MUST keep accepting a retired shape on replay, fork and poll; only emission narrows.
|
|
35
48
|
|
|
36
49
|
## v1 end-of-support (RFC 0174 §B.4)
|
|
37
50
|
|
|
@@ -51,8 +64,8 @@ Normative for any public conformance statement:
|
|
|
51
64
|
|
|
52
65
|
## What is `ext/` (RFC 0174 §E.2; RFC 0169 §B.3)
|
|
53
66
|
|
|
54
|
-
`spec/v2/core/`
|
|
67
|
+
`spec/v2/core/` stays within the word budget `scripts/check-core-budget.mjs` enforces (RFC 0190). Every `spec/v2/ext/<key>/` document MUST declare `witness` and both maturity axes (`technical`, `adoption`) in its header. A MUST with `witness: unwitnessable` MUST NOT appear in `core/`; a document whose only witness is "deferred to Active → Accepted" enters `ext/` or is deleted. An `ext/` family is advertised only under a wire-legal witness class (see `capabilities.md`).
|
|
55
68
|
|
|
56
69
|
## What a MUST means (RFC 0168 §B.1; Axiom 1)
|
|
57
70
|
|
|
58
|
-
Every MUST, SHOULD, and MAY in `core/` is a requirement with an id in `requirements.json` and a `witness` from `witnessable-unaided | witnessable-gated | seam-gated | claims-check | negative-existence`. A seam-gated MUST
|
|
71
|
+
Every MUST, SHOULD, and MAY in `core/` is a requirement with an id in `requirements.json` and a `witness` from `witnessable-unaided | witnessable-gated | seam-gated | claims-check | negative-existence`. A seam-gated MUST is governed by `conformance.md` §"Witness class".
|
package/spec/v2/core/packs.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The v2 contract for pack manifests, the registry tree, peer-dependency identifiers, and signing. The per-kind rules live in connection-packs.md, form-content-packs.md, and workflow-chain-packs.md; the capability vocabulary a pack requires is capabilities.md.
|
|
9
9
|
|
|
10
10
|
## The engine range
|
|
11
11
|
|
|
@@ -20,10 +20,8 @@ client MUST treat an absent record as "this host installs no packs" rather than
|
|
|
20
20
|
as an unknown.
|
|
21
21
|
|
|
22
22
|
`testMode` is DEPRECATED and MUST NOT be relied on by a client. It advertises
|
|
23
|
-
the v1 `/v1/packs-test/*` mirror, a conformance seam
|
|
24
|
-
§"The seams profile"
|
|
25
|
-
and the `/conformance/seams/` path space, not in the capability namespace. It
|
|
26
|
-
remains advertisable through the overlap because hosts already publish it; it is
|
|
23
|
+
the v1 `/v1/packs-test/*` mirror, a conformance seam (`conformance.md`
|
|
24
|
+
§"The seams profile"), and remains advertisable through the overlap because hosts already publish it; it is
|
|
27
25
|
removed at 3.0. A host mounting a test catalog SHOULD advertise the seams
|
|
28
26
|
profile instead, and MUST NOT treat `testMode` as a second way to claim one.
|
|
29
27
|
|
|
@@ -58,7 +56,7 @@ The 13 manifest schemas carry `$id` under `https://openwop.dev/spec/v2/`; the v1
|
|
|
58
56
|
| `security-advisory` | registry | closed |
|
|
59
57
|
| `prompt-ref` | leaf | none |
|
|
60
58
|
|
|
61
|
-
Every pack-authored document MUST admit `patternProperties` `^(openwop-|x-|vendor\.)`.
|
|
59
|
+
Every pack-authored document MUST admit `patternProperties` `^(openwop-|x-|vendor\.)`. A consumer that does not recognize a hatch property MUST ignore it and MUST NOT reject the document; the value is pack-authored and therefore untrusted (security-defaults.md).
|
|
62
60
|
|
|
63
61
|
## Signing
|
|
64
62
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
How a v2 host reads what a v1 host wrote, what happens to a run in flight at the cut, and what each persisted store becomes — so two hosts read one log one way.
|
|
9
9
|
|
|
10
10
|
## The codemap is data
|
|
11
11
|
|
|
@@ -25,30 +25,14 @@ The v2 cut renames event types that are persisted, indexed, and unique-keyed in
|
|
|
25
25
|
Discovery MUST advertise the value the host writes for new runs and nothing else; a host MUST hold one constant for this axis.
|
|
26
26
|
|
|
27
27
|
**Absent stays era `2` forever; it is never backfilled.** A host MUST NOT rewrite
|
|
28
|
-
historical rows to add an explicit `2`, and a reader MUST NOT require one.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
indistinguishable from pre-cut runs and every reader will translate them as era
|
|
32
|
-
`2` — a silent wrong read, not an error. So a host with more than one creation
|
|
33
|
-
path MUST begin stamping `3` on **all** of them in the same change; staging that
|
|
34
|
-
across deploys is the failure this rule exists to prevent.
|
|
35
|
-
|
|
36
|
-
**Collapsing to one constant is a precondition for advertising, not a
|
|
37
|
-
consequence.** A host whose creation paths disagree — one writing `2`, another
|
|
38
|
-
writing nothing — has no single value to advertise, and whatever it publishes is
|
|
39
|
-
false for some of its own runs. Unify the writers first, then advertise. This is
|
|
40
|
-
the same class of constraint as the writer rule below and is ordered the same
|
|
41
|
-
way: the store is made coherent before the wire describes it.
|
|
28
|
+
historical rows to add an explicit `2`, and a reader MUST NOT require one. A host with more than one creation
|
|
29
|
+
path MUST begin stamping `3` on **all** of them in the same change: an unstamped
|
|
30
|
+
path's runs read as era `2`, a silent wrong read.
|
|
42
31
|
|
|
43
32
|
**The snapshot field is required on the wire, and MAY be synthesized.**
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
fail the read; a missing *stored* era is not a read error. The consequence is
|
|
48
|
-
worth stating plainly: on the wire this field is never absent, so it cannot
|
|
49
|
-
falsify a host's era handling on its own. What falsifies that is the vocabulary
|
|
50
|
-
of the events themselves, which is why the reader and writer rules below carry
|
|
51
|
-
the obligation and this field only reports it.
|
|
33
|
+
For an era-`2` run with nothing stored, the host MUST supply `2` from the
|
|
34
|
+
absent-⇒-`2` rule rather than fail the read. The field therefore cannot falsify
|
|
35
|
+
era handling on its own; the reader and writer rules below carry the obligation.
|
|
52
36
|
|
|
53
37
|
## The `eventLog` family
|
|
54
38
|
|
|
@@ -88,35 +72,24 @@ A writer that emits a property a closed def cannot seat (RFC 0185 §B) MUST
|
|
|
88
72
|
mark the row with what it could not seat, so the refusal names the writer
|
|
89
73
|
instead of surfacing as an unexplained read failure (RFC 0187 §D.1).
|
|
90
74
|
|
|
91
|
-
This binds every writer for as long as an era-`2` run stays open
|
|
92
|
-
|
|
93
|
-
serving v2 is not the path — see §"Runs pinned to v1" — so the writer rule is
|
|
94
|
-
what makes an in-flight run safe across the cut. Its witness is
|
|
95
|
-
`v2-era-2-append-vocabulary`.
|
|
75
|
+
This binds every writer for as long as an era-`2` run stays open (§"Runs pinned
|
|
76
|
+
to v1"); its witness is `v2-era-2-append-vocabulary`.
|
|
96
77
|
|
|
97
78
|
### The v1 wire of an era-`3` log
|
|
98
79
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
their `/v1/…` path keys unchanged (§1.2), so the mirror case is forced and the
|
|
102
|
-
corpus owed it a rule: a run created today is era `3` and its log is stored in
|
|
103
|
-
v2 vocabulary, yet the same log must still be readable on `/v1/…` exactly as it
|
|
104
|
-
was before the cut.
|
|
80
|
+
Through the overlap (`versioning.md` §5) an era-`3` log, stored in v2
|
|
81
|
+
vocabulary, must still read on `/v1/…` exactly as before the cut.
|
|
105
82
|
|
|
106
83
|
A host serving both majors MUST therefore map an era-`3` log's `type` back to
|
|
107
84
|
its v1 spelling on the v1 read path, through the **same codemap row, inverted**.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
property at load rather than assume it; if a future row folds two v1 names onto
|
|
112
|
-
one v2 name, the inverse stops being a function and the host MUST refuse to
|
|
113
|
-
serve the v1 representation rather than guess which spelling to emit.
|
|
85
|
+
The inverse is exact only while `spec/v2/event-codemap.json` is a bijection; a
|
|
86
|
+
host MUST verify that at load and, if a row folds two v1 names onto one v2 name,
|
|
87
|
+
MUST refuse to serve the v1 representation rather than guess a spelling.
|
|
114
88
|
|
|
115
89
|
A type with NO codemap row — v2-only vocabulary, anything RFC 0185/0186 seated
|
|
116
90
|
— has no v1 spelling to invert to. A host MUST emit it unchanged on the v1 read
|
|
117
91
|
path, MUST NOT drop the row, and MUST NOT refuse the read for it (RFC 0187
|
|
118
|
-
§B.1)
|
|
119
|
-
lose data or make one new row cost an otherwise readable run.
|
|
92
|
+
§B.1).
|
|
120
93
|
|
|
121
94
|
### The seat
|
|
122
95
|
|
|
@@ -124,10 +97,6 @@ The adapter MUST sit at the storage boundary every reader passes through — the
|
|
|
124
97
|
|
|
125
98
|
The seat is a **claims-check** (conformance.md §Witness class): discharged by that disclosure and by audit, never by the wire. The rule binds **every** reader, including the ones the suite has no name for; `run-event.schema.json` records why three passing legs do not discharge it.
|
|
126
99
|
|
|
127
|
-
### Forking a v1 run
|
|
128
|
-
|
|
129
|
-
A fork of an era-`2` run MUST produce a prefix byte-equivalent to the translated parent, and its `run.started` MUST carry the legacy Subject where the parent had none (RFC 0176 §A.5; replay.md, identity.md).
|
|
130
|
-
|
|
131
100
|
## Runs pinned to v1
|
|
132
101
|
|
|
133
102
|
A non-terminal run a v2 host inherits carries `version.pinned` events naming change ids. The host MUST continue it or cancel it, never follow a pin silently.
|
|
@@ -136,7 +105,7 @@ A non-terminal run a v2 host inherits carries `version.pinned` events naming cha
|
|
|
136
105
|
| --- | --- |
|
|
137
106
|
| Every pinned change id is still implemented | The run MUST continue under the reader rule; the pin is honored verbatim and `version.pinned` is never rewritten. |
|
|
138
107
|
| Any pinned change id is no longer implemented | The host MUST cancel the run with `run.cancelled` reason `v1_pin_unsupported` and `cancelledBy: "v2-cutover"`; the certification bundle reports the count. |
|
|
139
|
-
| Suspended on an interrupt at the cut | The run continues under the row above; its
|
|
108
|
+
| Suspended on an interrupt at the cut | The run continues under the row above; its token drains per §"Everything else a v1 host persisted". |
|
|
140
109
|
|
|
141
110
|
"Drain" is retired as the only path. Multi-region skew is read-side only: after the cut a v2 region MUST NOT accept an era-`2` write for a run it has already stamped `3` (RFC 0176 §B.3). Discovery's `minClientVersion` rule is RFC 0172 row `C5.8`.
|
|
142
111
|
|
|
@@ -145,8 +114,8 @@ A non-terminal run a v2 host inherits carries `version.pinned` events naming cha
|
|
|
145
114
|
| Artifact | Requirement |
|
|
146
115
|
| --- | --- |
|
|
147
116
|
| Certification bundles | Never upgraded. A v1 bundle substantiates no new certification after 2026-11-10; every host produces a fresh v2-rc bundle before the cut (conformance.md). |
|
|
148
|
-
| Webhook deliveries |
|
|
149
|
-
| Interrupt resume tokens | Drained: a token that is **not** `ow2.`-prefixed is a v1 token and resolves under `kid: legacy` until `expiresAt`; new tokens carry the `ow2.` prefix (identity.md). The rule is written over the prefix
|
|
117
|
+
| Webhook deliveries | Dual-emitted, and queued deliveries drained, per webhooks.md §"Dual emission through the overlap". |
|
|
118
|
+
| Interrupt resume tokens | Drained: a token that is **not** `ow2.`-prefixed is a v1 token and resolves under `kid: legacy` until `expiresAt`; new tokens carry the `ow2.` prefix (identity.md). The rule is written over the prefix, never a segment count. |
|
|
150
119
|
| Layer-1 and Layer-2 records (idempotency, idempotent responses, invocation claims and logs, effect-escape ledger, dispatch outbox, envelope correlations) | Unchanged; keyed on ids the cut does not rename. `GET /runs/{runId}/effects` and `GET /runs/{runId}/compensation` are new reads over them (security-defaults.md). |
|
|
151
120
|
| Owner stamps | A run without a Subject MUST be legacy-stamped at first v2 read and MUST NOT be rewritten later (RFC 0170 §A.3). A host's stored owner fields are projected to the Subject; the projection is the host's to name. |
|
|
152
121
|
| Audit log | Never upgraded (RFC 0170). |
|
|
@@ -170,7 +139,7 @@ Template — one row per store:
|
|
|
170
139
|
| --- | --- | --- |
|
|
171
140
|
| events | v1 vocabulary; `UNIQUE (runId, sequence)` | `translated` |
|
|
172
141
|
| runs | no `eventLogSchemaVersion`; owner fields | `legacy-stamped` |
|
|
173
|
-
| interrupts |
|
|
142
|
+
| interrupts | un-prefixed (v1) tokens | `drained` |
|
|
174
143
|
| webhook subscriptions and queued deliveries | subscriptions; serialized deliveries | `unchanged`; `drained` |
|
|
175
144
|
| idempotency, invocation, outbox, correlation tables | keyed records | `unchanged` |
|
|
176
145
|
| audit log | audit facts | `never-upgraded` |
|
|
@@ -181,7 +150,7 @@ The reference hosts' dispositions are recorded in RFC 0176; a host MUST NOT deci
|
|
|
181
150
|
|
|
182
151
|
## Durable acceptance and recovery
|
|
183
152
|
|
|
184
|
-
RFC 0158 §A–§D
|
|
153
|
+
RFC 0158 §A–§D.
|
|
185
154
|
|
|
186
155
|
| Clause | Requirement |
|
|
187
156
|
| --- | --- |
|
|
@@ -193,8 +162,4 @@ RFC 0158 §A–§D, restated because its v1 homes (`idempotency.md`, `storage-ad
|
|
|
193
162
|
|
|
194
163
|
A host MAY claim a qualification rung (`durable-single-instance`, `durable-multi-instance`, `multi-region-qualified`; cumulative) only with the evidence named for it, and MUST NOT claim one from tests in which no process was terminated (§D.9). A rung is evidence, not a capability: discovery carries none, and the rung and its bounds are published in the certification bundle (conformance.md §Bundle v3).
|
|
195
164
|
|
|
196
|
-
## The corpus-tag pin
|
|
197
|
-
|
|
198
|
-
A consumer that vendors any file from `schemas/`, `api/`, or `spec/` MUST pin to a published `openwop-conformance/vX.Y.Z` tag, MUST record the tag, and MUST refuse a sync from any other ref. A v1.x consumer MUST NOT vendor `schemas/v2/` (RFC 0176 §E.1). The `corpus-tag-pinned` check verifies that each consumer's recorded tag resolves (conformance.md).
|
|
199
|
-
|
|
200
165
|
See also: overview.md, events.md, replay.md, identity.md, webhooks.md.
|
package/spec/v2/core/replay.md
CHANGED
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`POST /runs/{runId}:fork` makes any past state of a run re-executable: a replay proves that current code reproduces recorded history; a branch explores an alternative from a recorded point. This document states what a fork MUST reproduce, what it MUST NOT re-fire, and how a host proves the second.
|
|
9
9
|
|
|
10
10
|
## The surface
|
|
11
11
|
|
|
12
12
|
A host that advertises `replay` (capabilities.md) serves `forkRun` (`api/v2/openapi.yaml`, `POST /runs/{runId}:fork`) and `getEffectSeamManifest` (`GET /host/effect-seams`). The `replay` facet (`spec/v2/facets/replay.schema.json`) is `{ modes[], retention?, effectSeamsManifest }`; `modes` enumerates `fork | branch | rerun`; `effectSeamsManifest` is the constant `/host/effect-seams`. There is no `sideEffectSuppression` field: suppression is the only conforming replay behavior (RFC 0173 §B, row `C6.2`) and `none` is not a value.
|
|
13
13
|
|
|
14
|
-
The request body
|
|
14
|
+
The request body, `fromSeq` defaults and `201` response are runs.md §Fork. Events with `sequence < fromSeq` are fixed history; events `>= fromSeq` are re-executed.
|
|
15
15
|
|
|
16
16
|
| Rule | Requirement |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| `fromSeq` for `replay` | MAY be omitted; omission means `0` (full re-execution). |
|
|
19
|
-
| `fromSeq` for `branch` | MUST be supplied. |
|
|
20
|
-
| `runOptionsOverlay` | MUST be omitted or empty for `replay`; MAY be supplied for `branch`. |
|
|
21
18
|
| `fromSeq` out of range | `400`; a sequence absent from the source log is `422`. A `fromSeq` greater than the sequence of the source run's terminal run event MUST be refused `422 fork_point_invalid` — the fork would inherit a terminal event and then execute (RFC 0194 §C; binds only where a compensation tail follows the terminal event). |
|
|
22
19
|
| Source run not visible to the caller | `404`. |
|
|
23
|
-
| Response | `201` `{ runId, sourceRunId, fromSeq, mode, status, eventsUrl }`; the fork is a new run with its own log. |
|
|
24
|
-
|
|
25
|
-
The fork's `owner.tenant` and `owner.subject` MUST be copied verbatim from the source run (RFC 0165 §B.4; identity.md).
|
|
26
20
|
|
|
27
21
|
## Modes
|
|
28
22
|
|
|
@@ -34,7 +28,7 @@ The fork's `owner.tenant` and `owner.subject` MUST be copied verbatim from the s
|
|
|
34
28
|
|
|
35
29
|
The replay contract is observable-output-sequence determinism, not bit-equivalent execution (RFC 0041 §C):
|
|
36
30
|
|
|
37
|
-
1. The events at indices `[0, fromSeq)` MUST be byte-equivalent between source and replay, modulo per-region clock fields (RFC 0036 §E) and ULID time-component entropy when ULIDs are minted fresh. The
|
|
31
|
+
1. The events at indices `[0, fromSeq)` MUST be byte-equivalent between source and replay, modulo per-region clock fields (RFC 0036 §E) and ULID time-component entropy when ULIDs are minted fresh. The event at `fromSeq` is governed by §Divergence.
|
|
38
32
|
2. `variables`, `channels`, and `status` of the run snapshot at each index in that range MUST be byte-equivalent.
|
|
39
33
|
3. The bytes on the wire of underlying tool and LLM calls MAY differ, provided the observable state at each index is byte-equivalent.
|
|
40
34
|
|
|
@@ -82,9 +76,9 @@ Pure nodes and LLM calls served from the invocation log MUST re-execute live; ot
|
|
|
82
76
|
|
|
83
77
|
A host advertising `replay` MUST publish `schemas/v2/effect-seam-manifest.schema.json`-shaped data at `GET /host/effect-seams` (RFC 0173 §C.1): `{ manifestVersion: "1", host: { name, build }, seams[] }`, one row per outbound effect path its node runtime can reach, `{ seam, kind, guarded: true, guardedBy, branchReFires?, note? }`. The host owns the list. The suite drives one seam of each kind it can reach and observes no re-fire (`effect-seam-manifest`, conformance.md).
|
|
84
78
|
|
|
85
|
-
`kind` names the outbound **wire mechanism** the seam leaves the host by — not the suite's driving mechanism, and not the business purpose. It MUST be one of `http`, `smtp`, `queue`, `storage`, `provider-sdk`, `webhook-fanout`, `other`. Two seams that a host guards through one code path but that leave by different mechanisms are different `kind`s; two that leave by the same mechanism for different business reasons are one. `other` is the escape for a mechanism this list does not name — raw TCP, gRPC, a filesystem write, a device SDK — and a row using it MUST carry `note` naming that mechanism
|
|
79
|
+
`kind` names the outbound **wire mechanism** the seam leaves the host by — not the suite's driving mechanism, and not the business purpose. It MUST be one of `http`, `smtp`, `queue`, `storage`, `provider-sdk`, `webhook-fanout`, `other`. Two seams that a host guards through one code path but that leave by different mechanisms are different `kind`s; two that leave by the same mechanism for different business reasons are one. `other` is the escape for a mechanism this list does not name — raw TCP, gRPC, a filesystem write, a device SDK — and a row using it MUST carry `note` naming that mechanism.
|
|
86
80
|
|
|
87
|
-
**Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite
|
|
81
|
+
**Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`), which the scenario records `inapplicable` naming the mechanism. A host MUST NOT omit a seam because the suite cannot drive it, and MUST NOT relabel it as a `kind` the suite can drive. The manifest is a self-declaration whose false negatives are found by audit — negative-existence, not a witness.
|
|
88
82
|
|
|
89
83
|
## Replay-from-event-log internals
|
|
90
84
|
|
package/spec/v2/core/runs.md
CHANGED
|
@@ -37,7 +37,7 @@ Every operation accepts `OpenWOP-Version` (overview.md); every mutating operatio
|
|
|
37
37
|
|
|
38
38
|
## Create
|
|
39
39
|
|
|
40
|
-
The `createRun` body is closed at the composition (`unevaluatedProperties: false`): `workflowId` (REQUIRED unless `mode: eval`), `inputs`, `residency`, `tenantId`, `scopeId`, `callbackUrl` (interrupt.md §Callback delivery
|
|
40
|
+
The `createRun` body is closed at the composition (`unevaluatedProperties: false`): `workflowId` (REQUIRED unless `mode: eval`), `inputs`, `residency`, `tenantId`, `scopeId`, `callbackUrl` (interrupt.md §Callback delivery; a refusal is `400 validation_error`, `details.field: "callbackUrl"`), `mode`, `evalSuiteRef`, `agentId`, and the `RunOptions` fields `configurable`, `tags`, `metadata`. A body without `RunOptions` MUST be accepted as if it were `{}`.
|
|
41
41
|
|
|
42
42
|
| Header | Rule |
|
|
43
43
|
| --- | --- |
|
|
@@ -45,7 +45,7 @@ The `createRun` body is closed at the composition (`unevaluatedProperties: false
|
|
|
45
45
|
| `OpenWOP-Dedup: enforce` | The host MUST reject a duplicate `(tenantId, scopeId)` with `409 run_already_active` and `Retry-After`. |
|
|
46
46
|
| `OpenWOP-Force-Engine-Version` | Test keys only; the seams profile. A host MUST reject it on a production credential with `403`. |
|
|
47
47
|
|
|
48
|
-
The `201` response is `{ runId, status, eventsUrl, statusUrl? }`, `status` one of `pending`, `running`, `waiting-approval`, `waiting-input`, `waiting-external`. `eventsUrl` and `statusUrl` MUST resolve under the origin the request was made to — a relative path, or an absolute URL on the same origin — and MUST NOT downgrade the scheme; a link that names a different host (a backing service behind the origin) or `http://` on an `https://` origin is non-conformant. The base that minted `runId` MUST resolve it: `GET /runs/{runId}` and `GET /runs/{runId}/events/poll` at that base MUST answer `200` for the id the `201` returned, percent-encoded per identity.md §5 (a front door that decodes `%2F` before routing makes every tenant-bound id unreachable). `mode: eval` (with `evalSuiteRef` and `agentId` REQUIRED) starts an eval-suite projection that emits the content-free `eval.*` family and terminates with an `EvalSummary`; a host that does not advertise `agents.evalSuite` MUST reject it. A `
|
|
48
|
+
The `201` response is `{ runId, status, eventsUrl, statusUrl? }`, `status` one of `pending`, `running`, `waiting-approval`, `waiting-input`, `waiting-external`. `eventsUrl` and `statusUrl` MUST resolve under the origin the request was made to — a relative path, or an absolute URL on the same origin — and MUST NOT downgrade the scheme; a link that names a different host (a backing service behind the origin) or `http://` on an `https://` origin is non-conformant. The base that minted `runId` MUST resolve it: `GET /runs/{runId}` and `GET /runs/{runId}/events/poll` at that base MUST answer `200` for the id the `201` returned, percent-encoded per identity.md §5 (a front door that decodes `%2F` before routing makes every tenant-bound id unreachable). `mode: eval` (with `evalSuiteRef` and `agentId` REQUIRED) starts an eval-suite projection that emits the content-free `eval.*` family and terminates with an `EvalSummary`; a host that does not advertise `agents.evalSuite` MUST reject it. A host advertising `dataResidency` MUST reject a `residency.region` outside `dataResidency.regions` with `422 residency_unavailable` and create no run (§"Conversation and residency capabilities"). A workflow that references a capability-gated reserved node type on a host that does not advertise the capability MUST be rejected with `422 capability_required`.
|
|
49
49
|
|
|
50
50
|
`run.started` (events.md) MUST echo the run's `owner` block exactly as `RunSnapshot.owner` carries it (RFC 0170 §A.1); `transport` records `rest`, `mcp`, `a2a` or `ui`.
|
|
51
51
|
|
|
@@ -75,7 +75,7 @@ An unknown root key, an unknown key inside a section, or a dotted key (`ai.provi
|
|
|
75
75
|
| --- | --- |
|
|
76
76
|
| `owner` | `{ tenant, workspace?, subject }`, closed, `subject` REQUIRED (`schemas/v2/subject.schema.json`). `principal` and `principalKind` do not exist. A run created before the host emitted subjects reads with the legacy subject rule (identity.md), stamped at first read and never rewritten. |
|
|
77
77
|
| `status` | `pending`, `running`, `paused`, `waiting-approval`, `waiting-input`, `waiting-external`, `completed`, `failed`, `cancelling`, `cancelled`. `waiting-external` MUST be used when the suspended interrupt's `kind` is `external-event`. `cancelling` is the state between an accepted cancel and the terminal `cancelled`. The vocabulary grows by overview.md §0. |
|
|
78
|
-
| `eventLogSchemaVersion` | The era key, integer ≥ 2.
|
|
78
|
+
| `eventLogSchemaVersion` | The era key, integer ≥ 2 (persistence.md §"The era key"). |
|
|
79
79
|
| `engineVersion` | Integer. |
|
|
80
80
|
| `compensationStatus` | `none`, `pending`, `running`, `completed`, `partial`, `failed`, `manual`. A host that does not advertise `compensation` MUST omit it; a host that does MUST include it on every snapshot, `none` when never requested. |
|
|
81
81
|
| `currentNodeId` | Set while suspended; names the node holding the interrupt. |
|
|
@@ -94,13 +94,13 @@ The `200` SHOULD carry a strong `ETag` derived from the latest persisted `sequen
|
|
|
94
94
|
|
|
95
95
|
`cancelRun` accepts `{ reason? }` and answers `200 { runId, status }` with `status` `cancelling` or `cancelled`; the cascade MAY be asynchronous and the run emits `run.cancelled` when it completes. A cancel on a run that is already terminal (`completed`, `failed`, `cancelled`) MUST be refused `409 run_terminal`; a `200` whose `status` echoes the terminal state is outside this grammar. Cancelling a parent MUST NOT silently abandon an active compensation (security-defaults.md). `run.cancelled.parentRunId` with `reason: parent-cancelled` records a cascade from a parent.
|
|
96
96
|
|
|
97
|
-
A non-terminal run
|
|
97
|
+
A non-terminal run inherited from v1 continues, or is cancelled `v1_pin_unsupported`, per persistence.md §"Runs pinned to v1" (RFC 0176 §B.1).
|
|
98
98
|
|
|
99
99
|
`bulkCancelRuns` accepts `{ runIds[1..100], reason? }`; over the host's cap (RECOMMENDED 100) it MUST return `400 validation_error` with `details.maxRunIds`. The host MUST process each id independently, MUST return `200 { results[] }` in request order even when every id failed, and MUST enforce authorization per id: a run the caller cannot see yields `ok: false` with an error envelope in that entry, never a top-level `403` — `id_tenant_mismatch` (or `not_found` where existence is not leaked) when the id's tenant segment is not the caller's (identity.md §5 applies inside an entry exactly as on a path), `run_forbidden` for a run in the caller's tenant the caller may not cancel, `run_terminal` for a run already terminal. `ok: true` carries `status` `cancelling` or `cancelled`; `ok: false` carries the error envelope (errors.md).
|
|
100
100
|
|
|
101
101
|
## Pause and resume
|
|
102
102
|
|
|
103
|
-
`pauseRun` accepts `{ reason?, drainPolicy? }` with `drainPolicy` `immediate` (snapshot between events) or `drain-current-node` (default; the executing node reaches a terminal first) and answers `202 { runId, status: 'paused', pausedAt? }`; the transition emits `run.paused`, whose payload echoes the request's `drainPolicy` word. With `immediate`, the attempt that was executing is cut between events: it has no terminal node event, a host MUST NOT record `node.failed` (or any terminal node event) for it, and the resumed run's `node.started` begins a fresh attempt
|
|
103
|
+
`pauseRun` accepts `{ reason?, drainPolicy? }` with `drainPolicy` `immediate` (snapshot between events) or `drain-current-node` (default; the executing node reaches a terminal first) and answers `202 { runId, status: 'paused', pausedAt? }`; the transition emits `run.paused`, whose payload echoes the request's `drainPolicy` word. With `immediate`, the attempt that was executing is cut between events: it has no terminal node event, a host MUST NOT record `node.failed` (or any terminal node event) for it, and the resumed run's `node.started` begins a fresh attempt. The record of the interruption is `run.paused` itself; its payload MAY carry `interruptedNodeId` and `interruptedAttempt` so a `debug` consumer can see which attempt was cut. A run already paused, terminal, or otherwise unpausable MUST receive `409`: `run_terminal` when the run is terminal, else `run_state_conflict` with `details.runStatus` naming the status that refused it. `resumeRun` accepts `{ reason? }`, answers `202 { runId, status: 'running', resumedAt? }`, emits `run.resumed`, and MUST return `409` when the run is not paused — `run_terminal` or `run_state_conflict` by the same rule. Only `resumeRun` or a cancel exits `paused`. A replay MUST fold `run.paused` and `run.resumed` as no-ops for projected state.
|
|
104
104
|
|
|
105
105
|
## Fork
|
|
106
106
|
|
|
@@ -112,7 +112,7 @@ A non-terminal run a v2 host inherits from v1 whose `version.pinned` change ids
|
|
|
112
112
|
|
|
113
113
|
## Annotations, artifacts, eval summary
|
|
114
114
|
|
|
115
|
-
`createAnnotation` accepts `schemas/v2/annotation-create.schema.json` and returns `201` with `schemas/v2/annotation.schema.json`; `listAnnotations` returns `{ annotations[] }`. An annotation is a live notification (`run.annotated`), never a run event: it MUST NOT enter the event log and MUST be excluded from fork, replay and diff. `getArtifact`
|
|
115
|
+
`createAnnotation` accepts `schemas/v2/annotation-create.schema.json` and returns `201` with `schemas/v2/annotation.schema.json`; `listAnnotations` returns `{ annotations[] }`. An annotation is a live notification (`run.annotated`), never a run event: it MUST NOT enter the event log and MUST be excluded from fork, replay and diff. `getArtifact` answers `application/json` with an implementation-defined object or, when `Accept` prefers `application/a2a+json`, an A2A `Artifact` (`schemas/v2/artifact.schema.json`); a host SHOULD offer the latter. A body served as `application/a2a+json` MUST validate against that schema with `artifactId` equal to the path's, and a `url` Part in it MUST NOT resolve beyond the caller's `artifacts:read` authorization. `getEvalSummary` returns `schemas/v2/eval-summary.schema.json` for a terminal eval run, `409` while it is running, `404` when the run is not an eval run; the summary MUST be content-free of task output, rubric prose and credentials.
|
|
116
116
|
|
|
117
117
|
## Conversation and residency capabilities
|
|
118
118
|
|
|
@@ -121,8 +121,9 @@ workflow whose `nodes[].typeId` references `core.conversationGate` MUST be refus
|
|
|
121
121
|
does not advertise `conversationPrimitive`, at registration or at run creation, with `422
|
|
122
122
|
capability_required` naming the family in `details.requiredCapability`.
|
|
123
123
|
|
|
124
|
+
A conversation turn MAY carry `parts`, a non-empty array of A2A `Part` objects (`schemas/v2/part.schema.json`); its presence marks the turn A2A-shaped. A producer SHOULD emit it and keep `content` readable by consumers that predate it. A turn without `parts` stays valid on emission, replay and fork.
|
|
125
|
+
|
|
124
126
|
A host advertising `dataResidency` MUST honor-or-reject: accept a `residency` constraint naming a
|
|
125
127
|
region in `dataResidency.regions`, refuse one it does not advertise with `residency_unavailable`,
|
|
126
|
-
and MUST NOT silently accept-and-ignore.
|
|
127
|
-
region is a hollow advertisement. A host that does not advertise `dataResidency` MAY ignore or
|
|
128
|
+
and MUST NOT silently accept-and-ignore. A host that does not advertise `dataResidency` MAY ignore or
|
|
128
129
|
reject a `residency` constraint but MUST NOT claim to honor it.
|