@openwop/spec-artifacts 2.0.0-rc.0 → 2.0.0-rc.10

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.
@@ -180,6 +180,10 @@
180
180
  "type": "string",
181
181
  "format": "uri",
182
182
  "description": "OPTIONAL (RFC 0089). URL of the host's most recent conformance certification bundle (`conformance-certification-bundle.schema.json`) — a machine-readable attestation binding this host's claimed profiles to the reproducible run that substantiates them. Omitting it is fully conformant; clients MUST tolerate its absence."
183
+ },
184
+ "seamsProfile": {
185
+ "const": "openwop-conformance-seams-v2",
186
+ "description": "RFC 0168 §C.1 — the host serves the conformance seams profile (api/seams-v2.yaml) at /conformance/seams/…. Absent means the seam-driven scenarios record `blocked`, never a pass."
183
187
  }
184
188
  },
185
189
  "additionalProperties": false,
@@ -228,6 +232,45 @@
228
232
  "additionalProperties": true,
229
233
  "x-openwop-seeded-from": "v1"
230
234
  },
235
+ "signingKeys": {
236
+ "type": "array",
237
+ "description": "RFC 0168 §E.2 — the public keys this host signs certification bundles with. A bundle signature names a keyId; a verifier resolves it HERE, in the discovery document of the host the bundle is about, and checks the Ed25519 signature with the matching publicKey. Without this array a signature attests integrity only: it proves the bundle was not altered after signing and says nothing about who signed it, because a signer can mint a keypair and a keyId at will. RFC 0168 disposed of \"an Ed25519 attestation without a key registry is a signature nobody can check\" by naming this surface; this is that surface.",
238
+ "items": {
239
+ "type": "object",
240
+ "additionalProperties": false,
241
+ "required": [
242
+ "keyId",
243
+ "alg",
244
+ "publicKey"
245
+ ],
246
+ "properties": {
247
+ "keyId": {
248
+ "$ref": "ids.schema.json#/$defs/keyId",
249
+ "description": "The identifier a bundle signature carries in signature.keyId. Unique within this array."
250
+ },
251
+ "alg": {
252
+ "const": "ed25519",
253
+ "description": "RFC 0168 §E.2 fixes the attestation algorithm; no other value is defined."
254
+ },
255
+ "publicKey": {
256
+ "type": "string",
257
+ "pattern": "^[A-Za-z0-9_-]{43}$",
258
+ "description": "The Ed25519 public key, base64url, unpadded (32 bytes) — the half that verifies, never the half that signs."
259
+ },
260
+ "use": {
261
+ "enum": [
262
+ "certification-bundle"
263
+ ],
264
+ "description": "What the key signs. Closed, so a future use is an explicit addition rather than a reinterpretation of a key already published for something else."
265
+ },
266
+ "retiredAt": {
267
+ "type": "string",
268
+ "format": "date-time",
269
+ "description": "When the key stopped signing. A retired key stays listed so bundles it already signed remain verifiable; dropping it would silently invalidate historical evidence, which is the opposite of what an evidence trail is for."
270
+ }
271
+ }
272
+ }
273
+ },
231
274
  "discovery": {
232
275
  "type": "object",
233
276
  "description": "Discovery advertisement (capabilities-change-detection.md). Optional in v1; absence means the host serves only the public unauthenticated payload at /.well-known/openwop. Landed by RFC 0011.",
@@ -5034,6 +5077,29 @@
5034
5077
  "v1"
5035
5078
  ]
5036
5079
  }
5080
+ },
5081
+ "retryPolicy": {
5082
+ "type": "object",
5083
+ "additionalProperties": false,
5084
+ "required": [
5085
+ "maxAttempts",
5086
+ "backoff"
5087
+ ],
5088
+ "properties": {
5089
+ "maxAttempts": {
5090
+ "type": "integer",
5091
+ "minimum": 1
5092
+ },
5093
+ "backoff": {
5094
+ "type": "string",
5095
+ "enum": [
5096
+ "none",
5097
+ "fixed",
5098
+ "exponential"
5099
+ ]
5100
+ }
5101
+ },
5102
+ "description": "spec/v2/core/webhooks.md §Delivery — the policy a host retries a failed attempt under. The webhooks family carries it at v2; `triggerBridge.retryPolicy` is the v1 carrier and stays through the overlap."
5037
5103
  }
5038
5104
  },
5039
5105
  "allOf": [
@@ -100,7 +100,8 @@
100
100
  }
101
101
  },
102
102
  "signingKeyId": {
103
- "$ref": "ids.schema.json#/$defs/keyId"
103
+ "$ref": "ids.schema.json#/$defs/keyId",
104
+ "description": "The host key the signature was made with (the same keyId as signature.keyId)."
104
105
  },
105
106
  "relaxations": {
106
107
  "type": "array",
@@ -253,7 +254,8 @@
253
254
  "properties": {
254
255
  "id": {
255
256
  "type": "string",
256
- "pattern": "^openwop\\.requirement\\."
257
+ "pattern": "^openwop\\.(?!it\\.(?:artifact-schema-compile-bounded|artifact-type-legacy-ids|capability-example-root-layout|certification-floor-enforcement|chain-subchain-unsupported-refused|compensation-profile|core-manifest-and-extension-registry|discovery-canonical-family-no-shadow|edge-condition-truthy-falsy|effect-identity-composition|effect-identity-cross-scope|error-envelope-canonical-shape|event-codemap-complete|form-content-packs|multi-region-effect-vocabulary|normative-example-extraction|openapi-asyncapi-sdk-parity|pack-manifest-extensions|protocol-version-grammar|registry-declarative-kinds|rfc-0147-self-audit|rfc-lifecycle-coherence|semantic-digest-v2|spec-corpus-validity|spec-section-citations|tool-result-trust-monotone|v2-alias-covers-reword|v2-corpus-tag-pinned|v2-declaration-parity|v2-error-registry-prose-parity|v2-event-naming-rule|v2-events-channel-parity|v2-externally-gated-never-stable|v2-bundle-signature-attributable|v2-one-signing-scheme|v2-path-parity|v2-release-identity|v2-req-only-assertions|v2-seams-not-in-canonical|v2-security-defaults-table|v2-spec-artifacts-digest|v2-threat-model-template|v2-witness-required|versioned-composition-profiles|workflow-chain-internal-flag|workload-identity-profile)\\.)(requirement|it|scenario|floor|profile|family)\\.[a-z0-9]",
258
+ "description": "RFC 0168 §A.1 — `openwop.<area>.<slug>`. The suite mints six areas: `requirement` (an explicit `req()` id), `it` (a per-test id from the scenario file and title), `scenario` and `floor` (per-file rows), `profile` (a claimed-profile row) and `family` (a capability gate). The lookahead refuses every id minted from a corpus-coherence file: those run in the spec repo’s CI and MUST NOT appear in a host bundle (conformance.md §\"Two products, two ledgers\"; RFC 0168 §D.1). `scripts/check-req-only.mjs` fails when the list drifts from `conformance/src/coherence/`."
257
259
  },
258
260
  "scenario": {
259
261
  "type": "string"
@@ -349,11 +351,30 @@
349
351
  "suite.version",
350
352
  "discovery.sha256"
351
353
  ]
354
+ }
355
+ }
356
+ },
357
+ "verifierSignature": {
358
+ "type": "object",
359
+ "additionalProperties": false,
360
+ "required": [
361
+ "alg",
362
+ "keyId",
363
+ "sig"
364
+ ],
365
+ "properties": {
366
+ "alg": {
367
+ "const": "ed25519"
352
368
  },
353
- "verifierKeyId": {
369
+ "keyId": {
354
370
  "$ref": "ids.schema.json#/$defs/keyId"
371
+ },
372
+ "sig": {
373
+ "type": "string",
374
+ "pattern": "^[A-Za-z0-9_-]{86}$"
355
375
  }
356
- }
376
+ },
377
+ "description": "RFC 0168 §E.2 — required when any claimed profile is evidenceTier independent; the key MUST differ from the host's."
357
378
  }
358
379
  },
359
380
  "allOf": [
@@ -396,6 +417,26 @@
396
417
  "detail"
397
418
  ]
398
419
  }
420
+ },
421
+ {
422
+ "if": {
423
+ "properties": {
424
+ "claimedProfiles": {
425
+ "contains": {
426
+ "properties": {
427
+ "evidenceTier": {
428
+ "const": "independent"
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ },
435
+ "then": {
436
+ "required": [
437
+ "verifierSignature"
438
+ ]
439
+ }
399
440
  }
400
441
  ]
401
442
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/error-envelope.schema.json",
4
4
  "title": "ErrorEnvelope (v2)",
5
- "description": "GENERATED from spec/v2/errors.json by scripts/generate-error-envelope.mjs (RFC 0171 §B.1). 92 registered codes; vendor codes match ^(?!openwop\\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\\.[a-z][a-z0-9_]*$. Consumers MUST accept an unknown registered member and MUST NOT act on it (RFC 0171 §A.5).",
5
+ "description": "GENERATED from spec/v2/errors.json by scripts/generate-error-envelope.mjs (RFC 0171 §B.1). 94 registered codes; vendor codes match ^(?!openwop\\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\\.[a-z][a-z0-9_]*$. Consumers MUST accept an unknown registered member and MUST NOT act on it (RFC 0171 §A.5).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -40,6 +40,7 @@
40
40
  "event_type_unmapped",
41
41
  "forbidden",
42
42
  "force_engine_version_forbidden",
43
+ "fork_point_invalid",
43
44
  "id_tenant_mismatch",
44
45
  "idempotency_in_flight",
45
46
  "idempotency_key_invalid",
@@ -104,6 +105,7 @@
104
105
  "until_in_past",
105
106
  "validation_error",
106
107
  "version_conflict",
108
+ "webhook_url_rejected",
107
109
  "workspace_conflict",
108
110
  "workspace_membership_required",
109
111
  "workspace_too_large"
@@ -151,6 +153,7 @@
151
153
  "event_type_unmapped": 500,
152
154
  "forbidden": 403,
153
155
  "force_engine_version_forbidden": 403,
156
+ "fork_point_invalid": 422,
154
157
  "id_tenant_mismatch": 403,
155
158
  "idempotency_in_flight": 409,
156
159
  "idempotency_key_invalid": 400,
@@ -215,6 +218,7 @@
215
218
  "until_in_past": 400,
216
219
  "validation_error": 400,
217
220
  "version_conflict": 409,
221
+ "webhook_url_rejected": 400,
218
222
  "workspace_conflict": 409,
219
223
  "workspace_membership_required": 403,
220
224
  "workspace_too_large": 413
@@ -6,26 +6,123 @@
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "$defs": {
9
- "opaque": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{16,128}$", "description": "Host-minted opaque token: no `@`, no whitespace, no `/`; 16–128 characters." },
10
- "tenantId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}$", "x-openwop-minted": "host" },
11
- "workspaceId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}$", "x-openwop-minted": "host" },
12
- "subjectId": { "type": "string", "pattern": "^[^\\s/]{1,256}$", "description": "Issuer-scoped, stable, opaque (RFC 0170 §A); the issuer's grammar, never PII.", "x-openwop-minted": "host" },
13
- "runId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "description": "Tenant-bound: `<tenantId>/<opaque>`.", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
14
- "interruptId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
15
- "subscriptionId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
16
- "deliveryId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
17
- "effectId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
18
- "eventId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host" },
19
- "traceId": { "type": "string", "pattern": "^[0-9a-f]{32}$", "description": "W3C trace-context trace-id.", "x-openwop-minted": "host" },
20
- "spanId": { "type": "string", "pattern": "^[0-9a-f]{16}$", "description": "W3C trace-context parent-id.", "x-openwop-minted": "host" },
21
- "keyId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}$", "description": "A signing-key identifier (registry `signingKeys[].keyId`, resume-token `kid`, bundle signature `keyId`).", "x-openwop-minted": "registry" },
22
- "nodeId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "description": "Author-chosen within a workflow definition.", "x-openwop-minted": "author" },
23
- "workflowId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
24
- "agentId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
25
- "chainId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
26
- "pluginId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
27
- "templateId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
28
- "libraryId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
29
- "typeId": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-zA-Z0-9-]*)+$", "description": "A node type id (`core.ai.callPrompt`); the RFC 0107 grammar.", "x-openwop-minted": "author" }
9
+ "opaque": {
10
+ "type": "string",
11
+ "pattern": "^[A-Za-z0-9._~-]{16,128}$",
12
+ "description": "Host-minted opaque token: no `@`, no whitespace, no `/`; 16–128 characters."
13
+ },
14
+ "tenantId": {
15
+ "type": "string",
16
+ "pattern": "^(anon:)?[A-Za-z0-9._~-]{1,128}$",
17
+ "x-openwop-minted": "host",
18
+ "description": "A tenant identifier. The optional `anon:` prefix is the ONLY colon admitted, and it exists because RFC 0132 anonymous tenants are `anon:<hash>` while RFC 0170 §A.5 makes the anonymous surface first-class — without it a conforming host can serve an anonymous run with neither a valid `owner.tenant` nor a valid tenant-bound `runId`. `:` is a legal path character (RFC 3986 pchar), so this stays safe inside the `<tenantId>/<opaque>` run-id form. The alternative — renaming anonymous tenants at the cut — was rejected: it is a write to historical identity, which is the one thing the v2 migration does not do."
19
+ },
20
+ "workspaceId": {
21
+ "type": "string",
22
+ "pattern": "^[A-Za-z0-9._~-]{1,128}$",
23
+ "x-openwop-minted": "host"
24
+ },
25
+ "subjectId": {
26
+ "type": "string",
27
+ "pattern": "^[^\\s/]{1,256}$",
28
+ "description": "Issuer-scoped, stable, opaque (RFC 0170 §A); the issuer's grammar, never PII.",
29
+ "x-openwop-minted": "host"
30
+ },
31
+ "runId": {
32
+ "type": "string",
33
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
34
+ "description": "Tenant-bound: `<tenantId>/<opaque>`.",
35
+ "x-openwop-minted": "host",
36
+ "x-openwop-tenant-bound": true
37
+ },
38
+ "interruptId": {
39
+ "type": "string",
40
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
41
+ "x-openwop-minted": "host",
42
+ "x-openwop-tenant-bound": true
43
+ },
44
+ "subscriptionId": {
45
+ "type": "string",
46
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
47
+ "x-openwop-minted": "host",
48
+ "x-openwop-tenant-bound": true
49
+ },
50
+ "deliveryId": {
51
+ "type": "string",
52
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
53
+ "x-openwop-minted": "host",
54
+ "x-openwop-tenant-bound": true
55
+ },
56
+ "effectId": {
57
+ "type": "string",
58
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
59
+ "x-openwop-minted": "host",
60
+ "x-openwop-tenant-bound": true
61
+ },
62
+ "eventId": {
63
+ "type": "string",
64
+ "pattern": "^[A-Za-z0-9._~-]{16,128}$",
65
+ "x-openwop-minted": "host"
66
+ },
67
+ "traceId": {
68
+ "type": "string",
69
+ "pattern": "^[0-9a-f]{32}$",
70
+ "description": "W3C trace-context trace-id.",
71
+ "x-openwop-minted": "host"
72
+ },
73
+ "spanId": {
74
+ "type": "string",
75
+ "pattern": "^[0-9a-f]{16}$",
76
+ "description": "W3C trace-context parent-id.",
77
+ "x-openwop-minted": "host"
78
+ },
79
+ "keyId": {
80
+ "type": "string",
81
+ "pattern": "^[A-Za-z0-9._~-]{1,128}$",
82
+ "description": "A signing-key identifier (registry `signingKeys[].keyId`, resume-token `kid`, bundle signature `keyId`).",
83
+ "x-openwop-minted": "registry"
84
+ },
85
+ "nodeId": {
86
+ "type": "string",
87
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
88
+ "description": "Author-chosen within a workflow definition.",
89
+ "x-openwop-minted": "author"
90
+ },
91
+ "workflowId": {
92
+ "type": "string",
93
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
94
+ "x-openwop-minted": "author"
95
+ },
96
+ "agentId": {
97
+ "type": "string",
98
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
99
+ "x-openwop-minted": "author"
100
+ },
101
+ "chainId": {
102
+ "type": "string",
103
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
104
+ "x-openwop-minted": "author"
105
+ },
106
+ "pluginId": {
107
+ "type": "string",
108
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
109
+ "x-openwop-minted": "author"
110
+ },
111
+ "templateId": {
112
+ "type": "string",
113
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
114
+ "x-openwop-minted": "author"
115
+ },
116
+ "libraryId": {
117
+ "type": "string",
118
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
119
+ "x-openwop-minted": "author"
120
+ },
121
+ "typeId": {
122
+ "type": "string",
123
+ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-zA-Z0-9-]*)+$",
124
+ "description": "A node type id (`core.ai.callPrompt`); the RFC 0107 grammar.",
125
+ "x-openwop-minted": "author"
126
+ }
30
127
  }
31
128
  }
@@ -119,6 +119,16 @@
119
119
  "type": "boolean",
120
120
  "default": false,
121
121
  "description": "When true, an unmet peer dependency degrades (inert + surfaced in `degraded[]`) rather than failing install."
122
+ },
123
+ "facets": {
124
+ "type": "array",
125
+ "minItems": 1,
126
+ "uniqueItems": true,
127
+ "items": {
128
+ "type": "string",
129
+ "pattern": "^[a-z][A-Za-z0-9]*$"
130
+ },
131
+ "description": "Facets of the peer family the pack needs (packs.md §Peer dependencies; RFC 0177 §B.1). Each MUST be a facet the declaration file lists for that family; the family key alone means the family's presence."
122
132
  }
123
133
  }
124
134
  }
@@ -158,29 +158,8 @@
158
158
  }
159
159
  },
160
160
  "runtime": {
161
- "type": "object",
162
- "required": [
163
- "language"
164
- ],
165
- "properties": {
166
- "language": {
167
- "type": "string",
168
- "enum": [
169
- "javascript",
170
- "typescript",
171
- "wasm",
172
- "remote"
173
- ],
174
- "description": "Pack runtime. `javascript`/`typescript` for in-process Node executors; `wasm` for sandboxed WASM (RFC 0008 ABI); `remote` for agent-only packs that dispatch via remote LLM providers + don't ship local executor bytecode."
175
- },
176
- "entry": {
177
- "type": "string"
178
- },
179
- "abiVersion": {
180
- "type": "string"
181
- }
182
- },
183
- "additionalProperties": false
161
+ "$ref": "node-pack-manifest.schema.json#/$defs/Runtime",
162
+ "description": "The pack runtime block, byte-for-byte the node-pack manifest's (`format`, `minRuntimeVersion`, `requires` included)."
184
163
  },
185
164
  "peerDependencies": {
186
165
  "type": "object",
@@ -189,6 +168,9 @@
189
168
  "type": "string"
190
169
  }
191
170
  },
171
+ "peerDependenciesMeta": {
172
+ "$ref": "node-pack-manifest.schema.json#/properties/peerDependenciesMeta"
173
+ },
192
174
  "dependencies": {
193
175
  "type": "object",
194
176
  "description": "Pack dependencies (other packs). Wire-locked at install time per `pack-lockfile.schema.json`.",
@@ -246,13 +228,13 @@
246
228
  },
247
229
  "tarballUrl": {
248
230
  "type": "string",
249
- "description": "Registry-relative URL for the signed tarball. Always `/v1/packs/{name}/-/{version}.tgz`.",
250
- "pattern": "^/v1/packs/[^/]+/-/[^/]+\\.tgz$"
231
+ "description": "Registry-relative URL for the signed tarball. Always `/v2/packs/{name}/-/{version}.tgz` (RFC 0177 §A.3 full-path templates).",
232
+ "pattern": "^/v2/packs/[^/]+/-/[^/]+\\.tgz$"
251
233
  },
252
234
  "signatureUrl": {
253
235
  "type": "string",
254
- "description": "Registry-relative URL for the detached Ed25519 signature. Always `/v1/packs/{name}/-/{version}.sig`.",
255
- "pattern": "^/v1/packs/[^/]+/-/[^/]+\\.sig$"
236
+ "description": "Registry-relative URL for the detached signature. Always `/v2/packs/{name}/-/{version}.sig`.",
237
+ "pattern": "^/v2/packs/[^/]+/-/[^/]+\\.sig$"
256
238
  },
257
239
  "yanked": {
258
240
  "type": "boolean",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/run-event-payloads.schema.json",
4
4
  "title": "RunEventPayloads",
5
- "description": "Per-RunEventType payload schemas. The base RunEventDoc shape (run-event.schema.json) leaves `payload` permissive for forward-compat. This schema defines the canonical payload contract for each known RunEventType. Consumers MAY pin strict payload validation via `$defs.<typeId>` and `ajv.validate(schema.$defs[event.type], event.payload)`. Unknown event types MUST be tolerated (no $defs match → fold best-effort).\n\n117 variants from `run-event.schema.json#$defs.RunEventType` are covered, grouped into ~20 shape families with shared $defs. Naming convention: camelCase keys mirror dotted RunEventType names (e.g., `run.started` → `runStarted`). v2: every definition closed; _typeIndex normative; the legacy interrupt payloads (approval*/clarification*) removed per deprecation row legacy-interrupt-payloads.",
5
+ "description": "Per-RunEventType payload schemas. The base RunEventDoc shape (run-event.schema.json) leaves `payload` permissive for forward-compat. This schema defines the canonical payload contract for each known RunEventType. Consumers MAY pin strict payload validation via `$defs.<typeId>` and `ajv.validate(schema.$defs[event.type], event.payload)`. Unknown event types MUST be tolerated (no $defs match → fold best-effort).\n\n118 variants from the generated `type` branch of `run-event.schema.json` are covered (the v2 vocabulary is `spec/v2/event-codemap.json`; the old `$defs.RunEventType` twin is deleted — it held 36 retired v1 spellings and nothing referenced it), grouped into ~20 shape families with shared $defs. Naming convention: camelCase keys mirror dotted RunEventType names (e.g., `run.started` → `runStarted`). v2: every definition closed; _typeIndex normative; the legacy interrupt payloads (approval*/clarification*) removed per deprecation row legacy-interrupt-payloads.",
6
6
  "type": "object",
7
7
  "$defs": {
8
8
  "_typeIndex": {
@@ -153,7 +153,8 @@
153
153
  },
154
154
  {
155
155
  "type": "string",
156
- "pattern": "^(?!openwop\\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\\.[a-z][a-z0-9]*(-[a-z0-9]+)*(\\.[a-z][a-z0-9]*(-[a-z0-9]+)*)?$"
156
+ "pattern": "^(?!openwop\\.)(?!(?:agent|approval|artifact|authorization|budget|cap|channel|clarification|commitment|compensation|connector|context|conversation|deployment|dispatch|egress|envelope|eval|goal|import|interrupt|lease|log|memory|model|negotiation|node|orchestrator|output|prompt|proposal|provider|replay|roster|run|tool|trigger|variable|version|voice|workflow|workflow-chain|workspace)\\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\\.[a-z][a-z0-9]*(-[a-z0-9]+)*(\\.[a-z][a-z0-9]*(-[a-z0-9]+)*)?$",
157
+ "description": "RFC 0171 §A.1 — the positive vendor branch `<org>.<domain-or-name>`. The lookahead bans `openwop.` and every first segment the closed enum registers (agent, approval, artifact, authorization, budget, cap, channel, clarification, commitment, compensation, connector, context, conversation, deployment, dispatch, egress, envelope, eval, goal, import, interrupt, lease, log, memory, model, negotiation, node, orchestrator, output, prompt, proposal, provider, replay, roster, run, tool, trigger, variable, version, voice, workflow, workflow-chain, workspace), so the two branches are disjoint (a registered type matches exactly one) and a typo of a registered domain (`run.startd`) is refused instead of validating as a vendor type — the v1 defect this rule replaces. Regenerate the segment list with scripts/check-v2-schemas.mjs when the enum grows; the corpus wrapper v2-event-naming-rule asserts it."
157
158
  }
158
159
  ],
159
160
  "description": "RFC 0171 §A.1 — a registered protocol type (closed enum generated from spec/v2/event-codemap.json) or a vendor event under <org>.<name>; openwop. is the only reserved prefix. Consumers MUST accept an unknown registered member and MUST NOT act on it (§A.5)."
@@ -194,131 +195,7 @@
194
195
  "description": "Optional ID of the event that caused this one (e.g., node.completed caused by approval.received). Lets projections build causal chains without inferring from timing."
195
196
  }
196
197
  },
197
- "$comment": "RFC 0094 §G + COMPATIBILITY.md §\"Schema closure\": RunEventDoc is a SERVER-EMITTED shape, so it is open (`additionalProperties: true`) v1.x hosts may add optional fields additively without breaking schema-validating clients. Client-submitted shapes stay closed at their outermost composition.",
198
+ "$comment": "RFC 0171 §A / `events.md` §\"The envelope\": in v2 RunEventDoc is CLOSED (`additionalProperties: false`). This reverses the v1 rule, which left the envelope open so v1.x hosts could add optional fields additively. The v2 envelope carries host-specific data in the registered vendor branch, not at the root. Do not restore the v1 sentence: a host that follows it emits root-level extension fields and fails `v2-event-type-closed`, and the schema keyword is the contract while a comment is only a claim about it.",
198
199
  "additionalProperties": false,
199
- "$defs": {
200
- "RunEventType": {
201
- "type": "string",
202
- "description": "Discriminator for event payload shape. Dotted naming convention. New variants extend the union — readers MUST NOT throw on unknown types (forward-compat: fold best-effort, ignore unknowns). RFC 0151 §D adds the six content-free `compensation.*` events. They are added to this CLOSED enum because without them a conforming host cannot emit them at all: the RFC named the events, the enum did not contain them, and any host following §D would have produced run events that fail schema validation. Payloads carry opaque node/effect IDs, attempt, ordering model, and closed reason codes — never provider bodies or credentials (§D/§G).",
203
- "enum": [
204
- "run.started",
205
- "run.completed",
206
- "run.failed",
207
- "run.cancelled",
208
- "run.resuming",
209
- "run.paused",
210
- "run.resumed",
211
- "run.restored-from-snapshot",
212
- "run.dead_lettered",
213
- "node.started",
214
- "node.completed",
215
- "node.failed",
216
- "node.suspended",
217
- "node.suspend-failed",
218
- "node.resumed",
219
- "node.retried",
220
- "node.skipped",
221
- "node.cancelled",
222
- "approval.requested",
223
- "approval.received",
224
- "approval.granted",
225
- "approval.rejected",
226
- "approval.overridden",
227
- "clarification.requested",
228
- "clarification.resolved",
229
- "interrupt.requested",
230
- "interrupt.resolved",
231
- "channel.written",
232
- "channel.presence",
233
- "context.summarized",
234
- "artifact.created",
235
- "output.chunk",
236
- "variable.changed",
237
- "log.appended",
238
- "version.pinned",
239
- "workflow.restored",
240
- "workflow.loopback-limit",
241
- "workflow.stalled",
242
- "cap.breached",
243
- "lease.acquired",
244
- "lease.renewed",
245
- "lease.lost",
246
- "lease.handed-off",
247
- "replay.diverged",
248
- "replay.divergedAtRefusal",
249
- "agent.reasoned",
250
- "agent.reasoning.delta",
251
- "provider.usage",
252
- "prompt.composed",
253
- "agent.promptResolved",
254
- "model.capability.substituted",
255
- "model.capability.insufficient",
256
- "envelope.retry.attempted",
257
- "envelope.retry.exhausted",
258
- "envelope.refusal",
259
- "envelope.truncated",
260
- "envelope.nlToFormat.engaged",
261
- "envelope.recovery.applied",
262
- "agent.toolCalled",
263
- "agent.toolReturned",
264
- "agent.handoff",
265
- "agent.decided",
266
- "agent.verified",
267
- "agent.invocation.started",
268
- "agent.invocation.completed",
269
- "eval.started",
270
- "eval.scored",
271
- "eval.completed",
272
- "deployment.promoted",
273
- "deployment.rolled-back",
274
- "deployment.canary.adjusted",
275
- "deployment.state.changed",
276
- "roster.run.initiated",
277
- "tool.session.opened",
278
- "tool.session.closed",
279
- "egress.decided",
280
- "trigger.subscription.state.changed",
281
- "trigger.delivery.attempted",
282
- "budget.reserved",
283
- "budget.consumed",
284
- "budget.threshold.crossed",
285
- "budget.exhausted",
286
- "runOrchestrator.decided",
287
- "node.dispatched",
288
- "core.dispatch.fanOut",
289
- "core.dispatch.join",
290
- "conversation.opened",
291
- "conversation.exchanged",
292
- "conversation.closed",
293
- "memory.compacted",
294
- "memory.written",
295
- "agent.memory.consolidated",
296
- "commitment.fired",
297
- "workspace.updated",
298
- "core.workflowChain.event",
299
- "core.workflowChain.confidence-escalated",
300
- "connector.authorized",
301
- "connector.auth_expired",
302
- "authorization.decided",
303
- "proposal.created",
304
- "proposal.activated",
305
- "goal.evaluated",
306
- "goal.closed",
307
- "import.applied",
308
- "voice.speech_start",
309
- "voice.transcript",
310
- "voice.endpoint_candidate",
311
- "voice.turn_commit",
312
- "voice.synthesis_chunk",
313
- "voice.barge_in",
314
- "voice.cancelled",
315
- "compensation.requested",
316
- "compensation.started",
317
- "compensation.completed",
318
- "compensation.failed",
319
- "compensation.paused",
320
- "compensation.manual_intervention_required"
321
- ]
322
- }
323
- }
200
+ "$defs": {}
324
201
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/run-snapshot.schema.json",
4
4
  "title": "RunSnapshot",
5
- "description": "Projected run state returned by `GET /v1/runs/{runId}`. Source is the run's append-only event log (run-event.schema.json) folded through the `RunProjection`. Forward-compat tolerant: readers MUST ignore unknown fields rather than reject.",
5
+ "description": "Projected run state returned by `GET /runs/{runId}`. Source is the runs append-only event log (run-event.schema.json) folded through the `RunProjection`. CLOSED in v2 (`additionalProperties: false`): a reader MUST NOT expect to ignore unknown root fields, because there are none to ignore — host-specific state goes in `extensions`. The v1 schema was open and therefore never had to declare what hosts actually served; closing the object is what made that visible.",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "eventLogSchemaVersion",
@@ -201,6 +201,27 @@
201
201
  }
202
202
  },
203
203
  "additionalProperties": false
204
+ },
205
+ "parentRunId": {
206
+ "$ref": "ids.schema.json#/$defs/runId",
207
+ "description": "RFC 0022 / `runs.md` §forkRun — the run this one was forked from, absent on a root run. Declared because the object is closed: a fork’s lineage is real protocol state, and an undeclared field on a closed object is deleted rather than ignored."
208
+ },
209
+ "inputs": {
210
+ "type": "object",
211
+ "description": "RFC 0022 §A — the inputs the run was created with. Declared for the same reason as parentRunId: the v1 wire carried it under an open schema, and closing the object without a seat would force every host to drop it.",
212
+ "additionalProperties": true
213
+ },
214
+ "extensions": {
215
+ "type": "object",
216
+ "additionalProperties": false,
217
+ "patternProperties": {
218
+ "^[a-z][a-z0-9]*(-[a-z0-9]+)*\\.[a-z][a-z0-9]*(-[a-z0-9]+)*$": {
219
+ "type": "object",
220
+ "additionalProperties": true,
221
+ "description": "A vendor/host extension record; its shape is the org’s, declared open on purpose."
222
+ }
223
+ },
224
+ "description": "RFC 0169 §A.4 — the seat for host-specific snapshot state, keyed `<org>.<name>`. Closing an object without an extension seat does not make hosts stop having extra state; it makes them delete it or serve an invalid document. Every other closed v2 surface has this seat and the snapshot was the omission."
204
225
  }
205
226
  },
206
227
  "additionalProperties": false
@@ -4,7 +4,7 @@
4
4
  "provenance": {
5
5
  "suite": {
6
6
  "package": "@openwop/openwop-conformance",
7
- "version": "2.0.0-rc.0"
7
+ "version": "2.0.0-rc.10"
8
8
  },
9
9
  "note": "Derived from the corpus at generation time. Regenerate with --write; verify with --check."
10
10
  },
@@ -419,5 +419,5 @@
419
419
  "$id": "https://openwop.dev/spec/v1/workspace-file.schema.json"
420
420
  }
421
421
  ],
422
- "digest": "b4fec837a0301ce9015b610bd3ec6ff055cf6ae716049dac18b5fd8184a1ff7c"
422
+ "digest": "2f27e94651be9988594694e4ccc5c71f521fa1bd5d404c671958485aecb8adf8"
423
423
  }