@openwop/openwop-conformance 1.0.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.
- package/LICENSE +201 -0
- package/README.md +241 -0
- package/api/asyncapi.yaml +481 -0
- package/api/openapi.yaml +830 -0
- package/api/redocly.yaml +8 -0
- package/coverage.md +80 -0
- package/dist/cli.js +161 -0
- package/fixtures/conformance-a2a-task-roundtrip.json +27 -0
- package/fixtures/conformance-agent-identity.json +27 -0
- package/fixtures/conformance-agent-low-confidence.json +29 -0
- package/fixtures/conformance-agent-memory-cross-tenant.json +28 -0
- package/fixtures/conformance-agent-memory-redaction.json +32 -0
- package/fixtures/conformance-agent-memory-roundtrip.json +32 -0
- package/fixtures/conformance-agent-memory-ttl.json +31 -0
- package/fixtures/conformance-agent-pack-export.json +26 -0
- package/fixtures/conformance-agent-pack-install.json +26 -0
- package/fixtures/conformance-agent-pack-provenance.json +31 -0
- package/fixtures/conformance-agent-reasoning.json +29 -0
- package/fixtures/conformance-approval.json +27 -0
- package/fixtures/conformance-cancellable.json +33 -0
- package/fixtures/conformance-cap-breach.json +27 -0
- package/fixtures/conformance-capability-missing.json +23 -0
- package/fixtures/conformance-channel-ttl.json +60 -0
- package/fixtures/conformance-clarification.json +30 -0
- package/fixtures/conformance-conversation-capability-negotiation.json +23 -0
- package/fixtures/conformance-conversation-lifecycle.json +32 -0
- package/fixtures/conformance-conversation-replay.json +33 -0
- package/fixtures/conformance-conversation-vs-clarification.json +26 -0
- package/fixtures/conformance-delay.json +33 -0
- package/fixtures/conformance-dispatch-loop.json +38 -0
- package/fixtures/conformance-failure.json +23 -0
- package/fixtures/conformance-idempotent.json +30 -0
- package/fixtures/conformance-identity.json +32 -0
- package/fixtures/conformance-interrupt-auth-required.json +28 -0
- package/fixtures/conformance-interrupt-external-event.json +33 -0
- package/fixtures/conformance-interrupt-parent-child-cancel-child.json +27 -0
- package/fixtures/conformance-interrupt-parent-child-cancel.json +26 -0
- package/fixtures/conformance-interrupt-quorum.json +30 -0
- package/fixtures/conformance-mcp-tool-roundtrip.json +32 -0
- package/fixtures/conformance-message-reducer.json +31 -0
- package/fixtures/conformance-multi-node.json +21 -0
- package/fixtures/conformance-noop.json +23 -0
- package/fixtures/conformance-orchestrator-dispatch.json +47 -0
- package/fixtures/conformance-orchestrator-low-confidence.json +41 -0
- package/fixtures/conformance-orchestrator-terminate.json +44 -0
- package/fixtures/conformance-stream-text.json +26 -0
- package/fixtures/conformance-subworkflow-child.json +21 -0
- package/fixtures/conformance-subworkflow-parent.json +49 -0
- package/fixtures/conformance-version-fold.json +23 -0
- package/fixtures/conformance-wasm-pack-roundtrip.json +25 -0
- package/fixtures/pack-manifests/pack-private-example.json +26 -0
- package/fixtures.md +404 -0
- package/package.json +48 -0
- package/schemas/README.md +75 -0
- package/schemas/agent-manifest.schema.json +107 -0
- package/schemas/agent-ref.schema.json +53 -0
- package/schemas/capabilities.schema.json +287 -0
- package/schemas/channel-written-payload.schema.json +55 -0
- package/schemas/conversation-event.schema.json +120 -0
- package/schemas/conversation-turn.schema.json +72 -0
- package/schemas/debug-bundle.schema.json +196 -0
- package/schemas/dispatch-config.schema.json +46 -0
- package/schemas/error-envelope.schema.json +25 -0
- package/schemas/memory-entry.schema.json +36 -0
- package/schemas/memory-list-options.schema.json +21 -0
- package/schemas/node-pack-manifest.schema.json +235 -0
- package/schemas/orchestrator-decision.schema.json +60 -0
- package/schemas/run-event-payloads.schema.json +663 -0
- package/schemas/run-event.schema.json +116 -0
- package/schemas/run-options.schema.json +81 -0
- package/schemas/run-orchestrator-decided-event.schema.json +20 -0
- package/schemas/run-snapshot.schema.json +121 -0
- package/schemas/suspend-request.schema.json +182 -0
- package/schemas/workflow-definition.schema.json +430 -0
- package/src/cli.ts +187 -0
- package/src/lib/a2a-fake-peer.ts +233 -0
- package/src/lib/canaries.ts +186 -0
- package/src/lib/driver.ts +96 -0
- package/src/lib/env.ts +49 -0
- package/src/lib/fixtures.ts +93 -0
- package/src/lib/mcp-fake-server.ts +185 -0
- package/src/lib/multi-agent-capabilities.ts +155 -0
- package/src/lib/multiProcess.ts +141 -0
- package/src/lib/otel-collector.ts +312 -0
- package/src/lib/paths.ts +198 -0
- package/src/lib/polling.ts +81 -0
- package/src/lib/profiles.ts +258 -0
- package/src/lib/sse.ts +172 -0
- package/src/scenarios/a2a-task-roundtrip.test.ts +149 -0
- package/src/scenarios/agentConfidenceEscalation.test.ts +61 -0
- package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +54 -0
- package/src/scenarios/agentMemoryRedactionContract.test.ts +46 -0
- package/src/scenarios/agentMemoryRoundTrip.test.ts +52 -0
- package/src/scenarios/agentMemoryTtlExpiry.test.ts +47 -0
- package/src/scenarios/agentMessageReducer.test.ts +57 -0
- package/src/scenarios/agentMetadata.test.ts +56 -0
- package/src/scenarios/agentPackExport.test.ts +45 -0
- package/src/scenarios/agentPackInstall.test.ts +50 -0
- package/src/scenarios/agentPackProvenance.test.ts +53 -0
- package/src/scenarios/agentReasoningEvents.test.ts +72 -0
- package/src/scenarios/append-ordering.test.ts +91 -0
- package/src/scenarios/approval-payload.test.ts +120 -0
- package/src/scenarios/audit-log-integrity.test.ts +106 -0
- package/src/scenarios/auth.test.ts +55 -0
- package/src/scenarios/byok-roundtrip.test.ts +166 -0
- package/src/scenarios/cancellation.test.ts +68 -0
- package/src/scenarios/cap-breach.test.ts +149 -0
- package/src/scenarios/channel-ttl.test.ts +70 -0
- package/src/scenarios/configurable-schema.test.ts +76 -0
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +39 -0
- package/src/scenarios/conversationLifecycle.test.ts +64 -0
- package/src/scenarios/conversationReplayDeterminism.test.ts +52 -0
- package/src/scenarios/conversationVsLegacySuspend.test.ts +46 -0
- package/src/scenarios/cost-attribution.test.ts +207 -0
- package/src/scenarios/debugBundle.test.ts +222 -0
- package/src/scenarios/discovery.test.ts +147 -0
- package/src/scenarios/dispatchLoop.test.ts +52 -0
- package/src/scenarios/errors.test.ts +144 -0
- package/src/scenarios/eventOrdering.test.ts +144 -0
- package/src/scenarios/failure-path.test.ts +46 -0
- package/src/scenarios/fixtures-gating.test.ts +137 -0
- package/src/scenarios/fixtures-valid.test.ts +140 -0
- package/src/scenarios/highConcurrency.test.ts +263 -0
- package/src/scenarios/idempotency.test.ts +83 -0
- package/src/scenarios/idempotencyRetry.test.ts +130 -0
- package/src/scenarios/identity-passthrough.test.ts +54 -0
- package/src/scenarios/interrupt-approval.test.ts +97 -0
- package/src/scenarios/interrupt-auth-required-resume.test.ts +88 -0
- package/src/scenarios/interrupt-clarification.test.ts +45 -0
- package/src/scenarios/interrupt-external-event-correlation.test.ts +113 -0
- package/src/scenarios/interrupt-parent-child-cascade.test.ts +102 -0
- package/src/scenarios/interrupt-quorum-resolution.test.ts +97 -0
- package/src/scenarios/interruptRace.test.ts +176 -0
- package/src/scenarios/maliciousManifest.test.ts +154 -0
- package/src/scenarios/mcp-discoverability.test.ts +129 -0
- package/src/scenarios/mcp-tool-roundtrip.test.ts +149 -0
- package/src/scenarios/multi-node-ordering.test.ts +60 -0
- package/src/scenarios/multi-region-idempotency.test.ts +52 -0
- package/src/scenarios/orchestratorConservativePath.test.ts +63 -0
- package/src/scenarios/orchestratorDispatch.test.ts +66 -0
- package/src/scenarios/orchestratorTermination.test.ts +54 -0
- package/src/scenarios/otel-emission.test.ts +113 -0
- package/src/scenarios/otel-trace-propagation.test.ts +90 -0
- package/src/scenarios/pack-registry-publish.test.ts +93 -0
- package/src/scenarios/pack-registry.test.ts +328 -0
- package/src/scenarios/pause-resume.test.ts +109 -0
- package/src/scenarios/policies.test.ts +162 -0
- package/src/scenarios/profileDerivation.test.ts +335 -0
- package/src/scenarios/providerPolicyEnforcement.test.ts +132 -0
- package/src/scenarios/rate-limit-envelope.test.ts +97 -0
- package/src/scenarios/redaction.test.ts +254 -0
- package/src/scenarios/redactionAdversarial.test.ts +162 -0
- package/src/scenarios/replay-fork-arbitrary.test.ts +347 -0
- package/src/scenarios/replay-fork.test.ts +216 -0
- package/src/scenarios/replayDeterminism.test.ts +171 -0
- package/src/scenarios/route-coverage.test.ts +129 -0
- package/src/scenarios/runs-lifecycle.test.ts +65 -0
- package/src/scenarios/runtime-capabilities.test.ts +118 -0
- package/src/scenarios/spec-corpus-validity.test.ts +1257 -0
- package/src/scenarios/staleClaim.test.ts +223 -0
- package/src/scenarios/stream-modes-buffer.test.ts +148 -0
- package/src/scenarios/stream-modes-mixed.test.ts +149 -0
- package/src/scenarios/stream-modes.test.ts +139 -0
- package/src/scenarios/streamReconnect.test.ts +162 -0
- package/src/scenarios/subworkflow.test.ts +126 -0
- package/src/scenarios/version-negotiation.test.ts +157 -0
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +47 -0
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +69 -0
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +74 -0
- package/src/scenarios/wasm-pack-load.test.ts +75 -0
- package/src/scenarios/wasm-pack-memory-cap.test.ts +43 -0
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +61 -0
- package/src/scenarios/webhook-sig-algorithm.test.ts +61 -0
- package/src/setup.ts +173 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/workflow-definition.schema.json",
|
|
4
|
+
"title": "WorkflowDefinition",
|
|
5
|
+
"description": "DAG of typed nodes, edges, triggers, and variables that an OpenWOP host executes. Canonical OpenWOP v1 workflow-definition shape.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["id", "name", "version", "nodes", "edges", "triggers", "variables", "metadata", "settings"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"id": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Unique workflow identifier. Recommended format: lowercase, hyphen-separated.",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"maxLength": 128,
|
|
14
|
+
"pattern": "^[a-z][a-z0-9_-]*$"
|
|
15
|
+
},
|
|
16
|
+
"name": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 1,
|
|
19
|
+
"maxLength": 256
|
|
20
|
+
},
|
|
21
|
+
"description": { "type": "string" },
|
|
22
|
+
"version": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Semver-like workflow version. Tracked separately from engineVersion + eventLogSchemaVersion (see version-negotiation.md).",
|
|
25
|
+
"minLength": 1
|
|
26
|
+
},
|
|
27
|
+
"type": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Optional category for filtering."
|
|
30
|
+
},
|
|
31
|
+
"isActive": { "type": "boolean" },
|
|
32
|
+
"status": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": ["active", "inactive", "draft", "archived"]
|
|
35
|
+
},
|
|
36
|
+
"tenantId": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Optional tenant/workspace scoping. The protocol uses the neutral term `tenantId`."
|
|
39
|
+
},
|
|
40
|
+
"scopeId": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Optional project/scope correlation. The protocol uses the neutral term `scopeId`."
|
|
43
|
+
},
|
|
44
|
+
"slug": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "URL-friendly slug.",
|
|
47
|
+
"pattern": "^[a-z0-9][a-z0-9_-]*$"
|
|
48
|
+
},
|
|
49
|
+
"nodes": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": { "$ref": "#/$defs/WorkflowNode" },
|
|
52
|
+
"minItems": 1
|
|
53
|
+
},
|
|
54
|
+
"edges": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": { "$ref": "#/$defs/WorkflowEdge" }
|
|
57
|
+
},
|
|
58
|
+
"triggers": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": { "$ref": "#/$defs/WorkflowTrigger" }
|
|
61
|
+
},
|
|
62
|
+
"variables": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": { "$ref": "#/$defs/WorkflowVariable" }
|
|
65
|
+
},
|
|
66
|
+
"groups": {
|
|
67
|
+
"type": "array",
|
|
68
|
+
"items": { "$ref": "#/$defs/NodeGroup" },
|
|
69
|
+
"description": "Visual node groups (organizational only; do not affect execution)."
|
|
70
|
+
},
|
|
71
|
+
"channels": {
|
|
72
|
+
"description": "Optional typed state channels (see channels-and-reducers.md). When present, channel-aware mode applies.",
|
|
73
|
+
"type": "object",
|
|
74
|
+
"additionalProperties": { "$ref": "#/$defs/ChannelDeclaration" }
|
|
75
|
+
},
|
|
76
|
+
"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.",
|
|
78
|
+
"type": "object"
|
|
79
|
+
},
|
|
80
|
+
"metadata": { "$ref": "#/$defs/WorkflowMetadata" },
|
|
81
|
+
"settings": { "$ref": "#/$defs/WorkflowSettings" },
|
|
82
|
+
"acceptsInheritedArtifacts": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"items": { "type": "object" },
|
|
85
|
+
"description": "Declares which inherited artifacts this workflow accepts when run as a child of a sub-workflow."
|
|
86
|
+
},
|
|
87
|
+
"createdAt": { "type": "string", "format": "date-time" },
|
|
88
|
+
"updatedAt": { "type": "string", "format": "date-time" }
|
|
89
|
+
},
|
|
90
|
+
"additionalProperties": false,
|
|
91
|
+
"$defs": {
|
|
92
|
+
"WorkflowNode": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"required": ["id", "typeId", "name", "position", "config", "inputs"],
|
|
95
|
+
"properties": {
|
|
96
|
+
"id": { "type": "string", "minLength": 1 },
|
|
97
|
+
"typeId": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Canonical node type ID (e.g., 'core.ai.callPrompt', 'core.chat.approvalGate'). Reserved prefixes: 'core.*' for spec-canonical, 'vendor.<org>.*' for third-party.",
|
|
100
|
+
"minLength": 1,
|
|
101
|
+
"pattern": "^[a-z][a-zA-Z0-9._-]*$"
|
|
102
|
+
},
|
|
103
|
+
"name": { "type": "string", "minLength": 1 },
|
|
104
|
+
"position": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"required": ["x", "y"],
|
|
107
|
+
"properties": {
|
|
108
|
+
"x": { "type": "number" },
|
|
109
|
+
"y": { "type": "number" }
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"config": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"description": "Node configuration (pre-execution constants)."
|
|
115
|
+
},
|
|
116
|
+
"inputs": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"additionalProperties": { "$ref": "#/$defs/PortValue" },
|
|
119
|
+
"description": "Input port connections. Keys are port names; values are PortValue references."
|
|
120
|
+
},
|
|
121
|
+
"credentialsRef": { "type": "string" },
|
|
122
|
+
"settings": { "type": "object" },
|
|
123
|
+
"disabled": { "type": "boolean", "default": false },
|
|
124
|
+
"notes": { "type": "string" },
|
|
125
|
+
"groupId": { "type": "string" },
|
|
126
|
+
"agent": {
|
|
127
|
+
"$ref": "agent-ref.schema.json",
|
|
128
|
+
"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."
|
|
129
|
+
},
|
|
130
|
+
"envelopeContract": { "type": "object" },
|
|
131
|
+
"artifactType": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": "Artifact type this node produces or reviews (first-class typed field — replaces the deprecated config.outputArtifactType bag entry)."
|
|
134
|
+
},
|
|
135
|
+
"cardType": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"description": "Explicit chat card type override (first-class typed field — replaces the deprecated config.chatCard bag entry)."
|
|
138
|
+
},
|
|
139
|
+
"outputSensitivity": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"additionalProperties": { "type": "boolean" },
|
|
142
|
+
"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)."
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"additionalProperties": false
|
|
146
|
+
},
|
|
147
|
+
"WorkflowEdge": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"required": ["id", "sourceNodeId", "targetNodeId"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"id": { "type": "string", "minLength": 1 },
|
|
152
|
+
"sourceNodeId": { "type": "string", "minLength": 1 },
|
|
153
|
+
"sourceOutput": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"description": "Source output port key. Default 'output'."
|
|
156
|
+
},
|
|
157
|
+
"targetNodeId": { "type": "string", "minLength": 1 },
|
|
158
|
+
"targetInput": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"description": "Target input port key. Default 'input'."
|
|
161
|
+
},
|
|
162
|
+
"condition": { "$ref": "#/$defs/EdgeCondition" },
|
|
163
|
+
"label": { "type": "string" },
|
|
164
|
+
"triggerRule": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"enum": ["all_success", "any_success", "all_complete", "none_failed", "any_failed"],
|
|
167
|
+
"default": "all_success"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"additionalProperties": false
|
|
171
|
+
},
|
|
172
|
+
"EdgeCondition": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": {
|
|
175
|
+
"type": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"enum": ["expression", "equals", "notEquals", "contains", "regex"]
|
|
178
|
+
},
|
|
179
|
+
"left": { "type": "string", "description": "Left operand path (e.g., 'status', 'output.approved')." },
|
|
180
|
+
"right": { "description": "Right operand value (any JSON value)." },
|
|
181
|
+
"expression": { "type": "string", "description": "Used when type='expression'." }
|
|
182
|
+
},
|
|
183
|
+
"additionalProperties": false
|
|
184
|
+
},
|
|
185
|
+
"PortValue": {
|
|
186
|
+
"oneOf": [
|
|
187
|
+
{
|
|
188
|
+
"type": "object",
|
|
189
|
+
"required": ["type", "value"],
|
|
190
|
+
"properties": {
|
|
191
|
+
"type": { "const": "static" },
|
|
192
|
+
"value": {}
|
|
193
|
+
},
|
|
194
|
+
"additionalProperties": false
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"type": "object",
|
|
198
|
+
"required": ["type", "expression"],
|
|
199
|
+
"properties": {
|
|
200
|
+
"type": { "const": "expression" },
|
|
201
|
+
"expression": { "type": "string", "minLength": 1 }
|
|
202
|
+
},
|
|
203
|
+
"additionalProperties": false
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"type": "object",
|
|
207
|
+
"required": ["type", "nodeId", "outputKey"],
|
|
208
|
+
"properties": {
|
|
209
|
+
"type": { "const": "connection" },
|
|
210
|
+
"nodeId": { "type": "string", "minLength": 1 },
|
|
211
|
+
"outputKey": { "type": "string", "minLength": 1 },
|
|
212
|
+
"optional": { "type": "boolean", "default": false }
|
|
213
|
+
},
|
|
214
|
+
"additionalProperties": false
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"type": "object",
|
|
218
|
+
"required": ["type", "variableName"],
|
|
219
|
+
"properties": {
|
|
220
|
+
"type": { "const": "variable" },
|
|
221
|
+
"variableName": { "type": "string", "minLength": 1 },
|
|
222
|
+
"optional": { "type": "boolean", "default": false }
|
|
223
|
+
},
|
|
224
|
+
"additionalProperties": false
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
"WorkflowTrigger": {
|
|
229
|
+
"type": "object",
|
|
230
|
+
"required": ["id", "type"],
|
|
231
|
+
"properties": {
|
|
232
|
+
"id": { "type": "string", "minLength": 1 },
|
|
233
|
+
"type": {
|
|
234
|
+
"type": "string",
|
|
235
|
+
"enum": ["manual", "schedule", "webhook", "event", "artifact", "canvas", "envelope", "command", "chat-message", "channel-write"],
|
|
236
|
+
"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."
|
|
237
|
+
},
|
|
238
|
+
"name": { "type": "string" },
|
|
239
|
+
"description": { "type": "string" },
|
|
240
|
+
"config": { "type": "object" },
|
|
241
|
+
"enabled": { "type": "boolean", "default": true },
|
|
242
|
+
"nodeId": { "type": "string" },
|
|
243
|
+
"eventType": { "type": "string" }
|
|
244
|
+
},
|
|
245
|
+
"additionalProperties": false
|
|
246
|
+
},
|
|
247
|
+
"WorkflowVariable": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"required": ["name", "type"],
|
|
250
|
+
"properties": {
|
|
251
|
+
"name": { "type": "string", "minLength": 1 },
|
|
252
|
+
"type": {
|
|
253
|
+
"type": "string",
|
|
254
|
+
"enum": ["string", "number", "boolean", "object", "array"]
|
|
255
|
+
},
|
|
256
|
+
"description": { "type": "string" },
|
|
257
|
+
"required": { "type": "boolean", "default": false },
|
|
258
|
+
"defaultValue": {},
|
|
259
|
+
"sensitive": {
|
|
260
|
+
"type": "boolean",
|
|
261
|
+
"default": false,
|
|
262
|
+
"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)."
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
"additionalProperties": false
|
|
266
|
+
},
|
|
267
|
+
"NodeGroup": {
|
|
268
|
+
"type": "object",
|
|
269
|
+
"required": ["id", "name", "nodeIds", "position", "size"],
|
|
270
|
+
"properties": {
|
|
271
|
+
"id": { "type": "string", "minLength": 1 },
|
|
272
|
+
"name": { "type": "string", "minLength": 1 },
|
|
273
|
+
"color": { "type": "string" },
|
|
274
|
+
"collapsed": { "type": "boolean", "default": false },
|
|
275
|
+
"nodeIds": {
|
|
276
|
+
"type": "array",
|
|
277
|
+
"items": { "type": "string" }
|
|
278
|
+
},
|
|
279
|
+
"position": {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"required": ["x", "y"],
|
|
282
|
+
"properties": {
|
|
283
|
+
"x": { "type": "number" },
|
|
284
|
+
"y": { "type": "number" }
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
"size": {
|
|
288
|
+
"type": "object",
|
|
289
|
+
"required": ["width", "height"],
|
|
290
|
+
"properties": {
|
|
291
|
+
"width": { "type": "number" },
|
|
292
|
+
"height": { "type": "number" }
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
"additionalProperties": false
|
|
297
|
+
},
|
|
298
|
+
"ChannelDeclaration": {
|
|
299
|
+
"type": "object",
|
|
300
|
+
"required": ["reducer"],
|
|
301
|
+
"properties": {
|
|
302
|
+
"reducer": {
|
|
303
|
+
"type": "string",
|
|
304
|
+
"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>'.",
|
|
305
|
+
"pattern": "^(replace|append|merge|counter|votes|feedback|message|vendor\\.[a-z][a-z0-9_-]*\\.[a-z][a-z0-9_-]*)$"
|
|
306
|
+
},
|
|
307
|
+
"schema": { "type": "object" },
|
|
308
|
+
"default": {},
|
|
309
|
+
"maxSize": { "type": "integer", "minimum": 1 },
|
|
310
|
+
"ttlMs": {
|
|
311
|
+
"type": "integer",
|
|
312
|
+
"minimum": 1,
|
|
313
|
+
"maximum": 31536000000,
|
|
314
|
+
"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."
|
|
315
|
+
},
|
|
316
|
+
"options": { "type": "object" },
|
|
317
|
+
"access": { "$ref": "#/$defs/ChannelAccess" },
|
|
318
|
+
"schemaVersion": {
|
|
319
|
+
"type": "integer",
|
|
320
|
+
"minimum": 1,
|
|
321
|
+
"default": 1,
|
|
322
|
+
"description": "Integer version of the current `schema`. Increments whenever the channel author edits `schema`. Each `channel.written` event records this version at write time. (closes C4)"
|
|
323
|
+
},
|
|
324
|
+
"compatibleWith": {
|
|
325
|
+
"type": "array",
|
|
326
|
+
"items": { "type": "integer", "minimum": 1 },
|
|
327
|
+
"uniqueItems": true,
|
|
328
|
+
"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."
|
|
329
|
+
},
|
|
330
|
+
"sensitive": {
|
|
331
|
+
"type": "boolean",
|
|
332
|
+
"default": false,
|
|
333
|
+
"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)."
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
"additionalProperties": false
|
|
337
|
+
},
|
|
338
|
+
"ChannelAccess": {
|
|
339
|
+
"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).",
|
|
340
|
+
"oneOf": [
|
|
341
|
+
{ "const": "public" },
|
|
342
|
+
{ "const": "private" },
|
|
343
|
+
{
|
|
344
|
+
"type": "object",
|
|
345
|
+
"properties": {
|
|
346
|
+
"readers": { "$ref": "#/$defs/ChannelAccessList" },
|
|
347
|
+
"writers": { "$ref": "#/$defs/ChannelAccessList" }
|
|
348
|
+
},
|
|
349
|
+
"additionalProperties": false
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
},
|
|
353
|
+
"ChannelAccessList": {
|
|
354
|
+
"type": "array",
|
|
355
|
+
"description": "Allowlist entries. Each item matches against the requesting node's `nodeId` (exact) OR `typeId` (wildcard). Wildcard format: dotted prefix + '*' (e.g., 'core.ai.*'). Bare '*' matches any node.",
|
|
356
|
+
"items": {
|
|
357
|
+
"type": "string",
|
|
358
|
+
"minLength": 1,
|
|
359
|
+
"maxLength": 256,
|
|
360
|
+
"pattern": "^([a-z][a-zA-Z0-9._-]*\\*?|\\*)$"
|
|
361
|
+
},
|
|
362
|
+
"uniqueItems": true,
|
|
363
|
+
"maxItems": 256
|
|
364
|
+
},
|
|
365
|
+
"WorkflowMetadata": {
|
|
366
|
+
"type": "object",
|
|
367
|
+
"properties": {
|
|
368
|
+
"createdBy": { "type": "string" },
|
|
369
|
+
"createdAt": { "type": "string", "format": "date-time" },
|
|
370
|
+
"updatedBy": { "type": "string" },
|
|
371
|
+
"updatedAt": { "type": "string", "format": "date-time" },
|
|
372
|
+
"tags": {
|
|
373
|
+
"type": "array",
|
|
374
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
375
|
+
"maxItems": 100
|
|
376
|
+
},
|
|
377
|
+
"category": { "type": "string" },
|
|
378
|
+
"author": { "type": "string" },
|
|
379
|
+
"codeVersion": { "type": "string" },
|
|
380
|
+
"customizedAt": { "type": "string", "format": "date-time" },
|
|
381
|
+
"customizedBy": { "type": "string" },
|
|
382
|
+
"forkedFrom": { "type": "string", "description": "ID of platform template this was forked from." },
|
|
383
|
+
"clonedFrom": { "type": "string", "description": "ID of workflow this was cloned from (project clone, not template fork)." },
|
|
384
|
+
"clonedAt": { "type": "string", "format": "date-time" },
|
|
385
|
+
"customProperties": { "type": "object" },
|
|
386
|
+
"complianceClass": {
|
|
387
|
+
"type": "string",
|
|
388
|
+
"enum": ["public", "pii", "phi", "pci", "regulated"],
|
|
389
|
+
"default": "public",
|
|
390
|
+
"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)."
|
|
391
|
+
},
|
|
392
|
+
"complianceConfig": {
|
|
393
|
+
"type": "object",
|
|
394
|
+
"description": "Optional per-workflow overrides for compliance behavior. The masking mode (`mask` | `omit` | `hash` | `passthrough`) defaults to the server's `Capabilities.compliance.defaultMode`; setting it here forces a specific mode for this workflow. Domain-specific extensions (HIPAA's 18 PHI identifiers, GDPR special categories) can live here as opt-in fields.",
|
|
395
|
+
"properties": {
|
|
396
|
+
"maskingMode": {
|
|
397
|
+
"type": "string",
|
|
398
|
+
"enum": ["mask", "omit", "hash", "passthrough"],
|
|
399
|
+
"description": "Per-workflow override of the server's default masking mode."
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
"additionalProperties": true
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
"WorkflowSettings": {
|
|
407
|
+
"type": "object",
|
|
408
|
+
"properties": {
|
|
409
|
+
"timeout": {
|
|
410
|
+
"type": "integer",
|
|
411
|
+
"minimum": 0,
|
|
412
|
+
"description": "Maximum run duration in milliseconds."
|
|
413
|
+
},
|
|
414
|
+
"maxRetries": {
|
|
415
|
+
"type": "integer",
|
|
416
|
+
"minimum": 0
|
|
417
|
+
},
|
|
418
|
+
"logLevel": {
|
|
419
|
+
"type": "string",
|
|
420
|
+
"enum": ["debug", "info", "warn", "error"]
|
|
421
|
+
},
|
|
422
|
+
"maxLoopbackIterations": {
|
|
423
|
+
"type": "integer",
|
|
424
|
+
"minimum": 1,
|
|
425
|
+
"default": 5
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `openwop-conformance` — operator-facing CLI for running the openwop
|
|
4
|
+
* conformance suite against a deployed server.
|
|
5
|
+
*
|
|
6
|
+
* Wraps `vitest` with friendlier args + structured exit codes so it
|
|
7
|
+
* works as the `npm test` entry for downstream packages.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* openwop-conformance --base-url https://api.example.com --api-key hk_test_123
|
|
11
|
+
* openwop-conformance --offline # server-free subset only
|
|
12
|
+
* openwop-conformance --filter discovery # category filter
|
|
13
|
+
* openwop-conformance --base-url ... --api-key ... --filter "interrupt|cancellation"
|
|
14
|
+
*
|
|
15
|
+
* Environment variables override flags (per the conformance harness's
|
|
16
|
+
* existing convention):
|
|
17
|
+
* OPENWOP_BASE_URL, OPENWOP_API_KEY, OPENWOP_IMPLEMENTATION_NAME,
|
|
18
|
+
* OPENWOP_IMPLEMENTATION_VERSION, OPENWOP_LIFECYCLE_TIMEOUT_MS
|
|
19
|
+
*
|
|
20
|
+
* Exit codes:
|
|
21
|
+
* 0 all scenarios pass
|
|
22
|
+
* 1 one or more scenarios failed
|
|
23
|
+
* 2 suite couldn't start (missing required args, etc)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { dirname, resolve as resolvePath } from 'node:path';
|
|
29
|
+
|
|
30
|
+
interface ParsedArgs {
|
|
31
|
+
readonly baseUrl: string | undefined;
|
|
32
|
+
readonly apiKey: string | undefined;
|
|
33
|
+
readonly offline: boolean;
|
|
34
|
+
readonly filter: string | undefined;
|
|
35
|
+
readonly help: boolean;
|
|
36
|
+
readonly impl: string | undefined;
|
|
37
|
+
readonly implVersion: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
41
|
+
let baseUrl: string | undefined;
|
|
42
|
+
let apiKey: string | undefined;
|
|
43
|
+
let offline = false;
|
|
44
|
+
let filter: string | undefined;
|
|
45
|
+
let help = false;
|
|
46
|
+
let impl: string | undefined;
|
|
47
|
+
let implVersion: string | undefined;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < argv.length; i++) {
|
|
50
|
+
const arg = argv[i] ?? '';
|
|
51
|
+
if (arg === '-h' || arg === '--help') {
|
|
52
|
+
help = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === '--offline') {
|
|
56
|
+
offline = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const eq = arg.indexOf('=');
|
|
60
|
+
const flag = eq === -1 ? arg : arg.slice(0, eq);
|
|
61
|
+
const inlineValue = eq === -1 ? undefined : arg.slice(eq + 1);
|
|
62
|
+
const nextValue = (): string | undefined => {
|
|
63
|
+
if (inlineValue !== undefined) return inlineValue;
|
|
64
|
+
const next = argv[i + 1];
|
|
65
|
+
if (next !== undefined && !next.startsWith('-')) {
|
|
66
|
+
i++;
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
switch (flag) {
|
|
73
|
+
case '--base-url':
|
|
74
|
+
baseUrl = nextValue();
|
|
75
|
+
break;
|
|
76
|
+
case '--api-key':
|
|
77
|
+
apiKey = nextValue();
|
|
78
|
+
break;
|
|
79
|
+
case '--filter':
|
|
80
|
+
filter = nextValue();
|
|
81
|
+
break;
|
|
82
|
+
case '--impl':
|
|
83
|
+
case '--implementation-name':
|
|
84
|
+
impl = nextValue();
|
|
85
|
+
break;
|
|
86
|
+
case '--impl-version':
|
|
87
|
+
case '--implementation-version':
|
|
88
|
+
implVersion = nextValue();
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
if (arg.startsWith('-')) {
|
|
92
|
+
// Unknown flag — pass through to vitest by ignoring here.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { baseUrl, apiKey, offline, filter, help, impl, implVersion };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const HELP_TEXT = `openwop-conformance — run the openwop conformance suite against a server
|
|
101
|
+
|
|
102
|
+
Usage:
|
|
103
|
+
openwop-conformance [options]
|
|
104
|
+
|
|
105
|
+
Required (unless --offline):
|
|
106
|
+
--base-url <url> openwop server base URL (or set OPENWOP_BASE_URL env var)
|
|
107
|
+
--api-key <key> Bearer-style API key (or set OPENWOP_API_KEY env var)
|
|
108
|
+
|
|
109
|
+
Filtering:
|
|
110
|
+
--offline Run only the server-free subset (fixtures + spec corpus)
|
|
111
|
+
--filter <pattern> Pass through to vitest --testNamePattern
|
|
112
|
+
|
|
113
|
+
Implementation labels (cosmetic — surface in failure messages):
|
|
114
|
+
--impl <name> Implementation name (env: OPENWOP_IMPLEMENTATION_NAME)
|
|
115
|
+
--impl-version <version> Implementation version (env: OPENWOP_IMPLEMENTATION_VERSION)
|
|
116
|
+
|
|
117
|
+
Other:
|
|
118
|
+
--help, -h Show this message
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
openwop-conformance --offline
|
|
122
|
+
openwop-conformance --base-url https://api.example.com --api-key hk_test_abc
|
|
123
|
+
openwop-conformance --filter "discovery|errors"
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
function main(): never {
|
|
127
|
+
const args = parseArgs(process.argv.slice(2));
|
|
128
|
+
|
|
129
|
+
if (args.help) {
|
|
130
|
+
process.stdout.write(HELP_TEXT);
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Env vars OVERRIDE flags only when the flag was unset (consistent
|
|
135
|
+
// with the rest of the harness — env wins on the absence of CLI input).
|
|
136
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
137
|
+
if (args.baseUrl) env.OPENWOP_BASE_URL = args.baseUrl;
|
|
138
|
+
if (args.apiKey) env.OPENWOP_API_KEY = args.apiKey;
|
|
139
|
+
if (args.impl) env.OPENWOP_IMPLEMENTATION_NAME = args.impl;
|
|
140
|
+
if (args.implVersion) env.OPENWOP_IMPLEMENTATION_VERSION = args.implVersion;
|
|
141
|
+
|
|
142
|
+
if (!args.offline && (!env.OPENWOP_BASE_URL || !env.OPENWOP_API_KEY)) {
|
|
143
|
+
process.stderr.write(
|
|
144
|
+
'openwop-conformance: --base-url and --api-key are required (or use --offline).\n' +
|
|
145
|
+
'Run `openwop-conformance --help` for usage.\n',
|
|
146
|
+
);
|
|
147
|
+
process.exit(2);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Resolve the conformance directory relative to this script's location
|
|
151
|
+
// so the CLI works regardless of the caller's cwd. Both the source
|
|
152
|
+
// path (`src/cli.ts`) and the compiled path (`dist/cli.js`) live ONE
|
|
153
|
+
// directory below the package root, so the same `..` works either way.
|
|
154
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
155
|
+
const conformanceRoot = resolvePath(here, '..');
|
|
156
|
+
|
|
157
|
+
// Build vitest argv. server-free subset is `fixtures-valid` +
|
|
158
|
+
// `spec-corpus-validity`; the offline flag scopes the run to those.
|
|
159
|
+
// Pass --config explicitly so vitest doesn't auto-discover an
|
|
160
|
+
// ancestor config (e.g., a parent monorepo's vite.config.ts) when
|
|
161
|
+
// the conformance package is used as a workspace member.
|
|
162
|
+
const vitestArgs: string[] = ['run', '--config', resolvePath(conformanceRoot, 'vitest.config.ts')];
|
|
163
|
+
if (args.offline) {
|
|
164
|
+
vitestArgs.push(
|
|
165
|
+
'src/scenarios/fixtures-valid.test.ts',
|
|
166
|
+
'src/scenarios/spec-corpus-validity.test.ts',
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (args.filter) {
|
|
170
|
+
vitestArgs.push('--testNamePattern', args.filter);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = spawnSync('npx', ['vitest', ...vitestArgs], {
|
|
174
|
+
cwd: conformanceRoot,
|
|
175
|
+
env,
|
|
176
|
+
stdio: 'inherit',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (result.error) {
|
|
180
|
+
process.stderr.write(`openwop-conformance: failed to spawn vitest: ${String(result.error)}\n`);
|
|
181
|
+
process.exit(2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
process.exit(result.status ?? 1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main();
|