@openwop/spec-artifacts 2.42.6 → 2.42.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CORPUS-STAMP.json +64 -63
- package/api/seams-v2.yaml +187 -322
- 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
|
@@ -1,74 +1,99 @@
|
|
|
1
1
|
# Security Defaults
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `sandbox`, `compensation`, `purposePropagation`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
In v2 a security behavior is not an opt-in flag. This document is the obligation table: which surface binds which behavior, the invariant, and the witness.
|
|
9
9
|
|
|
10
10
|
## The rule
|
|
11
11
|
|
|
12
|
-
A security-load-bearing behavior is an obligation of the surface that needs it
|
|
12
|
+
A security-load-bearing behavior is an obligation of the surface that needs it. Advertising the surface binds the behavior; no discovery field gates it.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- Every obligation in `core/` MUST name a surface, an invariant, and a witness class other than `unwitnessable`. A row that cannot is not in `core/`.
|
|
15
|
+
- A host MUST NOT advertise a surface whose obligation it has relaxed.
|
|
15
16
|
|
|
16
17
|
## The obligation table
|
|
17
18
|
|
|
18
|
-
| Surface advertised | Obligation
|
|
19
|
+
| Surface advertised | Obligation | Witness | Invariant |
|
|
19
20
|
| --- | --- | --- | --- |
|
|
20
|
-
| any lane in `auth.lanes[]` | the lane obligations
|
|
21
|
-
| both `saml` and `scim` lanes | the leaver contract (
|
|
22
|
-
| `replay` (any mode) | side-effect suppression
|
|
23
|
-
| `webhooks` | durable delivery
|
|
24
|
-
| `interrupt` with `approversList` or `refKinds` | enforcement
|
|
25
|
-
| `packs` (pack execution) | isolation
|
|
26
|
-
| `compensation` |
|
|
27
|
-
| `idempotency` | Layer-2 effect identity
|
|
28
|
-
| an `oauth2` or `oidc` lane | protected-resource metadata and challenges (identity.md §2.5) | witnessable-gated | `auth-challenge-no-oracle` |
|
|
21
|
+
| any lane in `auth.lanes[]` | the lane obligations (§Auth lanes) | unaided or seam-gated per lane | `sender-constraint-no-bearer-downgrade`; per-lane rows |
|
|
22
|
+
| both `saml` and `scim` lanes | the leaver contract (mandatory) | seam-gated | `subject-link-leaver-deny`, `subject-link-mandatory-when-both-advertised` |
|
|
23
|
+
| `replay` (any mode) | side-effect suppression (§Replay suppression) | witnessable-gated via the effect-seam manifest | `replay-fanout-no-refire`; effect-seam rows registered at Accepted |
|
|
24
|
+
| `webhooks` | durable delivery (§Webhook durability) | witnessable-gated (`webhook-signed-delivery` + dead-letter leg) | registered at Accepted |
|
|
25
|
+
| `interrupt` with `approversList` or `refKinds` | approver enforcement (§Approver enforcement) | witnessable-gated | registered at Accepted |
|
|
26
|
+
| `packs` (pack execution) | isolation (§Sandbox isolation) | witnessable-gated (eight `sandbox-*` scenarios) | `node-pack-sandbox-*` |
|
|
27
|
+
| `compensation` | plan, attempt and inverse-action obligations (§Compensation) | witnessable-gated (reads) + seam-gated (operator actions) | `compensation-replay-no-refire`, `compensation-effect-id-retry-stable` |
|
|
28
|
+
| `idempotency` | Layer-2 effect identity (§Layer-2 effect identity) | witnessable-gated (fixture provider) | `logical-effect-id-retry-stable` |
|
|
29
|
+
| an `oauth2` or `oidc` lane | protected-resource metadata and challenges ([identity.md §2.5](identity.md)) | witnessable-gated | `auth-challenge-no-oracle` |
|
|
29
30
|
| any outbound request | no inbound credential on an onward hop (§Onward hops) | seam-gated | `inbound-credential-no-passthrough` |
|
|
30
31
|
|
|
31
32
|
### Auth lanes
|
|
32
33
|
|
|
33
|
-
The obligations are identity.md §2 and bind on advertisement
|
|
34
|
+
The obligations are [identity.md §2](identity.md) and bind on advertisement:
|
|
35
|
+
|
|
36
|
+
- the verify → bind → audience → resolve → fail-closed pipeline;
|
|
37
|
+
- a named trust root as `subject.issuer`;
|
|
38
|
+
- revocation for the lane;
|
|
39
|
+
- the advertised `minimumAssurance` floor, with `mtls.required` becoming `key-bound`;
|
|
40
|
+
- lane-scoped delegation proof.
|
|
34
41
|
|
|
35
42
|
### Replay suppression
|
|
36
43
|
|
|
37
|
-
Stated in replay.md §Suppression and §"The effect-seam manifest"; the `replay-side-effect-suppression` scenario witnesses it.
|
|
44
|
+
Side-effect suppression with `recorded-outcome` semantics is the only conforming behavior; `none` is not a value. Stated in replay.md §Suppression and §"The effect-seam manifest"; the `replay-side-effect-suppression` scenario witnesses it.
|
|
38
45
|
|
|
39
46
|
### Webhook durability
|
|
40
47
|
|
|
41
|
-
Stated in webhooks.md §Durability; the `webhook-durable-delivery` scenario witnesses it.
|
|
48
|
+
Durable delivery means retries per the advertised policy with backoff, dead-letter on exhaustion, and at-least-once delivery; best-effort is not a conforming delivery mode. Stated in webhooks.md §Durability; the `webhook-durable-delivery` scenario witnesses it.
|
|
42
49
|
|
|
43
50
|
### Approver enforcement
|
|
44
51
|
|
|
45
|
-
Stated in interrupt.md §"Approver enforcement"; the `approver-enforced` scenario witnesses it.
|
|
52
|
+
A resolver not in the list, group, or role MUST be refused; `refKinds[]` stays a facet. Stated in interrupt.md §"Approver enforcement"; the `approver-enforced` scenario witnesses it.
|
|
46
53
|
|
|
47
54
|
### Sandbox isolation
|
|
48
55
|
|
|
49
|
-
A host that executes third-party packs
|
|
56
|
+
A host that executes third-party packs:
|
|
57
|
+
|
|
58
|
+
- MUST enforce the eight `node-pack-sandbox-*` invariants of `SECURITY/invariants.yaml`: `no-process`, `network-gated`, `fs-gated`, `no-env`, `timeout`, `memory-cap`, `isolated-context`, `no-eval`;
|
|
59
|
+
- MUST advertise `sandbox.isolationModel ∈ wasm | process | container | vm` (`spec/v2/facets/sandbox.schema.json`). `node:vm` is not a value. `sandbox.isolationModel` names the mechanism and never relaxes the property.
|
|
60
|
+
|
|
61
|
+
A host that cannot isolate MUST NOT execute third-party packs; it MAY register and validate them. The `no-eval` row stays reference-impl in `ext/sandbox-runtime-notes`. The `pack-isolation` scenario drives the eight legs.
|
|
50
62
|
|
|
51
63
|
The remaining `sandbox` facets name the bound each invariant already carries: `allowedHostCalls` is the host-call allowlist `network-gated` and `fs-gated` enforce, `memoryLimitBytes` is the ceiling of `memory-cap`, and `wallClockLimitMs` is the ceiling of `timeout`. An advertised bound MUST be enforced; none of the three gates the invariant, which binds whether or not the facet is advertised.
|
|
52
64
|
|
|
53
65
|
### Compensation
|
|
54
66
|
|
|
55
|
-
A host that advertises `compensation` MUST serve `GET /runs/{runId}/compensation` (`schemas/v2/compensation-projection.schema.json`), keyed on the node and attempt the operator family uses
|
|
67
|
+
A host that advertises `compensation` MUST serve `GET /runs/{runId}/compensation` (`schemas/v2/compensation-projection.schema.json`), keyed on the node and attempt the operator family uses. The read projection and the operator action family are the canonical wire. A host that does not advertise `compensation` has no obligation.
|
|
56
68
|
|
|
57
|
-
The facets bind the policy shape (`schemas/v2/compensation-policy.schema.json`):
|
|
69
|
+
The facets bind the policy shape (`schemas/v2/compensation-policy.schema.json`):
|
|
70
|
+
|
|
71
|
+
- `compensation.orderingModels` MUST list `reverse-completion` and MAY add `dependency-graph`; a policy naming a model outside it MUST be refused at registration.
|
|
72
|
+
- `compensation.profileVersion` participates in the inverse-action identity, so a policy naming a different one MUST be refused.
|
|
73
|
+
- `compensation.manualIntervention` is the `manual` status — a host advertising it records the unwind rather than abandoning it.
|
|
58
74
|
|
|
59
75
|
### Layer-2 effect identity
|
|
60
76
|
|
|
61
|
-
A host that advertises `idempotency` MUST serve `GET /runs/{runId}/effects`; the keying, provider-key, retention and projection rules are idempotency.md §"Layer 2: effect identity"
|
|
77
|
+
Layer-2 effect identity is keyed on business identity; the activity recipe is the fallback. A host that advertises `idempotency` MUST serve `GET /runs/{runId}/effects`; the keying, provider-key, retention and projection rules are idempotency.md §"Layer 2: effect identity".
|
|
62
78
|
|
|
63
79
|
### Onward hops
|
|
64
80
|
|
|
65
|
-
A host MUST NOT attach a credential it received inbound
|
|
81
|
+
A host MUST NOT attach a credential it received inbound to any outbound request: A2A, MCP, webhook, callback, `httpClient` or connector. Inbound credentials are an `Authorization`, `Cookie` or `Proxy-Authorization` value, a DPoP proof, an interrupt token, a peer's bearer, or credential material carried in a body.
|
|
82
|
+
|
|
83
|
+
Outbound authentication uses only credentials the host holds for that destination (oauth.md). A verified delegation chain is not a passthrough: it carries provenance, never the inbound credential ([identity.md §2.4](identity.md)).
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
One credential is not inbound in this sense: an A2A push-config credential (`authentication.credentials`, `token`) is one the client gave the host for its registered push URL ([interop.md §"A2A push delivery"](interop.md)). A host:
|
|
86
|
+
|
|
87
|
+
- MAY attach it only to a push delivery to that URL's origin;
|
|
88
|
+
- MUST NOT attach it after a redirect or to any other request;
|
|
89
|
+
- MUST discard it when the config is deleted or the task's final push is attempted;
|
|
90
|
+
- MUST hold it by reference outside the event log, run state, debug bundle and every response.
|
|
91
|
+
|
|
92
|
+
A host advertising `purposePropagation` MUST re-emit a `permittedPurposes` label it received (A2A `metadata.openwop.permittedPurposes`, `TriggerEvent.permittedPurposes`) on every onward hop of the same data, narrowing and never widening, and MUST treat `[]` as no onward use. `purposePropagation.propagatesOnward` is `false` only on a host with no onward hop. The family advertises propagation, not enforcement.
|
|
68
93
|
|
|
69
94
|
## Relaxations
|
|
70
95
|
|
|
71
|
-
A relaxation, where one is legitimate — a development deployment, a single-tenant appliance — is an operator setting, never a discovery field
|
|
96
|
+
A relaxation, where one is legitimate — a development deployment, a single-tenant appliance — is an operator setting, never a discovery field. Every relaxation a host runs under MUST be recorded in its certification bundle as `host.relaxations[]` (`schemas/v2/certification-bundle.schema.json`): `{ obligation, durability, reason }`, `durability ∈ session | deployment | persisted`.
|
|
72
97
|
|
|
73
98
|
| Durability | Meaning |
|
|
74
99
|
| --- | --- |
|
|
@@ -80,7 +105,7 @@ A bundle that records a relaxation MUST NOT certify the profile the relaxed obli
|
|
|
80
105
|
|
|
81
106
|
## Three dispositions
|
|
82
107
|
|
|
83
|
-
Every security obligation in `core/` is exactly one of
|
|
108
|
+
Every security obligation in `core/` is exactly one of:
|
|
84
109
|
|
|
85
110
|
| Disposition | Where | Requirement |
|
|
86
111
|
| --- | --- | --- |
|
|
@@ -88,23 +113,22 @@ Every security obligation in `core/` is exactly one of (RFC 0173 §D.1):
|
|
|
88
113
|
| extension | `spec/v2/ext/` | MUST declare a witness class and both maturity axes. |
|
|
89
114
|
| removed | — | No text survives. |
|
|
90
115
|
|
|
91
|
-
Operation ids in the declaration file are canonical, and aliases are migration
|
|
92
|
-
register rows. The provider semantic-option registry is
|
|
93
|
-
`spec/v2/ext/provider-idempotency/registry.json`; provider qualification uses a
|
|
94
|
-
fixture that rejects a changed idempotency key (§D.2).
|
|
116
|
+
Operation ids in the declaration file are canonical, and aliases are migration register rows. The provider semantic-option registry is `spec/v2/ext/provider-idempotency/registry.json`; provider qualification uses a fixture that rejects a changed idempotency key.
|
|
95
117
|
|
|
96
118
|
## Threat models
|
|
97
119
|
|
|
98
|
-
The following threat-model artifacts are required:
|
|
99
|
-
|
|
100
120
|
| Artifact | Requirement |
|
|
101
121
|
| --- | --- |
|
|
102
122
|
| `SECURITY/threat-model-replay.md` §6 Residual risks | MUST record branch re-fires, seams outside the manifest, and the manifest as a self-declaration. |
|
|
103
|
-
| `SECURITY/threat-model-replay.md` §7 Verification, §8 References | MUST name the manifest scenario and `fork-a-v1-run
|
|
123
|
+
| `SECURITY/threat-model-replay.md` §7 Verification, §8 References | MUST name the manifest scenario and `fork-a-v1-run`. |
|
|
104
124
|
| `SECURITY/threat-model-interop.md` | MUST cover downgrade, identity, and cross-tenant risks in protocol composition. |
|
|
105
125
|
|
|
126
|
+
A threat model missing a sibling section fails the template gate.
|
|
127
|
+
|
|
106
128
|
## Migration
|
|
107
129
|
|
|
108
130
|
Rows `C6.1`–`C6.9` are `spec/v1/migrations.json` entries.
|
|
109
131
|
|
|
110
132
|
See also: identity.md, replay.md, webhooks.md, capabilities.md, conformance.md.
|
|
133
|
+
|
|
134
|
+
*Sources: RFC 0150, RFC 0163, RFC 0164, RFC 0170, RFC 0173, RFC 0214.*
|
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
# Tool catalog
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `toolCatalog`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
A host's tool catalog lists the tools a caller may invoke, with a projection onto MCP `ToolAnnotations`. The shapes are `schemas/v2/tool-descriptor.schema.json` and `schemas/v2/compact-tool-descriptor.schema.json`.
|
|
9
9
|
|
|
10
10
|
## The catalog
|
|
11
11
|
|
|
12
|
-
A host advertising `toolCatalog` MUST serve `GET /tools` (a `ToolDescriptor[]`) and `GET /tools/{toolId}`, both read-only.
|
|
12
|
+
A host advertising `toolCatalog` MUST serve `GET /tools` (a `ToolDescriptor[]`) and `GET /tools/{toolId}`, both read-only.
|
|
13
|
+
|
|
14
|
+
- The list MUST hold only tools the caller may invoke in its tenant.
|
|
15
|
+
- An unknown or unauthorized `toolId` MUST return `404`.
|
|
16
|
+
- `toolCatalog.sources` names the sources projected; a consumer MUST tolerate any subset.
|
|
17
|
+
- A host SHOULD return tools sorted by `toolId`, so an unchanged catalog reads identically.
|
|
13
18
|
|
|
14
19
|
## The descriptor
|
|
15
20
|
|
|
16
|
-
`toolId` MUST be unique in the catalog and stable for a host version.
|
|
21
|
+
- `toolId` MUST be unique in the catalog and stable for a host version.
|
|
22
|
+
- `safetyTier: "exec"` MUST carry `source: "host-extension"`.
|
|
23
|
+
- A descriptor MUST NOT carry credential material.
|
|
24
|
+
- The host MUST assign `safetyTier`, `replayPolicy` and `egress` itself, and MUST NOT copy them from an MCP server's `annotations`, which are untrusted.
|
|
25
|
+
- A `source: "mcp"` tool the host has not classified MUST be `safetyTier: "write"`.
|
|
26
|
+
|
|
27
|
+
`annotations`, when present, MUST carry all four MCP hints, derived from those fields and not from MCP defaults (upstream, `destructiveHint` and `openWorldHint` default to `true`):
|
|
17
28
|
|
|
18
|
-
|
|
29
|
+
| Hint | True iff |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `readOnlyHint` | `safetyTier` is `pure` or `read` |
|
|
32
|
+
| `destructiveHint` | `safetyTier` is `write` or `exec` |
|
|
33
|
+
| `idempotentHint` | `replayPolicy` is `deterministic` or `idempotent` |
|
|
34
|
+
| `openWorldHint` | `egress` is not `none` |
|
|
19
35
|
|
|
20
36
|
## Views and sessions
|
|
21
37
|
|
|
22
|
-
A host advertising `toolCatalog.compactView` MUST answer `?view=compact` on both endpoints with `CompactToolDescriptor`s (the list as `{ tools: [...] }`) carrying the standard view's `toolId` set
|
|
38
|
+
A host advertising `toolCatalog.compactView` MUST answer `?view=compact` on both endpoints with `CompactToolDescriptor`s (the list as `{ tools: [...] }`) carrying the standard view's `toolId` set.
|
|
39
|
+
|
|
40
|
+
- A compact `inputSchema` MUST NOT use `$ref`, `oneOf`, `allOf`, `anyOf`, `not`, `patternProperties` or `dependentSchemas` at any depth.
|
|
41
|
+
- Any other `view`, or a host not advertising it, yields the standard view.
|
|
42
|
+
|
|
43
|
+
A host advertising `toolCatalog.sessionLifecycle` MAY bracket calls with content-free `tool.session.opened` and `tool.session.closed`; a consumer MUST tolerate their absence.
|
|
44
|
+
|
|
45
|
+
*Sources: RFC 0078, RFC 0112, RFC 0204.*
|
|
@@ -1,77 +1,102 @@
|
|
|
1
1
|
# Versioning and Release
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
|
|
5
5
|
## Why this exists
|
|
6
6
|
|
|
7
7
|
How a v2 host selects a major, what each version axis means, and what a release is.
|
|
8
8
|
|
|
9
|
-
## 1. Major negotiation
|
|
9
|
+
## 1. Major negotiation
|
|
10
10
|
|
|
11
11
|
### 1.1 Advertisement
|
|
12
12
|
|
|
13
|
-
A v2 host MUST advertise
|
|
13
|
+
A v2 host MUST advertise two root fields, both REQUIRED in `schemas/v2/capabilities.schema.json` ([capabilities.md](capabilities.md)):
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
- `protocolVersions[]` — every `<major>.<minor>` it serves, each matching `^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`. Through the overlap that is `["1.<n>", "2.<m>"]`; after v1 end-of-support, `["2.<m>"]`.
|
|
16
|
+
- `preferredVersion` — MUST be a member of `protocolVersions[]`. Through the overlap it MUST name a 1.x member, because a header-less request is a v1 client's ([capabilities.md](capabilities.md) §1; §1.3). On a host serving a single major, it MUST equal `protocolVersion`.
|
|
17
|
+
|
|
18
|
+
A host that drops v1 advertises a `2.x` `preferredVersion`; its header-less representation becomes the closed v2 root.
|
|
19
|
+
|
|
20
|
+
A v2 consumer reads `preferredVersion` as the header-less default. When it is absent on a v1 document, the default is `max(protocolVersions[])`, else `protocolVersion`. The suite's `--target-major` defaults from it.
|
|
16
21
|
|
|
17
22
|
### 1.2 Paths
|
|
18
23
|
|
|
19
|
-
v1 operations keep their `/v1/…` path keys unchanged through the overlap.
|
|
24
|
+
- v1 operations keep their `/v1/…` path keys unchanged through the overlap.
|
|
25
|
+
- v2 operations are unversioned path keys on a bare origin (`servers[].url = https://{host}`): `/runs`, `/runs/{runId}`, `/.well-known/openwop`. There is no `/v2/` path space.
|
|
26
|
+
- An unversioned path is the v2 surface; v1's rule that it answers `400` does not apply.
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
Advertising a major is a claim about the whole **path space**, not about `/.well-known/openwop` alone (§1.3 selects that resource's representation):
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
- A host that advertises a major in `protocolVersions[]` MUST reach, under that major, every operation named in `spec/v2/path-manifest.json` that it serves under the other.
|
|
31
|
+
- If `/v1/<op>` answers and the unversioned `/<op>` returns `404` under the advertised major, the host MUST NOT advertise that major until the surface is reachable.
|
|
32
|
+
- Seam and proprietary paths are not manifest operations and need no per-major twin.
|
|
24
33
|
|
|
25
|
-
`spec/v2/path-manifest.json` (generated)
|
|
34
|
+
`spec/v2/path-manifest.json` (generated) lists operations (`method`, `path`, `operationId`) and channels (`name`, `address`) on a bare origin. Every path in it is unversioned; the pairing above compares each row with its `/v1` twin, derived by prefixing.
|
|
35
|
+
|
|
36
|
+
OpenAPI (`api/v2/openapi.yaml`), AsyncAPI (`api/v2/asyncapi.yaml`) and any kept proto MUST resolve to identical absolute paths for the shared event stream (`scripts/check-path-parity.mjs`). Seams are in [conformance.md](conformance.md).
|
|
26
37
|
|
|
27
38
|
### 1.3 The request header
|
|
28
39
|
|
|
29
|
-
A request on an unversioned path MAY carry `OpenWOP-Version: <major>` or `OpenWOP-Version: <major>.<minor
|
|
40
|
+
A request on an unversioned path MAY carry `OpenWOP-Version: <major>` or `OpenWOP-Version: <major>.<minor>`. `2` and `2.0` select the same major, and a host MUST accept both. Only the major selects: a minor in the header is informational. What pins a minor is `minClientVersion` plus the additive rules (`COMPATIBILITY.md` §2.4).
|
|
30
41
|
|
|
31
42
|
| Condition | Host behavior |
|
|
32
43
|
| --- | --- |
|
|
33
44
|
| Header names a major in `protocolVersions[]` | MUST serve that major |
|
|
34
|
-
| Header names a major not in `protocolVersions[]` | MUST answer `406` `protocol_version_unsupported
|
|
45
|
+
| Header names a major not in `protocolVersions[]` | MUST answer `406` `protocol_version_unsupported`, with `details.protocolVersions[]` echoing the list |
|
|
35
46
|
| Header absent on an unversioned path | MUST serve `preferredVersion`'s major |
|
|
36
47
|
| `/v1/…` path with `OpenWOP-Version` other than `1` | MUST answer `400` `protocol_version_mismatch` |
|
|
37
48
|
|
|
38
|
-
A request on a `/v1/…` path key MUST NOT carry `OpenWOP-Version` with a value other than `1`.
|
|
49
|
+
A request on a `/v1/…` path key MUST NOT carry `OpenWOP-Version` with a value other than `1`. `protocol_version_unsupported`, `protocol_version_mismatch` and `client_version_unsupported` (§1.5) are rows in `spec/v2/errors.json` ([errors.md](errors.md)).
|
|
39
50
|
|
|
40
51
|
### 1.4 The response header
|
|
41
52
|
|
|
42
|
-
Every protocol response MUST carry `OpenWOP-Version: <major>.<minor>` naming the contract that produced it. Reporting a version other than the one used is a silent downgrade and non-conformant
|
|
53
|
+
Every protocol response MUST carry `OpenWOP-Version: <major>.<minor>` naming the contract that produced it, `/v1/` responses included (REQUIRED in v2). Reporting a version other than the one used is a silent downgrade and non-conformant (scenario `dual-stack-negotiation`).
|
|
54
|
+
|
|
55
|
+
A *protocol response* is one produced by an operation named in `spec/v2/path-manifest.json` (or its `/v1/` twin through the overlap). A shell, hosting fallback, conformance seam or proprietary route has no protocol version to name.
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
`spec/v2/path-manifest.json` (or its `/v1/` twin through the overlap). A shell,
|
|
46
|
-
hosting fallback, conformance seam, or proprietary route has no protocol
|
|
47
|
-
version to name.
|
|
57
|
+
On a manifest-named path:
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
- A non-protocol response MUST NOT carry `OpenWOP-Version` and MUST NOT be `application/json`.
|
|
60
|
+
- A reader, a cache or the suite MUST NOT count a response without the header, or with a `text/html` body, as reaching the operation (`reachedUnderMajor2`).
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
A vendor path (§5) is not a shared name and is unconstrained.
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
#### Content negotiation on a shared name
|
|
65
|
+
|
|
66
|
+
A host MAY serve a protocol operation and a page under one unversioned name, selecting on `Accept`, iff:
|
|
67
|
+
|
|
68
|
+
1. A request identifying as a protocol client — `OpenWOP-Version` present, **or** an `Accept` admitting `application/json` without preferring `text/html` (absent and `*/*` included) — MUST get the protocol response for the applicable major (§1.3) with `OpenWOP-Version`. Only an explicit `text/html` preference selects the page.
|
|
69
|
+
2. The page obeys the rules above.
|
|
55
70
|
3. The response carries `Vary: Accept, OpenWOP-Version`.
|
|
56
71
|
|
|
57
72
|
Otherwise the page MUST move off the shared name.
|
|
58
73
|
|
|
59
74
|
### 1.5 Client precedence and `minClientVersion`
|
|
60
75
|
|
|
61
|
-
When both majors are advertised, a v2 client MUST select the highest major it implements that the host lists
|
|
76
|
+
When both majors are advertised, a v2 client MUST select the highest major it implements that the host lists. A v1 client (no header, `/v1/` paths) is unaffected.
|
|
77
|
+
|
|
78
|
+
`minClientVersion` (axis 15) is optional. When a host advertises it:
|
|
62
79
|
|
|
63
|
-
|
|
80
|
+
- It MUST use the axis-1 grammar.
|
|
81
|
+
- A host MAY refuse a client below it. A refusal MUST be `426` `client_version_unsupported`.
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
## 2. The 18 version axes
|
|
84
|
+
|
|
85
|
+
Dispositions:
|
|
86
|
+
|
|
87
|
+
- `unify` — one type and grammar, with a codemod.
|
|
88
|
+
- `first-class` — own schema-enforced grammar and negotiation rule.
|
|
89
|
+
- `retire` — absorbed into the capability record's `{status, since, until?}`.
|
|
90
|
+
- `delete` — removed, with a register row.
|
|
66
91
|
|
|
67
92
|
| # | Axis | Disposition | v2 grammar | Owner |
|
|
68
93
|
| --- | --- | --- | --- | --- |
|
|
69
|
-
| 1 | `protocolVersion` | first-class;
|
|
94
|
+
| 1 | `protocolVersion` | first-class; `preferredVersion`'s twin for v1 readers through the overlap, removed after | `^(0\|[1-9][0-9]*)\.(0\|[1-9][0-9]*)$` | this document |
|
|
70
95
|
| 2 | `protocolVersions[]` + `preferredVersion` | first-class, negotiation input | as #1 | this document |
|
|
71
96
|
| 3 | `engineVersion` | unify: integer everywhere; codemod `openwop.codemod.engine-version-unify` | `integer, minimum 0` | this document |
|
|
72
97
|
| 4 | `eventLogSchemaVersion` | first-class, the era key | integer; v2 writes `3` | `persistence.md` |
|
|
73
98
|
| 5 | per-event `schemaVersion` | first-class; §0 growth rule | integer | `events.md` |
|
|
74
|
-
| 6 | `schemaVersions` map | first-class; the map moves into the record's `kinds` seat
|
|
99
|
+
| 6 | `schemaVersions` map | first-class; the map moves into the record's `kinds` seat | `propertyNames` = the envelope-kind grammar, values `integer, minimum 0`; not a closed enum — a vendor kind is host-published, never corpus-declared | `events.md` |
|
|
75
100
|
| 7 | `version.pinned` | first-class; the v1-pinned-run disposition | integer min/max | `persistence.md` |
|
|
76
101
|
| 8 | `contractProvenance` | delete | — | `capabilities.md` |
|
|
77
102
|
| 9 | `minimumSuiteVersion` | retire into `spec/v2/declaration.json` | semver | `capabilities.md` |
|
|
@@ -80,51 +105,72 @@ When both majors are advertised, a v2 client MUST select the highest major it im
|
|
|
80
105
|
| 12 | MCP `revisions[]` / `preferredVersion` | first-class facet of `mcp` | date | `interop.md` |
|
|
81
106
|
| 13 | `multiAgent.executionModel.version` | first-class | integer with a schema `maximum` the suite reads | `events.md` |
|
|
82
107
|
| 14 | OpenAPI / AsyncAPI `info.version` | generated from the corpus tag | semver | this document |
|
|
83
|
-
| 15 | `minClientVersion` | first-class
|
|
108
|
+
| 15 | `minClientVersion` | first-class (§1.5) | as #1 | this document |
|
|
84
109
|
| 16 | channel `schemaVersion` / `compatibleWith` | first-class | integer / range | `events.md` |
|
|
85
110
|
| 17 | webhook signature scheme | retire into `deprecations.json` | — | `webhooks.md` |
|
|
86
111
|
| 18 | pack `engines.openwop` + `registryVersion` | first-class with the absent-ceiling rule | semver range / semver | `packs.md` |
|
|
87
112
|
|
|
88
|
-
One grammar covers protocol, envelope-kind
|
|
113
|
+
- One grammar covers protocol, envelope-kind and pack axes wherever a version is `<major>.<minor>` (#1, #2, #11, #15).
|
|
114
|
+
- `typeId@<semver>` is a pack axis (`packs.md`); the `2` in `typeId@2.0.0` never means `OpenWOP-Version: 2`.
|
|
115
|
+
- `docs/PROTOCOL-STATUS.md` carries one row per axis.
|
|
89
116
|
|
|
90
117
|
### 2.1 `engineVersion` (axis 3)
|
|
91
118
|
|
|
92
|
-
`engineVersion` MUST be an integer (`minimum 0`) at the discovery root and on every per-event carrier.
|
|
119
|
+
- `engineVersion` MUST be an integer (`minimum 0`) at the discovery root and on every per-event carrier.
|
|
120
|
+
- A persisted v1 run document that carries the string form is legacy-stamped: the reader MUST normalise it to an integer and MUST NOT rewrite the stored document.
|
|
121
|
+
- The codemod `openwop.codemod.engine-version-unify` MUST refuse any value not matching `^(0|[1-9][0-9]*)$`.
|
|
93
122
|
|
|
94
|
-
### 2.2 `eventLogSchemaVersion` (axis 4
|
|
123
|
+
### 2.2 `eventLogSchemaVersion` (axis 4)
|
|
95
124
|
|
|
96
125
|
`eventLogSchemaVersion` is the era key; the schema floor is `minimum 2`. Its stamping, absent-⇒-`2` and discovery rules are `persistence.md` §"The era key"; the reader contract is `persistence.md`.
|
|
97
126
|
|
|
98
|
-
## 3. Where v2 lives
|
|
127
|
+
## 3. Where v2 lives
|
|
128
|
+
|
|
129
|
+
- `spec/v2/core/` and `spec/v2/ext/<key>/` hold the prose.
|
|
130
|
+
- `schemas/v2/` holds every v2 schema, with `$id` under `https://openwop.dev/spec/v2/`; the site publishes them at `/spec/v2/`.
|
|
131
|
+
- The flat `schemas/` tree (v1 `$id`s) is read-only. v1 `$id` values are immutable identifiers: a domain move is answered by a redirect, never a rewrite.
|
|
132
|
+
- AsyncAPI `servers.production.pathname` is empty; every channel address carries its own path, as OpenAPI path keys do.
|
|
133
|
+
|
|
134
|
+
## 4. One release identity
|
|
135
|
+
|
|
136
|
+
The corpus tag `v2.<minor>.<patch>` (release candidates `v2.0.0-rc.<n>`) is the only release event; suite, SDKs, registry and site derive from it. `spec/v2/release.json` carries the next tag as `version`, and only the release PR that cuts the tag bumps it.
|
|
137
|
+
|
|
138
|
+
- Every human-surface version (README banner, `docs/PROTOCOL-STATUS.md`, OpenAPI and AsyncAPI `info.version`, `conformance/package.json`) MUST be generated from it and checked with `--check` in the merge gate.
|
|
139
|
+
- The published tarball digest MUST equal the tree's as a release precondition.
|
|
140
|
+
- The identity and advertised-versions checks keep their three-outcome discipline ([conformance.md](conformance.md)).
|
|
141
|
+
- A consumer that vendors any file from `schemas/`, `api/` or `spec/` MUST pin to a published tag, record it, and refuse a sync from any other ref.
|
|
142
|
+
- A v1.x consumer MUST NOT vendor `schemas/v2/`.
|
|
143
|
+
|
|
144
|
+
## 5. The overlap
|
|
99
145
|
|
|
100
|
-
|
|
146
|
+
Through the overlap a host:
|
|
101
147
|
|
|
102
|
-
|
|
148
|
+
- MUST advertise both majors (§1.1);
|
|
149
|
+
- MUST emit `OpenWOP-Version` on every response (§1.4);
|
|
150
|
+
- MUST serve `/.well-known/openwop` as one resource whose representation the request header selects ([capabilities.md](capabilities.md)).
|
|
103
151
|
|
|
104
|
-
The
|
|
152
|
+
The dual-stack scenario creates one run through `/v1/runs` with no header and reads it through `/runs` with `OpenWOP-Version: 2`; the response headers name the contract used.
|
|
105
153
|
|
|
106
|
-
A
|
|
154
|
+
A run minted under major 1 and read under major 2 MUST use the tenant-bound projection `<tenantId>/<v1-id>` ([identity.md](identity.md) §5). A host MUST NOT return a bare v1 id in a major-2 response.
|
|
107
155
|
|
|
108
|
-
|
|
156
|
+
### Retirement
|
|
109
157
|
|
|
110
|
-
|
|
158
|
+
The overlap ends at v1 end-of-support ([overview.md](overview.md)): `protocolVersions[]` drops the `1.<n>` member, and every alias carrying the `v1-end-of-support` trigger is removed.
|
|
111
159
|
|
|
112
|
-
**
|
|
113
|
-
|
|
114
|
-
bare v1 id in a major-2 response.
|
|
160
|
+
- **Retirement is atomic.** §1.1 admits no state in which both majors are advertised and `2.x` is preferred, so dropping v1 retires the whole `/v1` path space at once.
|
|
161
|
+
- **Retirement changes every header-less request's default contract**, from major 1 to major 2. Before retirement, a host MUST check for collisions between manifest top-level path segments and non-protocol unversioned routes, and MUST move each colliding route or apply §1.4 content negotiation.
|
|
115
162
|
|
|
116
|
-
|
|
163
|
+
### Host-proprietary paths: `/host/<org>/…`
|
|
117
164
|
|
|
118
|
-
|
|
165
|
+
Every vendor namespace — capability records ([capabilities.md](capabilities.md) §3.2), error codes, event types, pack properties — is keyed to an org registered in `spec/v2/declaration.json`; paths join that pattern.
|
|
119
166
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
colliding route or apply §1.4 content negotiation.
|
|
167
|
+
- A host MAY serve operations the manifest does not name under `/host/<org>/…` for its registered org. Such a path has no major, is served regardless of `OpenWOP-Version`, is never a protocol operation, is never measured, and is outside §1.4.
|
|
168
|
+
- An org MUST NOT be named after a manifest segment under `/host/` (`reservedOrgs`).
|
|
169
|
+
- A host SHOULD advertise the mount under `extensions.<org>.<name>`.
|
|
170
|
+
- A `/v1/host/<org>/…` twin MAY ride the overlap; it retires atomically with `/v1`.
|
|
125
171
|
|
|
126
|
-
|
|
172
|
+
## 6. Migration rows
|
|
127
173
|
|
|
128
|
-
|
|
174
|
+
Rows `C5.1`–`C5.9` are `spec/v1/migrations.json` entries (`C5.2` is owned by `events.md`). The persisted-data disposition for each is `not-persisted`, except `C5.1` (legacy-stamped) and `C5.7` (never-upgraded).
|
|
129
175
|
|
|
130
|
-
|
|
176
|
+
*Sources: RFC 0167, RFC 0168, RFC 0172, RFC 0176, RFC 0179, RFC 0181, RFC 0193.*
|