@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
package/fixtures.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# openwop Conformance Suite — Fixture Workflow Contract
|
|
2
|
+
|
|
3
|
+
> **Status: FINAL v1 (2026-05-10).** Defines the standardized fixture workflows every OpenWOP-compliant server MUST seed before the conformance suite can exercise run-lifecycle, idempotency, stream-mode, interrupt, and replay scenarios. Stable surface for external review. Keywords MUST, SHOULD, MAY follow [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). Status legend per `../spec/v1/auth.md`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
Run-lifecycle conformance tests need a stable target — a workflow whose `workflowId`, expected events, and terminal status are agreed in advance. Without this, every implementation defines its own test workflows and the conformance suite can't run cross-implementation.
|
|
10
|
+
|
|
11
|
+
This document defines a small set of fixture workflows whose canonical definitions live alongside the conformance suite (`fixtures/*.json`). An OpenWOP-compliant server MUST seed these fixtures into its workflow store before running the conformance suite against itself.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Seeding contract
|
|
16
|
+
|
|
17
|
+
An OpenWOP-compliant server MUST:
|
|
18
|
+
|
|
19
|
+
1. Accept the canonical JSON fixture definitions in `fixtures/*.json` verbatim (they validate against `../schemas/workflow-definition.schema.json`).
|
|
20
|
+
2. Persist each fixture under its declared `id` so that subsequent `GET /v1/workflows/{id}` returns the seeded definition.
|
|
21
|
+
3. Treat seeding as idempotent — running the seeder repeatedly MUST NOT produce duplicate runs, error states, or version drift.
|
|
22
|
+
4. Expose the seeded fixtures to runs created with the conformance suite's API key.
|
|
23
|
+
|
|
24
|
+
How a server seeds is implementation-specific. Servers MAY:
|
|
25
|
+
|
|
26
|
+
- Auto-seed at startup when `OPENWOP_CONFORMANCE_SEED=true` env var is set.
|
|
27
|
+
- Provide a CLI command (e.g., `openwop-server seed --conformance`).
|
|
28
|
+
- Document a manual upload procedure.
|
|
29
|
+
|
|
30
|
+
Servers MUST NOT require fixtures to be re-uploaded on every conformance run — the suite assumes they are already present.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Fixture catalog
|
|
35
|
+
|
|
36
|
+
All fixtures MUST advertise:
|
|
37
|
+
|
|
38
|
+
- **`workflowId`** — exact string clients use to start runs
|
|
39
|
+
- **Trigger** — must be `manual` so the conformance suite can call `POST /v1/runs` without channel-specific setup
|
|
40
|
+
- **Inputs** — schema declared via `variables[]`
|
|
41
|
+
- **Expected behavior** — terminal status, expected event types, timing bounds
|
|
42
|
+
|
|
43
|
+
| Fixture | `workflowId` | Purpose | Terminal status | Bounded duration |
|
|
44
|
+
|---|---|---|---|---|
|
|
45
|
+
| Noop | `conformance-noop` | Cheapest possible run-lifecycle test | `completed` | ≤ 5s |
|
|
46
|
+
| Identity | `conformance-identity` | Verifies input/output passthrough | `completed` | ≤ 5s |
|
|
47
|
+
| Delay | `conformance-delay` | Verifies poll/SSE behavior over time | `completed` | ≤ 30s (input-controlled) |
|
|
48
|
+
| Failure | `conformance-failure` | Verifies error-event surface | `failed` | ≤ 5s |
|
|
49
|
+
| Approval | `conformance-approval` | Verifies HITL approval interrupt + resume | `completed` after resolve | unbounded (suspends) |
|
|
50
|
+
| Clarification | `conformance-clarification` | Verifies HITL clarification interrupt + resume | `completed` after resolve | unbounded (suspends) |
|
|
51
|
+
| Multi-node | `conformance-multi-node` | Verifies edge ordering + per-node events | `completed` | ≤ 10s |
|
|
52
|
+
| Idempotent | `conformance-idempotent` | Verifies `Idempotency-Key` cache | `completed` | ≤ 5s |
|
|
53
|
+
| Cancellable | `conformance-cancellable` | Verifies `:cancel` endpoint mid-run | `cancelled` after cancel | ≤ 60s (input-controlled) |
|
|
54
|
+
| Capability Missing | `conformance-capability-missing` | Verifies dispatch refusal on unsatisfied `requires` | `failed` (`error.code='capability_not_provided'`) | ≤ 5s |
|
|
55
|
+
| Dispatch Loop | `conformance-dispatch-loop` | Verifies `core.dispatch` loop mechanism | `completed` | ≤ 30s |
|
|
56
|
+
| Interrupt — Quorum | `conformance-interrupt-quorum` | Verifies `openwop-interrupt-quorum` profile (multi-approver, majority rejection) | `completed` after 3 accepts, `failed` after quorum reject | unbounded (suspends) |
|
|
57
|
+
| Interrupt — External Event | `conformance-interrupt-external-event` | Verifies `openwop-interrupt-external-event` profile (correlation-matched callback) | `completed` after matching POST, `failed` on timeout | ≤ 60s (timeoutMs configured) |
|
|
58
|
+
| Interrupt — Auth Required | `conformance-interrupt-auth-required` | Verifies `openwop-interrupt-auth-required` profile (bearer-token resume only) | `completed` after bearer resolve | unbounded (suspends) |
|
|
59
|
+
| Interrupt — Parent/Child Cancel | `conformance-interrupt-parent-child-cancel` + `conformance-interrupt-parent-child-cancel-child` | Verifies `openwop-interrupt-parent-child` cancel cascade | `cancelled` (both runs) | ≤ 30s |
|
|
60
|
+
| Agent Identity | `conformance-agent-identity` | Phase 1 — `RunSnapshot.agent` / `runOrchestrator` AgentRef wire-shape | `completed` | ≤ 10s |
|
|
61
|
+
| Agent Reasoning | `conformance-agent-reasoning` | Phase 1 — `agent.*` event family emission + `callId` pairing | `completed` | ≤ 15s |
|
|
62
|
+
| Agent Low-Confidence | `conformance-agent-low-confidence` | Phase 1 / CP-1 — confidence < threshold suspends with `node.suspended { reason: 'low-confidence' }` | `waiting-approval` (suspends) | unbounded (suspends) |
|
|
63
|
+
| Message Reducer | `conformance-message-reducer` | Phase 1 — `message` reducer idempotency on duplicate `messageId` | `completed` | ≤ 10s |
|
|
64
|
+
| Agent Pack Install | `conformance-agent-pack-install` | Phase 2 — pack `agents[]` surface as AgentManifest at `GET /v1/packs` | `completed` | ≤ 5s |
|
|
65
|
+
| Agent Pack Export | `conformance-agent-pack-export` | Phase 2 — workspace agents project to AgentManifest at `GET /v1/packs/export` | `completed` | ≤ 5s |
|
|
66
|
+
| Agent Pack Provenance | `conformance-agent-pack-provenance` | Phase 2 — `sourceManifestId` provenance round-trip | `completed` | ≤ 10s |
|
|
67
|
+
| Agent Memory Round-Trip | `conformance-agent-memory-roundtrip` | Phase 3 — `MemoryAdapter.list/get` write → read | `completed` | ≤ 15s |
|
|
68
|
+
| Agent Memory Cross-Tenant | `conformance-agent-memory-cross-tenant` | Phase 3 / CTI-1 — cross-tenant probe MUST return `[]` / `null` | `completed` | ≤ 10s |
|
|
69
|
+
| Agent Memory Redaction | `conformance-agent-memory-redaction` | Phase 3 / SR-1 — BYOK plaintext surfaces as `[REDACTED:<id>]` on read | `completed` | ≤ 15s |
|
|
70
|
+
| Agent Memory TTL | `conformance-agent-memory-ttl` | Phase 3 — `expiresAt` excludes expired entries from list/get | `completed` | ≤ 10s |
|
|
71
|
+
| Conversation Lifecycle | `conformance-conversation-lifecycle` | Phase 4 — open → exchange → close event ordering | `completed` | ≤ 20s |
|
|
72
|
+
| Conversation vs Clarification | `conformance-conversation-vs-clarification` | Phase 4 — conversation suspend emits `conversation.*`, NOT `clarification.*` | `completed` | ≤ 15s |
|
|
73
|
+
| Conversation Replay | `conformance-conversation-replay` | Phase 4 — `:fork` preserves conversation channel projection | `completed` | ≤ 30s |
|
|
74
|
+
| Conversation Capability Negotiation | `conformance-conversation-capability-negotiation` | Phase 4 — INVERTED gate: hosts without `conversationPrimitive: true` MUST refuse | `failed` / refusal | ≤ 5s |
|
|
75
|
+
| Orchestrator Dispatch | `conformance-orchestrator-dispatch` | Phase 5 — supervisor → `next-worker` → dispatch round-trip | `completed` | ≤ 60s |
|
|
76
|
+
| Orchestrator Terminate | `conformance-orchestrator-terminate` | Phase 5 / CO-3 — terminate decision is final | `completed` | ≤ 30s |
|
|
77
|
+
| Orchestrator Low-Confidence | `conformance-orchestrator-low-confidence` | Phase 5 / CP-1 — supervisor low-confidence suspend | `waiting-approval` (suspends) | unbounded (suspends) |
|
|
78
|
+
| MCP Tool Roundtrip | `conformance-mcp-tool-roundtrip` | Track 6 — host invokes a tool on the conformance suite's synthetic MCP server; trust-boundary visibility in the event log | `completed` | ≤ 30s |
|
|
79
|
+
| A2A Task Roundtrip | `conformance-a2a-task-roundtrip` | Track 6 — host consumes the conformance suite's synthetic A2A peer; covers drift points #3 (`AUTH_REQUIRED`) and #4 (`REJECTED`) | `failed` or `waiting-input` (per `driftScenario` input) | ≤ 30s |
|
|
80
|
+
| WASM Pack Roundtrip | `conformance-wasm-pack-roundtrip` | RFC 0008 — invokes `vendor.openwop.rust-hello.greet` (loaded WASM pack); exercises required exports + at least one import | `completed` | ≤ 10s |
|
|
81
|
+
|
|
82
|
+
The `messages`-mode stream fixture (AI token streaming) is covered by the deterministic mock-provider surface in `spec/v1/run-options.md`. Hosts that do not advertise `Capabilities.testing.mockProviders` skip-equivalent on those scenarios.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Per-fixture contracts
|
|
87
|
+
|
|
88
|
+
### `conformance-noop`
|
|
89
|
+
|
|
90
|
+
- **Purpose**: cheapest run-lifecycle test. Used by the conformance suite's `runs.test.ts` to verify create/read/terminal-event/cleanup work end-to-end.
|
|
91
|
+
- **Inputs**: none.
|
|
92
|
+
- **Expected events** (in order, `updates` mode):
|
|
93
|
+
1. `run.started`
|
|
94
|
+
2. `node.completed` (single node, typeId `core.noop`)
|
|
95
|
+
3. `run.completed`
|
|
96
|
+
- **Terminal status**: `completed`.
|
|
97
|
+
- **Duration bound**: server MUST reach terminal state within 5s of accepting the run.
|
|
98
|
+
|
|
99
|
+
### `conformance-identity`
|
|
100
|
+
|
|
101
|
+
- **Purpose**: verify input → output passthrough.
|
|
102
|
+
- **Inputs**:
|
|
103
|
+
- `payload` (object, required) — arbitrary JSON.
|
|
104
|
+
- **Expected behavior**: terminal `RunSnapshot.variables.payload` MUST deep-equal the input `payload`.
|
|
105
|
+
- **Terminal status**: `completed`.
|
|
106
|
+
|
|
107
|
+
### `conformance-delay`
|
|
108
|
+
|
|
109
|
+
- **Purpose**: verify the engine handles in-flight runs (status transitions over time, SSE keep-alives, poll fallback).
|
|
110
|
+
- **Inputs**:
|
|
111
|
+
- `delayMs` (integer, required, 0 ≤ value ≤ 30000) — server MUST sleep for this duration before completing.
|
|
112
|
+
- **Expected behavior**: `GET /v1/runs/{runId}` MUST return `status: "running"` while the delay is in flight; `status: "completed"` after.
|
|
113
|
+
- **Terminal status**: `completed`.
|
|
114
|
+
|
|
115
|
+
### `conformance-failure`
|
|
116
|
+
|
|
117
|
+
- **Purpose**: verify the failure path (error event shape, terminal `failed` state).
|
|
118
|
+
- **Inputs**: none.
|
|
119
|
+
- **Expected events**:
|
|
120
|
+
1. `run.started`
|
|
121
|
+
2. `node.failed`
|
|
122
|
+
3. `run.failed`
|
|
123
|
+
- **Terminal `RunSnapshot.error`**: MUST be a `{code, message}` object with both fields as strings.
|
|
124
|
+
- **Terminal status**: `failed`.
|
|
125
|
+
|
|
126
|
+
### `conformance-approval`
|
|
127
|
+
|
|
128
|
+
- **Purpose**: verify HITL approval interrupt + resume.
|
|
129
|
+
- **Inputs**: none.
|
|
130
|
+
- **Behavior**:
|
|
131
|
+
1. Run starts and reaches an `approvalGate` node that calls `ctx.interrupt({kind: 'approval', ...})`.
|
|
132
|
+
2. Server emits `interrupt.requested` (and SHOULD also emit `approval.requested` for back-compat).
|
|
133
|
+
3. Run status MUST be `waiting-approval`.
|
|
134
|
+
4. After client POSTs `{action: 'accept'}` to `/v1/runs/{runId}/interrupt`, server emits `approval.received` and resumes.
|
|
135
|
+
5. Run reaches `completed`.
|
|
136
|
+
- **Terminal status (after accept)**: `completed`.
|
|
137
|
+
- **Resolve schema**: `{action: "accept" | "reject"}`. Server MUST reject any other shape with 400.
|
|
138
|
+
|
|
139
|
+
### `conformance-clarification`
|
|
140
|
+
|
|
141
|
+
- **Purpose**: verify HITL clarification interrupt + resume.
|
|
142
|
+
- **Inputs**: none.
|
|
143
|
+
- **Behavior**:
|
|
144
|
+
1. Run starts and reaches a `clarificationGate` node.
|
|
145
|
+
2. Server emits `clarification.requested` carrying `questions: [{id: "q1", question: "What is your favorite color?"}]`.
|
|
146
|
+
3. After client POSTs `{answers: {q1: "blue"}}`, server emits `clarification.resolved`.
|
|
147
|
+
4. Run reaches `completed`.
|
|
148
|
+
- **Terminal status (after resolve)**: `completed`.
|
|
149
|
+
|
|
150
|
+
### `conformance-multi-node`
|
|
151
|
+
|
|
152
|
+
- **Purpose**: verify multi-node DAG ordering + per-node events.
|
|
153
|
+
- **Inputs**: none.
|
|
154
|
+
- **Topology**: three nodes A → B → C, all `core.noop`.
|
|
155
|
+
- **Expected behavior**: `node.completed` events MUST arrive in the order A, B, C (assertable via `event.sequence` ordering).
|
|
156
|
+
- **Terminal status**: `completed`.
|
|
157
|
+
|
|
158
|
+
### `conformance-idempotent`
|
|
159
|
+
|
|
160
|
+
- **Purpose**: verify `Idempotency-Key` cache (rest-endpoints.md §6 + `idempotency.md`).
|
|
161
|
+
- **Inputs**:
|
|
162
|
+
- `nonce` (string, required) — caller-supplied; server MUST NOT use this for any side effect, only for de-duplication semantics tests.
|
|
163
|
+
- **Expected behavior**:
|
|
164
|
+
- `POST /v1/runs` with the same `Idempotency-Key` and same body twice → second response MUST replay the first (`openwop-Idempotent-Replay: true` header) and MUST NOT create a second run.
|
|
165
|
+
- Same `Idempotency-Key` with a different body → 409.
|
|
166
|
+
- **Terminal status**: `completed`.
|
|
167
|
+
|
|
168
|
+
### `conformance-cancellable`
|
|
169
|
+
|
|
170
|
+
- **Purpose**: verify `:cancel` mid-run.
|
|
171
|
+
- **Inputs**:
|
|
172
|
+
- `delayMs` (integer, required, 1 ≤ value ≤ 60000) — wait long enough for the conformance test to issue cancel.
|
|
173
|
+
- **Expected behavior**:
|
|
174
|
+
1. Run reaches `running`.
|
|
175
|
+
2. Client posts `POST /v1/runs/{runId}:cancel`.
|
|
176
|
+
3. Server emits `run.cancelled` within 5s.
|
|
177
|
+
4. Subsequent `GET /v1/runs/{runId}` MUST return `status: "cancelled"`.
|
|
178
|
+
- **Terminal status**: `cancelled`.
|
|
179
|
+
|
|
180
|
+
### `conformance-capability-missing`
|
|
181
|
+
|
|
182
|
+
- **Purpose**: verify runtime-capability dispatch refusal — when a node declares `requires: ['<unsupported>']`, the engine MUST refuse to call its executor and terminate the run with the structured error.
|
|
183
|
+
- **Inputs**: none.
|
|
184
|
+
- **Topology**: single `conformance.requiresMissing` node. The fixture node declares `requires: ['conformance.never-provided']` — a sentinel capability id reserved by spec; production hosts MUST NOT register a provider for it.
|
|
185
|
+
- **Expected behavior**:
|
|
186
|
+
1. Run starts and the engine reaches the single node.
|
|
187
|
+
2. Pre-dispatch capability check fails because no host provider satisfies `conformance.never-provided`.
|
|
188
|
+
3. Server emits `node.failed` with the underlying error, then `run.failed`.
|
|
189
|
+
4. Run reaches terminal `failed`.
|
|
190
|
+
- **Terminal `RunSnapshot.error`**: `error.code === 'capability_not_provided'`. `error.message` MUST name the missing capability id (`conformance.never-provided`) verbatim so operators can act without grepping logs.
|
|
191
|
+
- **Terminal status**: `failed`.
|
|
192
|
+
- **Server prerequisites**: the host MUST have registered the `conformance.requiresMissing` NodeModule before seeding the fixture. Hosts that don't register this fixture node MAY mark this scenario optional in their conformance manifest.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### `conformance-dispatch-loop`
|
|
197
|
+
|
|
198
|
+
- **Purpose**: verify the `core.dispatch` loop mechanism (Phase 6 / RFC 0007). Tests that `runOrchestrator.decided` events correctly trigger delegation to child runs and loop termination.
|
|
199
|
+
- **Topology**: two nodes: `core.orchestrator.supervisor` and `core.dispatch`, looping back to each other.
|
|
200
|
+
- **Inputs**: none.
|
|
201
|
+
- **Conformance test driver**:
|
|
202
|
+
1. POST `/v1/runs` with `{workflowId: "conformance-dispatch-loop"}`.
|
|
203
|
+
2. The orchestrator node must emit `runOrchestrator.decided` with `next-worker` then `terminate`.
|
|
204
|
+
3. Poll until terminal.
|
|
205
|
+
4. **Assert** terminal status is `completed`.
|
|
206
|
+
5. **Assert** event log contains `next-worker` and `terminate` decisions.
|
|
207
|
+
- **Terminal status**: `completed`.
|
|
208
|
+
- **Server prerequisites**: The host MUST advertise `capabilities.agents.dispatch: true`. Since the test does not mock the orchestrator prompt logic, the host's `core.orchestrator.supervisor` implementation (or fixture override) must deterministically emit a `next-worker` followed by `terminate`.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## `conformance-version-fold` (closes F5)
|
|
213
|
+
|
|
214
|
+
- **Purpose**: verify forward-compat fold-best-effort tolerance across the spec's engine-version cross-version interop matrix (`version-negotiation.md` §Cross-version interop matrix). Uses the test-keys-only `X-Force-Engine-Version` header to drive the same workflow at three different engine versions from a single deployed server — no multi-version fleet needed.
|
|
215
|
+
- **Fixture topology**: a single `core.noop` node. The workflow itself is trivial; the test exercises the server's READ path (projection, event-log fold) under each forced engine version.
|
|
216
|
+
- **Inputs**: none.
|
|
217
|
+
- **Conformance test driver**:
|
|
218
|
+
1. Read the server's `Capabilities.testing.forceEngineVersionRange = { min, max }`.
|
|
219
|
+
2. For each version `v` in `[min, current, max]` (deduped):
|
|
220
|
+
- POST `/v1/runs` with body `{workflowId: "conformance-version-fold"}` AND header `X-Force-Engine-Version: v`. Use a test API key.
|
|
221
|
+
- Poll until terminal.
|
|
222
|
+
- **Assert** terminal status is `completed`.
|
|
223
|
+
- **Assert** `GET /v1/runs/{runId}` returns a valid `RunSnapshot` (the projection tolerates the version mismatch via fold-best-effort).
|
|
224
|
+
- **Assert** `GET /v1/runs/{runId}/events/poll?lastSequence=0&timeout=1` returns a non-empty `events[]` array (event log is readable).
|
|
225
|
+
- **Negative paths**:
|
|
226
|
+
- Same fixture with a production API key returns `403 force_engine_version_forbidden`.
|
|
227
|
+
- Same fixture with `X-Force-Engine-Version: <out-of-range>` returns `400 unsupported_force_engine_version`.
|
|
228
|
+
- **Cross-link**: see `version-negotiation.md` §Conformance via X-Force-Engine-Version for the underlying matrix. The fixture is intentionally minimal (single noop) so the test isolates version-fold tolerance from any node-specific behavior.
|
|
229
|
+
|
|
230
|
+
This fixture closes F5 without requiring any new server-side test infrastructure beyond the `X-Force-Engine-Version` header. Servers that don't advertise `forceEngineVersionRange` in Capabilities can mark this fixture optional in their conformance manifest.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## `conformance-stream-text` (closes F1)
|
|
235
|
+
|
|
236
|
+
- **Purpose**: verify the `messages` SSE stream mode end-to-end through a deterministic AI mock. Without a mock provider, conformance suites can't exercise streaming AI without burning real API budget; with one, the test is fully reproducible.
|
|
237
|
+
- **Fixture topology**: a single `core.ai.callPrompt` (or similar AI-bearing typeId) node. The node's actual prompt content is irrelevant — the conformance driver intercepts the AI dispatch via `configurable.mockProvider`.
|
|
238
|
+
- **Inputs**: none.
|
|
239
|
+
- **Conformance test driver**:
|
|
240
|
+
1. POST `/v1/runs` with body:
|
|
241
|
+
```jsonc
|
|
242
|
+
{
|
|
243
|
+
"workflowId": "conformance-stream-text",
|
|
244
|
+
"configurable": {
|
|
245
|
+
"mockProvider": {
|
|
246
|
+
"id": "stream-text",
|
|
247
|
+
"config": {
|
|
248
|
+
"tokens": ["Hello", " ", "world", "!"],
|
|
249
|
+
"delayMsPerToken": 10,
|
|
250
|
+
"finishReason": "stop",
|
|
251
|
+
"usage": { "promptTokens": 5, "completionTokens": 4, "totalTokens": 9 }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
Use a test API key (server returns 403 on production keys per `run-options.md` §Authorization).
|
|
258
|
+
2. Subscribe to `/v1/runs/{runId}/events?streamMode=messages`.
|
|
259
|
+
3. **Assert** chunk arrival order: `["Hello", " ", "world", "!"]` — same order as `tokens`.
|
|
260
|
+
4. **Assert** the final chunk has `isLast: true`, `meta.finishReason === "stop"`, `meta.usage.completionTokens === 4`.
|
|
261
|
+
5. **Assert** SSE stream closes on terminal — server-closed, not timeout.
|
|
262
|
+
6. **Assert** terminal status is `completed`.
|
|
263
|
+
- **Negative paths**:
|
|
264
|
+
- Same fixture with a production API key returns `403 mock_provider_forbidden`.
|
|
265
|
+
- Same fixture with `mockProvider.id: "does-not-exist"` returns `400 unsupported_mock_provider`.
|
|
266
|
+
- **Replay assertion**: forking the run with `mode: replay` produces a byte-identical event log (mock providers are inherently replay-deterministic — no Layer-2 invocation log needed).
|
|
267
|
+
|
|
268
|
+
This fixture is the canonical `messages`-mode test. Once it's wired into the conformance suite, the suite gains 5+ new server-required scenarios. Servers that don't yet support the mock-provider extension can mark this fixture optional in their conformance manifest until they do.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## `conformance-subworkflow-parent` + `conformance-subworkflow-child` (closes F2)
|
|
273
|
+
|
|
274
|
+
> **Status: included in the v1.0 conformance baseline.** The canonical sub-workflow node typeId is `core.subWorkflow` per `node-packs.md` §"Reserved Core OpenWOP typeIds." A host MUST register that node module before advertising the sub-workflow fixture IDs.
|
|
275
|
+
|
|
276
|
+
- **Purpose**: verify child-run lifecycle when a parent workflow invokes a child via `core.subWorkflow`. Specifically: child `run.started` and `run.completed` events fire; child outputs flow back into the parent's variables; cancelling the parent cancels the child.
|
|
277
|
+
- **Topology**:
|
|
278
|
+
- **Child (`conformance-subworkflow-child`)**: single `core.identity` node that echoes its `payload` input. Reuses the existing identity contract.
|
|
279
|
+
- **Parent (`conformance-subworkflow-parent`)**: one `core.subWorkflow` node configured with `workflowId = "conformance-subworkflow-child"` and `inputs = {payload: {fromParent: true}}`. The node's output port `result` carries the child's terminal `RunSnapshot.variables.payload`.
|
|
280
|
+
- **Inputs (parent)**: none.
|
|
281
|
+
- **Conformance test driver assertions**:
|
|
282
|
+
1. Parent run reaches terminal `completed`.
|
|
283
|
+
2. Two distinct `runId`s appear in the event log query — one for the parent, one for the child.
|
|
284
|
+
3. Parent's `RunSnapshot.variables` contains `result.payload === {fromParent: true}` (the round-trip from invoke + identity echo).
|
|
285
|
+
4. Cancelling the parent mid-flight cancels the child (`run.cancelled` events on both within 5s).
|
|
286
|
+
|
|
287
|
+
Spec design choices:
|
|
288
|
+
|
|
289
|
+
- Canonical sub-workflow node typeId — `core.subWorkflow`. It uses the same `core.<conceptName>` flat-camelCase pattern as the other reserved core nodes (`core.start`, `core.delay`, `core.loop`, `core.parallel`, `core.interrupt`, etc.). Sub-workflow invocation is a workflow primitive expressed as a node — same shape as `core.interrupt`.
|
|
290
|
+
- Child run's RunSnapshot is owned by the same workspace/tenant as the parent — sub-workflow doesn't cross authorization boundaries.
|
|
291
|
+
- Child's events are NOT inlined into the parent's event log; they live in the child's own subcollection. The parent emits a single `node.started` / `node.completed` pair around the invoke node, not all of the child's events.
|
|
292
|
+
- Cancellation cascade: parent cancel MUST cancel the child within 5s (suite's existing cancellation timing bound).
|
|
293
|
+
- Trace correlation (already specced — see `observability.md` §Sub-workflow attributes / O2): child's `openwop.run` is a parent-child span of the invoke-node's `openwop.node.<typeId>`, with required attributes `openwop.parent.{run_id, workflow_id, node_id}`. Conformance assertion: the child run's first event-log entry's `engineVersion` field SHOULD be on the same trace as the parent's invoke-node — verifiable post-run via the OTel exporter if available.
|
|
294
|
+
|
|
295
|
+
The fixture JSONs and matching `subworkflow.test.ts` are part of the current conformance suite.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## `conformance-cap-breach` (closes F4 spec-side; runtime impl pending)
|
|
300
|
+
|
|
301
|
+
> **Status: spec firm — closed via the unified `cap.breached` design in `capabilities.md` §Engine-enforced limits.** Runtime counter implementation does not require an `eventLogSchemaVersion` bump because `cap.breached` already exists with `kind: 'node-executions'` in its enum. Add the fixture JSON when a host exposes the per-run counter fixture.
|
|
302
|
+
|
|
303
|
+
- **Purpose**: verify the `Capabilities.limits.maxNodeExecutions` ceiling clamps `RunOptions.configurable.recursionLimit` at run-start, and that the engine emits `cap.breached` + transitions to `failed` when the per-run counter exceeds the resolved limit.
|
|
304
|
+
- **Topology**: 10 sequential `core.noop` nodes (`a → b → c → … → j`). A run completes naturally if no per-run override is supplied. With `configurable.recursionLimit = 5`, the run MUST trip after the 5th node.
|
|
305
|
+
- **Inputs**: none.
|
|
306
|
+
- **Conformance test driver**:
|
|
307
|
+
1. POST `/v1/runs` with `{workflowId: "conformance-cap-breach", configurable: {recursionLimit: 5}}`.
|
|
308
|
+
2. Server SHOULD validate `recursionLimit ≤ Capabilities.limits.maxNodeExecutions`. If `maxNodeExecutions` is `100` (default), `5` is fine.
|
|
309
|
+
3. Poll until terminal.
|
|
310
|
+
4. **Assert** terminal status is `failed`.
|
|
311
|
+
5. **Assert** `RunSnapshot.error.code === "recursion_limit_exceeded"`.
|
|
312
|
+
6. **Assert** the event log contains a `cap.breached` event with `payload: {kind: "node-executions", limit: 5, observed: 6}` (or whichever `observed > limit` value the engine recorded).
|
|
313
|
+
- **Negative path**: same fixture without the override completes normally (10 `node.completed` events, terminal `completed`).
|
|
314
|
+
|
|
315
|
+
This fixture is unblocked when:
|
|
316
|
+
|
|
317
|
+
1. A host exposes CC-1's hard invariant (per-run `nodeExecutionCount` counter; `RunEvent` schema support for `cap.breached` payload's `kind: "node-executions"` value; run transition to `failed` on exceedance).
|
|
318
|
+
2. The conformance suite's `openwop-conformance` driver gains the matching scenario file `cap-breach.test.ts`.
|
|
319
|
+
|
|
320
|
+
Both items are tracked as v1.x conformance expansion work.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## `conformance-channel-ttl` (closes C3 — channel TTL reducer fold)
|
|
325
|
+
|
|
326
|
+
> **Status: included in the v1.0 conformance baseline.** Server-side `core.channelWrite` support is exercised by `src/scenarios/channel-ttl.test.ts`.
|
|
327
|
+
|
|
328
|
+
- **Purpose**: verify the `append` reducer applies `ttlMs` filter at write time, dropping entries older than the cutoff.
|
|
329
|
+
- **Topology**: 4 sequential `core.channelWrite` nodes targeting channel `events` with `ttlMs: 200`, separated by a `core.delay` of 300ms between writes 3 and 4. The 4th write fires after the TTL window has elapsed.
|
|
330
|
+
- **Inputs**: none. Each node carries a static `value` in its `config` (`a`, `b`, `c`, `d` respectively).
|
|
331
|
+
- **Conformance test driver**:
|
|
332
|
+
1. POST `/v1/runs` with `{workflowId: "conformance-channel-ttl"}`.
|
|
333
|
+
2. Poll until terminal.
|
|
334
|
+
3. **Assert** terminal status is `completed`.
|
|
335
|
+
4. **Assert** `RunSnapshot.variables.events.length === 1` — the 3 priors aged out at the 4th write.
|
|
336
|
+
5. **Assert** `RunSnapshot.variables.events[0].value === "d"` — the surviving entry is the post-delay write.
|
|
337
|
+
6. **Assert** `typeof RunSnapshot.variables.events[0]._ts === "number"` — entries carry numeric write timestamps.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## NodeModule registration
|
|
342
|
+
|
|
343
|
+
The fixtures reference these typeIds:
|
|
344
|
+
|
|
345
|
+
| typeId | Required by | Behavior |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `core.noop` | noop, multi-node, approval, clarification, cancellable | Immediate completion, no output |
|
|
348
|
+
| `core.identity` | identity | Echo `input.payload` to `output.payload` |
|
|
349
|
+
| `core.delay` | delay, cancellable | Sleep `config.delayMs` ms |
|
|
350
|
+
| `core.fail` | failure | Throw with `code: "conformance_test_failure"`, message: "Intentional conformance failure" |
|
|
351
|
+
| `core.approvalGate` | approval | Call `ctx.interrupt({kind: 'approval', ...})` |
|
|
352
|
+
| `core.clarificationGate` | clarification | Call `ctx.interrupt({kind: 'clarification', ...})` |
|
|
353
|
+
| `conformance.requiresMissing` | capability-missing | Declares `requires: ['conformance.never-provided']`; engine MUST refuse dispatch. Opt-in fixture registration is recommended so production deployments don't expose the fixture surface. |
|
|
354
|
+
|
|
355
|
+
An OpenWOP-compliant server's NodeModule registry MUST include implementations for all six core typeIds before seeding fixtures. The `conformance.requiresMissing` fixture node is opt-in — see the row above.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Versioning
|
|
360
|
+
|
|
361
|
+
Each fixture's JSON has its own `version` field. The OpenWOP v1.0 conformance suite targets fixture version 1.0. Fixture spec breaking changes MUST bump the major; the suite MUST refuse to run against an unrecognized fixture version with a clear error message.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## File layout
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
conformance/
|
|
369
|
+
fixtures.md — this file
|
|
370
|
+
fixtures/
|
|
371
|
+
conformance-noop.json
|
|
372
|
+
conformance-identity.json
|
|
373
|
+
conformance-delay.json
|
|
374
|
+
conformance-failure.json
|
|
375
|
+
conformance-approval.json
|
|
376
|
+
conformance-clarification.json
|
|
377
|
+
conformance-multi-node.json
|
|
378
|
+
conformance-idempotent.json
|
|
379
|
+
conformance-cancellable.json
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Each JSON is a valid `WorkflowDefinition` per `../schemas/workflow-definition.schema.json`. Servers MUST treat them as opaque blobs to seed verbatim — do not transform field names or strip fields.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Pack-manifest fixtures
|
|
387
|
+
|
|
388
|
+
The `fixtures/pack-manifests/` sub-directory holds canonical pack manifests used as schema-level proof points (validated server-free against `../schemas/node-pack-manifest.schema.json`). They are NOT seeded into a server — they exist to assert the canonical schema accepts each documented pack-name scope.
|
|
389
|
+
|
|
390
|
+
| Fixture | `name` | Purpose |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| `pack-private-example` | `private.example-host.example-tools` | Asserts the v1.0 pack-name pattern accepts the `private.<host>.*` scope reserved for host-internal registries. |
|
|
393
|
+
|
|
394
|
+
Pack-manifest fixtures are exercised by the server-free `fixtures-valid.test.ts` scenarios — adding one runs the schema validator against it automatically.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## References
|
|
399
|
+
|
|
400
|
+
- `README.md` — conformance suite operator docs
|
|
401
|
+
- `../schemas/workflow-definition.schema.json` — every fixture validates against this
|
|
402
|
+
- `../rest-endpoints.md` — endpoint contracts the fixtures exercise
|
|
403
|
+
- `../interrupt.md` — HITL primitive used by approval + clarification fixtures
|
|
404
|
+
- `../idempotency.md` — semantics the idempotent fixture exercises
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openwop/openwop-conformance",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/openwop/openwop.git",
|
|
8
|
+
"directory": "conformance"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"openwop-conformance": "./dist/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"fixtures",
|
|
19
|
+
"fixtures.md",
|
|
20
|
+
"coverage.md",
|
|
21
|
+
"api",
|
|
22
|
+
"schemas",
|
|
23
|
+
"vitest.config.ts",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"build:cli": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
32
|
+
"cli": "tsx src/cli.ts",
|
|
33
|
+
"prepack": "bash scripts/pack-vendor.sh",
|
|
34
|
+
"postpack": "rm -rf api schemas"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"ajv": "^8.17.0",
|
|
41
|
+
"ajv-formats": "^3.0.1",
|
|
42
|
+
"vitest": "^3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "^5.6.0",
|
|
46
|
+
"@types/node": "^22.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# OpenWOP Spec v1 — JSON Schemas
|
|
2
|
+
|
|
3
|
+
> **Status: FINAL v1 (2026-05-10).** Hand-authored from prose specs. JSON Schema 2020-12. Validate with Ajv2020 (`require('ajv/dist/2020')`), `python-jsonschema`, or any other 2020-12 implementation. Implementations MAY pin to these schemas; servers MUST accept any JSON document that validates against them.
|
|
4
|
+
|
|
5
|
+
| Schema | Source spec | Coverage |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `agent-manifest.schema.json` | `node-packs.md` + agent-pack RFCs | Agent manifest entries distributed alongside node-pack manifests |
|
|
8
|
+
| `agent-ref.schema.json` | `agent-memory.md` + agent-identity RFC | Multi-Agent Shift Phase 1 — slim runtime AgentRef projection carried on `RunSnapshot.agent` / `runOrchestrator`, `WorkflowNode.agent?`, and `agent.*` event payloads |
|
|
9
|
+
| `capabilities.schema.json` | `capabilities.md` | `/.well-known/openwop` response — protocolVersion + supportedEnvelopes + schemaVersions + limits + optional v1 discovery surface |
|
|
10
|
+
| `channel-written-payload.schema.json` | `channels-and-reducers.md` §Channel write event | Payload of the `channel.written` RunEvent — write input + reducer name |
|
|
11
|
+
| `conversation-event.schema.json` | `channels-and-reducers.md` + conversation RFC | Multi-turn conversation event shape for orchestrator-driven HITL flows |
|
|
12
|
+
| `conversation-turn.schema.json` | `channels-and-reducers.md` + conversation RFC | Conversation turn shape for user/agent/system messages |
|
|
13
|
+
| `debug-bundle.schema.json` | `debug-bundle.md` | Portable run diagnostic export from `GET /v1/runs/{runId}/debug-bundle` |
|
|
14
|
+
| `dispatch-config.schema.json` | `node-packs.md` + dispatch RFC | Configuration shape for `core.dispatch` / sub-workflow routing |
|
|
15
|
+
| `error-envelope.schema.json` | `rest-endpoints.md` + `auth.md` | Canonical `{error, message, details?}` shape returned on every non-2xx |
|
|
16
|
+
| `memory-entry.schema.json` | memory-layer RFC | Persisted agent memory entry shape |
|
|
17
|
+
| `memory-list-options.schema.json` | memory-layer RFC | Query options for listing agent memory entries |
|
|
18
|
+
| `node-pack-manifest.schema.json` | `node-packs.md` | Pack manifest (`pack.json`) — name, version, engines, nodes[], runtime, signing |
|
|
19
|
+
| `orchestrator-decision.schema.json` | `node-packs.md` + orchestrator RFC | Decision output shape for orchestrator routing nodes |
|
|
20
|
+
| `run-event-payloads.schema.json` | `run-event.schema.json` §RunEventType | Per-RunEventType payload contracts, indexed by `$defs.<typeId>` for opt-in strict validation |
|
|
21
|
+
| `run-event.schema.json` | `version-negotiation.md` + `RunEventDoc` | Event log envelope + event type enum |
|
|
22
|
+
| `run-options.schema.json` | `run-options.md` | Per-run input overlay (configurable + tags + metadata) on `POST /v1/runs` |
|
|
23
|
+
| `run-orchestrator-decided-event.schema.json` | orchestrator RFC + `observability.md` | Event payload for orchestrator decisions |
|
|
24
|
+
| `run-snapshot.schema.json` | `rest-endpoints.md` §RunSnapshot | Projected run state from `GET /v1/runs/{runId}` |
|
|
25
|
+
| `suspend-request.schema.json` | `interrupt.md` | `InterruptPayload` with 8 `kind` discriminators (approval, clarification, external-event, custom, conversation.start, conversation.exchange, conversation.close, low-confidence) |
|
|
26
|
+
| `workflow-definition.schema.json` | `channels-and-reducers.md` + `node-packs.md` | DAG of nodes + edges + triggers + variables + channels |
|
|
27
|
+
|
|
28
|
+
## Validating against the schemas
|
|
29
|
+
|
|
30
|
+
### TypeScript / Node
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import Ajv2020 from 'ajv/dist/2020';
|
|
34
|
+
import addFormats from 'ajv-formats';
|
|
35
|
+
import schema from './run-event.schema.json';
|
|
36
|
+
|
|
37
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
38
|
+
addFormats(ajv);
|
|
39
|
+
const validate = ajv.compile(schema);
|
|
40
|
+
|
|
41
|
+
if (!validate(myEvent)) {
|
|
42
|
+
console.error(validate.errors);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Python
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import json
|
|
50
|
+
import jsonschema
|
|
51
|
+
|
|
52
|
+
schema = json.load(open('run-event.schema.json'))
|
|
53
|
+
jsonschema.validate(my_event, schema) # raises ValidationError on failure
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Cross-reference
|
|
57
|
+
|
|
58
|
+
- **Conformance test suite (P2-F4)** — black-box tests that fixture-validate against these schemas.
|
|
59
|
+
- **Reference SDKs (P2-F3)** — generate types via `quicktype` or `json-schema-to-typescript`.
|
|
60
|
+
- **OpenAPI 3.1 YAML** — references these schemas via `$ref` instead of inlining.
|
|
61
|
+
|
|
62
|
+
## Open gaps
|
|
63
|
+
|
|
64
|
+
| # | Gap | Owner |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| JS1 | Per-`RunEventType` payload schemas — done (2026-04-26: `run-event-payloads.schema.json` covers all 38 variants in ~15 shape families). Top-level `run-event.schema.json` `payload` stays permissive for forward-compat; consumers MAY pin strict validation via `$defs.<typeId>`. | ✅ |
|
|
67
|
+
| JS2 | `Capabilities` schema — done (2026-04-26: `capabilities.schema.json` lifted from `Capabilities.ts`) | ✅ |
|
|
68
|
+
| JS3 | `RunOptions` schema (configurable + tags + metadata) — done (2026-04-26: `run-options.schema.json` lifted from `run-options.md`) | ✅ |
|
|
69
|
+
| JS4 | Channel-write event payload schema — done (2026-04-26: `channel-written-payload.schema.json` lifted from channels-and-reducers.md §Channel write event) | ✅ |
|
|
70
|
+
| JS5 | Error-envelope schema — done (2026-04-26: `error-envelope.schema.json` hoisted from inline OpenAPI) | ✅ |
|
|
71
|
+
| JS6 | `RunSnapshot` schema — done (2026-04-26: `run-snapshot.schema.json` hoisted from inline OpenAPI) | ✅ |
|
|
72
|
+
|
|
73
|
+
## Versioning
|
|
74
|
+
|
|
75
|
+
Schemas are versioned via `$id` URL (`/spec/v1/`). Breaking changes go to `/spec/v2/`. Non-breaking additions stay on v1 with `$comment` notes documenting added fields.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/agent-manifest.schema.json",
|
|
4
|
+
"title": "AgentManifest",
|
|
5
|
+
"description": "Manifest entry for an agent shipped inside a `pack.json`'s `agents[]` array. Carries everything a host needs to instantiate the agent without reading the rest of the pack tarball — only `systemPromptRef` and the two `*SchemaRef` fields under `handoff` reference external tarball files. See RFC 0003 + `node-packs.md` §`agents[]` extension.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["agentId", "persona", "modelClass"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"agentId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Globally-unique manifest agentId. Pattern matches the namespace tiers from `node-packs.md` §Naming, MINUS the `host:<id>` slim-runtime form (which is reserved for host-internal AgentRefs and is NOT a valid manifest namespace). Convention: `<tier>.<org>.<pack>.<agent>` for vendor/community packs; `core.<name>` for spec-canonical agents.",
|
|
12
|
+
"pattern": "^(core|vendor|community|private|local)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
|
|
13
|
+
"minLength": 3,
|
|
14
|
+
"maxLength": 256
|
|
15
|
+
},
|
|
16
|
+
"persona": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Human-readable agent name (e.g., `\"Workflow Supervisor\"`, `\"Brief Writer\"`). Free-form; spec doesn't constrain values. NOT the `harnessRole` enum used by some hosts for tool-tier filtering.",
|
|
19
|
+
"minLength": 1,
|
|
20
|
+
"maxLength": 200
|
|
21
|
+
},
|
|
22
|
+
"modelClass": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Closed enum from RFC 0002's `AgentRef` schema. Vendor extensions follow the `<vendor>.<class>` convention from `host-extensions.md` (e.g., `openwop.creative-writing`).",
|
|
25
|
+
"enum": [
|
|
26
|
+
"reasoning",
|
|
27
|
+
"writing",
|
|
28
|
+
"coding",
|
|
29
|
+
"research",
|
|
30
|
+
"classification",
|
|
31
|
+
"general"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"systemPrompt": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Inline system-prompt body. Mutually exclusive with `systemPromptRef` — exactly one MUST be present (enforced by the `oneOf` clause below).",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
},
|
|
39
|
+
"systemPromptRef": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "URI-reference to a prompt file inside the pack tarball (e.g., `prompts/supervisor.md`). Mutually exclusive with `systemPrompt`. Useful when the prompt body is large enough that inlining would bloat the manifest.",
|
|
42
|
+
"minLength": 1
|
|
43
|
+
},
|
|
44
|
+
"toolAllowlist": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": { "type": "string", "minLength": 1 },
|
|
47
|
+
"description": "Tool identifiers the agent MAY invoke. Format: `<scope>:<tool-id>` where scope is `openwop:` (Core/protocol tools), `mcp:` (MCP-namespaced tools), or `<vendor>.<host>` for host-extension tools. Hosts MUST enforce this allowlist when dispatching the agent (see RFC 0002 §A14 mapping for the reference-impl `ToolPermissionService.filterTools(harnessRole)` derivation pattern). Hosts MAY treat absence as `[]` (no tools) or as `*` (host-default tool surface) — that policy is host-configured."
|
|
48
|
+
},
|
|
49
|
+
"memoryShape": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"description": "Declares which memory backends the agent reads/writes. Hosts that advertise `capabilities.agents.memoryBackends` MAY filter manifests during install based on these flags. The full memory-shape contract lands in RFC 0004 (Phase 3); RFC 0003 only declares the descriptor field and reserves `longTerm: true` as the redaction-harness trigger.",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"properties": {
|
|
54
|
+
"scratchpad": {
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"description": "Per-task ephemeral notes. Host-managed; evicted on goal completion."
|
|
57
|
+
},
|
|
58
|
+
"conversation": {
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"description": "Per-run dialogue with user / orchestrator. Lives within the run's conversation channel (see RFC 0005, deferred to Phase 4)."
|
|
61
|
+
},
|
|
62
|
+
"longTerm": {
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"description": "Cross-run memory. Requires `memoryRef` resolution per RFC 0004. Hosts MUST honor the redaction harness when `longTerm: true` is set; the descriptor is the canonical signal."
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"confidence": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"description": "Default confidence calibration shipped with the agent. Host runs MAY override at run-time via `RunOptions.configurable.escalationThreshold` per RFC 0002 §F.",
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"properties": {
|
|
73
|
+
"defaultThreshold": {
|
|
74
|
+
"type": "number",
|
|
75
|
+
"minimum": 0,
|
|
76
|
+
"maximum": 1,
|
|
77
|
+
"description": "Below this threshold, an `agent.decided` event with `confidence < threshold` triggers the escalation contract from RFC 0002 §F. When unset, host falls back to the run's resolved threshold (per RFC 0002 §F: default 0.7)."
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"handoff": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"description": "URI references to JSON Schemas pinning the agent's input contract (`taskSchemaRef`) and output contract (`returnSchemaRef`). Both refs are tarball-relative paths (e.g., `schemas/<agent>-task.schema.json`). Hosts validate inbound task payloads against `taskSchemaRef` before dispatch, and outbound results against `returnSchemaRef` before persistence. Manifests that ship without handoff schemas accept opaque task payloads — useful for free-form chat agents but weaker for cross-host interop.",
|
|
84
|
+
"additionalProperties": false,
|
|
85
|
+
"properties": {
|
|
86
|
+
"taskSchemaRef": { "type": "string", "minLength": 1 },
|
|
87
|
+
"returnSchemaRef": { "type": "string", "minLength": 1 }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"label": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "Optional short label for UI surfaces. Falls back to `persona` when absent.",
|
|
93
|
+
"minLength": 1,
|
|
94
|
+
"maxLength": 100
|
|
95
|
+
},
|
|
96
|
+
"description": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "Optional one-line summary for UI / catalog surfaces.",
|
|
99
|
+
"maxLength": 500
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"additionalProperties": false,
|
|
103
|
+
"oneOf": [
|
|
104
|
+
{ "required": ["systemPrompt"], "not": { "required": ["systemPromptRef"] } },
|
|
105
|
+
{ "required": ["systemPromptRef"], "not": { "required": ["systemPrompt"] } }
|
|
106
|
+
]
|
|
107
|
+
}
|