@openwop/openwop-conformance 1.1.1 → 1.2.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 (86) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +2 -2
  3. package/coverage.md +26 -14
  4. package/fixtures/conformance-agent-low-confidence.json +7 -4
  5. package/fixtures/conformance-agent-pack-handoff-schema-validation.json +30 -0
  6. package/fixtures/conformance-agent-reasoning.json +23 -4
  7. package/fixtures/conformance-dispatch-cross-worker-handoff-child-a.json +27 -0
  8. package/fixtures/conformance-dispatch-cross-worker-handoff-child-b.json +25 -0
  9. package/fixtures/conformance-dispatch-cross-worker-handoff.json +60 -0
  10. package/fixtures/conformance-dispatch-input-mapping-child.json +25 -0
  11. package/fixtures/conformance-dispatch-input-mapping.json +49 -0
  12. package/fixtures/conformance-dispatch-output-mapping-child.json +27 -0
  13. package/fixtures/conformance-dispatch-output-mapping.json +49 -0
  14. package/fixtures/conformance-subworkflow-input-mapping-child.json +27 -0
  15. package/fixtures/conformance-subworkflow-input-mapping.json +33 -0
  16. package/fixtures.md +12 -2
  17. package/package.json +1 -1
  18. package/schemas/README.md +7 -0
  19. package/schemas/agent-ref.schema.json +1 -1
  20. package/schemas/ai-envelope.schema.json +106 -0
  21. package/schemas/capabilities.schema.json +248 -0
  22. package/schemas/core-conformance-mock-agent-config.schema.json +147 -0
  23. package/schemas/dispatch-config.schema.json +26 -0
  24. package/schemas/envelopes/clarification.request.schema.json +43 -0
  25. package/schemas/envelopes/error.schema.json +26 -0
  26. package/schemas/envelopes/schema.request.schema.json +22 -0
  27. package/schemas/envelopes/schema.response.schema.json +22 -0
  28. package/schemas/node-pack-manifest.schema.json +5 -0
  29. package/schemas/pack-lockfile.schema.json +16 -0
  30. package/schemas/workflow-chain-pack-manifest.schema.json +226 -0
  31. package/src/lib/webhook-receiver.ts +137 -0
  32. package/src/lib/workflow-chain-expansion.ts +213 -0
  33. package/src/scenarios/agentPackCatalog.test.ts +216 -0
  34. package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +146 -0
  35. package/src/scenarios/agentReasoningEvents.test.ts +58 -7
  36. package/src/scenarios/agents-run-tool-allowlist.test.ts +182 -0
  37. package/src/scenarios/ai-envelope-shape.test.ts +362 -0
  38. package/src/scenarios/aiEnvelope.capBreached.test.ts +173 -0
  39. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +150 -0
  40. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +69 -0
  41. package/src/scenarios/aiEnvelope.redaction.test.ts +73 -0
  42. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +87 -0
  43. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +143 -0
  44. package/src/scenarios/aiEnvelope.universalKinds.test.ts +176 -0
  45. package/src/scenarios/append-ordering.test.ts +44 -0
  46. package/src/scenarios/artifact-auth.test.ts +58 -0
  47. package/src/scenarios/blob-cross-tenant-isolation.test.ts +66 -0
  48. package/src/scenarios/blob-presign-expiry.test.ts +66 -0
  49. package/src/scenarios/blob-roundtrip.test.ts +48 -0
  50. package/src/scenarios/cache-cross-tenant-isolation.test.ts +61 -0
  51. package/src/scenarios/cache-ttl-expiry.test.ts +47 -0
  52. package/src/scenarios/dispatch-cross-worker-handoff.test.ts +98 -0
  53. package/src/scenarios/dispatch-input-mapping.test.ts +94 -0
  54. package/src/scenarios/dispatch-output-mapping.test.ts +65 -0
  55. package/src/scenarios/fs-path-traversal.test.ts +124 -0
  56. package/src/scenarios/idempotency-key-determinism.test.ts +230 -0
  57. package/src/scenarios/interrupt-token-matrix.test.ts +126 -0
  58. package/src/scenarios/kv-atomic-increment.test.ts +74 -0
  59. package/src/scenarios/kv-cas.test.ts +75 -0
  60. package/src/scenarios/kv-cross-tenant-isolation.test.ts +85 -0
  61. package/src/scenarios/kv-ttl-expiry.test.ts +47 -0
  62. package/src/scenarios/mcp-server-elicitation-bridge.test.ts +92 -0
  63. package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +80 -0
  64. package/src/scenarios/mcp-server-resource-roundtrip.test.ts +82 -0
  65. package/src/scenarios/mcp-server-sampling-bridge.test.ts +84 -0
  66. package/src/scenarios/mcp-server-tool-roundtrip.test.ts +107 -0
  67. package/src/scenarios/mcp-server-untrusted-args.test.ts +105 -0
  68. package/src/scenarios/pause-resume.test.ts +43 -0
  69. package/src/scenarios/queue-ack-nack-dlq.test.ts +67 -0
  70. package/src/scenarios/queue-cross-tenant-isolation.test.ts +66 -0
  71. package/src/scenarios/queue-publish-consume-roundtrip.test.ts +48 -0
  72. package/src/scenarios/search-bm25-roundtrip.test.ts +47 -0
  73. package/src/scenarios/spec-corpus-validity.test.ts +17 -1
  74. package/src/scenarios/sql-injection-rejection.test.ts +84 -0
  75. package/src/scenarios/sql-transaction-atomicity.test.ts +66 -0
  76. package/src/scenarios/stream-subscribe-from-beginning.test.ts +66 -0
  77. package/src/scenarios/subworkflow-input-mapping.test.ts +100 -0
  78. package/src/scenarios/table-cross-tenant-isolation.test.ts +65 -0
  79. package/src/scenarios/table-cursor-pagination.test.ts +47 -0
  80. package/src/scenarios/table-schema-enforcement.test.ts +47 -0
  81. package/src/scenarios/vector-knn-roundtrip.test.ts +48 -0
  82. package/src/scenarios/webhook-receiver-adversarial.test.ts +210 -0
  83. package/src/scenarios/workflow-chain-expansion.test.ts +366 -0
  84. package/src/scenarios/workflow-chain-pack-manifest-validation.test.ts +232 -0
  85. package/src/scenarios/workflow-chain-pack-signature-verification.test.ts +138 -0
  86. package/src/scenarios/workflow-chain-unresolvable-typeid.test.ts +170 -0
@@ -0,0 +1,105 @@
1
+ /**
2
+ * mcp-server-untrusted-args — RFC 0020 §D + SECURITY/invariants.yaml
3
+ * `mcp-server-untrusted-args`.
4
+ *
5
+ * Status: ACTIVE (advertisement + behavioral). Asserts that tools/call
6
+ * with arguments violating the registered inputSchema is rejected with
7
+ * JSON-RPC `-32602 invalid params` BEFORE any workflow side-effects.
8
+ *
9
+ * @see RFCS/0020-host-mcp-server-composition.md
10
+ * @see SECURITY/invariants.yaml — mcp-server-untrusted-args
11
+ */
12
+
13
+ import { describe, it, expect } from 'vitest';
14
+ import { driver } from '../lib/driver.js';
15
+
16
+ interface DiscoveryDoc {
17
+ capabilities?: Record<string, unknown>;
18
+ }
19
+
20
+ async function readCap(): Promise<Record<string, unknown> | null> {
21
+ const res = await driver.get('/.well-known/openwop');
22
+ const body = res.json as DiscoveryDoc | undefined;
23
+ const top = body?.capabilities as Record<string, unknown> | undefined;
24
+ const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
25
+ const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
26
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
27
+ }
28
+
29
+ async function rpc(method: string, params?: Record<string, unknown>) {
30
+ const id = Math.floor(Math.random() * 1e6);
31
+ const req: Record<string, unknown> = { jsonrpc: '2.0', id, method };
32
+ if (params !== undefined) req.params = params;
33
+ const res = await driver.post('/v1/host/sample/mcp', req);
34
+ return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string; data?: unknown } } };
35
+ }
36
+
37
+ const TEST_TOOL_NAME = `inj_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
38
+
39
+ async function registerStrictWorkflow(): Promise<boolean> {
40
+ const res = await driver.post('/v1/host/sample/workflows', {
41
+ workflowId: `mcp.untrusted.${Date.now()}`,
42
+ nodes: [
43
+ {
44
+ nodeId: 'expose',
45
+ typeId: 'core.openwop.mcp.expose-tool',
46
+ config: {
47
+ name: TEST_TOOL_NAME,
48
+ description: 'Strict-schema tool',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: { text: { type: 'string' } },
52
+ required: ['text'],
53
+ additionalProperties: false,
54
+ },
55
+ },
56
+ },
57
+ ],
58
+ });
59
+ return res.status === 200 || res.status === 201;
60
+ }
61
+
62
+ describe('mcp-server-untrusted-args: advertisement shape (RFC 0020)', () => {
63
+ it('capabilities.mcp.serverMount is well-formed when present', async () => {
64
+ const cap = await readCap();
65
+ if (cap === null) return;
66
+ expect(typeof cap.supported).toBe('boolean');
67
+ });
68
+ });
69
+
70
+ describe('mcp-server-untrusted-args: behavioral (RFC 0020 §D)', () => {
71
+ it('tools/call with malformed arguments is rejected with JSON-RPC -32602 BEFORE workflow start', async () => {
72
+ const cap = await readCap();
73
+ if (!cap || cap.supported !== true) return;
74
+ if (!(await registerStrictWorkflow())) return;
75
+
76
+ const r = await rpc('tools/call', {
77
+ name: TEST_TOOL_NAME,
78
+ arguments: { wrongField: 'no' },
79
+ });
80
+ if (r.status === 404) return;
81
+ expect(r.status, 'JSON-RPC envelope MUST 200').toBe(200);
82
+ expect(
83
+ r.body.error?.code,
84
+ driver.describe(
85
+ 'SECURITY/invariants.yaml mcp-server-untrusted-args',
86
+ 'malformed arguments MUST be rejected with -32602 invalid params before workflow start',
87
+ ),
88
+ ).toBe(-32602);
89
+ expect(r.body.error?.data, 'error.data MUST carry validation violations').toBeDefined();
90
+ });
91
+
92
+ it('tools/call with valid arguments is accepted', async () => {
93
+ const cap = await readCap();
94
+ if (!cap || cap.supported !== true) return;
95
+ const r = await rpc('tools/call', {
96
+ name: TEST_TOOL_NAME,
97
+ arguments: { text: 'hello' },
98
+ });
99
+ if (r.status === 404) return;
100
+ expect(r.status).toBe(200);
101
+ if (r.body.error) {
102
+ expect(r.body.error.code, 'valid args MUST NOT trigger -32602').not.toBe(-32602);
103
+ }
104
+ });
105
+ });
@@ -226,3 +226,46 @@ describe.skipIf(SKIP)('pause/resume: :pause-during-suspend race', () => {
226
226
  });
227
227
  });
228
228
  });
229
+
230
+ // CF-2 close-out — drain-policy discrimination per
231
+ // `capabilities.md` §`runs.pauseResume`. When a host advertises
232
+ // `drainPolicies[]`, each advertised value MUST be accepted with 202.
233
+ // Skips entirely when no advertisement is present.
234
+ describe.skipIf(SKIP)('pause/resume: drainPolicy discrimination per capabilities advertisement', () => {
235
+ it('every drainPolicy advertised by the host is accepted on :pause', async () => {
236
+ const disco = await driver.get('/.well-known/openwop');
237
+ const drainPolicies =
238
+ (disco.json as {
239
+ capabilities?: { runs?: { pauseResume?: { drainPolicies?: string[] } } };
240
+ }).capabilities?.runs?.pauseResume?.drainPolicies ?? [];
241
+ if (drainPolicies.length === 0) {
242
+ // eslint-disable-next-line no-console
243
+ console.warn('[pause-resume] host advertises no drainPolicies; skipping policy-discrimination subtest');
244
+ return;
245
+ }
246
+
247
+ for (const policy of drainPolicies) {
248
+ const create = await driver.post('/v1/runs', {
249
+ workflowId: FIXTURE!,
250
+ inputs: { delaySeconds: 30 },
251
+ });
252
+ expect(create.status).toBe(201);
253
+ const runId = (create.json as { runId: string }).runId;
254
+
255
+ await pollUntilStatus(runId, 'running', { timeoutMs: 10_000 });
256
+
257
+ const pause = await driver.post(`/v1/runs/${encodeURIComponent(runId)}:pause`, {
258
+ reason: `conformance-drainpolicy-${policy}`,
259
+ drainPolicy: policy,
260
+ });
261
+ expect(pause.status, driver.describe(
262
+ 'capabilities.md §`runs.pauseResume.drainPolicies` + rest-endpoints.md POST /v1/runs/{runId}:pause',
263
+ `host-advertised drainPolicy='${policy}' MUST be accepted on :pause`,
264
+ )).toBe(202);
265
+
266
+ await driver.post(`/v1/runs/${encodeURIComponent(runId)}/cancel`, {
267
+ reason: 'conformance-cleanup',
268
+ });
269
+ }
270
+ });
271
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * queue-ack-nack-dlq — RFC 0017 advertisement-shape verification + behavioral placeholders.
3
+ *
4
+ * Status: ACTIVE (advertisement-shape). RFC 0017 promoted to `Active`
5
+ * 2026-05-17. The matching `capabilities.queueBus` block has landed in
6
+ * `schemas/capabilities.schema.json`. This scenario asserts the advertisement
7
+ * shape against any host that boots the conformance suite, and keeps the
8
+ * deeper behavioral assertions as `it.todo()` until a reference host wires
9
+ * a test seam.
10
+ *
11
+ * Summary: nack returns for redelivery; deadLetter routes to the configured DLQ.
12
+ *
13
+ * @see RFCS/0017-*.md
14
+ */
15
+
16
+ import { describe, it, expect } from 'vitest';
17
+ import { driver } from '../lib/driver.js';
18
+
19
+ interface DiscoveryDoc {
20
+ capabilities?: Record<string, unknown>;
21
+ }
22
+
23
+ async function readCap(): Promise<Record<string, unknown> | null> {
24
+ const res = await driver.get('/.well-known/openwop');
25
+ const body = res.json as DiscoveryDoc | undefined;
26
+ const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
28
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
+ }
30
+
31
+ describe('queue-ack-nack-dlq: advertisement shape (RFC 0017)', () => {
32
+ it('capabilities.queueBus is either absent or a well-formed object', async () => {
33
+ const cap = await readCap();
34
+ if (cap === null) return; // host doesn't advertise — skip
35
+ expect(
36
+ typeof cap.supported,
37
+ driver.describe(
38
+ 'capabilities.schema.json §queueBus',
39
+ 'capabilities.queueBus.supported MUST be a boolean when present',
40
+ ),
41
+ ).toBe('boolean');
42
+ });
43
+
44
+ it('deadLetterSupported is a boolean when set', async () => {
45
+ const cap = await readCap();
46
+ if (!cap || cap.supported !== true) return;
47
+ const subParts = ["deadLetterSupported"];
48
+ let sub: unknown = cap;
49
+ for (const p of subParts) {
50
+ if (sub && typeof sub === 'object') sub = (sub as Record<string, unknown>)[p];
51
+ else { sub = undefined; break; }
52
+ }
53
+ if (sub === undefined) return; // optional sub-field
54
+ expect(
55
+ typeof sub,
56
+ driver.describe(
57
+ 'RFC 0017 §A',
58
+ 'queueBus.deadLetterSupported MUST be boolean when present',
59
+ ),
60
+ ).toBe('boolean');
61
+ });
62
+ });
63
+
64
+ describe('queue-ack-nack-dlq: behavioral assertions (placeholders — need host test seam)', () => {
65
+ it.todo("nack(requeue=true) → message is redelivered on next consume");
66
+ it.todo("deadLetter → message appears on the configured DLQ");
67
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * queue-cross-tenant-isolation — RFC 0017 §C + SECURITY/invariants.yaml
3
+ * `queue-cross-tenant-isolation`.
4
+ *
5
+ * Status: ACTIVE (advertisement + behavioral). Asserts that messages
6
+ * published under tenant A on topic T MUST NOT be consumed under tenant B
7
+ * on the same topic.
8
+ *
9
+ * @see RFCS/0017-host-queue-bus-capability.md
10
+ */
11
+
12
+ import { describe, it, expect } from 'vitest';
13
+ import { driver } from '../lib/driver.js';
14
+
15
+ interface DiscoveryDoc {
16
+ capabilities?: Record<string, unknown>;
17
+ }
18
+
19
+ async function readCap(): Promise<Record<string, unknown> | null> {
20
+ const res = await driver.get('/.well-known/openwop');
21
+ const body = res.json as DiscoveryDoc | undefined;
22
+ const top = body?.capabilities as Record<string, unknown> | undefined;
23
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
24
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
25
+ }
26
+
27
+ async function call(tenantId: string, op: string, args: Record<string, unknown>) {
28
+ return driver.post('/v1/host/sample/test/surface', { tenantId, surface: 'queueBus', op, args });
29
+ }
30
+
31
+ describe('queue-cross-tenant-isolation: advertisement shape (RFC 0017)', () => {
32
+ it('capabilities.queueBus is either absent or a well-formed object', async () => {
33
+ const cap = await readCap();
34
+ if (cap === null) return;
35
+ expect(
36
+ typeof cap.supported,
37
+ driver.describe(
38
+ 'capabilities.schema.json §queueBus',
39
+ 'capabilities.queueBus.supported MUST be a boolean when present',
40
+ ),
41
+ ).toBe('boolean');
42
+ });
43
+ });
44
+
45
+ describe('queue-cross-tenant-isolation: behavioral (RFC 0017 §C)', () => {
46
+ it('publish under tenant A → consume under tenant B returns not-found', async () => {
47
+ const cap = await readCap();
48
+ if (!cap || cap.supported !== true) return;
49
+ const topic = `xtenant.${Date.now()}.${Math.random().toString(36).slice(2, 6)}`;
50
+
51
+ const pubRes = await call('tenant-a', 'publish', { topic, payload: { hello: 'A' } });
52
+ if (pubRes.status === 404) return;
53
+ expect(pubRes.status, 'publish MUST succeed').toBe(200);
54
+
55
+ const consRes = await call('tenant-b', 'consume', { topic, timeoutMs: 100 });
56
+ expect(consRes.status).toBe(200);
57
+ const body = consRes.json as { found?: boolean };
58
+ expect(
59
+ body.found,
60
+ driver.describe(
61
+ 'SECURITY/invariants.yaml queue-cross-tenant-isolation',
62
+ 'tenant B MUST NOT consume tenant A messages on the same topic',
63
+ ),
64
+ ).toBe(false);
65
+ });
66
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * queue-publish-consume-roundtrip — RFC 0017 advertisement-shape verification + behavioral placeholders.
3
+ *
4
+ * Status: ACTIVE (advertisement-shape). RFC 0017 promoted to `Active`
5
+ * 2026-05-17. The matching `capabilities.queueBus` block has landed in
6
+ * `schemas/capabilities.schema.json`. This scenario asserts the advertisement
7
+ * shape against any host that boots the conformance suite, and keeps the
8
+ * deeper behavioral assertions as `it.todo()` until a reference host wires
9
+ * a test seam.
10
+ *
11
+ * Summary: publish + consume + ack roundtrip.
12
+ *
13
+ * @see RFCS/0017-*.md
14
+ */
15
+
16
+ import { describe, it, expect } from 'vitest';
17
+ import { driver } from '../lib/driver.js';
18
+
19
+ interface DiscoveryDoc {
20
+ capabilities?: Record<string, unknown>;
21
+ }
22
+
23
+ async function readCap(): Promise<Record<string, unknown> | null> {
24
+ const res = await driver.get('/.well-known/openwop');
25
+ const body = res.json as DiscoveryDoc | undefined;
26
+ const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
28
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
+ }
30
+
31
+ describe('queue-publish-consume-roundtrip: advertisement shape (RFC 0017)', () => {
32
+ it('capabilities.queueBus is either absent or a well-formed object', async () => {
33
+ const cap = await readCap();
34
+ if (cap === null) return; // host doesn't advertise — skip
35
+ expect(
36
+ typeof cap.supported,
37
+ driver.describe(
38
+ 'capabilities.schema.json §queueBus',
39
+ 'capabilities.queueBus.supported MUST be a boolean when present',
40
+ ),
41
+ ).toBe('boolean');
42
+ });
43
+ });
44
+
45
+ describe('queue-publish-consume-roundtrip: behavioral assertions (placeholders — need host test seam)', () => {
46
+ it.todo("publish → consume returns the message with the right payload + headers");
47
+ it.todo("ack removes the message; subsequent consume returns not-found within timeout");
48
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * search-bm25-roundtrip — RFC 0018 advertisement-shape verification + behavioral placeholders.
3
+ *
4
+ * Status: ACTIVE (advertisement-shape). RFC 0018 promoted to `Active`
5
+ * 2026-05-17. The matching `capabilities.searchIndex` block has landed in
6
+ * `schemas/capabilities.schema.json`. This scenario asserts the advertisement
7
+ * shape against any host that boots the conformance suite, and keeps the
8
+ * deeper behavioral assertions as `it.todo()` until a reference host wires
9
+ * a test seam.
10
+ *
11
+ * Summary: index then query returns relevant documents.
12
+ *
13
+ * @see RFCS/0018-*.md
14
+ */
15
+
16
+ import { describe, it, expect } from 'vitest';
17
+ import { driver } from '../lib/driver.js';
18
+
19
+ interface DiscoveryDoc {
20
+ capabilities?: Record<string, unknown>;
21
+ }
22
+
23
+ async function readCap(): Promise<Record<string, unknown> | null> {
24
+ const res = await driver.get('/.well-known/openwop');
25
+ const body = res.json as DiscoveryDoc | undefined;
26
+ const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["searchIndex"] : undefined;
28
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
+ }
30
+
31
+ describe('search-bm25-roundtrip: advertisement shape (RFC 0018)', () => {
32
+ it('capabilities.searchIndex is either absent or a well-formed object', async () => {
33
+ const cap = await readCap();
34
+ if (cap === null) return; // host doesn't advertise — skip
35
+ expect(
36
+ typeof cap.supported,
37
+ driver.describe(
38
+ 'capabilities.schema.json §searchIndex',
39
+ 'capabilities.searchIndex.supported MUST be a boolean when present',
40
+ ),
41
+ ).toBe('boolean');
42
+ });
43
+ });
44
+
45
+ describe('search-bm25-roundtrip: behavioral assertions (placeholders — need host test seam)', () => {
46
+ it.todo("index 3 docs → query returns relevance-ranked hits");
47
+ });
@@ -69,7 +69,23 @@ import {
69
69
  // ── Helpers ─────────────────────────────────────────────────────────────
70
70
 
71
71
  function listJsonFiles(dir: string): string[] {
72
- return readdirSync(dir).filter((f) => f.endsWith('.json'));
72
+ // Recurse into subdirectories so e.g. `schemas/envelopes/*.schema.json`
73
+ // appears as `envelopes/<file>` to match the README's path-prefixed
74
+ // table entries. Preserves the non-recursive-relative-output contract
75
+ // for files directly under `dir`.
76
+ const out: string[] = [];
77
+ const walk = (subPath: string): void => {
78
+ const fullPath = subPath === '' ? dir : `${dir}/${subPath}`;
79
+ for (const entry of readdirSync(fullPath, { withFileTypes: true })) {
80
+ if (entry.isDirectory()) {
81
+ walk(subPath === '' ? entry.name : `${subPath}/${entry.name}`);
82
+ } else if (entry.isFile() && entry.name.endsWith('.json')) {
83
+ out.push(subPath === '' ? entry.name : `${subPath}/${entry.name}`);
84
+ }
85
+ }
86
+ };
87
+ walk('');
88
+ return out;
73
89
  }
74
90
 
75
91
  function listScenarioTestFiles(dir: string): string[] {
@@ -0,0 +1,84 @@
1
+ /**
2
+ * sql-injection-rejection — RFC 0018 §C + SECURITY/invariants.yaml
3
+ * `sql-parametric-only`.
4
+ *
5
+ * Status: ACTIVE (advertisement + behavioral). The host's SQL surface
6
+ * MUST treat parameter values as literal data, not SQL fragments. We
7
+ * verify by binding an injection-shape string as a parameter and
8
+ * confirming it returns no rows (parametric binding turns it into a
9
+ * literal value comparison rather than an OR-true).
10
+ *
11
+ * @see RFCS/0018-host-sql-vector-search-capability.md
12
+ */
13
+
14
+ import { describe, it, expect } from 'vitest';
15
+ import { driver } from '../lib/driver.js';
16
+
17
+ interface DiscoveryDoc {
18
+ capabilities?: Record<string, unknown>;
19
+ }
20
+
21
+ async function readCap(): Promise<Record<string, unknown> | null> {
22
+ const res = await driver.get('/.well-known/openwop');
23
+ const body = res.json as DiscoveryDoc | undefined;
24
+ const top = body?.capabilities as Record<string, unknown> | undefined;
25
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["sql"] : undefined;
26
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
27
+ }
28
+
29
+ async function call(op: string, args: Record<string, unknown>) {
30
+ return driver.post('/v1/host/sample/test/surface', { tenantId: 'tenant-a', surface: 'sql', op, args });
31
+ }
32
+
33
+ describe('sql-injection-rejection: advertisement shape (RFC 0018)', () => {
34
+ it('capabilities.sql is either absent or a well-formed object', async () => {
35
+ const cap = await readCap();
36
+ if (cap === null) return;
37
+ expect(
38
+ typeof cap.supported,
39
+ driver.describe(
40
+ 'capabilities.schema.json §sql',
41
+ 'capabilities.sql.supported MUST be a boolean when present',
42
+ ),
43
+ ).toBe('boolean');
44
+ });
45
+ });
46
+
47
+ describe('sql-injection-rejection: behavioral (RFC 0018 §C)', () => {
48
+ it('parametric SELECT with bound user input rejects injection-shape strings as data', async () => {
49
+ const cap = await readCap();
50
+ if (!cap || cap.supported !== true) return;
51
+
52
+ const create = await call('execute', {
53
+ sql: `CREATE TABLE IF NOT EXISTS sql_inj_t (id TEXT PRIMARY KEY, body TEXT)`,
54
+ params: [],
55
+ });
56
+ if (create.status === 404) return;
57
+ await call('execute', { sql: `INSERT OR REPLACE INTO sql_inj_t VALUES (?, ?)`, params: ['k1', 'ok'] });
58
+
59
+ // Parametric round-trip MUST succeed.
60
+ const okRes = await call('query', {
61
+ sql: `SELECT body FROM sql_inj_t WHERE id = ?`,
62
+ params: ['k1'],
63
+ });
64
+ expect(okRes.status).toBe(200);
65
+ const okBody = okRes.json as { rows?: Array<Record<string, unknown>> };
66
+ expect(okBody.rows?.[0]?.body, 'parametric round-trip MUST return stored value').toBe('ok');
67
+
68
+ // Injection-shape input MUST be bound as a literal value, not SQL.
69
+ const attack = `' OR '1'='1`;
70
+ const attackRes = await call('query', {
71
+ sql: `SELECT body FROM sql_inj_t WHERE id = ?`,
72
+ params: [attack],
73
+ });
74
+ expect(attackRes.status).toBe(200);
75
+ const attackBody = attackRes.json as { rows?: Array<Record<string, unknown>> };
76
+ expect(
77
+ Array.isArray(attackBody.rows) ? attackBody.rows.length : -1,
78
+ driver.describe(
79
+ 'SECURITY/invariants.yaml sql-parametric-only',
80
+ 'parametric binding MUST treat injection-shape input as a literal value, not SQL',
81
+ ),
82
+ ).toBe(0);
83
+ });
84
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * sql-transaction-atomicity — RFC 0018 advertisement-shape verification + behavioral placeholders.
3
+ *
4
+ * Status: ACTIVE (advertisement-shape). RFC 0018 promoted to `Active`
5
+ * 2026-05-17. The matching `capabilities.sql` block has landed in
6
+ * `schemas/capabilities.schema.json`. This scenario asserts the advertisement
7
+ * shape against any host that boots the conformance suite, and keeps the
8
+ * deeper behavioral assertions as `it.todo()` until a reference host wires
9
+ * a test seam.
10
+ *
11
+ * Summary: transactions MUST be atomic; partial failure rolls back.
12
+ *
13
+ * @see RFCS/0018-*.md
14
+ */
15
+
16
+ import { describe, it, expect } from 'vitest';
17
+ import { driver } from '../lib/driver.js';
18
+
19
+ interface DiscoveryDoc {
20
+ capabilities?: Record<string, unknown>;
21
+ }
22
+
23
+ async function readCap(): Promise<Record<string, unknown> | null> {
24
+ const res = await driver.get('/.well-known/openwop');
25
+ const body = res.json as DiscoveryDoc | undefined;
26
+ const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["sql"] : undefined;
28
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
+ }
30
+
31
+ describe('sql-transaction-atomicity: advertisement shape (RFC 0018)', () => {
32
+ it('capabilities.sql is either absent or a well-formed object', async () => {
33
+ const cap = await readCap();
34
+ if (cap === null) return; // host doesn't advertise — skip
35
+ expect(
36
+ typeof cap.supported,
37
+ driver.describe(
38
+ 'capabilities.schema.json §sql',
39
+ 'capabilities.sql.supported MUST be a boolean when present',
40
+ ),
41
+ ).toBe('boolean');
42
+ });
43
+
44
+ it('transactions is a boolean when set', async () => {
45
+ const cap = await readCap();
46
+ if (!cap || cap.supported !== true) return;
47
+ const subParts = ["transactions"];
48
+ let sub: unknown = cap;
49
+ for (const p of subParts) {
50
+ if (sub && typeof sub === 'object') sub = (sub as Record<string, unknown>)[p];
51
+ else { sub = undefined; break; }
52
+ }
53
+ if (sub === undefined) return; // optional sub-field
54
+ expect(
55
+ typeof sub,
56
+ driver.describe(
57
+ 'RFC 0018 §A',
58
+ 'sql.transactions MUST be boolean when present',
59
+ ),
60
+ ).toBe('boolean');
61
+ });
62
+ });
63
+
64
+ describe('sql-transaction-atomicity: behavioral assertions (placeholders — need host test seam)', () => {
65
+ it.todo("transaction with N statements where N-th fails → no rows from earlier statements visible");
66
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * stream-subscribe-from-beginning — RFC 0017 advertisement-shape verification + behavioral placeholders.
3
+ *
4
+ * Status: ACTIVE (advertisement-shape). RFC 0017 promoted to `Active`
5
+ * 2026-05-17. The matching `capabilities.queueBus` block has landed in
6
+ * `schemas/capabilities.schema.json`. This scenario asserts the advertisement
7
+ * shape against any host that boots the conformance suite, and keeps the
8
+ * deeper behavioral assertions as `it.todo()` until a reference host wires
9
+ * a test seam.
10
+ *
11
+ * Summary: Stream subscribers with fromBeginning=true receive records published before subscription.
12
+ *
13
+ * @see RFCS/0017-*.md
14
+ */
15
+
16
+ import { describe, it, expect } from 'vitest';
17
+ import { driver } from '../lib/driver.js';
18
+
19
+ interface DiscoveryDoc {
20
+ capabilities?: Record<string, unknown>;
21
+ }
22
+
23
+ async function readCap(): Promise<Record<string, unknown> | null> {
24
+ const res = await driver.get('/.well-known/openwop');
25
+ const body = res.json as DiscoveryDoc | undefined;
26
+ const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
28
+ return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
+ }
30
+
31
+ describe('stream-subscribe-from-beginning: advertisement shape (RFC 0017)', () => {
32
+ it('capabilities.queueBus is either absent or a well-formed object', async () => {
33
+ const cap = await readCap();
34
+ if (cap === null) return; // host doesn't advertise — skip
35
+ expect(
36
+ typeof cap.supported,
37
+ driver.describe(
38
+ 'capabilities.schema.json §queueBus',
39
+ 'capabilities.queueBus.supported MUST be a boolean when present',
40
+ ),
41
+ ).toBe('boolean');
42
+ });
43
+
44
+ it('stream.supported is a boolean when set', async () => {
45
+ const cap = await readCap();
46
+ if (!cap || cap.supported !== true) return;
47
+ const subParts = ["stream","supported"];
48
+ let sub: unknown = cap;
49
+ for (const p of subParts) {
50
+ if (sub && typeof sub === 'object') sub = (sub as Record<string, unknown>)[p];
51
+ else { sub = undefined; break; }
52
+ }
53
+ if (sub === undefined) return; // optional sub-field
54
+ expect(
55
+ typeof sub,
56
+ driver.describe(
57
+ 'RFC 0017 §A',
58
+ 'queueBus.stream.supported MUST be boolean when present',
59
+ ),
60
+ ).toBe('boolean');
61
+ });
62
+ });
63
+
64
+ describe('stream-subscribe-from-beginning: behavioral assertions (placeholders — need host test seam)', () => {
65
+ it.todo("publish 5 records then subscribe(fromBeginning=true) → consumer receives all 5");
66
+ });