@openwop/spec-artifacts 2.35.1 → 2.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CORPUS-STAMP.json +103 -89
  2. package/api/openapi.yaml +325 -4
  3. package/api/seams-v2.yaml +342 -1
  4. package/api/v2/asyncapi.yaml +1 -1
  5. package/api/v2/openapi.yaml +564 -8
  6. package/package.json +1 -1
  7. package/schemas/a2a-task-state.schema.json +3 -3
  8. package/schemas/artifact-type-pack-manifest.schema.json +3 -3
  9. package/schemas/capabilities.schema.json +34 -1
  10. package/schemas/connection-pack-manifest.schema.json +14 -0
  11. package/schemas/debug-bundle.schema.json +20 -0
  12. package/schemas/dispatch-config.schema.json +1 -1
  13. package/schemas/localized-content-language-settings.schema.json +3 -3
  14. package/schemas/localized-content-page.schema.json +2 -2
  15. package/schemas/localized-content-section.schema.json +3 -3
  16. package/schemas/run-event-payloads.schema.json +4 -2
  17. package/schemas/suspend-request.schema.json +61 -4
  18. package/schemas/v2/a2a-task-state.schema.json +10 -9
  19. package/schemas/v2/agent-deployment-transition.schema.json +4 -4
  20. package/schemas/v2/agent-deployment.schema.json +7 -7
  21. package/schemas/v2/agent-eval-suite.schema.json +10 -10
  22. package/schemas/v2/agent-inventory-response.schema.json +8 -2
  23. package/schemas/v2/artifact-type-pack-manifest.schema.json +3 -3
  24. package/schemas/v2/artifact.schema.json +84 -0
  25. package/schemas/v2/capabilities.schema.json +82 -8
  26. package/schemas/v2/certification-bundle.schema.json +1 -1
  27. package/schemas/v2/channel-presence-payload.schema.json +3 -3
  28. package/schemas/v2/connection-pack-manifest.schema.json +14 -0
  29. package/schemas/v2/conversation-event.schema.json +9 -2
  30. package/schemas/v2/conversation-turn.schema.json +19 -12
  31. package/schemas/v2/dispatch-config.schema.json +1 -1
  32. package/schemas/v2/envelopes/ui.a2ui-surface.schema.json +920 -22
  33. package/schemas/v2/error-envelope.schema.json +11 -1
  34. package/schemas/v2/eval-summary.schema.json +1 -2
  35. package/schemas/v2/goal.schema.json +4 -4
  36. package/schemas/v2/localized-content-language-settings.schema.json +3 -3
  37. package/schemas/v2/localized-content-page.schema.json +2 -2
  38. package/schemas/v2/localized-content-section.schema.json +3 -3
  39. package/schemas/v2/node-pack-manifest.schema.json +105 -0
  40. package/schemas/v2/part.schema.json +81 -0
  41. package/schemas/v2/proposal.schema.json +6 -6
  42. package/schemas/v2/run-event-payloads.schema.json +5 -3
  43. package/schemas/v2/self-hosted-runner-dispatch-frame.schema.json +2 -2
  44. package/schemas/v2/self-hosted-runner-result-frame.schema.json +1 -1
  45. package/schemas/v2/suspend-request.schema.json +251 -6
  46. package/schemas/v2/tool-descriptor.schema.json +246 -2
  47. package/schemas/v2/trigger-event.schema.json +1 -2
  48. package/schemas/v2/trigger-subscription.schema.json +1 -2
  49. package/schemas/v2/webhook-verification.schema.json +22 -0
  50. package/schemas/v2/workflow-definition.schema.json +25 -25
  51. package/spec/v1/alias-detectors.json +13 -1
  52. package/spec/v1/core-standard-manifest.json +2 -2
  53. package/spec/v1/deprecations.json +45 -1
  54. package/spec/v1/deprecations.schema.json +119 -4
  55. package/spec/v1/gaps.json +1925 -4
  56. package/spec/v2/README.md +5 -2
  57. package/spec/v2/core/capabilities.md +13 -26
  58. package/spec/v2/core/conformance.md +5 -8
  59. package/spec/v2/core/connection-packs.md +1 -1
  60. package/spec/v2/core/conversation.md +1 -1
  61. package/spec/v2/core/errors.md +8 -3
  62. package/spec/v2/core/events.md +8 -20
  63. package/spec/v2/core/form-content-packs.md +2 -2
  64. package/spec/v2/core/headers.md +5 -4
  65. package/spec/v2/core/host-services.md +6 -2
  66. package/spec/v2/core/idempotency.md +1 -1
  67. package/spec/v2/core/identity.md +23 -12
  68. package/spec/v2/core/interop.md +34 -4
  69. package/spec/v2/core/interrupt.md +6 -3
  70. package/spec/v2/core/node-pack-runtimes.md +20 -0
  71. package/spec/v2/core/oauth.md +32 -0
  72. package/spec/v2/core/overview.md +19 -6
  73. package/spec/v2/core/packs.md +4 -6
  74. package/spec/v2/core/persistence.md +20 -55
  75. package/spec/v2/core/replay.md +5 -11
  76. package/spec/v2/core/runs.md +9 -8
  77. package/spec/v2/core/security-defaults.md +18 -20
  78. package/spec/v2/core/tool-catalog.md +22 -0
  79. package/spec/v2/core/versioning.md +10 -28
  80. package/spec/v2/core/webhooks.md +16 -4
  81. package/spec/v2/core/workflow-chain-packs.md +1 -1
  82. package/spec/v2/corrections.json +32 -0
  83. package/spec/v2/corrections.schema.json +49 -0
  84. package/spec/v2/declaration.json +18 -13
  85. package/spec/v2/declaration.schema.json +3 -1
  86. package/spec/v2/errors.json +46 -1
  87. package/spec/v2/ext/a2uiSurface/README.md +131 -17
  88. package/spec/v2/ext/dataIntegration/README.md +5 -0
  89. package/spec/v2/facets/a2a.schema.json +4 -0
  90. package/spec/v2/facets/auth.schema.json +24 -0
  91. package/spec/v2/facets/mcp.schema.json +4 -0
  92. package/spec/v2/facets/oauth.schema.json +70 -0
  93. package/spec/v2/facets/webhooks.schema.json +23 -2
  94. package/spec/v2/id-field-bindings.json +2 -0
  95. package/spec/v2/interop-map.json +639 -0
  96. package/spec/v2/interop-map.schema.json +458 -0
  97. package/spec/v2/migrations.json +7 -0
  98. package/spec/v2/migrations.schema.json +58 -0
  99. package/spec/v2/path-manifest.json +6 -1
  100. package/spec/v2/release.json +3 -3
  101. package/spec/v2/surface-baseline.json +7981 -0
@@ -1,11 +1,11 @@
1
1
  # Security Defaults
2
2
 
3
3
  > **Status: Stable · RFC 0173 (§A–§E), 0164 §22, 0170 §B.**
4
- > **Normative home:** `sandbox`, `compensation`.
4
+ > **Normative home:** `sandbox`, `compensation`, `purposePropagation`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- v1 protected a tenant only when a host volunteered a boolean: fourteen auth-family flags, `replay.sideEffectSuppression`, `webhooks.durable`, `interrupt.approverRouting`, and `sandbox.supported` each gated a MUST. RFC 0164 §22 named the pattern — opt-in security is the pattern the corpus keeps regretting — and RFC 0173 applies that ruling to the whole corpus. This document is the obligation table: which surface binds which behavior, the invariant, and the witness.
8
+ RFC 0173 replaces v1's opt-in security flags (RFC 0164 §22). This document is the obligation table: which surface binds which behavior, the invariant, and the witness.
9
9
 
10
10
  ## The rule
11
11
 
@@ -25,22 +25,24 @@ A host MUST NOT advertise a surface whose obligation it has relaxed (§A.2).
25
25
  | `packs` (pack execution) | isolation: the eight `node-pack-sandbox-*` invariants bind for pack code; `sandbox.isolationModel` names the mechanism and never relaxes the property (replaces `sandbox.supported`) | witnessable-gated (eight `sandbox-*` scenarios) | `node-pack-sandbox-*` |
26
26
  | `compensation` | the plan, attempt, and inverse-action obligations with the read projection `GET /runs/{runId}/compensation` and the operator action family as canonical wire (replaces `compensation.supported` with seam-only evidence) | witnessable-gated (reads) + seam-gated (operator actions) | `compensation-replay-no-refire`, `compensation-effect-id-retry-stable` |
27
27
  | `idempotency` | Layer-2 effect identity keyed on business identity; the activity recipe is the fallback; `GET /runs/{runId}/effects` is the read | witnessable-gated (fixture provider) | `logical-effect-id-retry-stable` |
28
+ | an `oauth2` or `oidc` lane | protected-resource metadata and challenges (identity.md §2.5) | witnessable-gated | `auth-challenge-no-oracle` |
29
+ | any outbound request | no inbound credential on an onward hop (§Onward hops) | seam-gated | `inbound-credential-no-passthrough` |
28
30
 
29
31
  ### Auth lanes
30
32
 
31
- The fourteen gate fields are removed from `schemas/v2/capabilities.schema.json`; `auth.lanes[]` carries `{ lane, issuers[], revocation, minimumAssurance, delegationProofs[] }` as facets. The obligations are stated once in identity.md and bind on advertisement.
33
+ The obligations are identity.md §2 and bind on advertisement.
32
34
 
33
35
  ### Replay suppression
34
36
 
35
- A host that advertises `replay` MUST suppress external effects during a `replay` fork and MUST publish the effect-seam manifest at `GET /host/effect-seams` (`schemas/v2/effect-seam-manifest.schema.json`, RFC 0173 §C.1). The `replay-side-effect-suppression` scenario asserts every manifest row is suppressed and drives one seam of each kind to observe no re-fire. A host that cannot suppress MUST NOT advertise `replay`. The manifest is a self-declaration: a seam omitted is invisible to the suite, and its completeness is recorded as negative-existence, found by audit rather than witnessed.
37
+ Stated in replay.md §Suppression and §"The effect-seam manifest"; the `replay-side-effect-suppression` scenario witnesses it.
36
38
 
37
39
  ### Webhook durability
38
40
 
39
- A host that advertises `webhooks` MUST retry a failed delivery per its advertised `retryPolicy` (`maxAttempts`, `backoff`), MUST route an exhausted delivery to the dead-letter sink, and MUST deliver at least once; subscribers dedup on `(OpenWOP-Webhook-Id, runId, sequence)` (webhooks.md). The `webhook-durable-delivery` scenario observes retry then dead-letter.
41
+ Stated in webhooks.md §Durability; the `webhook-durable-delivery` scenario witnesses it.
40
42
 
41
43
  ### Approver enforcement
42
44
 
43
- A host that surfaces `approversList`, or advertises `refKinds` including `group` or `role`, MUST refuse a resolution from a principal outside the list, group, or role at resolve time. Membership MUST be resolved at decision time and MUST NOT be re-resolved during replay (replay.md). The `approver-enforced` scenario submits a non-listed resolver and observes the refusal.
45
+ Stated in interrupt.md §"Approver enforcement"; the `approver-enforced` scenario witnesses it.
44
46
 
45
47
  ### Sandbox isolation
46
48
 
@@ -50,13 +52,19 @@ The remaining `sandbox` facets name the bound each invariant already carries: `a
50
52
 
51
53
  ### Compensation
52
54
 
53
- A host that advertises `compensation` MUST serve `GET /runs/{runId}/compensation` (`schemas/v2/compensation-projection.schema.json`): `{ runId, status, plan[], attempts[] }`, the plan carrying `{ nodeId, order, policy?, irreversibleEffect? }` and each attempt `{ nodeId, attempt, outcome, at, reason? }`, keyed on the node and attempt the operator family uses. The trichotomy of §D.1 resolves to core obligation with a declared witness; a host that does not advertise `compensation` has no obligation.
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; a host that does not advertise `compensation` has no obligation.
54
56
 
55
57
  The facets bind the policy shape (`schemas/v2/compensation-policy.schema.json`): `compensation.orderingModels` MUST list `reverse-completion` and MAY add `dependency-graph`, and a policy naming a model outside it MUST be refused at registration; `compensation.profileVersion` participates in the inverse-action identity, so a policy naming a different one MUST be refused; `compensation.manualIntervention` is the `manual` status above — a host advertising it records the unwind rather than abandoning it.
56
58
 
57
59
  ### Layer-2 effect identity
58
60
 
59
- A host that advertises `idempotency` MUST assign a logical effect id once per effect, stable across transport retries, and MUST inject it as the provider's idempotency key (RFC 0150 §B). Where a provider exposes no business key, the v1 activity recipe is the documented fallback. `GET /runs/{runId}/effects` (`schemas/v2/effect-ledger-projection.schema.json`) serves `{ runId, effects[] }`, each `{ effectId, nodeId, attempt, invocationId?, keying: business-identity | activity-recipe, providerKey?, state: claimed | completed | released | escaped, at }`, content-free of provider payloads. Layer-2 retention MUST be at least 14 days (RFC 0170 §D.3). No deployed history holds a v1 recipe key, so no dual-read migration exists (RFC 0147 UQ2).
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" (RFC 0150 §B).
62
+
63
+ ### Onward hops
64
+
65
+ A host MUST NOT attach a credential it received inbound (an `Authorization`, `Cookie` or `Proxy-Authorization` value, a DPoP proof, an interrupt token, a peer's bearer, or credential material carried in a body) to any outbound request: A2A, MCP, webhook, callback, `httpClient` or connector. 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).
66
+
67
+ 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.
60
68
 
61
69
  ## Relaxations
62
70
 
@@ -68,7 +76,7 @@ A relaxation, where one is legitimate — a development deployment, a single-ten
68
76
  | `deployment` | Set at deploy time. |
69
77
  | `persisted` | Survives restarts and is auditable. |
70
78
 
71
- A bundle that records a relaxation MUST NOT certify the profile the relaxed obligation belongs to; the `relaxation-recorded` scenario verifies it unaided (conformance.md). RFC 0158's ladder is the model: evidence lives in the bundle, and a field that let a host assert a property with nothing behind it is the failure the ladder prevents.
79
+ A bundle that records a relaxation MUST NOT certify the profile the relaxed obligation belongs to; the `relaxation-recorded` scenario verifies it unaided (conformance.md).
72
80
 
73
81
  ## Three dispositions
74
82
 
@@ -97,16 +105,6 @@ The following threat-model artifacts are required:
97
105
 
98
106
  ## Migration
99
107
 
100
- | Row | v1 | v2 |
101
- | --- | --- | --- |
102
- | `C6.1` | fourteen `auth.*` gate flags | obligations of the lane; `auth.lanes[]` facets |
103
- | `C6.2` | `replay.sideEffectSuppression: none \| recorded-outcome` | suppression is the only replay behavior; the manifest is the witness |
104
- | `C6.3` | `webhooks.durable` opt-in | durable delivery binds with `webhooks`; undelivered best-effort deliveries are not translated |
105
- | `C6.4` | `interrupt.approverRouting` gate | enforcement binds with the fields |
106
- | `C6.5` | `sandbox.supported` gate | isolation binds with pack execution; `node:vm` not a value |
107
- | `C6.6` | `compensation.supported` with seam-only evidence | core obligation with the read projection; persisted plans and attempts unchanged |
108
- | `C6.7` | unimplemented activity recipe | business-identity keying; `GET /runs/{runId}/effects` |
109
- | `C6.8` | none | `host.relaxations[]` in bundle v3 |
110
- | `C6.9` | five-section replay threat model; no interop model | sibling sections; `threat-model-interop.md` |
108
+ Rows `C6.1`–`C6.9` are `spec/v1/migrations.json` entries.
111
109
 
112
110
  See also: identity.md, replay.md, webhooks.md, capabilities.md, conformance.md.
@@ -0,0 +1,22 @@
1
+ # Tool catalog
2
+
3
+ > **Status: Stable · RFC 0078, RFC 0112.**
4
+ > **Normative home:** `toolCatalog`.
5
+
6
+ ## Why this exists
7
+
8
+ v2 carried the catalog only by pointing at `spec/v1/tool-catalog.md`. This is its v2 contract, plus a projection onto MCP `ToolAnnotations` (RFC 0204). The shapes are `schemas/v2/tool-descriptor.schema.json` and `schemas/v2/compact-tool-descriptor.schema.json`.
9
+
10
+ ## The catalog
11
+
12
+ A host advertising `toolCatalog` MUST serve `GET /tools` (a `ToolDescriptor[]`) and `GET /tools/{toolId}`, both read-only. The list MUST hold only tools the caller may invoke in its tenant, and an unknown or unauthorized `toolId` MUST return `404`. `toolCatalog.sources` names the sources projected; a consumer MUST tolerate any subset. A host SHOULD return tools sorted by `toolId`, so an unchanged catalog reads identically.
13
+
14
+ ## The descriptor
15
+
16
+ `toolId` MUST be unique in the catalog and stable for a host version. `safetyTier: "exec"` MUST carry `source: "host-extension"`. A descriptor MUST NOT carry credential material. The host MUST assign `safetyTier`, `replayPolicy` and `egress` itself and MUST NOT copy them from an MCP server's `annotations`, which are untrusted; a `source: "mcp"` tool it has not classified MUST be `safetyTier: "write"`.
17
+
18
+ `annotations`, when present, MUST carry all four MCP hints, derived from those fields and not from MCP defaults (`destructiveHint` and `openWorldHint` default to `true` upstream): `readOnlyHint` is true iff `safetyTier` is `pure` or `read`, `destructiveHint` iff it is `write` or `exec`, `idempotentHint` iff `replayPolicy` is `deterministic` or `idempotent`, and `openWorldHint` unless `egress` is `none`.
19
+
20
+ ## Views and sessions
21
+
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; a compact `inputSchema` MUST NOT use `$ref`, `oneOf`, `allOf`, `anyOf`, `not`, `patternProperties` or `dependentSchemas` at any depth. Any other `view`, or a host not advertising it, yields the standard view. A host advertising `toolCatalog.sessionLifecycle` MAY bracket calls with content-free `tool.session.opened` and `tool.session.closed`; a consumer MUST tolerate their absence.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Why this exists
6
6
 
7
- v1 negotiated on one scalar, could not advertise two majors, split `engineVersion` across two types, and presumed a `/v2/` path space that the `/v1/v1` defect already showed is the wrong model. This document is the one place a v2 host reads to learn how a major is selected, what each version axis means, and what a release is.
7
+ How a v2 host selects a major, what each version axis means, and what a release is.
8
8
 
9
9
  ## 1. Major negotiation (RFC 0172 §A)
10
10
 
@@ -12,23 +12,21 @@ v1 negotiated on one scalar, could not advertise two majors, split `engineVersio
12
12
 
13
13
  A v2 host MUST advertise `protocolVersions[]` (grammar `^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$` per member) containing every `<major>.<minor>` it serves, and a root `preferredVersion` that MUST be a member of `protocolVersions[]`. Both are REQUIRED root metadata in `schemas/v2/capabilities.schema.json` (see `capabilities.md`). Through the overlap a host serves `["1.<n>", "2.<m>"]`; after v1 end-of-support it serves `["2.<m>"]`.
14
14
 
15
- **Through the overlap `preferredVersion` MUST name a 1.x member.** A header-less request is a v1 client's request: `capabilities.md` §1 makes the header-less representation the v1 document, and §1.3 makes the header-less default `preferredVersion`'s major, so on a host whose `protocolVersions[]` contains any `1.x` member the two rules agree only when `preferredVersion` is that `1.x`. A host that drops v1 from `protocolVersions[]` advertises a `2.x` `preferredVersion` and its header-less representation becomes the closed v2 root. On a host serving a single major, `preferredVersion` MUST equal `protocolVersion` (RFC 0179 §A.1). A v2 consumer reads `preferredVersion` as the header-less default; when it is absent on a v1 document the consumer's default is `max(protocolVersions[])`, else `protocolVersion` (RFC 0179 §A.2). The suite's `--target-major` defaults from it (RFC 0168 §D.3).
15
+ **Through the overlap `preferredVersion` MUST name a 1.x member**, because a header-less request is a v1 client's (`capabilities.md` §1; §1.3). A host that drops v1 from `protocolVersions[]` advertises a `2.x` `preferredVersion` and its header-less representation becomes the closed v2 root. On a host serving a single major, `preferredVersion` MUST equal `protocolVersion` (RFC 0179 §A.1). A v2 consumer reads `preferredVersion` as the header-less default; when it is absent on a v1 document the consumer's default is `max(protocolVersions[])`, else `protocolVersion` (RFC 0179 §A.2). The suite's `--target-major` defaults from it (RFC 0168 §D.3).
16
16
 
17
17
  ### 1.2 Paths
18
18
 
19
19
  v1 operations keep their `/v1/…` path keys unchanged through the overlap. 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. An unversioned path is the v2 surface; the v1 MUST that servers answer `400` for unversioned roots is retracted for v2.
20
20
 
21
- 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. Advertising a major is a claim about the **path space**, not about `/.well-known/openwop` alone — that resource's representation is *selected* by the request header (§1.3), so it answers correctly for a host that has mounted nothing else, and every discovery-level probe of the advertisement passes with it. Concretely: if `/v1/<op>` answers and the unversioned `/<op>` returns `404` under the advertised major, the advertisement overstates what the host serves and the host MUST NOT advertise that major until the surface is reachable. The pairing is normative because a lone `404` cannot distinguish *"this host does not serve that operation"* from *"this host serves it and did not mount it under this major"*, and only the second is a defect.
21
+ 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: advertising a major is a claim about the **path space**, not about `/.well-known/openwop` alone (§1.3 selects that resource's representation). 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.
22
22
 
23
- The manifest defines the scope of this pairing rule. Seam paths and proprietary
24
- paths are not manifest operations and do not require a per-major twin. The
25
- canonical OpenAPI therefore contains no conformance-seam operation.
23
+ Seam and proprietary paths are not manifest operations and need no per-major twin.
26
24
 
27
- `spec/v2/path-manifest.json` (generated) carries operations (`method`, `path`, `operationId`) and channels (`name`, `address`) on a bare origin, and **every path in it is unversioned** — there are no `/v1` rows. The `/v1` twin of a manifest row is derived by prefixing, which is what the pairing above compares. 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`); the canonical OpenAPI MUST contain no seam or test-mode operation (those live in the seams profile, see `conformance.md`).
25
+ `spec/v2/path-manifest.json` (generated) carries operations (`method`, `path`, `operationId`) and channels (`name`, `address`) on a bare origin, and **every path in it is unversioned** — there are no `/v1` rows. The `/v1` twin of a manifest row is derived by prefixing, which is what the pairing above compares. 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: `conformance.md`).
28
26
 
29
27
  ### 1.3 The request header
30
28
 
31
- 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, and what pins a minor is `minClientVersion` plus the additive rules.
29
+ 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, and what pins a minor is `minClientVersion` plus the additive rules (`COMPATIBILITY.md` §2.4).
32
30
 
33
31
  | Condition | Host behavior |
34
32
  | --- | --- |
@@ -62,10 +60,6 @@ Otherwise the page MUST move off the shared name.
62
60
 
63
61
  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. `minClientVersion` (axis 15, grammar as axis 1) is a MUST: a host MAY refuse a client below it with `426` `client_version_unsupported`.
64
62
 
65
- `OpenWOP-Version` selects by major. The `<major>.<minor>` spelling is accepted so
66
- a client may echo a `protocolVersions[]` member. Minor compatibility is governed
67
- by `minClientVersion` and the additive-change rules.
68
-
69
63
  ## 2. The 18 version axes (RFC 0172 §B; RFC 0167 §E.1)
70
64
 
71
65
  `unify` = one type and grammar with a codemod; `first-class` = own schema-enforced grammar and negotiation rule; `retire` = absorbed into the capability record's `{status, since, until?}`; `delete` = removed with a register row.
@@ -99,7 +93,7 @@ One grammar covers protocol, envelope-kind, and pack axes wherever a version is
99
93
 
100
94
  ### 2.2 `eventLogSchemaVersion` (axis 4; RFC 0176 §A.2)
101
95
 
102
- `eventLogSchemaVersion` is the era key. A v2 host MUST stamp `3` on every run it creates. A run document without the field on a store that has ever been written by a v1 host MUST read as `2` (v1 era). The v1 rule for `< 2` (snapshot fallback, no projection write-through) is unchanged. Discovery advertises the value the host writes for new runs and nothing else; the schema floor is `minimum 2`. The reader contract is `persistence.md`.
96
+ `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`.
103
97
 
104
98
  ## 3. Where v2 lives (RFC 0172 §C)
105
99
 
@@ -117,12 +111,11 @@ Through the overlap a host MUST advertise both majors (§1.1), MUST emit `OpenWO
117
111
 
118
112
  **A run minted under major 1 and read under major 2 MUST use the tenant-bound
119
113
  projection** `<tenantId>/<v1-id>` (`identity.md` §5). A host MUST NOT return a
120
- bare v1 id in a major-2 response. The tenant segment is required for the
121
- `id_tenant_mismatch` check; `ids.schema.json` has no legacy unprefixed branch.
114
+ bare v1 id in a major-2 response.
122
115
 
123
116
  The overlap ends at v1 end-of-support (`overview.md`), when `protocolVersions[]` drops the `1.<n>` member and every alias carrying the `v1-end-of-support` trigger is removed.
124
117
 
125
- **Retirement is atomic, and that is a consequence of §1.1 rather than a separate rule.** Through the overlap `preferredVersion` MUST name a `1.x` member; a host that drops v1 from `protocolVersions[]` advertises a `2.x` `preferredVersion`. There is no legal intermediate state in which both majors are advertised and `2.x` is preferred, so flipping `preferredVersion` ahead of the drop is not a smaller first step — it is the same step. Dropping v1 therefore retires the whole `/v1` path space at once, not incrementally.
118
+ **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.
126
119
 
127
120
  **Retirement changes every header-less request's default contract.** Before
128
121
  end-of-support, a header-less unversioned request uses major 1; afterward it uses
@@ -134,15 +127,4 @@ colliding route or apply §1.4 content negotiation.
134
127
 
135
128
  ## 6. Migration rows (RFC 0172)
136
129
 
137
- | Row | v1 | v2 |
138
- | --- | --- | --- |
139
- | `C5.1` | `engineVersion` integer at root, string on five carriers | integer everywhere; codemod `engine-version-unify` |
140
- | `C5.3` | — | root `preferredVersion` |
141
- | `C5.4` | — | `OpenWOP-Version` request/response header; three error codes |
142
- | `C5.5` | `/v1/<op>` path keys | unversioned `/<op>` keys (v1 keys retained through the overlap) |
143
- | `C5.6` | `400` for unversioned roots | unversioned roots are the v2 surface |
144
- | `C5.7` | `$id` base `/spec/v1/` | `/spec/v2/` (new files; v1 `$id`s immutable) |
145
- | `C5.8` | `minClientVersion` advisory | MUST (§1.5) |
146
- | `C5.9` | `info.version` hand-maintained | generated from the corpus tag |
147
-
148
- Row `C5.2` (channel state-key prefixes → typed channels) is owned by `events.md`. Every row is a `spec/v1/migrations.json` entry; the persisted-data disposition for each is `not-persisted` except `C5.1` (legacy-stamped) and `C5.7` (never-upgraded).
130
+ 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).
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## Why this exists
7
7
 
8
- Polling is inefficient and SSE cannot reach server-to-server consumers. A client registers a URL and an event filter once; the host POSTs matching events, signed, as they happen. In v2 durable delivery binds with the surface — a signed event that may be dropped is not a delivery contract.
8
+ A client registers a URL and an event filter once; the host POSTs matching events, signed, as they happen. In v2 durable delivery binds with the surface — a signed event that may be dropped is not a delivery contract.
9
9
 
10
10
  ## Surfaces
11
11
 
@@ -13,14 +13,14 @@ A host that advertises `webhooks` (capabilities.md) serves `registerWebhook` (`P
13
13
 
14
14
  | Operation | Request | Response |
15
15
  | --- | --- | --- |
16
- | `registerWebhook` | `{ url, events[], secret?, tags? }`; `url` MUST be `https://`; `events[]` MUST be non-empty v2 event type names (events.md) | `201 { webhookId }` |
16
+ | `registerWebhook` | `{ url, events[], secret?, tags?, signatureAlgorithms? }`; `url` MUST be `https://`; `events[]` MUST be non-empty v2 event type names (events.md) | `201 { webhookId }` |
17
17
  | `unregisterWebhook` | path `webhookId` | `204`; `404` when unknown; `403` when the caller is outside the subscription's tenant |
18
18
 
19
19
  A subscription MUST receive only events from runs within its tenant scope; cross-tenant delivery is a protocol violation whatever the filter says (invariant `webhook-cross-tenant-isolation`). `tags` narrows delivery to runs whose options carry an overlapping tag.
20
20
 
21
21
  ## Delivery
22
22
 
23
- The delivery envelope is generated from the same payload definition as the event itself and the CloudEvents mapping — one source, three renderings (RFC 0171 §A.4). The body is `{ runId, workspaceId?, event }` where `event` is the verbatim run event (events.md), and it MUST validate against `schemas/v2/webhook-delivery.schema.json`. `workspaceId` is present exactly when `RunSnapshot.owner.workspace` is (`identity.md` §1) — a host MUST NOT substitute its tenant id for an absent workspace.
23
+ The delivery envelope is generated from the event's payload definition (events.md §Payloads). The body is `{ runId, workspaceId?, event }` where `event` is the verbatim run event (events.md), and it MUST validate against `schemas/v2/webhook-delivery.schema.json`. `workspaceId` is present exactly when `RunSnapshot.owner.workspace` is (`identity.md` §1) — a host MUST NOT substitute its tenant id for an absent workspace.
24
24
 
25
25
  The envelope's `runId` MUST use the tenant-bound v2 form defined by
26
26
  `identity.md` §5, matching the nested event and every response representation.
@@ -43,6 +43,18 @@ A host MUST send all five on every delivery. The signed bytes are `{timestamp}.{
43
43
 
44
44
  A subscriber MUST verify before acting: reject a timestamp more than ±5 minutes from its clock; compute `HMAC-SHA256({timestamp}.{rawBody}, secret)`; compare in constant time. A subscriber MUST reject an unrecognized `OpenWOP-Signature-Algorithm` value. Subscribers SHOULD track `(OpenWOP-Webhook-Id, runId, sequence)` for at-least-once deduplication. A host MUST NOT log the secret.
45
45
 
46
+ ### Standard Webhooks
47
+
48
+ `standard-webhooks-1` names the symmetric scheme of Standard Webhooks 1.0.0 (RFC 0201). Its in-header `v1,` token is that standard's, not the OpenWOP scheme id `v1`; neither is read as the other, and `OpenWOP-Signature-Algorithm` stays `v1`.
49
+
50
+ **Opt-in.** A subscription carries the scheme only when `registerWebhook` sends `signatureAlgorithms` listing it and `v1`, with a `secret` of the form `whsec_<base64 of 24–64 bytes>`; otherwise, or when the host does not advertise the id, `400 validation_error`. The `201` echoes the applied list. Every other subscription is unchanged.
51
+
52
+ **Endpoint verification.** Before answering `201`, the host MUST send the request of `schemas/v2/webhook-verification.schema.json` to `url` under §Egress, signed as below, and MUST refuse `400 webhook_endpoint_unverified`, persisting nothing, unless a `2xx` arrives within 10 s whose JSON `challenge` equals the one sent. A host MUST NOT verify a subscription that did not opt in.
53
+
54
+ **Delivery.** Each delivery to an opted-in subscription adds `webhook-id`, `webhook-timestamp` (equal to `OpenWOP-Timestamp`) and `webhook-signature`: space-separated `v1,<base64 HMAC-SHA256>` entries over `{webhook-id}.{webhook-timestamp}.{rawBody}`, keyed by the decoded secret. `webhook-id` matches `^[A-Za-z0-9_-]{16,128}$`, MUST be identical on every attempt of one `(webhookId, runId, sequence)` and MUST differ across them.
55
+
56
+ **Rotation.** A host advertising `webhooks.secretRotation` serves `rotateWebhookSecret`. For `overlapSeconds` after a rotation, `webhook-signature` MUST carry one entry per secret and `OpenWOP-Signature` stays on the previous secret; afterwards only the new secret signs.
57
+
46
58
  ### Dual emission through the overlap
47
59
 
48
60
  A host advertising both majors MUST send, on every delivery, the `X-openwop-*` family alongside the `OpenWOP-*` family with identical values (RFC 0165 §C.1, RFC 0176 §D.2). A v2 receiver MUST accept a delivery carrying only the `X-openwop-*` family under scheme `v1`, verifying the same bytes. This adds no signature scheme. Per-subscription secrets are unchanged across the cut; deliveries queued before the cut are drained under their own retry policy with the payload they were serialized with (persistence.md). The `X-openwop-*` family is removed on its register date.
@@ -64,6 +76,6 @@ A host MUST NOT deliver events a `replay` fork re-emits as fixed history; replay
64
76
 
65
77
  ## Egress
66
78
 
67
- At registration a host MUST reject (`400 webhook_url_rejected`) non-`https://` URLs, RFC 1918 and loopback and link-local ranges, IPv6 ULA, cloud metadata hosts, and `localhost`. At delivery time a host MUST re-resolve the hostname, validate every resolved address against the same denied ranges plus its own denylist, connect to the validated address without re-resolving, and refuse to follow redirects (invariant `webhook-delivery-egress-revalidation`, reference-impl tier). An IPv4-mapped IPv6 address (`::ffff:0:0/96`) MUST be judged by the IPv4 address it embeds, whatever its spelling — a URL parser writes `[::ffff:127.0.0.1]` as `::ffff:7f00:1`. An address embedding IPv4 in another standard translation form (IPv4-compatible `::/96`, NAT64 `64:ff9b::/96`, 6to4 `2002::/16`) SHOULD be judged the same way, and those prefixes SHOULD NOT be denied wholesale: an IPv6-only host behind DNS64 is handed `64:ff9b::<public IPv4>` for a public destination. A host SHOULD refuse every destination the IANA special-purpose address registries mark not globally reachable (RFC 0196 §B).
79
+ At registration a host MUST reject (`400 webhook_url_rejected`) non-`https://` URLs, RFC 1918 and loopback and link-local ranges, IPv6 ULA, cloud metadata hosts, and `localhost`. At delivery time a host MUST re-resolve the hostname, validate every resolved address against the same denied ranges plus its own denylist, connect to the validated address without re-resolving, and refuse to follow redirects (invariant `webhook-delivery-egress-revalidation`, reference-impl tier). An IPv4-mapped IPv6 address (`::ffff:0:0/96`) MUST be judged by the IPv4 address it embeds, whatever its spelling. An address embedding IPv4 in another standard translation form (IPv4-compatible `::/96`, NAT64 `64:ff9b::/96`, 6to4 `2002::/16`) SHOULD be judged the same way, and those prefixes SHOULD NOT be denied wholesale. A host SHOULD refuse every destination the IANA special-purpose address registries mark not globally reachable (RFC 0196 §B).
68
80
 
69
81
  See also: events.md, replay.md, persistence.md, security-defaults.md.
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A workflow-chain pack ships a reusable fragment a host expands into a concrete definition, and may compose other chains as co-registered children. v1 left three lifecycle questions open: which version a chain reference binds to, who owns a shared child, and whether `{{params.*}}` may survive into a persisted definition. v2 decides all three. The manifest is `schemas/v2/workflow-chain-pack-manifest.schema.json`; installation and signing follow packs.md.
8
+ A workflow-chain pack ships a reusable fragment a host expands into a concrete definition, and may compose other chains as co-registered children. The manifest is `schemas/v2/workflow-chain-pack-manifest.schema.json`; installation and signing follow packs.md.
9
9
 
10
10
  ## Exact pins
11
11
 
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "./corrections.schema.json",
3
+ "$comment": "RFC 0197 §A.4 — v2 Class-3 corrections. Seeded with P3 only. The 2.8.2 removal of `workflowChainPacks.deferredParameters` and `.hostExpansionSeam` (P1) is deliberately NOT seeded: a committed bundle carried `deferredParameters` and then failed `capabilities-root-closed`, so the row would fail R3 — which is the point. P1 is recorded as the motivating defect in COMPATIBILITY.md §3a, not laundered into this register.",
4
+ "version": 1,
5
+ "updated": "2026-09-22",
6
+ "rows": [
7
+ {
8
+ "id": "openwop.correction.v2.1",
9
+ "date": "2026-09-20",
10
+ "class": "w3c-class-3",
11
+ "releasedIn": "2.32.0",
12
+ "rfc": "0177",
13
+ "pointers": [
14
+ "schemas/v2/artifact-type-pack-manifest.schema.json#/signing",
15
+ "schemas/v2/chat-card-pack-manifest.schema.json#/signing",
16
+ "schemas/v2/connection-pack-manifest.schema.json#/signing",
17
+ "schemas/v2/form-content-pack-manifest.schema.json#/signing",
18
+ "schemas/v2/node-pack-manifest.schema.json#/signing",
19
+ "schemas/v2/prompt-pack-manifest.schema.json#/signing",
20
+ "schemas/v2/workflow-chain-pack-manifest.schema.json#/signing",
21
+ "schemas/v2/workflow-chain-pack-manifest.schema.json#/chains/[]/dag/nodes/[]/config"
22
+ ],
23
+ "compatibilityEntry": "the v2 bare pack-manifest `signing` block and `FragmentNode.config`",
24
+ "priorShapeCensus": {
25
+ "bundles": 0,
26
+ "manifests": 0,
27
+ "manifestsScanned": 190,
28
+ "note": "The prior shape is a manifest shape, not a discovery shape, so no bundle could carry it and the bundle leg is 0 by construction. The registry leg was measured at the correction: 0 of 190 published v2 pack.json documents validated against the schemas BEFORE the change and 185 of 190 after, so no published document carried the `{ publicKeyRef, signatureRef, method }` block the schemas described. `spec/v2/core/packs.md` §Signing, RFC 0177 §C.3 and migration row openwop.migration.C10.1 had already replaced it with `{ keyId, scheme }`; the schemas were the outlier against the prose, the Accepted RFC and the registry at once. `signing` stays OPTIONAL on a bare manifest, so no previously-valid unsigned document is invalidated. FragmentNode.config was closed by the v1→v2 seed against its own description, failing 73 of 81 published chain packs; reopening it narrows nothing."
29
+ }
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v2/corrections.schema.json",
4
+ "title": "OpenWOP v2 Class-3 corrections register",
5
+ "description": "Shape of spec/v2/corrections.json (RFC 0197 §A.4). A W3C Process Class 3 correction — one that MAY affect conformance but adds no feature — MAY change a v2 shape outside the §A.2 retirement predicate only when nothing conforming stops conforming AND R3 holds for the prior shape (no committed v2 host bundle and no published registry manifest carries it). This register is the machine-readable half of the COMPATIBILITY.md §3 entry: scripts/check-v2-retirement.mjs re-runs the bundle and registry scan over every row, so `priorShapeCensus` is a claim the gate falsifies rather than prose nobody re-reads. scripts/check-v2-surface-monotone.mjs admits a baseline removal whose pointer a row names. Data, not a wire artifact.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["$schema", "version", "updated", "rows"],
9
+ "properties": {
10
+ "$schema": { "type": "string", "const": "./corrections.schema.json" },
11
+ "$comment": { "type": "string" },
12
+ "version": { "type": "integer", "const": 1 },
13
+ "updated": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
14
+ "rows": { "type": "array", "minItems": 0, "items": { "$ref": "#/$defs/row" } }
15
+ },
16
+ "$defs": {
17
+ "row": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["id", "date", "class", "pointers", "compatibilityEntry", "priorShapeCensus"],
21
+ "properties": {
22
+ "id": { "type": "string", "pattern": "^openwop\\.correction\\.v2\\.[0-9]+$" },
23
+ "date": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "description": "The date the correction landed, matching its COMPATIBILITY.md §3 entry." },
24
+ "class": { "type": "string", "const": "w3c-class-3", "description": "Only Class 3 belongs here. A Class 2 editorial change moves no shape and needs no row; anything that invalidates a conforming document is a major (§A.1), not a correction." },
25
+ "releasedIn": { "type": "string", "pattern": "^2\\.[0-9]+(\\.[0-9]+)?$", "description": "The corpus release that carried the correction." },
26
+ "rfc": { "type": ["string", "null"], "pattern": "^[0-9]{4}$", "description": "The RFC under which the correction landed, or null when it landed under an already-Accepted RFC with no new filing." },
27
+ "pointers": {
28
+ "type": "array",
29
+ "minItems": 1,
30
+ "description": "Every schema pointer the correction moved, as `<repo-relative file>#<INSTANCE pointer>` \u2014 the document-shaped path scripts/generate-v2-surface-baseline.mjs prints (`#/signing`, `#/chains/[]/dag/nodes/[]/config`), never the schema-shaped one (`#/properties/signing`), because a `$defs` name is not a surface. scripts/check-v2-surface-monotone.mjs matches a baseline removal against this list, so a pointer left out is a removal the monotone gate refuses.",
31
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9_./-]+#(/[^#|]*)?$" }
32
+ },
33
+ "compatibilityEntry": { "type": "string", "minLength": 1, "description": "A literal substring of the COMPATIBILITY.md §3 \"correction on record\" entry. The gate fails when it drifts — a register row whose prose entry has been reworded away is an unrecorded shape change." },
34
+ "priorShapeCensus": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["bundles", "manifests", "note"],
38
+ "description": "RFC 0197 R3, evaluated for the PRIOR shape at the time of the correction. Both counts MUST be 0; `manifests: null` is not an absence of evidence that may be read as evidence of absence — the gate refuses it.",
39
+ "properties": {
40
+ "bundles": { "type": "integer", "minimum": 0, "description": "Committed evidence/v2-host-bundles/*.json whose discovery.document carried the prior shape." },
41
+ "manifests": { "type": ["integer", "null"], "minimum": 0, "description": "Published registry manifests that carried it. null means no census was run." },
42
+ "manifestsScanned": { "type": ["integer", "null"], "minimum": 0, "description": "How many manifests the census read. A count of 0 out of 0 witnesses nothing." },
43
+ "note": { "type": "string", "minLength": 1, "description": "How the census was taken and why nothing conforming stopped conforming." }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
@@ -61,7 +61,7 @@
61
61
  "key": "implementation",
62
62
  "kind": "metadata",
63
63
  "schema": "metadata/implementation",
64
- "note": null,
64
+ "note": "RFC 0208 §G",
65
65
  "disposition": "kept"
66
66
  },
67
67
  {
@@ -323,7 +323,7 @@
323
323
  {
324
324
  "key": "nodePackRuntimes",
325
325
  "kind": "family",
326
- "normativeText": ["spec/v1/node-packs.md"],
326
+ "normativeText": ["spec/v2/core/node-pack-runtimes.md"],
327
327
  "anchor": "core",
328
328
  "section": "core/capabilities.md#nodePackRuntimes",
329
329
  "peerDependencyId": "nodePackRuntimes",
@@ -405,7 +405,7 @@
405
405
  {
406
406
  "key": "purposePropagation",
407
407
  "kind": "family",
408
- "normativeText": ["spec/v1/capabilities.md"],
408
+ "normativeText": ["spec/v2/core/security-defaults.md"],
409
409
  "anchor": "core",
410
410
  "section": "core/capabilities.md#purposePropagation",
411
411
  "peerDependencyId": "purposePropagation",
@@ -464,7 +464,7 @@
464
464
  {
465
465
  "key": "credentials",
466
466
  "kind": "family",
467
- "normativeText": ["spec/v1/host-capabilities.md"],
467
+ "normativeText": ["spec/v2/core/oauth.md"],
468
468
  "anchor": "core",
469
469
  "section": "core/capabilities.md#credentials",
470
470
  "peerDependencyId": "credentials",
@@ -530,7 +530,7 @@
530
530
  {
531
531
  "key": "oauth",
532
532
  "kind": "family",
533
- "normativeText": ["spec/v1/host-capabilities.md"],
533
+ "normativeText": ["spec/v2/core/oauth.md"],
534
534
  "anchor": "core",
535
535
  "section": "core/capabilities.md#oauth",
536
536
  "peerDependencyId": "oauth",
@@ -541,11 +541,13 @@
541
541
  },
542
542
  "facets": [
543
543
  "grants",
544
- "providers"
544
+ "providers",
545
+ "credentialInterrupt"
545
546
  ],
546
547
  "floorScenarios": [],
547
548
  "requirementIds": [],
548
- "owningRfc": "0047"
549
+ "owningRfc": "0047",
550
+ "facetsFrom": "spec/v2/facets/oauth.schema.json"
549
551
  },
550
552
  {
551
553
  "key": "authorization",
@@ -990,7 +992,7 @@
990
992
  "requirementIds": [],
991
993
  "owningRfc": "0078",
992
994
  "normativeText": [
993
- "spec/v1/tool-catalog.md"
995
+ "spec/v2/core/tool-catalog.md"
994
996
  ]
995
997
  },
996
998
  {
@@ -1146,6 +1148,7 @@
1146
1148
  "facets": [
1147
1149
  "deadLetter",
1148
1150
  "retryPolicy",
1151
+ "secretRotation",
1149
1152
  "signatureAlgorithms"
1150
1153
  ],
1151
1154
  "floorScenarios": [],
@@ -1200,7 +1203,8 @@
1200
1203
  "agentCardUrl",
1201
1204
  "streaming",
1202
1205
  "pushNotifications",
1203
- "durableTasks"
1206
+ "durableTasks",
1207
+ "agentCards"
1204
1208
  ],
1205
1209
  "floorScenarios": [],
1206
1210
  "requirementIds": [],
@@ -1458,7 +1462,7 @@
1458
1462
  {
1459
1463
  "key": "mcp",
1460
1464
  "kind": "family",
1461
- "normativeText": ["spec/v2/core/interop.md"],
1465
+ "normativeText": ["spec/v2/core/interop.md", "spec/v2/core/host-services.md"],
1462
1466
  "anchor": "core",
1463
1467
  "section": "core/capabilities.md#mcp",
1464
1468
  "peerDependencyId": "mcp",
@@ -1476,7 +1480,8 @@
1476
1480
  "features",
1477
1481
  "serverUrls",
1478
1482
  "serverMount",
1479
- "mrtr"
1483
+ "mrtr",
1484
+ "client"
1480
1485
  ],
1481
1486
  "floorScenarios": [],
1482
1487
  "requirementIds": [],
@@ -1759,8 +1764,8 @@
1759
1764
  ],
1760
1765
  "floorScenarios": [],
1761
1766
  "requirementIds": [],
1762
- "owningRfc": "0114",
1763
- "reason": "RFC 0169 \u00a7C.5 (deltaTransport claims-check; ext/ unless a behavioral witness lands)"
1767
+ "owningRfc": "0209",
1768
+ "reason": "RFC 0169 \u00a7C.5 (claims-check; ext/); RFC 0209: the ext README is the v2 contract for `ui.a2ui-surface` schema version 2; facet `deltaTransport` deprecated for v2 (RFC 0197 retirement path)"
1764
1769
  },
1765
1770
  {
1766
1771
  "key": "brand",
@@ -212,7 +212,9 @@
212
212
  ]
213
213
  },
214
214
  "until": {
215
- "type": "string"
215
+ "type": "string",
216
+ "pattern": "^2\\.[0-9]+$",
217
+ "description": "RFC 0197 \u00a7C.8 \u2014 the CORPUS shape-hold: no surface of this family is retired before this 2.x minor. scripts/check-v2-retirement.mjs R5 refuses a removal whose `removeIn` is not later than it. This is NOT the wire record\u2019s `until` (spec/v2/core/capabilities.md \u00a72), which is the host\u2019s own timeline for its own offer \u2014 two fields with one name and different owners, which is why the distinction is written here. No declaration row sets it today."
216
218
  }
217
219
  }
218
220
  },
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://openwop.dev/spec/v2/errors.schema.json",
3
3
  "$comment": "RFC 0171 §B.1 — the error registry: one row per code {code, httpStatus, retriable, details, since, deprecated?}. schemas/v2/error-envelope.schema.json is GENERATED from it (scripts/generate-error-envelope.mjs). Retry timing lives in Retry-After only (§B.2). statusSource records whether the status was stated in v1 prose, a v1 convention, or decided here. details is null until a code's details schema is registered (a null row means details is an open object for that code — closure over details is the RFC 0171 Phase 3 follow-up G-row).",
4
4
  "version": "2.0",
5
- "updated": "2026-09-18",
5
+ "updated": "2026-09-23",
6
6
  "vendorCodePattern": "^(?!openwop\\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\\.[a-z][a-z0-9_]*$",
7
7
  "rows": [
8
8
  {
@@ -167,6 +167,15 @@
167
167
  "since": "1.0",
168
168
  "source": "spec/v1/node-packs.md"
169
169
  },
170
+ {
171
+ "code": "unsupported_runtime",
172
+ "httpStatus": 400,
173
+ "statusSource": "v1 convention (node-packs.md)",
174
+ "retriable": false,
175
+ "details": null,
176
+ "since": "1.0",
177
+ "source": "spec/v1/node-packs.md"
178
+ },
170
179
  {
171
180
  "code": "protocol_version_mismatch",
172
181
  "httpStatus": 400,
@@ -261,6 +270,33 @@
261
270
  "since": "2.0",
262
271
  "source": "spec/v2/core/webhooks.md §Egress"
263
272
  },
273
+ {
274
+ "code": "webhook_endpoint_unverified",
275
+ "httpStatus": 400,
276
+ "statusSource": "v2 decision",
277
+ "retriable": false,
278
+ "details": null,
279
+ "since": "2.36",
280
+ "source": "RFC 0201 §D"
281
+ },
282
+ {
283
+ "code": "connector_auth_declined",
284
+ "httpStatus": 401,
285
+ "statusSource": "v2 decision",
286
+ "retriable": false,
287
+ "details": null,
288
+ "since": "2.36",
289
+ "source": "RFC 0199 §C.4"
290
+ },
291
+ {
292
+ "code": "connection_auth_metadata_mismatch",
293
+ "httpStatus": 422,
294
+ "statusSource": "v2 decision",
295
+ "retriable": false,
296
+ "details": null,
297
+ "since": "2.36",
298
+ "source": "RFC 0199 §B.3"
299
+ },
264
300
  {
265
301
  "code": "audience_mismatch",
266
302
  "httpStatus": 401,
@@ -288,6 +324,15 @@
288
324
  "since": "2.0",
289
325
  "source": "RFC 0170 §B.3"
290
326
  },
327
+ {
328
+ "code": "credential_lifetime_exceeded",
329
+ "httpStatus": 401,
330
+ "statusSource": "v2 decision",
331
+ "retriable": false,
332
+ "details": null,
333
+ "since": "2.36",
334
+ "source": "RFC 0210 §B"
335
+ },
291
336
  {
292
337
  "code": "delegation_expired",
293
338
  "httpStatus": 401,