@openwop/openwop-conformance 1.2.0 → 1.3.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 (53) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +2 -2
  3. package/api/redocly.yaml +15 -0
  4. package/coverage.md +2 -1
  5. package/fixtures/conformance-agent-reasoning-streaming.json +37 -0
  6. package/fixtures/conformance-dispatch-cancellable-child.json +27 -0
  7. package/fixtures/conformance-dispatch-deterministic-fail-child.json +30 -0
  8. package/fixtures/conformance-dispatch-input-mapping-no-default.json +49 -0
  9. package/fixtures/conformance-dispatch-per-worker-override.json +59 -0
  10. package/fixtures/conformance-subworkflow-input-mapping-no-default.json +33 -0
  11. package/fixtures.md +6 -0
  12. package/package.json +1 -1
  13. package/schemas/capabilities.schema.json +16 -0
  14. package/schemas/core-conformance-mock-agent-config.schema.json +5 -0
  15. package/schemas/run-event-payloads.schema.json +35 -1
  16. package/schemas/run-event.schema.json +2 -0
  17. package/src/lib/driver.ts +15 -0
  18. package/src/lib/env.ts +51 -0
  19. package/src/lib/event-log-query.ts +62 -0
  20. package/src/lib/fixtures.ts +38 -1
  21. package/src/lib/host-toggle.ts +54 -0
  22. package/src/lib/multi-agent-capabilities.ts +10 -0
  23. package/src/lib/otel-scrape.ts +59 -0
  24. package/src/scenarios/agentReasoningStreaming.test.ts +193 -0
  25. package/src/scenarios/aiEnvelope.capBreached.test.ts +97 -9
  26. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +128 -10
  27. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +236 -21
  28. package/src/scenarios/aiEnvelope.redaction.test.ts +204 -24
  29. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +158 -19
  30. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +59 -8
  31. package/src/scenarios/aiEnvelope.universalKinds.test.ts +100 -9
  32. package/src/scenarios/blob-presign-expiry.test.ts +35 -2
  33. package/src/scenarios/blob-roundtrip.test.ts +0 -0
  34. package/src/scenarios/cache-ttl-expiry.test.ts +28 -2
  35. package/src/scenarios/dispatch-cross-worker-handoff.test.ts +34 -3
  36. package/src/scenarios/dispatch-input-mapping.test.ts +75 -6
  37. package/src/scenarios/dispatch-output-mapping.test.ts +96 -6
  38. package/src/scenarios/fixtures-gating.test.ts +139 -1
  39. package/src/scenarios/kv-ttl-expiry.test.ts +33 -2
  40. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +19 -0
  41. package/src/scenarios/pack-registry-publish.test.ts +231 -51
  42. package/src/scenarios/provider-usage.test.ts +185 -0
  43. package/src/scenarios/queue-ack-nack-dlq.test.ts +57 -3
  44. package/src/scenarios/queue-publish-consume-roundtrip.test.ts +43 -3
  45. package/src/scenarios/replay-llm-cache-key.test.ts +166 -25
  46. package/src/scenarios/search-bm25-roundtrip.test.ts +47 -2
  47. package/src/scenarios/sql-transaction-atomicity.test.ts +31 -2
  48. package/src/scenarios/stream-subscribe-from-beginning.test.ts +39 -2
  49. package/src/scenarios/subworkflow-input-mapping.test.ts +77 -7
  50. package/src/scenarios/table-cursor-pagination.test.ts +40 -2
  51. package/src/scenarios/table-schema-enforcement.test.ts +39 -2
  52. package/src/scenarios/vector-knn-roundtrip.test.ts +43 -3
  53. package/src/scenarios/workflow-chain-host-expansion.test.ts +202 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Workflow-chain pack expansion — live-host gate (RFC 0013 Phase 3).
3
+ *
4
+ * Capability-gated scenario. Skips when the host doesn't advertise
5
+ * `capabilities.workflowChainPacks.supported: true`. Asserts the host's
6
+ * vendor-prefixed expansion endpoint (`POST /v1/host/sample/workflow-
7
+ * chain:expand` — vendor prefix per `host-extensions.md` §"Canonical
8
+ * prefixes") returns expanded fragments equivalent to the spec-
9
+ * authoritative `expandChain()` reference library.
10
+ *
11
+ * Why this exists: the four server-free chain scenarios
12
+ * (manifest-validation, signature-verification, expansion,
13
+ * unresolvable-typeid) cover the pure logic. This scenario proves a
14
+ * reference host wraps the algorithm correctly — fetch + verify +
15
+ * locate + expand — and emits the same wire shape any consumer
16
+ * implementing the spec would. Without it, the RFC's "reference host
17
+ * implements expansion" acceptance criterion cannot be verified
18
+ * end-to-end against an actual deployment.
19
+ *
20
+ * Coverage:
21
+ * 1. Discovery advertises the capability (precondition for the rest).
22
+ * 2. Positive — 1-node chain expands; substituted config + rewritten
23
+ * id + propagated capabilities match the pure-library output for
24
+ * the same input.
25
+ * 3. Positive — 2-node chain with edges expands; edge endpoints
26
+ * reference the rewritten ids.
27
+ * 4. Negative — unknown packName → 404 `pack_not_found`.
28
+ * 5. Negative — known pack, unknown chainId → 404 `chain_not_found`.
29
+ * 6. Negative — malformed body (no chainId) → 422 `invalid_request`.
30
+ *
31
+ * @see spec/v1/workflow-chain-packs.md §"Expansion semantics (normative)"
32
+ * @see capabilities.md §workflowChainPacks
33
+ * @see RFCS/0013-workflow-chain-packs.md (Phase 3)
34
+ */
35
+
36
+ import { describe, it, expect } from 'vitest';
37
+
38
+ import { driver } from '../lib/driver.js';
39
+ import { loadEnv } from '../lib/env.js';
40
+ import { behaviorGate } from '../lib/behavior-gate.js';
41
+
42
+ const PROFILE = 'workflowChainPacks';
43
+ const SAMPLE_PACK = 'vendor.openwop.workflow-chain-sample';
44
+ const CHAIN_1_NODE = 'vendor.openwop.workflow-chain-sample.summarize-text';
45
+ const CHAIN_2_NODE = 'vendor.openwop.workflow-chain-sample.fetch-and-summarize';
46
+ const EXPAND_PATH = '/v1/host/sample/workflow-chain:expand';
47
+
48
+ interface ChainCaps {
49
+ supported?: boolean;
50
+ }
51
+
52
+ async function isExpansionAdvertised(): Promise<boolean> {
53
+ const disco = await driver.get('/.well-known/openwop');
54
+ const caps =
55
+ (disco.json as { capabilities?: { workflowChainPacks?: ChainCaps } }).capabilities
56
+ ?.workflowChainPacks ?? {};
57
+ return caps.supported === true;
58
+ }
59
+
60
+ describe('workflow-chain-host-expansion: live host wraps expansion algorithm correctly', () => {
61
+ it('host discovery advertises workflowChainPacks.supported when expansion is implemented', async () => {
62
+ loadEnv();
63
+ if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
64
+
65
+ const disco = await driver.get('/.well-known/openwop');
66
+ const caps = (disco.json as { capabilities?: { workflowChainPacks?: ChainCaps } }).capabilities
67
+ ?.workflowChainPacks;
68
+ expect(
69
+ caps,
70
+ driver.describe(
71
+ 'capabilities.md §workflowChainPacks',
72
+ 'host advertising the capability MUST set `supported: true` in the discovery block',
73
+ ),
74
+ ).toBeDefined();
75
+ expect(caps?.supported).toBe(true);
76
+ });
77
+
78
+ it('positive — 1-node chain expansion via the host returns substituted config + rewritten id', async () => {
79
+ if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
80
+
81
+ const res = await driver.post(EXPAND_PATH, {
82
+ packName: SAMPLE_PACK,
83
+ chainId: CHAIN_1_NODE,
84
+ parameters: {
85
+ sourceText: 'The quick brown fox jumps over the lazy dog.',
86
+ targetLength: 'one-sentence',
87
+ tone: 'casual',
88
+ },
89
+ });
90
+
91
+ expect(res.status).toBe(200);
92
+ const body = res.json as {
93
+ expansionId: string;
94
+ chainId: string;
95
+ packName: string;
96
+ packVersion: string;
97
+ nodes: Array<{
98
+ id: string;
99
+ typeId: string;
100
+ config?: { systemPrompt?: string };
101
+ capabilities?: string[];
102
+ }>;
103
+ edges: Array<unknown>;
104
+ };
105
+
106
+ expect(body.chainId).toBe(CHAIN_1_NODE);
107
+ expect(body.packName).toBe(SAMPLE_PACK);
108
+ expect(body.packVersion).toBe('1.0.0');
109
+ expect(body.nodes).toHaveLength(1);
110
+ expect(body.edges).toHaveLength(0);
111
+ expect(typeof body.expansionId).toBe('string');
112
+ expect(body.expansionId.length).toBeGreaterThan(0);
113
+
114
+ const node = body.nodes[0]!;
115
+ // Step 6: id rewriting — chainId's dots become underscores +
116
+ // expansionId suffix + original fragment id.
117
+ expect(node.id).toMatch(
118
+ /^vendor_openwop_workflow-chain-sample_summarize-text_[a-f0-9]+_summarize-call$/,
119
+ );
120
+ expect(node.typeId).toBe('core.ai.callPrompt');
121
+
122
+ // Step 5: literal substitution.
123
+ const sysPrompt = node.config?.systemPrompt ?? '';
124
+ expect(sysPrompt).toContain('a one-sentence summary');
125
+ expect(sysPrompt).toContain('a casual tone');
126
+ expect(sysPrompt).toContain('The quick brown fox jumps over the lazy dog.');
127
+
128
+ // Step 8: capability propagation.
129
+ expect(node.capabilities).toEqual(['cacheable']);
130
+ });
131
+
132
+ it('positive — 2-node chain with edges expands with rewritten edge endpoints', async () => {
133
+ if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
134
+
135
+ const res = await driver.post(EXPAND_PATH, {
136
+ packName: SAMPLE_PACK,
137
+ chainId: CHAIN_2_NODE,
138
+ parameters: {
139
+ url: 'https://example.com/article',
140
+ targetLength: 'executive-summary',
141
+ },
142
+ });
143
+ expect(res.status).toBe(200);
144
+
145
+ const body = res.json as {
146
+ expansionId: string;
147
+ nodes: Array<{ id: string; typeId: string; capabilities?: string[] }>;
148
+ edges: Array<{ from: string; to: string }>;
149
+ };
150
+ expect(body.nodes).toHaveLength(2);
151
+ expect(body.edges).toHaveLength(1);
152
+
153
+ // Both expanded nodes get the same prefix; the edge's `from`/`to`
154
+ // refer to fragment node ids and so get rewritten with the same
155
+ // prefix (port suffix preserved).
156
+ const edge = body.edges[0]!;
157
+ const prefix = `vendor_openwop_workflow-chain-sample_fetch-and-summarize_${body.expansionId}_`;
158
+ expect(edge.from).toBe(`${prefix}fetch.body`);
159
+ expect(edge.to).toBe(`${prefix}summarize.sourceText`);
160
+
161
+ // side-effectful capability propagated to BOTH expanded nodes.
162
+ for (const node of body.nodes) {
163
+ expect(node.capabilities, `node ${node.id} inherits chain capability`).toEqual(['side-effectful']);
164
+ }
165
+ });
166
+
167
+ it('negative — unknown pack returns 404 pack_not_found', async () => {
168
+ if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
169
+
170
+ const res = await driver.post(EXPAND_PATH, {
171
+ packName: 'vendor.acme.does-not-exist',
172
+ chainId: 'whatever',
173
+ parameters: {},
174
+ });
175
+ expect(res.status).toBe(404);
176
+ expect((res.json as { error: string }).error).toBe('pack_not_found');
177
+ });
178
+
179
+ it('negative — known pack but unknown chainId returns 404 chain_not_found', async () => {
180
+ if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
181
+
182
+ const res = await driver.post(EXPAND_PATH, {
183
+ packName: SAMPLE_PACK,
184
+ chainId: 'vendor.openwop.workflow-chain-sample.does-not-exist',
185
+ parameters: {},
186
+ });
187
+ expect(res.status).toBe(404);
188
+ expect((res.json as { error: string }).error).toBe('chain_not_found');
189
+ });
190
+
191
+ it('negative — malformed request body returns 422 invalid_request', async () => {
192
+ if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
193
+
194
+ // Missing chainId.
195
+ const res = await driver.post(EXPAND_PATH, {
196
+ packName: SAMPLE_PACK,
197
+ parameters: {},
198
+ });
199
+ expect(res.status).toBe(422);
200
+ expect((res.json as { error: string }).error).toBe('invalid_request');
201
+ });
202
+ });