@openwop/spec-artifacts 2.42.6 → 2.42.8

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 (69) hide show
  1. package/CORPUS-STAMP.json +70 -67
  2. package/api/seams-v2.yaml +238 -322
  3. package/api/v2/asyncapi.yaml +83 -113
  4. package/api/v2/openapi.yaml +408 -724
  5. package/package.json +1 -1
  6. package/schemas/audit-verify-result.schema.json +2 -2
  7. package/schemas/v2/audit-verify-result.schema.json +2 -2
  8. package/schemas/v2/capabilities.schema.json +4 -7
  9. package/schemas/v2/error-envelope.schema.json +144 -69
  10. package/spec/v1/alias-detectors.json +6 -0
  11. package/spec/v1/core-standard-manifest.json +2 -2
  12. package/spec/v1/deprecations.json +22 -0
  13. package/spec/v1/gaps.json +188 -1
  14. package/spec/v1/migrations.json +18 -0
  15. package/spec/v2/README.md +35 -37
  16. package/spec/v2/core/capabilities.md +186 -119
  17. package/spec/v2/core/conformance.md +78 -27
  18. package/spec/v2/core/connection-packs.md +8 -3
  19. package/spec/v2/core/conversation.md +10 -4
  20. package/spec/v2/core/errors.md +30 -9
  21. package/spec/v2/core/events.md +116 -80
  22. package/spec/v2/core/form-content-packs.md +11 -13
  23. package/spec/v2/core/headers.md +23 -14
  24. package/spec/v2/core/host-services.md +22 -5
  25. package/spec/v2/core/i18n.md +64 -0
  26. package/spec/v2/core/idempotency.md +48 -36
  27. package/spec/v2/core/identity.md +110 -42
  28. package/spec/v2/core/interop.md +82 -21
  29. package/spec/v2/core/interrupt.md +79 -40
  30. package/spec/v2/core/node-pack-runtimes.md +18 -4
  31. package/spec/v2/core/oauth.md +40 -6
  32. package/spec/v2/core/overview.md +61 -33
  33. package/spec/v2/core/packs.md +53 -28
  34. package/spec/v2/core/persistence.md +61 -80
  35. package/spec/v2/core/portability.md +44 -0
  36. package/spec/v2/core/replay.md +56 -42
  37. package/spec/v2/core/runs.md +198 -60
  38. package/spec/v2/core/security-defaults.md +57 -33
  39. package/spec/v2/core/tool-catalog.md +29 -6
  40. package/spec/v2/core/versioning.md +115 -50
  41. package/spec/v2/core/webhooks.md +61 -20
  42. package/spec/v2/core/workflow-chain-packs.md +16 -14
  43. package/spec/v2/corrections.json +17 -1
  44. package/spec/v2/corrections.schema.json +1 -1
  45. package/spec/v2/declaration.json +26 -9
  46. package/spec/v2/declaration.schema.json +10 -0
  47. package/spec/v2/errors.json +121 -4
  48. package/spec/v2/errors.schema.json +38 -0
  49. package/spec/v2/ext/README.md +36 -23
  50. package/spec/v2/ext/a2uiSurface/README.md +75 -89
  51. package/spec/v2/ext/brand/README.md +15 -15
  52. package/spec/v2/ext/canvas/README.md +15 -15
  53. package/spec/v2/ext/chat/README.md +15 -15
  54. package/spec/v2/ext/coordination/README.md +15 -15
  55. package/spec/v2/ext/dataIntegration/README.md +17 -20
  56. package/spec/v2/ext/entities/README.md +15 -15
  57. package/spec/v2/ext/grpc-transport/README.md +6 -8
  58. package/spec/v2/ext/kanban/README.md +15 -15
  59. package/spec/v2/ext/knowledge/README.md +15 -15
  60. package/spec/v2/ext/launchStudio/README.md +16 -16
  61. package/spec/v2/ext/messaging/README.md +15 -15
  62. package/spec/v2/ext/portability/README.md +3 -17
  63. package/spec/v2/ext/provider-idempotency/README.md +7 -10
  64. package/spec/v2/ext/restTransport/README.md +30 -19
  65. package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
  66. package/spec/v2/ext/webResearch/README.md +16 -16
  67. package/spec/v2/profiles.json +6 -2
  68. package/spec/v2/release.json +2 -2
  69. package/spec/v2/surface-baseline.json +544 -2
@@ -1,77 +1,121 @@
1
1
  # Versioning and Release
2
2
 
3
- > **Status: Stable · RFC 0172, 0179, 0176.**
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 (RFC 0172 §A)
9
+ ## 1. Major negotiation
10
10
 
11
11
  ### 1.1 Advertisement
12
12
 
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>"]`.
13
+ A v2 host MUST advertise two root fields, both REQUIRED in `schemas/v2/capabilities.schema.json` ([capabilities.md](capabilities.md)):
14
14
 
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).
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. 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.
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.
27
+
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):
20
29
 
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.
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.
22
33
 
23
- Seam and proprietary paths are not manifest operations and need no per-major twin.
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.
24
35
 
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`).
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>` — `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).
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` with `details.protocolVersions[]` echoing the list |
35
- | Header absent on an unversioned path | MUST serve `preferredVersion`'s major |
45
+ | Header names a major not in `protocolVersions[]` | MUST answer `406` `protocol_version_unsupported`, with `details.protocolVersions[]` echoing the list |
46
+ | Header absent on `/.well-known/openwop` | MUST serve `preferredVersion`'s major |
47
+ | Header absent on any other unversioned path | MUST serve major 2: the path is the v2 surface (§1.2) |
36
48
  | `/v1/…` path with `OpenWOP-Version` other than `1` | MUST answer `400` `protocol_version_mismatch` |
37
49
 
38
- A request on a `/v1/…` path key MUST NOT carry `OpenWOP-Version` with a value other than `1`. All three codes are rows in `spec/v2/errors.json` (see `errors.md`).
50
+ `/.well-known/openwop` is the one resource both majors serve at the same unversioned path, so it is the only place a header-less request can come from a client of the earlier major.
51
+
52
+ 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
53
 
40
54
  ### 1.4 The response header
41
55
 
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; the `dual-stack-negotiation` scenario falsifies it. Emitting the header on `/v1/` responses is additive in v1.x and REQUIRED in v2.
56
+ 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`).
57
+
58
+ 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
59
 
44
- A *protocol response* is one produced by an operation named in
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.
60
+ On a manifest-named path:
48
61
 
49
- **On a manifest-named path, a non-protocol response MUST NOT carry `OpenWOP-Version` and MUST NOT be `application/json`**; 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`). A vendor path (§5) is not a shared name and is unconstrained.
62
+ - A non-protocol response MUST NOT carry `OpenWOP-Version` and MUST NOT be `application/json`.
63
+ - 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
64
 
51
- **Content negotiation on a shared name is permitted, with conditions.** A host MAY serve a protocol operation and a page under one unversioned name, selecting on `Accept`, iff:
65
+ A vendor path (§5) is not a shared name and is unconstrained.
52
66
 
53
- 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.
54
- 2. The page obeys the paragraph above.
67
+ #### Content negotiation on a shared name
68
+
69
+ A host MAY serve a protocol operation and a page under one unversioned name, selecting on `Accept`, iff:
70
+
71
+ 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.
72
+ 2. The page obeys the rules above.
55
73
  3. The response carries `Vary: Accept, OpenWOP-Version`.
56
74
 
57
75
  Otherwise the page MUST move off the shared name.
58
76
 
59
77
  ### 1.5 Client precedence and `minClientVersion`
60
78
 
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`.
79
+ 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.
80
+
81
+ A client announces the protocol version it implements in the `OpenWOP-Client-Version` request header, as `<major>.<minor>` or `<major>.<minor>.<patch>` (non-negative integers, no leading zeros). The value is the corpus release the client is built against (§4), not an SDK or product version.
82
+
83
+ - A client SHOULD send it on every request under major 2.
84
+ - The header is optional on every operation and never selects a major (§1.3). A host MUST NOT choose a major or a representation from it.
85
+ - A host MUST NOT refuse a request because it omits the header or sends a malformed value.
86
+ - A value outside the grammar MUST be treated as absent, and MUST NOT produce a `400`.
87
+ - The value is the client's claim. A host MUST NOT use it as an authentication or authorization input.
88
+
89
+ `minClientVersion` (axis 15) is optional. When a host advertises it:
62
90
 
63
- ## 2. The 18 version axes (RFC 0172 §B; RFC 0167 §E.1)
91
+ - It MUST use the axis-1 grammar.
92
+ - A client is below it when the client's major and minor, compared as integers, are less than the floor's. The patch never decides.
93
+ - A host MAY refuse a client below it. A refusal MUST be `426` `client_version_unsupported`, and MAY carry `details.minClientVersion` naming the floor ([errors.md](errors.md)).
94
+ - The discovery document is not exempt: a client refused there learns the floor from `details.minClientVersion`.
64
95
 
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.
96
+ A host MUST NOT answer `426` `client_version_unsupported` to a request that does not carry a well-formed `OpenWOP-Client-Version` below its advertised `minClientVersion`.
97
+
98
+ This header rule binds requests served under major 2. A request served under major 1 follows that major's frozen text.
99
+
100
+ Open gap: RFC 9110 §15.5.22 requires an `Upgrade` header on every `426`. This floor departs from it, and no `Upgrade` value is defined yet.
101
+
102
+ ## 2. The 18 version axes
103
+
104
+ Dispositions:
105
+
106
+ - `unify` — one type and grammar, with a codemod.
107
+ - `first-class` — own schema-enforced grammar and negotiation rule.
108
+ - `retire` — absorbed into the capability record's `{status, since, until?}`.
109
+ - `delete` — removed, with a register row.
66
110
 
67
111
  | # | Axis | Disposition | v2 grammar | Owner |
68
112
  | --- | --- | --- | --- | --- |
69
- | 1 | `protocolVersion` | first-class; kept as `preferredVersion`'s twin for v1 readers through the overlap, removed after | `^(0\|[1-9][0-9]*)\.(0\|[1-9][0-9]*)$` | this document |
113
+ | 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
114
  | 2 | `protocolVersions[]` + `preferredVersion` | first-class, negotiation input | as #1 | this document |
71
115
  | 3 | `engineVersion` | unify: integer everywhere; codemod `openwop.codemod.engine-version-unify` | `integer, minimum 0` | this document |
72
116
  | 4 | `eventLogSchemaVersion` | first-class, the era key | integer; v2 writes `3` | `persistence.md` |
73
117
  | 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 (RFC 0193) | `propertyNames` = the envelope-kind grammar, values `integer, minimum 0`; NOT a closed enum — a vendor kind is host-published and never corpus-declared | `events.md` |
118
+ | 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
119
  | 7 | `version.pinned` | first-class; the v1-pinned-run disposition | integer min/max | `persistence.md` |
76
120
  | 8 | `contractProvenance` | delete | — | `capabilities.md` |
77
121
  | 9 | `minimumSuiteVersion` | retire into `spec/v2/declaration.json` | semver | `capabilities.md` |
@@ -80,51 +124,72 @@ When both majors are advertised, a v2 client MUST select the highest major it im
80
124
  | 12 | MCP `revisions[]` / `preferredVersion` | first-class facet of `mcp` | date | `interop.md` |
81
125
  | 13 | `multiAgent.executionModel.version` | first-class | integer with a schema `maximum` the suite reads | `events.md` |
82
126
  | 14 | OpenAPI / AsyncAPI `info.version` | generated from the corpus tag | semver | this document |
83
- | 15 | `minClientVersion` | first-class MUST (§1.5) | as #1 | this document |
127
+ | 15 | `minClientVersion` | first-class (§1.5) | as #1 | this document |
84
128
  | 16 | channel `schemaVersion` / `compatibleWith` | first-class | integer / range | `events.md` |
85
129
  | 17 | webhook signature scheme | retire into `deprecations.json` | — | `webhooks.md` |
86
130
  | 18 | pack `engines.openwop` + `registryVersion` | first-class with the absent-ceiling rule | semver range / semver | `packs.md` |
87
131
 
88
- One grammar covers protocol, envelope-kind, and pack axes wherever a version is `<major>.<minor>` (#1, #2, #11, #15). `typeId@<semver>` is a pack axis (`packs.md`); the `2` in `typeId@2.0.0` never means `OpenWOP-Version: 2`. `docs/PROTOCOL-STATUS.md` carries one row per axis (RFC 0172 §D.2).
132
+ - One grammar covers protocol, envelope-kind and pack axes wherever a version is `<major>.<minor>` (#1, #2, #11, #15).
133
+ - `typeId@<semver>` is a pack axis (`packs.md`); the `2` in `typeId@2.0.0` never means `OpenWOP-Version: 2`.
134
+ - `docs/PROTOCOL-STATUS.md` carries one row per axis.
89
135
 
90
136
  ### 2.1 `engineVersion` (axis 3)
91
137
 
92
- `engineVersion` MUST be an integer (`minimum 0`) at the discovery root and on every per-event carrier. 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. The codemod `openwop.codemod.engine-version-unify` MUST refuse any value not matching `^(0|[1-9][0-9]*)$`.
138
+ - `engineVersion` MUST be an integer (`minimum 0`) at the discovery root and on every per-event carrier.
139
+ - 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.
140
+ - The codemod `openwop.codemod.engine-version-unify` MUST refuse any value not matching `^(0|[1-9][0-9]*)$`.
93
141
 
94
- ### 2.2 `eventLogSchemaVersion` (axis 4; RFC 0176 §A.2)
142
+ ### 2.2 `eventLogSchemaVersion` (axis 4)
95
143
 
96
144
  `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
145
 
98
- ## 3. Where v2 lives (RFC 0172 §C)
146
+ ## 3. Where v2 lives
147
+
148
+ - `spec/v2/core/` and `spec/v2/ext/<key>/` hold the prose.
149
+ - `schemas/v2/` holds every v2 schema, with `$id` under `https://openwop.dev/spec/v2/`; the site publishes them at `/spec/v2/`.
150
+ - 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.
151
+ - AsyncAPI `servers.production.pathname` is empty; every channel address carries its own path, as OpenAPI path keys do.
152
+
153
+ ## 4. One release identity
154
+
155
+ 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.
156
+
157
+ - 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.
158
+ - The published tarball digest MUST equal the tree's as a release precondition.
159
+ - The identity and advertised-versions checks keep their three-outcome discipline ([conformance.md](conformance.md)).
160
+ - 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.
161
+ - A v1.x consumer MUST NOT vendor `schemas/v2/`.
162
+
163
+ ## 5. The overlap
99
164
 
100
- `spec/v2/core/` and `spec/v2/ext/<key>/` hold the prose; `schemas/v2/` holds every v2 schema with `$id` under `https://openwop.dev/spec/v2/`; the site publishes them at `/spec/v2/`. The flat `schemas/` tree (v1 `$id`s) is read-only from the cut; v1 `$id` values are immutable identifiers, and a domain move is answered by a redirect, never a rewrite. AsyncAPI `servers.production.pathname` is empty and every channel address carries its own path, exactly as OpenAPI path keys do.
165
+ Through the overlap a host:
101
166
 
102
- ## 4. One release identity (RFC 0172 §D)
167
+ - MUST advertise both majors (§1.1);
168
+ - MUST emit `OpenWOP-Version` on every response (§1.4);
169
+ - MUST serve `/.well-known/openwop` as one resource whose representation the request header selects ([capabilities.md](capabilities.md)).
103
170
 
104
- 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 is bumped only by the release PR that cuts the tag. 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; the published tarball digest MUST equal the tree's as a release precondition. The identity and advertised-versions checks keep their three-outcome discipline (`conformance.md`).
171
+ 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
172
 
106
- 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; a v1.x consumer MUST NOT vendor `schemas/v2/` (RFC 0176 §E.1).
173
+ 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
174
 
108
- ## 5. The overlap (RFC 0167 §B.5; RFC 0176)
175
+ ### Retirement
109
176
 
110
- Through the overlap a host MUST advertise both majors (§1.1), MUST emit `OpenWOP-Version` on every response (§1.4), and MUST serve `/.well-known/openwop` as one resource whose representation the request header selects (`capabilities.md`). 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.
177
+ 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
178
 
112
- **A run minted under major 1 and read under major 2 MUST use the tenant-bound
113
- projection** `<tenantId>/<v1-id>` (`identity.md` §5). A host MUST NOT return a
114
- bare v1 id in a major-2 response.
179
+ - **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.
180
+ - **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
181
 
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.
182
+ ### Host-proprietary paths: `/host/<org>/…`
117
183
 
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.
184
+ 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
185
 
120
- **Retirement changes every header-less request's default contract.** Before
121
- end-of-support, a header-less unversioned request uses major 1; afterward it uses
122
- major 2. Before retirement, a host MUST check for collisions between manifest
123
- top-level path segments and non-protocol unversioned routes. It MUST move each
124
- colliding route or apply §1.4 content negotiation.
186
+ - 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.
187
+ - An org MUST NOT be named after a manifest segment under `/host/` (`reservedOrgs`).
188
+ - A host SHOULD advertise the mount under `extensions.<org>.<name>`.
189
+ - A `/v1/host/<org>/…` twin MAY ride the overlap; it retires atomically with `/v1`.
125
190
 
126
- **Host-proprietary paths live at `/host/<org>/…` (RFC 0181).** Every vendor namespace — capability records (`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. A host MAY serve operations the manifest does not name under `/host/<org>/…` for its registered org: no major in the path, served regardless of `OpenWOP-Version`, never a protocol operation, never measured, outside §1.4. An org MUST NOT be named after a manifest segment under `/host/` (`reservedOrgs`); a host SHOULD advertise the mount under `extensions.<org>.<name>`. A `/v1/host/<org>/…` twin MAY ride the overlap and retires atomically with `/v1`.
191
+ ## 6. Migration rows
127
192
 
128
- ## 6. Migration rows (RFC 0172)
193
+ 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
194
 
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).
195
+ *Sources: RFC 0167, RFC 0168, RFC 0172, RFC 0176, RFC 0179, RFC 0181, RFC 0193, RFC 0219.*
@@ -1,33 +1,36 @@
1
1
  # Webhooks
2
2
 
3
- > **Status: Stable · RFC 0165 §C.1, 0173 §B, 0176 §D.2, 0171 §A.4, 0215, 0217.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `webhooks`.
5
5
 
6
6
  ## Why this exists
7
7
 
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.
8
+ A client registers a URL and an event filter once; the host POSTs matching events, signed, as they happen. 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
 
12
- A host that advertises `webhooks` (capabilities.md) serves `registerWebhook` (`POST /webhooks`) and `unregisterWebhook` (`DELETE /webhooks/{webhookId}`) from `api/v2/openapi.yaml`. The facet (`spec/v2/facets/webhooks.schema.json`) is `{ signatureAlgorithms[] }`, which MUST list `"v1"`; there is no `durable` field.
12
+ A host that advertises `webhooks` ([capabilities.md](capabilities.md)) serves `registerWebhook` (`POST /webhooks`) and `unregisterWebhook` (`DELETE /webhooks/{webhookId}`) from `api/v2/openapi.yaml`. The facet (`spec/v2/facets/webhooks.schema.json`) is `{ signatureAlgorithms[] }`, which MUST list `"v1"`.
13
13
 
14
14
  | Operation | Request | Response |
15
15
  | --- | --- | --- |
16
- | `registerWebhook` | `{ url, events[], secret?, tags?, signatureAlgorithms? }`; `url` MUST be `https://`; `events[]` MUST be non-empty v2 event type names (events.md) | `201 { webhookId }` |
16
+ | `registerWebhook` | `{ url, events[], secret?, tags?, signatureAlgorithms? }` | `201 { webhookId, secret? }` |
17
17
  | `unregisterWebhook` | path `webhookId` | `204`; `404` when unknown; `403` when the caller is outside the subscription's tenant |
18
18
 
19
- A `204` from `unregisterWebhook` ends the subscription's deliveries, including retries already scheduled (§Durability; RFC 0215 §B).
19
+ On `registerWebhook`, `url` MUST be `https://` and `events[]` MUST be non-empty v2 event type names ([events.md](events.md)).
20
+
21
+ When `registerWebhook` omits `secret`, the host MUST generate one and return it as `secret` in the `201`. That response is the only one that carries it. A supplied secret MUST NOT be echoed.
22
+
23
+ A `204` from `unregisterWebhook` ends the subscription's deliveries, including retries already scheduled (§Durability).
20
24
 
21
25
  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.
22
26
 
23
27
  ## Delivery
24
28
 
25
- 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.
29
+ 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.
26
30
 
27
- The envelope's `runId` MUST use the tenant-bound v2 form defined by
28
- `identity.md` §5, matching the nested event and every response representation.
29
- This requirement applies to outbound delivery even though no versioned request
30
- exists at delivery time.
31
+ - The body MUST validate against `schemas/v2/webhook-delivery.schema.json`.
32
+ - `workspaceId` is present exactly when `RunSnapshot.owner.workspace` is ([identity.md §1](identity.md)); a host MUST NOT substitute its tenant id for an absent workspace.
33
+ - The envelope's `runId` MUST use the tenant-bound form of [identity.md §5](identity.md), matching the nested event and every response representation, even though no versioned request exists at delivery time.
31
34
 
32
35
  ### Headers
33
36
 
@@ -43,15 +46,21 @@ A host MUST send all five on every delivery. The signed bytes are `{timestamp}.{
43
46
 
44
47
  ### Verification
45
48
 
46
- 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.
49
+ A subscriber MUST verify before acting:
50
+
51
+ 1. Reject a timestamp more than ±5 minutes from its clock.
52
+ 2. Compute `HMAC-SHA256({timestamp}.{rawBody}, secret)`.
53
+ 3. Compare in constant time.
54
+
55
+ A subscriber MUST reject an unrecognized `OpenWOP-Signature-Algorithm` value, and SHOULD track `(OpenWOP-Webhook-Id, runId, sequence)` for at-least-once deduplication. A host MUST NOT log the secret.
47
56
 
48
57
  ### Standard Webhooks
49
58
 
50
- `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`.
59
+ `standard-webhooks-1` names the symmetric scheme of Standard Webhooks 1.0.0. 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`.
51
60
 
52
61
  **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.
53
62
 
54
- **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.
63
+ **Endpoint verification.** Before answering `201`, a host MUST send the request of `schemas/v2/webhook-verification.schema.json` to `url` under §Egress, signed as below. Unless a `2xx` arrives within 10 s whose JSON `challenge` equals the one sent, it MUST refuse `400 webhook_endpoint_unverified`, persisting nothing. A host MUST NOT verify a subscription that did not opt in.
55
64
 
56
65
  **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.
57
66
 
@@ -59,28 +68,60 @@ A subscriber MUST verify before acting: reject a timestamp more than ±5 minutes
59
68
 
60
69
  ### Dual emission through the overlap
61
70
 
62
- 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.
71
+ - A host advertising both majors MUST send, on every delivery, the `X-openwop-*` family alongside the `OpenWOP-*` family with identical values. This adds no signature scheme.
72
+ - A v2 receiver MUST accept a delivery carrying only the `X-openwop-*` family under scheme `v1`, verifying the same bytes.
73
+ - Per-subscription secrets are unchanged across the cut. Deliveries queued before the cut drain under their own retry policy with the payload they were serialized with ([persistence.md](persistence.md)).
74
+ - The `X-openwop-*` family is removed on its register date.
63
75
 
64
76
  ## Durability
65
77
 
66
- Durable delivery is an obligation of the `webhooks` surface (RFC 0173 §B; security-defaults.md). A host MUST:
78
+ Durable delivery is an obligation of the `webhooks` surface ([security-defaults.md](security-defaults.md)). A host MUST:
67
79
 
68
80
  - retry a failed attempt per its advertised `retryPolicy` (`maxAttempts`, `backoff ∈ none | fixed | exponential`) with backoff between attempts;
69
- - route a delivery whose retries are exhausted to the dead-letter sink, rather than drop it. A host advertising `webhooks.deadLetter` MUST serve `GET /webhooks/{webhookId}/dead-letters` (RFC 0188), and that record MUST NOT carry the delivered body, the delivery headers, or the subscription secret — a dead-letter read names a delivery, it does not replay one. The read belongs to a live subscription: after `unregisterWebhook` answers `204`, `GET /webhooks/{webhookId}/dead-letters` for that id MUST answer `404 not_found`, exactly as for a same-tenant id the host never minted, and the host MAY discard that subscription's records at the unregister. `expiresAt` bounds a record's retention only while its subscription exists (RFC 0217). This sink is the DELIVERY sink; the `deadLetter` family (RFC 0053) is the RUN sink and is a different thing;
81
+ - route a delivery whose retries are exhausted to the dead-letter sink (§Dead letters), rather than drop it;
70
82
  - deliver each matching event at least once; a receiver MAY observe the same event more than once;
71
- - not make the start of an attempt to one subscription wait for an attempt to a *different* subscription to finish (answered, failed, or timed out): one subscription's slow or dead receiver MUST NOT delay another subscription's deliveries (invariant `webhook-delivery-isolation`, RFC 0215 §A.1). The rule is about when an attempt starts, not how soon after an event it must start, and it names no mechanism: a lane per subscription, a concurrent pool or asynchronous I/O all meet it, and a sequential loop over a batch does not;
72
- - sustain that while at least **8** subscriptions have attempts outstanding that their receivers have not answered (RFC 0215 §A.2). A host MAY bound concurrent attempts beyond that, and SHOULD NOT let one tenant's unanswered attempts occupy capacity another tenant's deliveries need (§A.3);
73
- - after `unregisterWebhook` answers `204`, not start any further attempt for that subscription, including attempts already scheduled for retry (invariant `webhook-unregister-stops-delivery`, RFC 0215 §B). An attempt whose request the host had begun sending before the `204` MAY complete. The unregister does not oblige the host to route that subscription's undelivered events to the dead-letter sink: the exhaustion rule above governs deliveries of a live subscription;
83
+ - not make the start of an attempt to one subscription wait for an attempt to a *different* subscription to finish (answered, failed, or timed out) (§Delivery isolation);
84
+ - sustain that while at least **8** subscriptions have attempts outstanding that their receivers have not answered;
85
+ - after `unregisterWebhook` answers `204`, not start any further attempt for that subscription, including attempts already scheduled for retry (invariant `webhook-unregister-stops-delivery`);
74
86
  - dead-letter a `payload_unprojectable` delivery (events.md §Era-2) on the first attempt, never retry it.
75
87
 
76
88
  Best-effort delivery is not a conforming mode. A `3xx` response is a delivery failure, retried under the same policy. `webhook-durable-delivery` witnesses it.
77
89
 
90
+ ### Delivery isolation
91
+
92
+ One subscription's slow or dead receiver MUST NOT delay another subscription's deliveries (invariant `webhook-delivery-isolation`). The rule is about when an attempt starts, not how soon after an event it must start, and it names no mechanism: a lane per subscription, a concurrent pool or asynchronous I/O all meet it; a sequential loop over a batch does not.
93
+
94
+ A host MAY bound concurrent attempts beyond the floor of 8, and SHOULD NOT let one tenant's unanswered attempts occupy capacity another tenant's deliveries need.
95
+
96
+ ### Unregister
97
+
98
+ An attempt whose request the host had begun sending before `unregisterWebhook` answered `204` MAY complete. The unregister does not oblige the host to route that subscription's undelivered events to the dead-letter sink: the exhaustion rule governs deliveries of a live subscription.
99
+
100
+ ### Dead letters
101
+
102
+ This sink is the delivery sink; the `deadLetter` family is the run sink and is a different thing.
103
+
104
+ - A host advertising `webhooks.deadLetter` MUST serve `GET /webhooks/{webhookId}/dead-letters`.
105
+ - A record MUST NOT carry the delivered body, the delivery headers, or the subscription secret — a dead-letter read names a delivery, it does not replay one.
106
+ - The read belongs to a live subscription: after `unregisterWebhook` answers `204`, `GET /webhooks/{webhookId}/dead-letters` for that id MUST answer `404 not_found`, exactly as for a same-tenant id the host never minted. The host MAY discard that subscription's records at the unregister.
107
+ - `expiresAt` bounds a record's retention only while its subscription exists.
108
+
78
109
  ## Replay
79
110
 
80
111
  A host MUST NOT deliver events a `replay` fork re-emits as fixed history; replay-ness is read from the run, never from the event type (replay.md). A `branch` fork's events are new facts and are delivered.
81
112
 
82
113
  ## Egress
83
114
 
84
- 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). These delivery-time rules bind an A2A push identically (interop.md §"A2A push delivery").
115
+ 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`.
116
+
117
+ Address classification:
118
+
119
+ - An IPv4-mapped IPv6 address (`::ffff:0:0/96`) MUST be judged by the IPv4 address it embeds, whatever its spelling.
120
+ - 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.
121
+ - A host SHOULD refuse every destination the IANA special-purpose address registries mark not globally reachable.
122
+
123
+ 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). These delivery-time rules bind an A2A push identically (interop.md §"A2A push delivery").
85
124
 
86
125
  See also: events.md, replay.md, persistence.md, security-defaults.md.
126
+
127
+ *Sources: RFC 0053, RFC 0165, RFC 0171, RFC 0173, RFC 0176, RFC 0188, RFC 0196, RFC 0201, RFC 0215, RFC 0217.*
@@ -1,37 +1,39 @@
1
1
  # Workflow Chain Packs
2
2
 
3
- > **Status: Stable · RFC 0177, RFC 0133.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `workflowChainPacks`.
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. 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](packs.md).
9
9
 
10
10
  ## Exact pins
11
11
 
12
- Every reference a chain makes to a node type or an external chain MUST pin an exact version per referenced `typeId` (`core.ai.callPrompt@1.0.0`). A host MUST refuse to register a chain whose reference carries a range or no version. Ranges are a v2.x additive follow-up and do not exist in v2.0.
12
+ Every reference a chain makes to a node type or an external chain MUST pin an exact version per referenced `typeId` (`core.ai.callPrompt@1.0.0`). A host MUST refuse to register a chain whose reference carries a range or no version.
13
13
 
14
14
  ## Co-registered children
15
15
 
16
- When a parent chain expands a `subChainRef`, the child is registered as its own workflow under a deterministic child id. A host MUST:
16
+ When a parent chain expands a `subChainRef`, the child is registered as its own workflow. A host MUST:
17
17
 
18
- | Rule | Behavior |
19
- | --- | --- |
20
- | Identity | register the child under a deterministic id, so two parents composing the same child share one registration |
21
- | Reference count | count each parent that references the child; deleting a parent decrements the count |
22
- | Deletion | delete the child only when its last parent is deleted |
23
- | Ownership record | persist the resolved child version in the parent's ownership record, so a re-instantiation or `:fork` reproduces the same child |
18
+ - **Identity** — register the child under a deterministic id, so two parents composing the same child share one registration.
19
+ - **Reference count** — count each parent that references the child; deleting a parent decrements the count.
20
+ - **Deletion** — delete the child only when its last parent is deleted.
21
+ - **Ownership record** — persist the resolved child version in the parent's ownership record, so a re-instantiation or `:fork` reproduces the same child.
24
22
 
25
- The ownership record is a persistence.md store.
23
+ The ownership record is a [persistence.md](persistence.md) store.
26
24
 
27
25
  ## Parameter substitution
28
26
 
29
- `{{params.<name>}}` tokens are substituted at expansion time, when the author drops the tile. A persisted definition MUST NOT contain a `{{params.*}}` token, and a host MUST NOT defer substitution to dispatch time. A portable per-run deferral — materializing chain parameters into workflow variables bound through PromptTemplate `{{varName}}` slots and variable-sourced PortValues — is a named v2.x additive RFC and is not part of v2.0.
27
+ `{{params.<name>}}` tokens are substituted at expansion time, when the author drops the tile. A persisted definition MUST NOT contain a `{{params.*}}` token, and a host MUST NOT defer substitution to dispatch time.
30
28
 
31
29
  ## Composition depth
32
30
 
33
- A host MUST bound sub-chain nesting by `workflowChainPacks.subChains.maxDepth` (capabilities.md), default 8. A composition that exceeds the depth, or that transitively composes itself, MUST fail closed with `sub_chain_cycle`; the depth check and the cycle check compose as one guard.
31
+ A host MUST bound sub-chain nesting by `workflowChainPacks.subChains.maxDepth` ([capabilities.md](capabilities.md)), default 8. A composition that exceeds the depth, or that transitively composes itself, MUST fail closed with `sub_chain_cycle`; the depth check and the cycle check compose as one guard.
34
32
 
35
33
  ## Edge conditions
36
34
 
37
- Fragment edges carry the same `condition` and `triggerRule` shapes as a top-level definition (`schemas/v2/workflow-definition.schema.json`). `EdgeCondition.type` is one of `expression`, `equals`, `notEquals`, `contains`, `regex`, `truthy`, `falsy`; `truthy` and `falsy` take `left` and no `right`. A host MUST carry both fields through expansion verbatim and MUST honor them on expanded edges as on authored ones. form-content-packs.md reuses this operator set for field visibility.
35
+ Fragment edges carry the same `condition` and `triggerRule` shapes as a top-level definition (`schemas/v2/workflow-definition.schema.json`). `EdgeCondition.type` is one of `expression`, `equals`, `notEquals`, `contains`, `regex`, `truthy`, `falsy`; `truthy` and `falsy` take `left` and no `right`.
36
+
37
+ A host MUST carry both fields through expansion verbatim and MUST honor them on expanded edges as on authored ones. [form-content-packs.md](form-content-packs.md) reuses this operator set for field visibility.
38
+
39
+ *Sources: RFC 0133, RFC 0177.*
@@ -2,7 +2,7 @@
2
2
  "$schema": "./corrections.schema.json",
3
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
4
  "version": 1,
5
- "updated": "2026-09-22",
5
+ "updated": "2026-09-27",
6
6
  "rows": [
7
7
  {
8
8
  "id": "openwop.correction.v2.1",
@@ -27,6 +27,22 @@
27
27
  "manifestsScanned": 190,
28
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
29
  }
30
+ },
31
+ {
32
+ "id": "openwop.correction.v2.2",
33
+ "date": "2026-09-27",
34
+ "class": "w3c-class-3",
35
+ "rfc": "0172",
36
+ "pointers": [
37
+ "schemas/v2/capabilities.schema.json#/minClientVersion"
38
+ ],
39
+ "compatibilityEntry": "the v2 `minClientVersion` grammar",
40
+ "priorShapeCensus": {
41
+ "bundles": 0,
42
+ "manifests": 0,
43
+ "manifestsScanned": 156,
44
+ "note": "The prior shape is any string; the corrected shape is the axis-1 `<major>.<minor>` grammar that spec/v2/core/versioning.md §1.5 and the v2-min-client-version scenario already required of an advertised value. Bundle leg: all 8 committed evidence/v2-host-bundles/*.json advertise minClientVersion, and every value conforms (seven \"1.0\", MyndHyve \"0.1\"), so 0 carry a value the pattern rejects. The one \"0.1.0\" in the corpus is a major-1 witness under evidence/rfc-witnesses/, not a v2 bundle. Registry leg: minClientVersion is a discovery field, not a manifest field; 0 of 156 pack.json documents on openwop-registry origin/main (c15d3d6) carry it. The schema added a pattern only, so no property, type or required entry moved and check-v2-surface-monotone sees no change."
45
+ }
30
46
  }
31
47
  ]
32
48
  }
@@ -30,7 +30,7 @@
30
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
31
  "items": { "type": "string", "pattern": "^[A-Za-z0-9_./-]+#(/[^#|]*)?$" }
32
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." },
33
+ "compatibilityEntry": { "type": "string", "minLength": 1, "description": "A literal substring of the COMPATIBILITY.md §3 \"correction on record\" entry, which is kept in docs/COMPATIBILITY-CORRECTIONS-LOG.md. The gate fails when it drifts — a register row whose prose entry has been reworded away is an unrecorded shape change." },
34
34
  "priorShapeCensus": {
35
35
  "type": "object",
36
36
  "additionalProperties": false,