@openwop/openwop-conformance 1.72.2 → 1.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -2
  3. package/api/asyncapi.yaml +58 -0
  4. package/api/openapi.yaml +4 -1
  5. package/dist/cli.js +107 -1
  6. package/dist/lib/profiles.js +70 -4
  7. package/package.json +2 -1
  8. package/schemas/CORPUS-STAMP.json +2 -2
  9. package/schemas/README.md +2 -0
  10. package/schemas/capabilities.schema.json +2054 -572
  11. package/schemas/certification-bundle-v2.schema.json +108 -0
  12. package/schemas/run-event-payloads.schema.json +3411 -857
  13. package/schemas/run-event.schema.json +31 -9
  14. package/schemas/workflow-definition.schema.json +492 -130
  15. package/schemas/workload-identity.schema.json +73 -0
  16. package/src/cli.ts +119 -1
  17. package/src/lib/a2a-fake-peer.ts +20 -0
  18. package/src/lib/behavior-gate.ts +42 -7
  19. package/src/lib/llm-cache-key-recipe.ts +51 -0
  20. package/src/lib/mcp-fake-server.ts +20 -0
  21. package/src/lib/profiles.ts +95 -4
  22. package/src/lib/requirement-ledger.ts +138 -0
  23. package/src/lib/requirement-registry.ts +62 -0
  24. package/src/scenarios/a2a-version-negotiation.test.ts +159 -0
  25. package/src/scenarios/capability-example-root-layout.test.ts +113 -0
  26. package/src/scenarios/certification-bundle-v2.test.ts +157 -0
  27. package/src/scenarios/certification-floor-enforcement.test.ts +115 -0
  28. package/src/scenarios/compensation-behavior.test.ts +164 -0
  29. package/src/scenarios/compensation-profile.test.ts +175 -0
  30. package/src/scenarios/contract-provenance.test.ts +209 -0
  31. package/src/scenarios/core-manifest-and-extension-registry.test.ts +198 -0
  32. package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +219 -0
  33. package/src/scenarios/effect-identity-composition.test.ts +129 -0
  34. package/src/scenarios/effect-identity-cross-scope.test.ts +82 -0
  35. package/src/scenarios/mcp-version-negotiation.test.ts +159 -0
  36. package/src/scenarios/multi-region-effect-vocabulary.test.ts +175 -0
  37. package/src/scenarios/multi-region-idempotency.test.ts +17 -7
  38. package/src/scenarios/openapi-resolved-paths.test.ts +127 -0
  39. package/src/scenarios/protocol-version-grammar.test.ts +119 -0
  40. package/src/scenarios/requirement-ledger.test.ts +162 -0
  41. package/src/scenarios/rfc-0147-self-audit.test.ts +104 -0
  42. package/src/scenarios/rfc-lifecycle-coherence.test.ts +137 -0
  43. package/src/scenarios/semantic-digest-v2.test.ts +128 -0
  44. package/src/scenarios/semantic-digest-vectors.test.ts +140 -0
  45. package/src/scenarios/spec-corpus-validity.test.ts +22 -8
  46. package/src/scenarios/strict-behavior-gate.test.ts +120 -0
  47. package/src/scenarios/versioned-composition-profiles.test.ts +183 -0
  48. package/src/scenarios/workload-identity-behavior.test.ts +188 -0
  49. package/src/scenarios/workload-identity-profile.test.ts +175 -0
  50. package/vectors/semantic-request-digest-v2.json +236 -0
@@ -4,7 +4,17 @@
4
4
  "title": "WorkflowDefinition",
5
5
  "description": "DAG of typed nodes, edges, triggers, and variables that an OpenWOP host executes. Canonical OpenWOP v1 workflow-definition shape.",
6
6
  "type": "object",
7
- "required": ["id", "name", "version", "nodes", "edges", "triggers", "variables", "metadata", "settings"],
7
+ "required": [
8
+ "id",
9
+ "name",
10
+ "version",
11
+ "nodes",
12
+ "edges",
13
+ "triggers",
14
+ "variables",
15
+ "metadata",
16
+ "settings"
17
+ ],
8
18
  "properties": {
9
19
  "id": {
10
20
  "type": "string",
@@ -18,7 +28,9 @@
18
28
  "minLength": 1,
19
29
  "maxLength": 256
20
30
  },
21
- "description": { "type": "string" },
31
+ "description": {
32
+ "type": "string"
33
+ },
22
34
  "version": {
23
35
  "type": "string",
24
36
  "description": "Semver-like workflow version. Tracked separately from engineVersion + eventLogSchemaVersion (see version-negotiation.md).",
@@ -28,10 +40,17 @@
28
40
  "type": "string",
29
41
  "description": "Optional category for filtering."
30
42
  },
31
- "isActive": { "type": "boolean" },
43
+ "isActive": {
44
+ "type": "boolean"
45
+ },
32
46
  "status": {
33
47
  "type": "string",
34
- "enum": ["active", "inactive", "draft", "archived"]
48
+ "enum": [
49
+ "active",
50
+ "inactive",
51
+ "draft",
52
+ "archived"
53
+ ]
35
54
  },
36
55
  "tenantId": {
37
56
  "type": "string",
@@ -48,145 +67,279 @@
48
67
  },
49
68
  "nodes": {
50
69
  "type": "array",
51
- "items": { "$ref": "#/$defs/WorkflowNode" },
70
+ "items": {
71
+ "$ref": "#/$defs/WorkflowNode"
72
+ },
52
73
  "minItems": 1
53
74
  },
54
75
  "edges": {
55
76
  "type": "array",
56
- "items": { "$ref": "#/$defs/WorkflowEdge" }
77
+ "items": {
78
+ "$ref": "#/$defs/WorkflowEdge"
79
+ }
57
80
  },
58
81
  "triggers": {
59
82
  "type": "array",
60
- "items": { "$ref": "#/$defs/WorkflowTrigger" }
83
+ "items": {
84
+ "$ref": "#/$defs/WorkflowTrigger"
85
+ }
61
86
  },
62
87
  "variables": {
63
88
  "type": "array",
64
- "items": { "$ref": "#/$defs/WorkflowVariable" }
89
+ "items": {
90
+ "$ref": "#/$defs/WorkflowVariable"
91
+ }
65
92
  },
66
93
  "groups": {
67
94
  "type": "array",
68
- "items": { "$ref": "#/$defs/NodeGroup" },
95
+ "items": {
96
+ "$ref": "#/$defs/NodeGroup"
97
+ },
69
98
  "description": "Visual node groups (organizational only; do not affect execution)."
70
99
  },
71
100
  "channels": {
72
101
  "description": "Optional typed state channels (see channels-and-reducers.md). When present, channel-aware mode applies.",
73
102
  "type": "object",
74
- "additionalProperties": { "$ref": "#/$defs/ChannelDeclaration" }
103
+ "additionalProperties": {
104
+ "$ref": "#/$defs/ChannelDeclaration"
105
+ }
75
106
  },
76
107
  "configurableSchema": {
77
- "description": "Optional JSON Schema 2020-12 declaring which RunOptions.configurable keys this workflow accepts. When present, hosts MUST validate POST /v1/runs `configurable` payloads against this schema and reject mismatches with `validation_error`. Hosts MUST surface this schema on GET /v1/workflows/{workflowId} so clients can pre-flight-validate. See run-options.md §'Per-workflow configurableSchema'. Additive in v1.1.",
108
+ "description": "Optional JSON Schema 2020-12 declaring which RunOptions.configurable keys this workflow accepts. When present, hosts MUST validate POST /v1/runs `configurable` payloads against this schema and reject mismatches with `validation_error`. Hosts MUST surface this schema on GET /v1/workflows/{workflowId} so clients can pre-flight-validate. See run-options.md \u00a7'Per-workflow configurableSchema'. Additive in v1.1.",
78
109
  "type": "object"
79
110
  },
80
111
  "defaults": {
81
112
  "type": "object",
82
113
  "additionalProperties": false,
83
- "description": "RFC 0029 §B. Workflow-author-controlled per-kind fallback values that apply at resolution chain layer 3 (`workflow-defaults`) per `spec/v1/prompts.md` §\"Resolution chain (normative)\". Applied when neither the node (layer 1) nor the node's bound agent (layer 2) specifies a value for the kind. Future RFCs MAY add sibling defaults (e.g., `defaults.temperature`, `defaults.modelClass`) without colliding.",
114
+ "description": "RFC 0029 \u00a7B. Workflow-author-controlled per-kind fallback values that apply at resolution chain layer 3 (`workflow-defaults`) per `spec/v1/prompts.md` \u00a7\"Resolution chain (normative)\". Applied when neither the node (layer 1) nor the node's bound agent (layer 2) specifies a value for the kind. Future RFCs MAY add sibling defaults (e.g., `defaults.temperature`, `defaults.modelClass`) without colliding.",
84
115
  "properties": {
85
116
  "promptRefs": {
86
117
  "type": "object",
87
118
  "additionalProperties": false,
88
119
  "description": "Per-kind PromptRef fallbacks for layer 3 of the resolution chain.",
89
120
  "properties": {
90
- "system": { "$ref": "./prompt-ref.schema.json" },
91
- "user": { "$ref": "./prompt-ref.schema.json" },
92
- "few-shot": { "$ref": "./prompt-ref.schema.json" },
93
- "schema-hint": { "$ref": "./prompt-ref.schema.json" }
121
+ "system": {
122
+ "$ref": "./prompt-ref.schema.json"
123
+ },
124
+ "user": {
125
+ "$ref": "./prompt-ref.schema.json"
126
+ },
127
+ "few-shot": {
128
+ "$ref": "./prompt-ref.schema.json"
129
+ },
130
+ "schema-hint": {
131
+ "$ref": "./prompt-ref.schema.json"
132
+ }
94
133
  }
95
134
  }
96
135
  }
97
136
  },
98
- "metadata": { "$ref": "#/$defs/WorkflowMetadata" },
99
- "settings": { "$ref": "#/$defs/WorkflowSettings" },
137
+ "metadata": {
138
+ "$ref": "#/$defs/WorkflowMetadata"
139
+ },
140
+ "settings": {
141
+ "$ref": "#/$defs/WorkflowSettings"
142
+ },
100
143
  "acceptsInheritedArtifacts": {
101
144
  "type": "array",
102
- "items": { "type": "object" },
145
+ "items": {
146
+ "type": "object"
147
+ },
103
148
  "description": "Declares which inherited artifacts this workflow accepts when run as a child of a sub-workflow."
104
149
  },
105
- "createdAt": { "type": "string", "format": "date-time" },
106
- "updatedAt": { "type": "string", "format": "date-time" }
150
+ "createdAt": {
151
+ "type": "string",
152
+ "format": "date-time"
153
+ },
154
+ "updatedAt": {
155
+ "type": "string",
156
+ "format": "date-time"
157
+ }
107
158
  },
108
159
  "additionalProperties": false,
109
160
  "$defs": {
110
161
  "WorkflowNode": {
111
162
  "type": "object",
112
- "required": ["id", "typeId", "name", "position", "config", "inputs"],
163
+ "required": [
164
+ "id",
165
+ "typeId",
166
+ "name",
167
+ "position",
168
+ "config",
169
+ "inputs"
170
+ ],
113
171
  "properties": {
114
- "id": { "type": "string", "minLength": 1, "description": "Node identifier, unique within the workflow. Intentionally has NO `maxLength` — RFC 0013 chain expansion mints ids of the form `${chainIdSlug}_${expansionId}_${fragmentId}` where `chainIdSlug` is an unbounded reverse-DNS string, so an expanded node id can exceed any short cap. A host MUST NOT reject or truncate a node id by length (no host-imposed `maxLength` stricter than this schema)." },
172
+ "id": {
173
+ "type": "string",
174
+ "minLength": 1,
175
+ "description": "Node identifier, unique within the workflow. Intentionally has NO `maxLength` \u2014 RFC 0013 chain expansion mints ids of the form `${chainIdSlug}_${expansionId}_${fragmentId}` where `chainIdSlug` is an unbounded reverse-DNS string, so an expanded node id can exceed any short cap. A host MUST NOT reject or truncate a node id by length (no host-imposed `maxLength` stricter than this schema)."
176
+ },
115
177
  "typeId": {
116
178
  "type": "string",
117
179
  "description": "Canonical node type ID (e.g., 'core.ai.callPrompt', 'core.chat.approvalGate'). Reserved prefixes: 'core.*' for spec-canonical, 'vendor.<org>.*' for third-party.",
118
180
  "minLength": 1,
119
181
  "pattern": "^[a-z][a-zA-Z0-9._-]*$"
120
182
  },
121
- "name": { "type": "string", "minLength": 1 },
183
+ "name": {
184
+ "type": "string",
185
+ "minLength": 1
186
+ },
122
187
  "position": {
123
188
  "type": "object",
124
- "required": ["x", "y"],
189
+ "required": [
190
+ "x",
191
+ "y"
192
+ ],
125
193
  "properties": {
126
- "x": { "type": "number" },
127
- "y": { "type": "number" }
194
+ "x": {
195
+ "type": "number"
196
+ },
197
+ "y": {
198
+ "type": "number"
199
+ }
128
200
  }
129
201
  },
130
202
  "config": {
131
203
  "type": "object",
132
- "description": "Node configuration (pre-execution constants). The shape is per-typeId node-pack manifests declare each typeId's `configSchema` for install-time validation. By convention, the keys `systemPromptRef`, `userPromptRef`, and `additionalPromptRefs` MAY hold PromptRef values per `spec/v1/prompts.md` §\"PromptRef\" (RFC 0027). Hosts advertising `capabilities.prompts.supported: true` MUST resolve these keys; hosts without the capability MAY treat them as opaque strings. When both an inline body (e.g., `config.systemPrompt`) and a `*PromptRef` are present, the ref wins and the host MUST emit a `log.appended` warning with `code: \"prompt_ref_supersedes_inline\"` per RFC 0027 §C."
204
+ "description": "Node configuration (pre-execution constants). The shape is per-typeId \u2014 node-pack manifests declare each typeId's `configSchema` for install-time validation. By convention, the keys `systemPromptRef`, `userPromptRef`, and `additionalPromptRefs` MAY hold PromptRef values per `spec/v1/prompts.md` \u00a7\"PromptRef\" (RFC 0027). Hosts advertising `capabilities.prompts.supported: true` MUST resolve these keys; hosts without the capability MAY treat them as opaque strings. When both an inline body (e.g., `config.systemPrompt`) and a `*PromptRef` are present, the ref wins and the host MUST emit a `log.appended` warning with `code: \"prompt_ref_supersedes_inline\"` per RFC 0027 \u00a7C."
133
205
  },
134
206
  "inputs": {
135
207
  "type": "object",
136
- "additionalProperties": { "$ref": "#/$defs/PortValue" },
208
+ "additionalProperties": {
209
+ "$ref": "#/$defs/PortValue"
210
+ },
137
211
  "description": "Input port connections. Keys are port names; values are PortValue references."
138
212
  },
139
- "credentialsRef": { "type": "string" },
140
- "settings": { "type": "object" },
213
+ "credentialsRef": {
214
+ "type": "string"
215
+ },
216
+ "settings": {
217
+ "type": "object"
218
+ },
141
219
  "outputRole": {
142
220
  "type": "string",
143
- "enum": ["primary", "secondary"],
144
- "description": "RFC 0065 — author hint that this terminal node's output is the workflow's primary deliverable (`primary`) or an auxiliary output (`secondary`). Advisory: hosts MUST execute the workflow identically regardless of value. Tooling MAY use the hint to pick which of N terminal nodes' outputs to surface as the run's canonical artifact. Unknown values + absent values fall back to default behavior (show all terminal outputs)."
221
+ "enum": [
222
+ "primary",
223
+ "secondary"
224
+ ],
225
+ "description": "RFC 0065 \u2014 author hint that this terminal node's output is the workflow's primary deliverable (`primary`) or an auxiliary output (`secondary`). Advisory: hosts MUST execute the workflow identically regardless of value. Tooling MAY use the hint to pick which of N terminal nodes' outputs to surface as the run's canonical artifact. Unknown values + absent values fall back to default behavior (show all terminal outputs)."
226
+ },
227
+ "disabled": {
228
+ "type": "boolean",
229
+ "default": false
230
+ },
231
+ "notes": {
232
+ "type": "string"
233
+ },
234
+ "groupId": {
235
+ "type": "string"
145
236
  },
146
- "disabled": { "type": "boolean", "default": false },
147
- "notes": { "type": "string" },
148
- "groupId": { "type": "string" },
149
237
  "agent": {
150
238
  "$ref": "agent-ref.schema.json",
151
239
  "description": "Multi-Agent Shift Phase 1. Optional compile-time pinning of which agent executes this node. When set, the engine surfaces this AgentRef on the `RunSnapshot.agent` field while the node is active, and emits an `agent.handoff` event when control transitions from the prior node's agent (if different). Resolution at runtime: the engine MAY override via dispatch (RFC 0012 / `core.dispatch`) or orchestrator decision (RFC 0011); the node's `agent?` is the default authoring-time pin, not a hard binding."
152
240
  },
153
- "envelopeContract": { "type": "object" },
241
+ "envelopeContract": {
242
+ "type": "object"
243
+ },
154
244
  "artifactType": {
155
245
  "type": "string",
156
- "description": "Artifact type this node produces or reviews (first-class typed field replaces the deprecated config.outputArtifactType bag entry)."
246
+ "description": "Artifact type this node produces or reviews (first-class typed field \u2014 replaces the deprecated config.outputArtifactType bag entry)."
157
247
  },
158
248
  "cardType": {
159
249
  "type": "string",
160
- "description": "Explicit chat card type override (first-class typed field replaces the deprecated config.chatCard bag entry)."
250
+ "description": "Explicit chat card type override (first-class typed field \u2014 replaces the deprecated config.chatCard bag entry)."
161
251
  },
162
252
  "outputSensitivity": {
163
253
  "type": "object",
164
- "additionalProperties": { "type": "boolean" },
165
- "description": "Per-output-port sensitivity overrides. Map of port name → boolean. When true, the engine masks the named output value in `node.completed` event payloads. Layered on top of pack-level `nodes[].outputs[port].sensitive` declarations: workflow-level true takes precedence over pack-level false (and vice versa — last writer wins, but typically pack defaults are conservative and workflow overrides are explicit). See observability.md §Privacy classification (closes O5)."
254
+ "additionalProperties": {
255
+ "type": "boolean"
256
+ },
257
+ "description": "Per-output-port sensitivity overrides. Map of port name \u2192 boolean. When true, the engine masks the named output value in `node.completed` event payloads. Layered on top of pack-level `nodes[].outputs[port].sensitive` declarations: workflow-level true takes precedence over pack-level false (and vice versa \u2014 last writer wins, but typically pack defaults are conservative and workflow overrides are explicit). See observability.md \u00a7Privacy classification (closes O5)."
258
+ },
259
+ "compensation": {
260
+ "type": "object",
261
+ "description": "RFC 0151 \u00a7B \u2014 the inverse action for this node. OPTIONAL and closed. `nodeTypeId` MUST resolve at registration, so an unwind cannot fail on a typo discovered only during a failure. Inputs MUST derive from RECORDED FACTS: prompt or model regeneration MUST NOT construct a compensation input during replay, because a compensation built from a re-inferred value is not the inverse of what was actually done. A host MUST reject a compensation cycle.",
262
+ "additionalProperties": false,
263
+ "required": [
264
+ "nodeTypeId"
265
+ ],
266
+ "properties": {
267
+ "nodeTypeId": {
268
+ "type": "string",
269
+ "minLength": 1,
270
+ "description": "Node type implementing the inverse action. MUST resolve at registration time."
271
+ },
272
+ "inputMapping": {
273
+ "type": "object",
274
+ "description": "Derived from recorded facts only \u2014 node outputs and run inputs already in the event log.",
275
+ "additionalProperties": true
276
+ },
277
+ "retry": {
278
+ "type": "object",
279
+ "additionalProperties": false,
280
+ "properties": {
281
+ "maxAttempts": {
282
+ "type": "integer",
283
+ "minimum": 1
284
+ },
285
+ "backoffMs": {
286
+ "type": "integer",
287
+ "minimum": 0
288
+ }
289
+ }
290
+ },
291
+ "requiresApproval": {
292
+ "type": "boolean",
293
+ "description": "RFC 0151 \u00a7C \u2014 an inverse action can itself be harmful (RFC 0147 R9), so it may be gated behind the same approval surface as a forward effect."
294
+ }
295
+ }
166
296
  }
167
297
  },
168
298
  "additionalProperties": false
169
299
  },
170
300
  "WorkflowEdge": {
171
301
  "type": "object",
172
- "required": ["id", "sourceNodeId", "targetNodeId"],
302
+ "required": [
303
+ "id",
304
+ "sourceNodeId",
305
+ "targetNodeId"
306
+ ],
173
307
  "properties": {
174
- "id": { "type": "string", "minLength": 1 },
175
- "sourceNodeId": { "type": "string", "minLength": 1 },
308
+ "id": {
309
+ "type": "string",
310
+ "minLength": 1
311
+ },
312
+ "sourceNodeId": {
313
+ "type": "string",
314
+ "minLength": 1
315
+ },
176
316
  "sourceOutput": {
177
317
  "type": "string",
178
318
  "description": "Source output port key. Default 'output'."
179
319
  },
180
- "targetNodeId": { "type": "string", "minLength": 1 },
320
+ "targetNodeId": {
321
+ "type": "string",
322
+ "minLength": 1
323
+ },
181
324
  "targetInput": {
182
325
  "type": "string",
183
326
  "description": "Target input port key. Default 'input'."
184
327
  },
185
- "condition": { "$ref": "#/$defs/EdgeCondition" },
186
- "label": { "type": "string" },
328
+ "condition": {
329
+ "$ref": "#/$defs/EdgeCondition"
330
+ },
331
+ "label": {
332
+ "type": "string"
333
+ },
187
334
  "triggerRule": {
188
335
  "type": "string",
189
- "enum": ["all_success", "any_success", "all_complete", "none_failed", "any_failed"],
336
+ "enum": [
337
+ "all_success",
338
+ "any_success",
339
+ "all_complete",
340
+ "none_failed",
341
+ "any_failed"
342
+ ],
190
343
  "default": "all_success"
191
344
  }
192
345
  },
@@ -197,12 +350,28 @@
197
350
  "properties": {
198
351
  "type": {
199
352
  "type": "string",
200
- "enum": ["expression", "equals", "notEquals", "contains", "regex", "truthy", "falsy"],
201
- "description": "Operator. `equals`/`notEquals`/`contains` compare `left` to `right`; `regex` matches `left` against `right`; `expression` evaluates `expression`. RFC 0134: `truthy`/`falsy` test the resolved value at `left` for JS-style truthiness and take NO `right` operand (the edge contributes iff `left` is truthy — resp. falsy/absent). `left` is required for all operators except `expression`."
353
+ "enum": [
354
+ "expression",
355
+ "equals",
356
+ "notEquals",
357
+ "contains",
358
+ "regex",
359
+ "truthy",
360
+ "falsy"
361
+ ],
362
+ "description": "Operator. `equals`/`notEquals`/`contains` compare `left` to `right`; `regex` matches `left` against `right`; `expression` evaluates `expression`. RFC 0134: `truthy`/`falsy` test the resolved value at `left` for JS-style truthiness and take NO `right` operand (the edge contributes iff `left` is truthy \u2014 resp. falsy/absent). `left` is required for all operators except `expression`."
202
363
  },
203
- "left": { "type": "string", "description": "Left operand path (e.g., 'status', 'output.approved'). Required for every operator except `expression`." },
204
- "right": { "description": "Right operand value (any JSON value). Meaningless for `truthy`/`falsy` (RFC 0134) — a host MUST ignore it there, not error." },
205
- "expression": { "type": "string", "description": "Used when type='expression'." }
364
+ "left": {
365
+ "type": "string",
366
+ "description": "Left operand path (e.g., 'status', 'output.approved'). Required for every operator except `expression`."
367
+ },
368
+ "right": {
369
+ "description": "Right operand value (any JSON value). Meaningless for `truthy`/`falsy` (RFC 0134) \u2014 a host MUST ignore it there, not error."
370
+ },
371
+ "expression": {
372
+ "type": "string",
373
+ "description": "Used when type='expression'."
374
+ }
206
375
  },
207
376
  "additionalProperties": false
208
377
  },
@@ -210,40 +379,79 @@
210
379
  "oneOf": [
211
380
  {
212
381
  "type": "object",
213
- "required": ["type", "value"],
382
+ "required": [
383
+ "type",
384
+ "value"
385
+ ],
214
386
  "properties": {
215
- "type": { "const": "static" },
387
+ "type": {
388
+ "const": "static"
389
+ },
216
390
  "value": {}
217
391
  },
218
392
  "additionalProperties": false
219
393
  },
220
394
  {
221
395
  "type": "object",
222
- "required": ["type", "expression"],
396
+ "required": [
397
+ "type",
398
+ "expression"
399
+ ],
223
400
  "properties": {
224
- "type": { "const": "expression" },
225
- "expression": { "type": "string", "minLength": 1 }
401
+ "type": {
402
+ "const": "expression"
403
+ },
404
+ "expression": {
405
+ "type": "string",
406
+ "minLength": 1
407
+ }
226
408
  },
227
409
  "additionalProperties": false
228
410
  },
229
411
  {
230
412
  "type": "object",
231
- "required": ["type", "nodeId", "outputKey"],
413
+ "required": [
414
+ "type",
415
+ "nodeId",
416
+ "outputKey"
417
+ ],
232
418
  "properties": {
233
- "type": { "const": "connection" },
234
- "nodeId": { "type": "string", "minLength": 1 },
235
- "outputKey": { "type": "string", "minLength": 1 },
236
- "optional": { "type": "boolean", "default": false }
419
+ "type": {
420
+ "const": "connection"
421
+ },
422
+ "nodeId": {
423
+ "type": "string",
424
+ "minLength": 1
425
+ },
426
+ "outputKey": {
427
+ "type": "string",
428
+ "minLength": 1
429
+ },
430
+ "optional": {
431
+ "type": "boolean",
432
+ "default": false
433
+ }
237
434
  },
238
435
  "additionalProperties": false
239
436
  },
240
437
  {
241
438
  "type": "object",
242
- "required": ["type", "variableName"],
439
+ "required": [
440
+ "type",
441
+ "variableName"
442
+ ],
243
443
  "properties": {
244
- "type": { "const": "variable" },
245
- "variableName": { "type": "string", "minLength": 1 },
246
- "optional": { "type": "boolean", "default": false }
444
+ "type": {
445
+ "const": "variable"
446
+ },
447
+ "variableName": {
448
+ "type": "string",
449
+ "minLength": 1
450
+ },
451
+ "optional": {
452
+ "type": "boolean",
453
+ "default": false
454
+ }
247
455
  },
248
456
  "additionalProperties": false
249
457
  }
@@ -251,74 +459,154 @@
251
459
  },
252
460
  "WorkflowTrigger": {
253
461
  "type": "object",
254
- "required": ["id", "type"],
462
+ "required": [
463
+ "id",
464
+ "type"
465
+ ],
255
466
  "properties": {
256
- "id": { "type": "string", "minLength": 1 },
467
+ "id": {
468
+ "type": "string",
469
+ "minLength": 1
470
+ },
257
471
  "type": {
258
472
  "type": "string",
259
- "enum": ["manual", "schedule", "webhook", "event", "artifact", "canvas", "envelope", "command", "chat-message", "channel-write"],
260
- "description": "Trigger discriminator. The `channel-write` variant fires a node when a named channel receives a write (closes C2 — reactive cross-engine pattern). Its `config` shape: `{channel: string, onlyFrom?: 'child'|'parent'|'any', debounceMs?: integer}`. See channels-and-reducers.md §Distributed reducers."
261
- },
262
- "name": { "type": "string" },
263
- "description": { "type": "string" },
264
- "config": { "type": "object" },
265
- "enabled": { "type": "boolean", "default": true },
266
- "nodeId": { "type": "string" },
267
- "eventType": { "type": "string" }
473
+ "enum": [
474
+ "manual",
475
+ "schedule",
476
+ "webhook",
477
+ "event",
478
+ "artifact",
479
+ "canvas",
480
+ "envelope",
481
+ "command",
482
+ "chat-message",
483
+ "channel-write"
484
+ ],
485
+ "description": "Trigger discriminator. The `channel-write` variant fires a node when a named channel receives a write (closes C2 \u2014 reactive cross-engine pattern). Its `config` shape: `{channel: string, onlyFrom?: 'child'|'parent'|'any', debounceMs?: integer}`. See channels-and-reducers.md \u00a7Distributed reducers."
486
+ },
487
+ "name": {
488
+ "type": "string"
489
+ },
490
+ "description": {
491
+ "type": "string"
492
+ },
493
+ "config": {
494
+ "type": "object"
495
+ },
496
+ "enabled": {
497
+ "type": "boolean",
498
+ "default": true
499
+ },
500
+ "nodeId": {
501
+ "type": "string"
502
+ },
503
+ "eventType": {
504
+ "type": "string"
505
+ }
268
506
  },
269
507
  "additionalProperties": false
270
508
  },
271
509
  "WorkflowVariable": {
272
510
  "type": "object",
273
- "required": ["name", "type"],
511
+ "required": [
512
+ "name",
513
+ "type"
514
+ ],
274
515
  "properties": {
275
- "name": { "type": "string", "minLength": 1 },
516
+ "name": {
517
+ "type": "string",
518
+ "minLength": 1
519
+ },
276
520
  "type": {
277
521
  "type": "string",
278
- "enum": ["string", "number", "boolean", "object", "array"]
522
+ "enum": [
523
+ "string",
524
+ "number",
525
+ "boolean",
526
+ "object",
527
+ "array"
528
+ ]
529
+ },
530
+ "description": {
531
+ "type": "string"
532
+ },
533
+ "required": {
534
+ "type": "boolean",
535
+ "default": false
279
536
  },
280
- "description": { "type": "string" },
281
- "required": { "type": "boolean", "default": false },
282
537
  "defaultValue": {},
283
538
  "sensitive": {
284
539
  "type": "boolean",
285
540
  "default": false,
286
- "description": "When true, the engine masks this variable's value in persisted `variable.changed` events, `state.snapshot` projections, and `RunSnapshot.variables`. Reads inside NodeModule executors work normally; only persistence + external surfaces mask. See observability.md §Privacy classification (closes O5)."
541
+ "description": "When true, the engine masks this variable's value in persisted `variable.changed` events, `state.snapshot` projections, and `RunSnapshot.variables`. Reads inside NodeModule executors work normally; only persistence + external surfaces mask. See observability.md \u00a7Privacy classification (closes O5)."
287
542
  },
288
543
  "format": {
289
544
  "type": "string",
290
545
  "minLength": 1,
291
- "description": "RFC 0136 (`Active`). ADVISORY presentational hint for a `type: \"string\"` variable, drawn from the JSON-Schema format vocabulary (`email`, `uri`, `date`, `date-time`, `time`, `duration` are the v1 recognised set). Hosts SHOULD use it to choose an input affordance (e.g. an email keyboard). It is NOT a validation contract: a host MUST NOT reject a run, refuse a variable write, or fail validation because a value does not match, and MUST NOT assume a value matches when reading it. DELIBERATELY NOT AN ENUM an unrecognised value MUST round-trip and degrade to plain text rather than error (RFC 0136 requirement 2), which an enum would turn into a hard validation failure on a client-submitted (closed) shape. Ignored when `type` is not `\"string\"`. Orthogonal to `sensitive`: masking hides the VALUE on server-emitted surfaces, `format` describes the FIELD in the definition, so the two compose with no interaction."
546
+ "description": "RFC 0136 (`Active`). ADVISORY presentational hint for a `type: \"string\"` variable, drawn from the JSON-Schema format vocabulary (`email`, `uri`, `date`, `date-time`, `time`, `duration` are the v1 recognised set). Hosts SHOULD use it to choose an input affordance (e.g. an email keyboard). It is NOT a validation contract: a host MUST NOT reject a run, refuse a variable write, or fail validation because a value does not match, and MUST NOT assume a value matches when reading it. DELIBERATELY NOT AN ENUM \u2014 an unrecognised value MUST round-trip and degrade to plain text rather than error (RFC 0136 requirement 2), which an enum would turn into a hard validation failure on a client-submitted (closed) shape. Ignored when `type` is not `\"string\"`. Orthogonal to `sensitive`: masking hides the VALUE on server-emitted surfaces, `format` describes the FIELD in the definition, so the two compose with no interaction."
292
547
  }
293
548
  },
294
549
  "additionalProperties": false
295
550
  },
296
551
  "NodeGroup": {
297
552
  "type": "object",
298
- "required": ["id", "name", "nodeIds", "position", "size"],
553
+ "required": [
554
+ "id",
555
+ "name",
556
+ "nodeIds",
557
+ "position",
558
+ "size"
559
+ ],
299
560
  "properties": {
300
- "id": { "type": "string", "minLength": 1 },
301
- "name": { "type": "string", "minLength": 1 },
302
- "color": { "type": "string" },
303
- "collapsed": { "type": "boolean", "default": false },
561
+ "id": {
562
+ "type": "string",
563
+ "minLength": 1
564
+ },
565
+ "name": {
566
+ "type": "string",
567
+ "minLength": 1
568
+ },
569
+ "color": {
570
+ "type": "string"
571
+ },
572
+ "collapsed": {
573
+ "type": "boolean",
574
+ "default": false
575
+ },
304
576
  "nodeIds": {
305
577
  "type": "array",
306
- "items": { "type": "string" }
578
+ "items": {
579
+ "type": "string"
580
+ }
307
581
  },
308
582
  "position": {
309
583
  "type": "object",
310
- "required": ["x", "y"],
584
+ "required": [
585
+ "x",
586
+ "y"
587
+ ],
311
588
  "properties": {
312
- "x": { "type": "number" },
313
- "y": { "type": "number" }
589
+ "x": {
590
+ "type": "number"
591
+ },
592
+ "y": {
593
+ "type": "number"
594
+ }
314
595
  }
315
596
  },
316
597
  "size": {
317
598
  "type": "object",
318
- "required": ["width", "height"],
599
+ "required": [
600
+ "width",
601
+ "height"
602
+ ],
319
603
  "properties": {
320
- "width": { "type": "number" },
321
- "height": { "type": "number" }
604
+ "width": {
605
+ "type": "number"
606
+ },
607
+ "height": {
608
+ "type": "number"
609
+ }
322
610
  }
323
611
  }
324
612
  },
@@ -326,24 +614,35 @@
326
614
  },
327
615
  "ChannelDeclaration": {
328
616
  "type": "object",
329
- "required": ["reducer"],
617
+ "required": [
618
+ "reducer"
619
+ ],
330
620
  "properties": {
331
621
  "reducer": {
332
622
  "type": "string",
333
- "description": "Canonical names: 'replace', 'append', 'merge', 'counter', 'votes', 'feedback', 'message' (Multi-Agent Shift Phase 1 append-only + idempotent on `messageId`). Custom reducers MUST use 'vendor.<org>.<name>'.",
623
+ "description": "Canonical names: 'replace', 'append', 'merge', 'counter', 'votes', 'feedback', 'message' (Multi-Agent Shift Phase 1 \u2014 append-only + idempotent on `messageId`). Custom reducers MUST use 'vendor.<org>.<name>'.",
334
624
  "pattern": "^(replace|append|merge|counter|votes|feedback|message|vendor\\.[a-z][a-z0-9_-]*\\.[a-z][a-z0-9_-]*)$"
335
625
  },
336
- "schema": { "type": "object" },
626
+ "schema": {
627
+ "type": "object"
628
+ },
337
629
  "default": {},
338
- "maxSize": { "type": "integer", "minimum": 1 },
630
+ "maxSize": {
631
+ "type": "integer",
632
+ "minimum": 1
633
+ },
339
634
  "ttlMs": {
340
635
  "type": "integer",
341
636
  "minimum": 1,
342
637
  "maximum": 31536000000,
343
- "description": "Optional entry-age TTL in milliseconds (closes C3). Applies to `append` / `votes` / `feedback` reducers; ignored on others. Engine drops entries older than this age (lazy: on read or next write). Range: 1..1 year. Replay-safe uses original event timestamps for comparison. See channels-and-reducers.md §Channel TTL."
638
+ "description": "Optional entry-age TTL in milliseconds (closes C3). Applies to `append` / `votes` / `feedback` reducers; ignored on others. Engine drops entries older than this age (lazy: on read or next write). Range: 1..1 year. Replay-safe \u2014 uses original event timestamps for comparison. See channels-and-reducers.md \u00a7Channel TTL."
639
+ },
640
+ "options": {
641
+ "type": "object"
642
+ },
643
+ "access": {
644
+ "$ref": "#/$defs/ChannelAccess"
344
645
  },
345
- "options": { "type": "object" },
346
- "access": { "$ref": "#/$defs/ChannelAccess" },
347
646
  "schemaVersion": {
348
647
  "type": "integer",
349
648
  "minimum": 1,
@@ -352,28 +651,39 @@
352
651
  },
353
652
  "compatibleWith": {
354
653
  "type": "array",
355
- "items": { "type": "integer", "minimum": 1 },
654
+ "items": {
655
+ "type": "integer",
656
+ "minimum": 1
657
+ },
356
658
  "uniqueItems": true,
357
- "description": "Older schema versions whose persisted writes are forward-readable under the CURRENT schema. The engine validates each old write against the current schema during fold; pass = include, fail = hard error `channel_schema_breaking_change`. Empty/omitted = no backward compat (any older write trips the breaking-change error). For breaking edits, authors create a new channel name + a copy node see channels-and-reducers.md §Channel schema migration."
659
+ "description": "Older schema versions whose persisted writes are forward-readable under the CURRENT schema. The engine validates each old write against the current schema during fold; pass = include, fail = hard error `channel_schema_breaking_change`. Empty/omitted = no backward compat (any older write trips the breaking-change error). For breaking edits, authors create a new channel name + a copy node \u2014 see channels-and-reducers.md \u00a7Channel schema migration."
358
660
  },
359
661
  "sensitive": {
360
662
  "type": "boolean",
361
663
  "default": false,
362
- "description": "When true, the engine masks `channel.written` event payloads' `value` field. The reduced channel state in `RunSnapshot.channels` is also masked when read via the REST surface. See observability.md §Privacy classification (closes O5)."
664
+ "description": "When true, the engine masks `channel.written` event payloads' `value` field. The reduced channel state in `RunSnapshot.channels` is also masked when read via the REST surface. See observability.md \u00a7Privacy classification (closes O5)."
363
665
  }
364
666
  },
365
667
  "additionalProperties": false
366
668
  },
367
669
  "ChannelAccess": {
368
- "description": "Per-channel access control. See channels-and-reducers.md §Channel access control (closes C1). Three forms: 'public' (no restriction; same as omitting), 'private' (lockdown shorthand equivalent to {readers: [], writers: []}), or an explicit {readers?, writers?} object where each side is independently scoped (omitted = open, present = strict allowlist).",
670
+ "description": "Per-channel access control. See channels-and-reducers.md \u00a7Channel access control (closes C1). Three forms: 'public' (no restriction; same as omitting), 'private' (lockdown shorthand \u2014 equivalent to {readers: [], writers: []}), or an explicit {readers?, writers?} object where each side is independently scoped (omitted = open, present = strict allowlist).",
369
671
  "oneOf": [
370
- { "const": "public" },
371
- { "const": "private" },
672
+ {
673
+ "const": "public"
674
+ },
675
+ {
676
+ "const": "private"
677
+ },
372
678
  {
373
679
  "type": "object",
374
680
  "properties": {
375
- "readers": { "$ref": "#/$defs/ChannelAccessList" },
376
- "writers": { "$ref": "#/$defs/ChannelAccessList" }
681
+ "readers": {
682
+ "$ref": "#/$defs/ChannelAccessList"
683
+ },
684
+ "writers": {
685
+ "$ref": "#/$defs/ChannelAccessList"
686
+ }
377
687
  },
378
688
  "additionalProperties": false
379
689
  }
@@ -394,29 +704,71 @@
394
704
  "WorkflowMetadata": {
395
705
  "type": "object",
396
706
  "properties": {
397
- "createdBy": { "type": "string" },
398
- "createdAt": { "type": "string", "format": "date-time" },
399
- "updatedBy": { "type": "string" },
400
- "updatedAt": { "type": "string", "format": "date-time" },
707
+ "createdBy": {
708
+ "type": "string"
709
+ },
710
+ "createdAt": {
711
+ "type": "string",
712
+ "format": "date-time"
713
+ },
714
+ "updatedBy": {
715
+ "type": "string"
716
+ },
717
+ "updatedAt": {
718
+ "type": "string",
719
+ "format": "date-time"
720
+ },
401
721
  "tags": {
402
722
  "type": "array",
403
- "items": { "type": "string", "minLength": 1, "maxLength": 256 },
723
+ "items": {
724
+ "type": "string",
725
+ "minLength": 1,
726
+ "maxLength": 256
727
+ },
404
728
  "maxItems": 100
405
729
  },
406
- "category": { "type": "string" },
407
- "author": { "type": "string" },
408
- "codeVersion": { "type": "string" },
409
- "customizedAt": { "type": "string", "format": "date-time" },
410
- "customizedBy": { "type": "string" },
411
- "forkedFrom": { "type": "string", "description": "ID of platform template this was forked from." },
412
- "clonedFrom": { "type": "string", "description": "ID of workflow this was cloned from (project clone, not template fork)." },
413
- "clonedAt": { "type": "string", "format": "date-time" },
414
- "customProperties": { "type": "object" },
730
+ "category": {
731
+ "type": "string"
732
+ },
733
+ "author": {
734
+ "type": "string"
735
+ },
736
+ "codeVersion": {
737
+ "type": "string"
738
+ },
739
+ "customizedAt": {
740
+ "type": "string",
741
+ "format": "date-time"
742
+ },
743
+ "customizedBy": {
744
+ "type": "string"
745
+ },
746
+ "forkedFrom": {
747
+ "type": "string",
748
+ "description": "ID of platform template this was forked from."
749
+ },
750
+ "clonedFrom": {
751
+ "type": "string",
752
+ "description": "ID of workflow this was cloned from (project clone, not template fork)."
753
+ },
754
+ "clonedAt": {
755
+ "type": "string",
756
+ "format": "date-time"
757
+ },
758
+ "customProperties": {
759
+ "type": "object"
760
+ },
415
761
  "complianceClass": {
416
762
  "type": "string",
417
- "enum": ["public", "pii", "phi", "pci", "regulated"],
763
+ "enum": [
764
+ "public",
765
+ "pii",
766
+ "phi",
767
+ "pci",
768
+ "regulated"
769
+ ],
418
770
  "default": "public",
419
- "description": "Top-level workflow sensitivity tier. Sets the `openwop.compliance_class` span attribute on every span the run produces. Drives default retention / masking / export-gating policy at observability collectors. See observability.md §Privacy classification (closes O5)."
771
+ "description": "Top-level workflow sensitivity tier. Sets the `openwop.compliance_class` span attribute on every span the run produces. Drives default retention / masking / export-gating policy at observability collectors. See observability.md \u00a7Privacy classification (closes O5)."
420
772
  },
421
773
  "complianceConfig": {
422
774
  "type": "object",
@@ -424,7 +776,12 @@
424
776
  "properties": {
425
777
  "maskingMode": {
426
778
  "type": "string",
427
- "enum": ["mask", "omit", "hash", "passthrough"],
779
+ "enum": [
780
+ "mask",
781
+ "omit",
782
+ "hash",
783
+ "passthrough"
784
+ ],
428
785
  "description": "Per-workflow override of the server's default masking mode."
429
786
  }
430
787
  },
@@ -446,7 +803,12 @@
446
803
  },
447
804
  "logLevel": {
448
805
  "type": "string",
449
- "enum": ["debug", "info", "warn", "error"]
806
+ "enum": [
807
+ "debug",
808
+ "info",
809
+ "warn",
810
+ "error"
811
+ ]
450
812
  },
451
813
  "maxLoopbackIterations": {
452
814
  "type": "integer",