@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.
Files changed (175) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +241 -0
  3. package/api/asyncapi.yaml +481 -0
  4. package/api/openapi.yaml +830 -0
  5. package/api/redocly.yaml +8 -0
  6. package/coverage.md +80 -0
  7. package/dist/cli.js +161 -0
  8. package/fixtures/conformance-a2a-task-roundtrip.json +27 -0
  9. package/fixtures/conformance-agent-identity.json +27 -0
  10. package/fixtures/conformance-agent-low-confidence.json +29 -0
  11. package/fixtures/conformance-agent-memory-cross-tenant.json +28 -0
  12. package/fixtures/conformance-agent-memory-redaction.json +32 -0
  13. package/fixtures/conformance-agent-memory-roundtrip.json +32 -0
  14. package/fixtures/conformance-agent-memory-ttl.json +31 -0
  15. package/fixtures/conformance-agent-pack-export.json +26 -0
  16. package/fixtures/conformance-agent-pack-install.json +26 -0
  17. package/fixtures/conformance-agent-pack-provenance.json +31 -0
  18. package/fixtures/conformance-agent-reasoning.json +29 -0
  19. package/fixtures/conformance-approval.json +27 -0
  20. package/fixtures/conformance-cancellable.json +33 -0
  21. package/fixtures/conformance-cap-breach.json +27 -0
  22. package/fixtures/conformance-capability-missing.json +23 -0
  23. package/fixtures/conformance-channel-ttl.json +60 -0
  24. package/fixtures/conformance-clarification.json +30 -0
  25. package/fixtures/conformance-conversation-capability-negotiation.json +23 -0
  26. package/fixtures/conformance-conversation-lifecycle.json +32 -0
  27. package/fixtures/conformance-conversation-replay.json +33 -0
  28. package/fixtures/conformance-conversation-vs-clarification.json +26 -0
  29. package/fixtures/conformance-delay.json +33 -0
  30. package/fixtures/conformance-dispatch-loop.json +38 -0
  31. package/fixtures/conformance-failure.json +23 -0
  32. package/fixtures/conformance-idempotent.json +30 -0
  33. package/fixtures/conformance-identity.json +32 -0
  34. package/fixtures/conformance-interrupt-auth-required.json +28 -0
  35. package/fixtures/conformance-interrupt-external-event.json +33 -0
  36. package/fixtures/conformance-interrupt-parent-child-cancel-child.json +27 -0
  37. package/fixtures/conformance-interrupt-parent-child-cancel.json +26 -0
  38. package/fixtures/conformance-interrupt-quorum.json +30 -0
  39. package/fixtures/conformance-mcp-tool-roundtrip.json +32 -0
  40. package/fixtures/conformance-message-reducer.json +31 -0
  41. package/fixtures/conformance-multi-node.json +21 -0
  42. package/fixtures/conformance-noop.json +23 -0
  43. package/fixtures/conformance-orchestrator-dispatch.json +47 -0
  44. package/fixtures/conformance-orchestrator-low-confidence.json +41 -0
  45. package/fixtures/conformance-orchestrator-terminate.json +44 -0
  46. package/fixtures/conformance-stream-text.json +26 -0
  47. package/fixtures/conformance-subworkflow-child.json +21 -0
  48. package/fixtures/conformance-subworkflow-parent.json +49 -0
  49. package/fixtures/conformance-version-fold.json +23 -0
  50. package/fixtures/conformance-wasm-pack-roundtrip.json +25 -0
  51. package/fixtures/pack-manifests/pack-private-example.json +26 -0
  52. package/fixtures.md +404 -0
  53. package/package.json +48 -0
  54. package/schemas/README.md +75 -0
  55. package/schemas/agent-manifest.schema.json +107 -0
  56. package/schemas/agent-ref.schema.json +53 -0
  57. package/schemas/capabilities.schema.json +287 -0
  58. package/schemas/channel-written-payload.schema.json +55 -0
  59. package/schemas/conversation-event.schema.json +120 -0
  60. package/schemas/conversation-turn.schema.json +72 -0
  61. package/schemas/debug-bundle.schema.json +196 -0
  62. package/schemas/dispatch-config.schema.json +46 -0
  63. package/schemas/error-envelope.schema.json +25 -0
  64. package/schemas/memory-entry.schema.json +36 -0
  65. package/schemas/memory-list-options.schema.json +21 -0
  66. package/schemas/node-pack-manifest.schema.json +235 -0
  67. package/schemas/orchestrator-decision.schema.json +60 -0
  68. package/schemas/run-event-payloads.schema.json +663 -0
  69. package/schemas/run-event.schema.json +116 -0
  70. package/schemas/run-options.schema.json +81 -0
  71. package/schemas/run-orchestrator-decided-event.schema.json +20 -0
  72. package/schemas/run-snapshot.schema.json +121 -0
  73. package/schemas/suspend-request.schema.json +182 -0
  74. package/schemas/workflow-definition.schema.json +430 -0
  75. package/src/cli.ts +187 -0
  76. package/src/lib/a2a-fake-peer.ts +233 -0
  77. package/src/lib/canaries.ts +186 -0
  78. package/src/lib/driver.ts +96 -0
  79. package/src/lib/env.ts +49 -0
  80. package/src/lib/fixtures.ts +93 -0
  81. package/src/lib/mcp-fake-server.ts +185 -0
  82. package/src/lib/multi-agent-capabilities.ts +155 -0
  83. package/src/lib/multiProcess.ts +141 -0
  84. package/src/lib/otel-collector.ts +312 -0
  85. package/src/lib/paths.ts +198 -0
  86. package/src/lib/polling.ts +81 -0
  87. package/src/lib/profiles.ts +258 -0
  88. package/src/lib/sse.ts +172 -0
  89. package/src/scenarios/a2a-task-roundtrip.test.ts +149 -0
  90. package/src/scenarios/agentConfidenceEscalation.test.ts +61 -0
  91. package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +54 -0
  92. package/src/scenarios/agentMemoryRedactionContract.test.ts +46 -0
  93. package/src/scenarios/agentMemoryRoundTrip.test.ts +52 -0
  94. package/src/scenarios/agentMemoryTtlExpiry.test.ts +47 -0
  95. package/src/scenarios/agentMessageReducer.test.ts +57 -0
  96. package/src/scenarios/agentMetadata.test.ts +56 -0
  97. package/src/scenarios/agentPackExport.test.ts +45 -0
  98. package/src/scenarios/agentPackInstall.test.ts +50 -0
  99. package/src/scenarios/agentPackProvenance.test.ts +53 -0
  100. package/src/scenarios/agentReasoningEvents.test.ts +72 -0
  101. package/src/scenarios/append-ordering.test.ts +91 -0
  102. package/src/scenarios/approval-payload.test.ts +120 -0
  103. package/src/scenarios/audit-log-integrity.test.ts +106 -0
  104. package/src/scenarios/auth.test.ts +55 -0
  105. package/src/scenarios/byok-roundtrip.test.ts +166 -0
  106. package/src/scenarios/cancellation.test.ts +68 -0
  107. package/src/scenarios/cap-breach.test.ts +149 -0
  108. package/src/scenarios/channel-ttl.test.ts +70 -0
  109. package/src/scenarios/configurable-schema.test.ts +76 -0
  110. package/src/scenarios/conversationCapabilityNegotiation.test.ts +39 -0
  111. package/src/scenarios/conversationLifecycle.test.ts +64 -0
  112. package/src/scenarios/conversationReplayDeterminism.test.ts +52 -0
  113. package/src/scenarios/conversationVsLegacySuspend.test.ts +46 -0
  114. package/src/scenarios/cost-attribution.test.ts +207 -0
  115. package/src/scenarios/debugBundle.test.ts +222 -0
  116. package/src/scenarios/discovery.test.ts +147 -0
  117. package/src/scenarios/dispatchLoop.test.ts +52 -0
  118. package/src/scenarios/errors.test.ts +144 -0
  119. package/src/scenarios/eventOrdering.test.ts +144 -0
  120. package/src/scenarios/failure-path.test.ts +46 -0
  121. package/src/scenarios/fixtures-gating.test.ts +137 -0
  122. package/src/scenarios/fixtures-valid.test.ts +140 -0
  123. package/src/scenarios/highConcurrency.test.ts +263 -0
  124. package/src/scenarios/idempotency.test.ts +83 -0
  125. package/src/scenarios/idempotencyRetry.test.ts +130 -0
  126. package/src/scenarios/identity-passthrough.test.ts +54 -0
  127. package/src/scenarios/interrupt-approval.test.ts +97 -0
  128. package/src/scenarios/interrupt-auth-required-resume.test.ts +88 -0
  129. package/src/scenarios/interrupt-clarification.test.ts +45 -0
  130. package/src/scenarios/interrupt-external-event-correlation.test.ts +113 -0
  131. package/src/scenarios/interrupt-parent-child-cascade.test.ts +102 -0
  132. package/src/scenarios/interrupt-quorum-resolution.test.ts +97 -0
  133. package/src/scenarios/interruptRace.test.ts +176 -0
  134. package/src/scenarios/maliciousManifest.test.ts +154 -0
  135. package/src/scenarios/mcp-discoverability.test.ts +129 -0
  136. package/src/scenarios/mcp-tool-roundtrip.test.ts +149 -0
  137. package/src/scenarios/multi-node-ordering.test.ts +60 -0
  138. package/src/scenarios/multi-region-idempotency.test.ts +52 -0
  139. package/src/scenarios/orchestratorConservativePath.test.ts +63 -0
  140. package/src/scenarios/orchestratorDispatch.test.ts +66 -0
  141. package/src/scenarios/orchestratorTermination.test.ts +54 -0
  142. package/src/scenarios/otel-emission.test.ts +113 -0
  143. package/src/scenarios/otel-trace-propagation.test.ts +90 -0
  144. package/src/scenarios/pack-registry-publish.test.ts +93 -0
  145. package/src/scenarios/pack-registry.test.ts +328 -0
  146. package/src/scenarios/pause-resume.test.ts +109 -0
  147. package/src/scenarios/policies.test.ts +162 -0
  148. package/src/scenarios/profileDerivation.test.ts +335 -0
  149. package/src/scenarios/providerPolicyEnforcement.test.ts +132 -0
  150. package/src/scenarios/rate-limit-envelope.test.ts +97 -0
  151. package/src/scenarios/redaction.test.ts +254 -0
  152. package/src/scenarios/redactionAdversarial.test.ts +162 -0
  153. package/src/scenarios/replay-fork-arbitrary.test.ts +347 -0
  154. package/src/scenarios/replay-fork.test.ts +216 -0
  155. package/src/scenarios/replayDeterminism.test.ts +171 -0
  156. package/src/scenarios/route-coverage.test.ts +129 -0
  157. package/src/scenarios/runs-lifecycle.test.ts +65 -0
  158. package/src/scenarios/runtime-capabilities.test.ts +118 -0
  159. package/src/scenarios/spec-corpus-validity.test.ts +1257 -0
  160. package/src/scenarios/staleClaim.test.ts +223 -0
  161. package/src/scenarios/stream-modes-buffer.test.ts +148 -0
  162. package/src/scenarios/stream-modes-mixed.test.ts +149 -0
  163. package/src/scenarios/stream-modes.test.ts +139 -0
  164. package/src/scenarios/streamReconnect.test.ts +162 -0
  165. package/src/scenarios/subworkflow.test.ts +126 -0
  166. package/src/scenarios/version-negotiation.test.ts +157 -0
  167. package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +47 -0
  168. package/src/scenarios/wasm-pack-invoke-completed.test.ts +69 -0
  169. package/src/scenarios/wasm-pack-invoke-suspended.test.ts +74 -0
  170. package/src/scenarios/wasm-pack-load.test.ts +75 -0
  171. package/src/scenarios/wasm-pack-memory-cap.test.ts +43 -0
  172. package/src/scenarios/wasm-pack-replay-determinism.test.ts +61 -0
  173. package/src/scenarios/webhook-sig-algorithm.test.ts +61 -0
  174. package/src/setup.ts +173 -0
  175. package/vitest.config.ts +17 -0
@@ -0,0 +1,830 @@
1
+ openapi: 3.1.0
2
+
3
+ info:
4
+ title: Workflow Orchestration Protocol (openwop) API
5
+ version: "1.0"
6
+ summary: REST surface for declaring, executing, suspending, resuming, and observing multi-step workflows.
7
+ description: |
8
+ Canonical OpenAPI 3.1 specification for openwop-compliant servers. Generated from `rest-endpoints.md` and references the JSON Schemas in `schemas/`.
9
+
10
+ See spec docs for semantics:
11
+ - `auth.md` — API key + scope vocabulary
12
+ - `idempotency.md` — `Idempotency-Key` header contract
13
+ - `version-negotiation.md` — `engineVersion` + `eventLogSchemaVersion`
14
+ - `capabilities.md` — `/.well-known/openwop` handshake
15
+ - `stream-modes.md` — SSE consumption modes
16
+ - `run-options.md` — `configurable`/`tags`/`metadata`
17
+ - `interrupt.md` — HITL primitive
18
+ - `replay.md` — `:fork` endpoint
19
+ contact:
20
+ name: openwop spec working group
21
+ url: https://openwop.dev/spec/v1/
22
+ license:
23
+ name: Apache-2.0
24
+ identifier: Apache-2.0
25
+
26
+ externalDocs:
27
+ description: openwop spec v1 corpus
28
+ url: https://openwop.dev/spec/v1/
29
+
30
+ servers:
31
+ - url: https://{host}/v1
32
+ description: openwop-compliant server
33
+ variables:
34
+ host:
35
+ default: api.example.com
36
+ description: Replace with your server's hostname.
37
+
38
+ # ─────────────────────────────────────────────────────────────────────────────
39
+ # SECURITY
40
+ # ─────────────────────────────────────────────────────────────────────────────
41
+ security:
42
+ - ApiKeyAuth: []
43
+
44
+ # ─────────────────────────────────────────────────────────────────────────────
45
+ # TAGS
46
+ # ─────────────────────────────────────────────────────────────────────────────
47
+ tags:
48
+ - name: discovery
49
+ description: Public capability + spec discovery (no auth required).
50
+ - name: workflows
51
+ description: Workflow definition manifest.
52
+ - name: runs
53
+ description: Run lifecycle — create, read, stream, cancel, fork.
54
+ - name: hitl
55
+ description: Human-in-the-loop interrupts and approvals.
56
+ - name: artifacts
57
+ description: Run-produced artifacts.
58
+ - name: webhooks
59
+ description: Subscribe to run events via outbound HTTP.
60
+
61
+ # ─────────────────────────────────────────────────────────────────────────────
62
+ # PATHS
63
+ # ─────────────────────────────────────────────────────────────────────────────
64
+ paths:
65
+
66
+ # ── Discovery (unauthenticated) ─────────────────────────────────────────
67
+ /.well-known/openwop:
68
+ get:
69
+ tags: [discovery]
70
+ summary: Capability declaration handshake.
71
+ operationId: getCapabilities
72
+ security: []
73
+ responses:
74
+ '200':
75
+ description: Capabilities object — see `capabilities.md`.
76
+ headers:
77
+ Cache-Control:
78
+ schema: { type: string }
79
+ example: 'public, max-age=300'
80
+ Capabilities-Etag:
81
+ schema: { type: string }
82
+ description: Optional probe handle for mid-session capability change detection.
83
+ content:
84
+ application/json:
85
+ schema:
86
+ $ref: '#/components/schemas/Capabilities'
87
+ '503':
88
+ description: Server unable to compute capabilities (transient).
89
+ content:
90
+ application/json:
91
+ schema: { $ref: '#/components/schemas/Error' }
92
+
93
+ /v1/openapi.json:
94
+ get:
95
+ tags: [discovery]
96
+ summary: Self-describing OpenAPI 3.1 spec.
97
+ operationId: getOpenApiSpec
98
+ security: []
99
+ responses:
100
+ '200':
101
+ description: This document.
102
+ content:
103
+ application/json:
104
+ schema:
105
+ type: object
106
+ '503':
107
+ description: Server unable to serve spec (transient).
108
+ content:
109
+ application/json:
110
+ schema: { $ref: '#/components/schemas/Error' }
111
+
112
+ # ── Workflows ───────────────────────────────────────────────────────────
113
+ /v1/workflows/{workflowId}:
114
+ get:
115
+ tags: [workflows]
116
+ summary: Read a workflow definition.
117
+ operationId: getWorkflow
118
+ parameters:
119
+ - $ref: '#/components/parameters/WorkflowId'
120
+ responses:
121
+ '200':
122
+ description: Workflow definition.
123
+ content:
124
+ application/json:
125
+ schema:
126
+ $ref: '../schemas/workflow-definition.schema.json'
127
+ '401': { $ref: '#/components/responses/Unauthenticated' }
128
+ '403': { $ref: '#/components/responses/Forbidden' }
129
+ '404': { $ref: '#/components/responses/NotFound' }
130
+
131
+ # ── Runs ────────────────────────────────────────────────────────────────
132
+ /v1/runs:
133
+ post:
134
+ tags: [runs]
135
+ summary: Create a new run.
136
+ operationId: createRun
137
+ parameters:
138
+ - $ref: '#/components/parameters/IdempotencyKey'
139
+ - in: header
140
+ name: X-Dedup
141
+ schema:
142
+ type: string
143
+ enum: [enforce]
144
+ description: When set, server cross-host claim system rejects duplicate `(tenantId, scopeId)` pairs with `409 Conflict`.
145
+ - in: header
146
+ name: X-Force-Engine-Version
147
+ schema: { type: integer, minimum: 0 }
148
+ description: |
149
+ **Test-keys-only.** When set, the server emits events for this run AS IF it
150
+ were running the specified engine version (must be within the server's
151
+ advertised `Capabilities.testing.forceEngineVersionRange`). Used by the
152
+ conformance suite to verify version-negotiation fold-best-effort tolerance
153
+ across the spec's forward-compat matrix. Servers MUST reject on production
154
+ API keys with `403 force_engine_version_forbidden`. Closes F5.
155
+ requestBody:
156
+ required: true
157
+ content:
158
+ application/json:
159
+ schema:
160
+ # The body is the WorkflowId + inputs + transport-specific
161
+ # routing fields, plus the openwop RunOptions overlay (configurable,
162
+ # tags, metadata) hoisted into a first-class JSON Schema at
163
+ # ../schemas/run-options.schema.json. allOf composes the two
164
+ # so callers see one unified body shape.
165
+ allOf:
166
+ - type: object
167
+ required: [workflowId]
168
+ properties:
169
+ workflowId: { type: string, minLength: 1 }
170
+ inputs:
171
+ type: object
172
+ description: Workflow inputs (consumed by triggers/nodes).
173
+ tenantId:
174
+ type: string
175
+ description: Tenant scoping. Server typically defaults from API key.
176
+ scopeId:
177
+ type: string
178
+ description: Opaque correlation ID for `X-Dedup` semantics.
179
+ callbackUrl:
180
+ type: string
181
+ format: uri
182
+ description: Signed-token HITL callback URL (see `interrupt.md`).
183
+ additionalProperties: false
184
+ - $ref: '../schemas/run-options.schema.json'
185
+ responses:
186
+ '201':
187
+ description: Run accepted.
188
+ headers:
189
+ openwop-Idempotent-Replay:
190
+ schema: { type: boolean }
191
+ description: Set when the response was served from the idempotency cache.
192
+ content:
193
+ application/json:
194
+ schema:
195
+ type: object
196
+ required: [runId, status, eventsUrl]
197
+ properties:
198
+ runId: { type: string }
199
+ status:
200
+ type: string
201
+ enum: [pending, running, waiting-approval, waiting-input]
202
+ eventsUrl: { type: string, format: uri }
203
+ statusUrl: { type: string, format: uri }
204
+ '400': { $ref: '#/components/responses/ValidationError' }
205
+ '401': { $ref: '#/components/responses/Unauthenticated' }
206
+ '403': { $ref: '#/components/responses/Forbidden' }
207
+ '409':
208
+ description: '`X-Dedup` collision OR concurrent `Idempotency-Key` collision.'
209
+ headers:
210
+ Retry-After:
211
+ schema: { type: integer }
212
+ description: Seconds until the active claim is stale-eligible.
213
+ content:
214
+ application/json:
215
+ schema:
216
+ $ref: '#/components/schemas/RunClaimConflict'
217
+ '429': { $ref: '#/components/responses/RateLimited' }
218
+
219
+ /v1/runs/{runId}:
220
+ get:
221
+ tags: [runs]
222
+ summary: Read run state (cached projection).
223
+ operationId: getRun
224
+ parameters:
225
+ - $ref: '#/components/parameters/RunId'
226
+ responses:
227
+ '200':
228
+ description: Projected run state.
229
+ content:
230
+ application/json:
231
+ schema:
232
+ $ref: '#/components/schemas/RunSnapshot'
233
+ '401': { $ref: '#/components/responses/Unauthenticated' }
234
+ '403': { $ref: '#/components/responses/Forbidden' }
235
+ '404': { $ref: '#/components/responses/NotFound' }
236
+
237
+ /v1/runs/{runId}/events:
238
+ get:
239
+ tags: [runs]
240
+ summary: SSE stream of run events.
241
+ operationId: streamRunEvents
242
+ parameters:
243
+ - $ref: '#/components/parameters/RunId'
244
+ - in: query
245
+ name: streamMode
246
+ schema:
247
+ type: string
248
+ default: updates
249
+ pattern: '^(values|updates|messages|debug)(,(values|updates|messages|debug))*$'
250
+ description: |
251
+ Single mode: `values` / `updates` / `messages` / `debug`.
252
+ Mixed mode: comma-separated combination (e.g., `updates,messages`)
253
+ per S4 closure — server emits union-of-filters with per-event
254
+ `event:` field labeling which mode admitted each event.
255
+ `values` MUST NOT combine with other modes (state.snapshot semantics
256
+ need exclusive ownership). See `stream-modes.md`. Default `updates`.
257
+ - in: query
258
+ name: bufferMs
259
+ schema:
260
+ type: integer
261
+ minimum: 0
262
+ maximum: 5000
263
+ description: |
264
+ Optional batching hint per S3 closure. When set, the server
265
+ accumulates events for up to N ms (or until a forced-flush
266
+ trigger fires — terminal events, suspensions, connection close)
267
+ and emits a single SSE event with `event: batch` and `data:` as
268
+ a JSON array of `RunEventDoc`. Range 0..5000; `0` = no buffering.
269
+ See `stream-modes.md` §Aggregation hint.
270
+ - in: header
271
+ name: Last-Event-ID
272
+ schema: { type: string }
273
+ description: Resume from sequence after this ID.
274
+ responses:
275
+ '200':
276
+ description: SSE stream. Auto-closes on terminal event. Keep-alive comments every 30s.
277
+ content:
278
+ text/event-stream:
279
+ schema:
280
+ type: string
281
+ description: SSE events. Each event has `id:`, `event:`, `data:` per RFC 8895.
282
+ '400':
283
+ description: Unsupported `streamMode`.
284
+ content:
285
+ application/json:
286
+ schema:
287
+ $ref: '#/components/schemas/UnsupportedStreamMode'
288
+ '401': { $ref: '#/components/responses/Unauthenticated' }
289
+ '403': { $ref: '#/components/responses/Forbidden' }
290
+ '404': { $ref: '#/components/responses/NotFound' }
291
+
292
+ /v1/runs/{runId}/events/poll:
293
+ get:
294
+ tags: [runs]
295
+ summary: Long-poll fallback for non-SSE clients.
296
+ operationId: pollRunEvents
297
+ parameters:
298
+ - $ref: '#/components/parameters/RunId'
299
+ - in: query
300
+ name: lastSequence
301
+ schema: { type: integer, minimum: 0 }
302
+ - in: query
303
+ name: timeout
304
+ schema: { type: integer, minimum: 1, maximum: 60, default: 30 }
305
+ description: Seconds to wait for new events. Max 60.
306
+ responses:
307
+ '200':
308
+ description: Events since `lastSequence`.
309
+ content:
310
+ application/json:
311
+ schema:
312
+ type: object
313
+ required: [events, isComplete]
314
+ properties:
315
+ events:
316
+ type: array
317
+ items:
318
+ $ref: '../schemas/run-event.schema.json'
319
+ isComplete: { type: boolean }
320
+ '401': { $ref: '#/components/responses/Unauthenticated' }
321
+ '403': { $ref: '#/components/responses/Forbidden' }
322
+ '404': { $ref: '#/components/responses/NotFound' }
323
+
324
+ /v1/runs/{runId}/cancel:
325
+ post:
326
+ tags: [runs]
327
+ summary: Cancel an in-flight run.
328
+ operationId: cancelRun
329
+ parameters:
330
+ - $ref: '#/components/parameters/RunId'
331
+ - $ref: '#/components/parameters/IdempotencyKey'
332
+ requestBody:
333
+ content:
334
+ application/json:
335
+ schema:
336
+ type: object
337
+ properties:
338
+ reason: { type: string }
339
+ responses:
340
+ '200':
341
+ description: Run cancellation accepted (cascade may be async).
342
+ content:
343
+ application/json:
344
+ schema:
345
+ type: object
346
+ properties:
347
+ runId: { type: string }
348
+ status: { type: string, enum: [cancelled, cancelling] }
349
+ '401': { $ref: '#/components/responses/Unauthenticated' }
350
+ '403': { $ref: '#/components/responses/Forbidden' }
351
+ '404': { $ref: '#/components/responses/NotFound' }
352
+
353
+ /v1/runs/{runId}:fork:
354
+ post:
355
+ tags: [runs]
356
+ summary: Fork the run for replay or branch (see `replay.md`).
357
+ operationId: forkRun
358
+ parameters:
359
+ - $ref: '#/components/parameters/RunId'
360
+ - $ref: '#/components/parameters/IdempotencyKey'
361
+ requestBody:
362
+ required: true
363
+ content:
364
+ application/json:
365
+ schema:
366
+ type: object
367
+ required: [fromSeq, mode]
368
+ properties:
369
+ fromSeq:
370
+ type: integer
371
+ minimum: 0
372
+ description: Inclusive — events `< fromSeq` are fixed history; `>= fromSeq` are re-executed.
373
+ mode:
374
+ type: string
375
+ enum: [replay, branch]
376
+ runOptionsOverlay:
377
+ type: object
378
+ description: For `branch` mode only — caller-supplied `RunOptions` to overlay.
379
+ additionalProperties: false
380
+ responses:
381
+ '201':
382
+ description: Fork accepted, new run started.
383
+ content:
384
+ application/json:
385
+ schema:
386
+ type: object
387
+ required: [runId, sourceRunId, mode, status, eventsUrl]
388
+ properties:
389
+ runId: { type: string }
390
+ sourceRunId: { type: string }
391
+ fromSeq: { type: integer }
392
+ mode: { type: string, enum: [replay, branch] }
393
+ status: { type: string }
394
+ eventsUrl: { type: string, format: uri }
395
+ '400':
396
+ description: Invalid `fromSeq`, `replay` with non-empty `runOptionsOverlay`, etc.
397
+ content:
398
+ application/json:
399
+ schema: { $ref: '#/components/schemas/Error' }
400
+ '401': { $ref: '#/components/responses/Unauthenticated' }
401
+ '403': { $ref: '#/components/responses/Forbidden' }
402
+ '404': { $ref: '#/components/responses/NotFound' }
403
+ '422':
404
+ description: "`fromSeq` references a sequence number that doesn't exist in the source run's event log."
405
+ content:
406
+ application/json:
407
+ schema: { $ref: '#/components/schemas/Error' }
408
+
409
+ /v1/runs/{runId}:pause:
410
+ post:
411
+ tags: [runs]
412
+ summary: Administratively pause an in-flight run (RFC Track 13).
413
+ description: |
414
+ Operator-driven pause distinct from cancel (terminal) and HITL suspend (workflow-driven).
415
+ Emits a `run.paused` event when the pause takes effect; exit only via `:resume` or `:cancel`.
416
+ operationId: pauseRun
417
+ parameters:
418
+ - $ref: '#/components/parameters/RunId'
419
+ - $ref: '#/components/parameters/IdempotencyKey'
420
+ requestBody:
421
+ content:
422
+ application/json:
423
+ schema:
424
+ type: object
425
+ properties:
426
+ reason:
427
+ type: string
428
+ description: Free-form rationale, persisted on the `run.paused` event payload.
429
+ drainPolicy:
430
+ type: string
431
+ enum: [immediate, drain-current-node]
432
+ default: drain-current-node
433
+ description: |
434
+ `immediate` snapshots between events; `drain-current-node` lets the running node
435
+ reach a terminal before transitioning to `paused`.
436
+ additionalProperties: false
437
+ responses:
438
+ '202':
439
+ description: Pause requested; transition emits `run.paused` when complete.
440
+ content:
441
+ application/json:
442
+ schema:
443
+ type: object
444
+ required: [runId, status]
445
+ properties:
446
+ runId: { type: string }
447
+ status: { type: string, enum: [paused] }
448
+ pausedAt: { type: string, format: date-time }
449
+ '401': { $ref: '#/components/responses/Unauthenticated' }
450
+ '403': { $ref: '#/components/responses/Forbidden' }
451
+ '404': { $ref: '#/components/responses/NotFound' }
452
+ '409':
453
+ description: Run is already paused, terminal, or in a state that cannot be paused.
454
+ content:
455
+ application/json:
456
+ schema: { $ref: '#/components/schemas/Error' }
457
+
458
+ /v1/runs/{runId}:resume:
459
+ post:
460
+ tags: [runs]
461
+ summary: Resume a paused run (RFC Track 13).
462
+ description: |
463
+ Reverses a prior `:pause`. Run transitions from `paused` to `running` and emits `run.resumed`.
464
+ operationId: resumeRun
465
+ parameters:
466
+ - $ref: '#/components/parameters/RunId'
467
+ - $ref: '#/components/parameters/IdempotencyKey'
468
+ requestBody:
469
+ content:
470
+ application/json:
471
+ schema:
472
+ type: object
473
+ properties:
474
+ reason: { type: string }
475
+ additionalProperties: false
476
+ responses:
477
+ '202':
478
+ description: Resume requested.
479
+ content:
480
+ application/json:
481
+ schema:
482
+ type: object
483
+ required: [runId, status]
484
+ properties:
485
+ runId: { type: string }
486
+ status: { type: string, enum: [running] }
487
+ resumedAt: { type: string, format: date-time }
488
+ '401': { $ref: '#/components/responses/Unauthenticated' }
489
+ '403': { $ref: '#/components/responses/Forbidden' }
490
+ '404': { $ref: '#/components/responses/NotFound' }
491
+ '409':
492
+ description: Run is not currently paused.
493
+ content:
494
+ application/json:
495
+ schema: { $ref: '#/components/schemas/Error' }
496
+
497
+ # ── HITL ────────────────────────────────────────────────────────────────
498
+ /v1/runs/{runId}/interrupts/{nodeId}:
499
+ post:
500
+ tags: [hitl]
501
+ summary: Resolve an interrupt via the run-scoped surface.
502
+ operationId: resolveInterruptByRun
503
+ parameters:
504
+ - $ref: '#/components/parameters/RunId'
505
+ - in: path
506
+ name: nodeId
507
+ required: true
508
+ schema: { type: string, minLength: 1 }
509
+ - $ref: '#/components/parameters/IdempotencyKey'
510
+ requestBody:
511
+ required: true
512
+ content:
513
+ application/json:
514
+ schema:
515
+ type: object
516
+ required: [resumeValue]
517
+ properties:
518
+ resumeValue:
519
+ description: Validated against the interrupt's `resumeSchema` if declared.
520
+ additionalProperties: false
521
+ responses:
522
+ '200':
523
+ description: Interrupt resolved; executor unblocks.
524
+ content:
525
+ application/json:
526
+ schema:
527
+ type: object
528
+ properties:
529
+ runId: { type: string }
530
+ nodeId: { type: string }
531
+ status: { type: string }
532
+ '400': { $ref: '#/components/responses/ValidationError' }
533
+ '401': { $ref: '#/components/responses/Unauthenticated' }
534
+ '403': { $ref: '#/components/responses/Forbidden' }
535
+ '404':
536
+ description: Interrupt not found or already resolved.
537
+ content:
538
+ application/json:
539
+ schema: { $ref: '#/components/schemas/Error' }
540
+ '409':
541
+ description: Concurrent resolve — only one wins.
542
+ content:
543
+ application/json:
544
+ schema: { $ref: '#/components/schemas/Error' }
545
+ '422':
546
+ description: Run was cancelled while interrupt was pending.
547
+ content:
548
+ application/json:
549
+ schema: { $ref: '#/components/schemas/Error' }
550
+
551
+ /v1/interrupts/{token}:
552
+ parameters:
553
+ - in: path
554
+ name: token
555
+ required: true
556
+ schema: { type: string }
557
+ description: HMAC-signed token issued by the server at suspension time. Format `base64url(payload).hmac_sha256(secret, payload)`.
558
+ get:
559
+ tags: [hitl]
560
+ summary: Inspect an interrupt without resolving (signed-token surface).
561
+ operationId: inspectInterruptByToken
562
+ security: [] # token is the auth
563
+ responses:
564
+ '200':
565
+ description: Interrupt details.
566
+ content:
567
+ application/json:
568
+ schema: { $ref: '../schemas/suspend-request.schema.json' }
569
+ '410':
570
+ description: Token expired.
571
+ content:
572
+ application/json:
573
+ schema: { $ref: '#/components/schemas/Error' }
574
+ post:
575
+ tags: [hitl]
576
+ summary: Resolve an interrupt via signed token (asynchronous callback).
577
+ operationId: resolveInterruptByToken
578
+ security: [] # token is the auth
579
+ parameters:
580
+ - $ref: '#/components/parameters/IdempotencyKey'
581
+ requestBody:
582
+ required: true
583
+ content:
584
+ application/json:
585
+ schema:
586
+ type: object
587
+ required: [resumeValue]
588
+ properties:
589
+ resumeValue: {}
590
+ additionalProperties: false
591
+ responses:
592
+ '200':
593
+ description: Resolution accepted.
594
+ content:
595
+ application/json:
596
+ schema: { type: object }
597
+ '410':
598
+ description: Token expired.
599
+ content:
600
+ application/json:
601
+ schema: { $ref: '#/components/schemas/Error' }
602
+
603
+ # ── Artifacts ───────────────────────────────────────────────────────────
604
+ /v1/runs/{runId}/artifacts/{artifactId}:
605
+ get:
606
+ tags: [artifacts]
607
+ summary: Read a run-produced artifact.
608
+ operationId: getArtifact
609
+ parameters:
610
+ - $ref: '#/components/parameters/RunId'
611
+ - in: path
612
+ name: artifactId
613
+ required: true
614
+ schema: { type: string, minLength: 1 }
615
+ responses:
616
+ '200':
617
+ description: Artifact payload.
618
+ content:
619
+ application/json:
620
+ schema:
621
+ type: object
622
+ description: Implementation-defined artifact shape.
623
+ '401': { $ref: '#/components/responses/Unauthenticated' }
624
+ '403': { $ref: '#/components/responses/Forbidden' }
625
+ '404': { $ref: '#/components/responses/NotFound' }
626
+
627
+ # ── Webhooks ────────────────────────────────────────────────────────────
628
+ /v1/webhooks:
629
+ post:
630
+ tags: [webhooks]
631
+ summary: Register a webhook subscription.
632
+ operationId: registerWebhook
633
+ parameters:
634
+ - $ref: '#/components/parameters/IdempotencyKey'
635
+ requestBody:
636
+ required: true
637
+ content:
638
+ application/json:
639
+ schema:
640
+ type: object
641
+ required: [url, events]
642
+ properties:
643
+ url: { type: string, format: uri }
644
+ events:
645
+ type: array
646
+ items: { type: string }
647
+ description: Event types to subscribe to (see `run-event.schema.json` enum).
648
+ secret:
649
+ type: string
650
+ description: Server signs payloads with this secret using HMAC-SHA256.
651
+ tags:
652
+ type: array
653
+ items: { type: string }
654
+ description: Filter to runs carrying these tags (see `run-options.md`).
655
+ additionalProperties: false
656
+ responses:
657
+ '201':
658
+ description: Webhook registered.
659
+ content:
660
+ application/json:
661
+ schema:
662
+ type: object
663
+ required: [webhookId]
664
+ properties:
665
+ webhookId: { type: string }
666
+ '400': { $ref: '#/components/responses/ValidationError' }
667
+ '401': { $ref: '#/components/responses/Unauthenticated' }
668
+ '403': { $ref: '#/components/responses/Forbidden' }
669
+
670
+ /v1/webhooks/{webhookId}:
671
+ delete:
672
+ tags: [webhooks]
673
+ summary: Unregister a webhook.
674
+ operationId: unregisterWebhook
675
+ parameters:
676
+ - in: path
677
+ name: webhookId
678
+ required: true
679
+ schema: { type: string, minLength: 1 }
680
+ responses:
681
+ '204':
682
+ description: Unregistered.
683
+ '401': { $ref: '#/components/responses/Unauthenticated' }
684
+ '403': { $ref: '#/components/responses/Forbidden' }
685
+ '404': { $ref: '#/components/responses/NotFound' }
686
+
687
+ # ─────────────────────────────────────────────────────────────────────────────
688
+ # COMPONENTS
689
+ # ─────────────────────────────────────────────────────────────────────────────
690
+ components:
691
+
692
+ securitySchemes:
693
+ ApiKeyAuth:
694
+ type: http
695
+ scheme: bearer
696
+ bearerFormat: API key
697
+ description: |
698
+ openwop API key. Format implementation-defined; reference impl uses `hk_`/`hk_test_` prefixes.
699
+ Each key carries one or more scopes from the canonical vocabulary
700
+ (`manifest:read`, `runs:create`, `runs:read`, `runs:cancel`,
701
+ `artifacts:read`, `webhooks:manage`, `approvals:respond`).
702
+ See `auth.md`.
703
+
704
+ parameters:
705
+ WorkflowId:
706
+ in: path
707
+ name: workflowId
708
+ required: true
709
+ schema: { type: string, minLength: 1, maxLength: 128 }
710
+
711
+ RunId:
712
+ in: path
713
+ name: runId
714
+ required: true
715
+ schema: { type: string, minLength: 1, maxLength: 128 }
716
+
717
+ IdempotencyKey:
718
+ in: header
719
+ name: Idempotency-Key
720
+ required: false
721
+ schema:
722
+ type: string
723
+ maxLength: 255
724
+ description: |
725
+ Per-mutation idempotency token (see `idempotency.md` Layer 1).
726
+ Server caches `(tenantId, endpoint, key)` → response for ≥24h.
727
+ Duplicate requests return the cached response with header
728
+ `openwop-Idempotent-Replay: true`.
729
+
730
+ responses:
731
+ Unauthenticated:
732
+ description: Missing or invalid credential.
733
+ content:
734
+ application/json:
735
+ schema: { $ref: '#/components/schemas/Error' }
736
+
737
+ Forbidden:
738
+ description: Credential valid but lacks required scope or fails resource binding.
739
+ content:
740
+ application/json:
741
+ schema: { $ref: '#/components/schemas/Error' }
742
+
743
+ NotFound:
744
+ description: Resource doesn't exist or caller can't see it (do not leak existence).
745
+ content:
746
+ application/json:
747
+ schema: { $ref: '#/components/schemas/Error' }
748
+
749
+ ValidationError:
750
+ description: Request body or parameters malformed.
751
+ content:
752
+ application/json:
753
+ schema: { $ref: '#/components/schemas/Error' }
754
+
755
+ RateLimited:
756
+ description: Too many requests.
757
+ headers:
758
+ Retry-After:
759
+ schema: { type: integer }
760
+ content:
761
+ application/json:
762
+ schema: { $ref: '#/components/schemas/Error' }
763
+
764
+ schemas:
765
+
766
+ # Hoisted to first-class JSON Schemas in ../schemas/ so the SDK and
767
+ # conformance suite can validate against the same source. The
768
+ # in-line aliases below pull them via $ref so existing
769
+ # `#/components/schemas/Error` references keep working.
770
+
771
+ Error:
772
+ $ref: '../schemas/error-envelope.schema.json'
773
+
774
+ Capabilities:
775
+ $ref: '../schemas/capabilities.schema.json'
776
+
777
+ RunSnapshot:
778
+ $ref: '../schemas/run-snapshot.schema.json'
779
+
780
+ RunClaimConflict:
781
+ description: |
782
+ Specialization of the canonical `ErrorEnvelope` shape for
783
+ `run_already_active`. Conflict metadata lives under `details`
784
+ so the top-level error shape remains `{error, message, details?}`.
785
+ allOf:
786
+ - $ref: '#/components/schemas/Error'
787
+ - type: object
788
+ required: [error, message, details]
789
+ properties:
790
+ error:
791
+ type: string
792
+ enum: [run_already_active]
793
+ message: { type: string }
794
+ details:
795
+ type: object
796
+ required: [activeRunId, activeHost]
797
+ properties:
798
+ activeRunId: { type: string }
799
+ activeHost:
800
+ type: string
801
+ enum: [browser, cloud]
802
+ retryAfter:
803
+ type: integer
804
+ description: 'Seconds. Mirrors the `Retry-After` header.'
805
+
806
+ UnsupportedStreamMode:
807
+ description: |
808
+ Specialization of the canonical `ErrorEnvelope` shape (see
809
+ `error-envelope.schema.json`) for the `unsupported_stream_mode`
810
+ case. The `supported` array lives in `details` per the canonical
811
+ envelope's contextual-data slot, NOT at the top level. SDK
812
+ consumers using a generic ErrorEnvelope parser will find the
813
+ list under `details.supported` regardless of which validator
814
+ fired.
815
+ allOf:
816
+ - $ref: '#/components/schemas/Error'
817
+ - type: object
818
+ required: [error, message, details]
819
+ properties:
820
+ error:
821
+ type: string
822
+ enum: [unsupported_stream_mode]
823
+ message: { type: string }
824
+ details:
825
+ type: object
826
+ required: [supported]
827
+ properties:
828
+ supported:
829
+ type: array
830
+ items: { type: string, enum: [values, updates, messages, debug] }