@openwop/openwop-conformance 1.134.0 → 1.135.1

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 (58) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/schemas/CORPUS-STAMP.json +2 -2
  4. package/schemas/capabilities.schema.json +10 -0
  5. package/src/lib/discovery-capabilities.ts +21 -0
  6. package/src/lib/feedback.ts +2 -1
  7. package/src/lib/heartbeat.ts +2 -5
  8. package/src/lib/memoryAttribution.ts +2 -5
  9. package/src/lib/toolHooks.ts +2 -5
  10. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +2 -1
  11. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +2 -1
  12. package/src/scenarios/aiEnvelope.redaction.test.ts +2 -1
  13. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +3 -3
  14. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +2 -1
  15. package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -2
  16. package/src/scenarios/blob-cross-tenant-isolation.test.ts +2 -1
  17. package/src/scenarios/blob-presign-expiry.test.ts +2 -1
  18. package/src/scenarios/blob-roundtrip.test.ts +0 -0
  19. package/src/scenarios/cache-cross-tenant-isolation.test.ts +2 -1
  20. package/src/scenarios/cache-ttl-expiry.test.ts +2 -1
  21. package/src/scenarios/discovery-families-view.test.ts +38 -0
  22. package/src/scenarios/discovery.test.ts +6 -9
  23. package/src/scenarios/kv-atomic-increment.test.ts +2 -1
  24. package/src/scenarios/kv-cas.test.ts +2 -1
  25. package/src/scenarios/kv-cross-tenant-isolation.test.ts +2 -1
  26. package/src/scenarios/kv-ttl-expiry.test.ts +2 -1
  27. package/src/scenarios/mcp-server-elicitation-bridge.test.ts +2 -1
  28. package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +2 -1
  29. package/src/scenarios/mcp-server-resource-roundtrip.test.ts +2 -1
  30. package/src/scenarios/mcp-server-sampling-bridge.test.ts +2 -1
  31. package/src/scenarios/mcp-server-tool-roundtrip.test.ts +2 -1
  32. package/src/scenarios/mcp-server-untrusted-args.test.ts +2 -1
  33. package/src/scenarios/mcp-toolcall-redaction.test.ts +3 -4
  34. package/src/scenarios/media-url-inline-cap.test.ts +3 -5
  35. package/src/scenarios/metric-emission.test.ts +3 -4
  36. package/src/scenarios/node-module-required-capabilities-shape.test.ts +2 -1
  37. package/src/scenarios/otel-emission-grpc.test.ts +3 -6
  38. package/src/scenarios/otel-emission.test.ts +2 -1
  39. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +2 -1
  40. package/src/scenarios/otel-trace-propagation.test.ts +2 -1
  41. package/src/scenarios/pack-registry-isolation.test.ts +2 -1
  42. package/src/scenarios/pack-registry-publish.test.ts +2 -1
  43. package/src/scenarios/queue-ack-nack-dlq.test.ts +2 -1
  44. package/src/scenarios/queue-cross-tenant-isolation.test.ts +2 -1
  45. package/src/scenarios/queue-publish-consume-roundtrip.test.ts +2 -1
  46. package/src/scenarios/search-bm25-roundtrip.test.ts +2 -1
  47. package/src/scenarios/sql-injection-rejection.test.ts +2 -1
  48. package/src/scenarios/sql-transaction-atomicity.test.ts +2 -1
  49. package/src/scenarios/stream-subscribe-from-beginning.test.ts +2 -1
  50. package/src/scenarios/table-cross-tenant-isolation.test.ts +2 -1
  51. package/src/scenarios/table-cursor-pagination.test.ts +2 -1
  52. package/src/scenarios/table-schema-enforcement.test.ts +2 -1
  53. package/src/scenarios/vector-knn-roundtrip.test.ts +2 -1
  54. package/src/scenarios/versioned-composition-profiles.test.ts +18 -0
  55. package/src/scenarios/webhook-negative.test.ts +3 -3
  56. package/src/scenarios/webhook-sig-algorithm.test.ts +2 -1
  57. package/src/scenarios/webhook-signed-delivery.test.ts +3 -2
  58. package/src/scenarios/workflow-chain-host-expansion.test.ts +3 -6
@@ -21,6 +21,7 @@
21
21
  import { describe, it, expect } from 'vitest';
22
22
  import { softSkip } from '../lib/soft-skip.js';
23
23
  import { driver } from '../lib/driver.js';
24
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
24
25
  import { pollUntilTerminal } from '../lib/polling.js';
25
26
  import { isFixtureAdvertised } from '../lib/fixtures.js';
26
27
  import { getCollector } from '../lib/otel-collector.js';
@@ -43,10 +44,8 @@ interface MetricsCaps {
43
44
  async function metricsAdvertised(): Promise<MetricsCaps | null> {
44
45
  try {
45
46
  const disco = await driver.get('/.well-known/openwop');
46
- const caps = (disco.json as {
47
- capabilities?: { observability?: { metrics?: MetricsCaps } };
48
- }).capabilities;
49
- return caps?.observability?.metrics ?? null;
47
+ const caps = discoveryFamilies(disco.json) as { observability?: { metrics?: MetricsCaps } };
48
+ return caps.observability?.metrics ?? null;
50
49
  } catch {
51
50
  return null;
52
51
  }
@@ -20,6 +20,7 @@
20
20
 
21
21
  import { describe, it, expect, beforeAll } from 'vitest';
22
22
  import { driver } from '../lib/driver.js';
23
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
23
24
 
24
25
  /** RFC 0031 §C — spec-reserved capability identifiers. */
25
26
  const RESERVED_IDENTIFIERS: ReadonlySet<string> = new Set([
@@ -58,7 +59,7 @@ beforeAll(async () => {
58
59
  SKIP_REASON = 'discovery doc unreachable';
59
60
  return;
60
61
  }
61
- const caps = (disco.json as DiscoveryDoc).capabilities ?? {};
62
+ const caps = discoveryFamilies(disco.json) as NonNullable<DiscoveryDoc['capabilities']>;
62
63
  if (caps.modelCapabilities?.supported !== true) {
63
64
  SKIP_REASON = 'host does not advertise capabilities.modelCapabilities.supported: true';
64
65
  return;
@@ -28,6 +28,7 @@
28
28
  import { describe, it, expect } from 'vitest';
29
29
  import { softSkip } from '../lib/soft-skip.js';
30
30
  import { driver } from '../lib/driver.js';
31
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
31
32
  import { pollUntilTerminal } from '../lib/polling.js';
32
33
  import { isFixtureAdvertised } from '../lib/fixtures.js';
33
34
  import { getCollector, waitForRunSpans } from '../lib/otel-collector.js';
@@ -45,12 +46,8 @@ const FIXTURE = 'conformance-noop';
45
46
  async function advertisesGrpcExport(): Promise<boolean> {
46
47
  try {
47
48
  const disco = await driver.get('/.well-known/openwop');
48
- const caps = (disco.json as {
49
- capabilities?: {
50
- observability?: { otel?: { exportProtocols?: unknown } };
51
- };
52
- }).capabilities;
53
- const protocols = caps?.observability?.otel?.exportProtocols;
49
+ const caps = discoveryFamilies(disco.json) as { observability?: { otel?: { exportProtocols?: unknown } } };
50
+ const protocols = caps.observability?.otel?.exportProtocols;
54
51
  return Array.isArray(protocols) && protocols.includes('grpc');
55
52
  } catch {
56
53
  return false;
@@ -26,6 +26,7 @@
26
26
  import { describe, it, expect } from 'vitest';
27
27
  import { softSkip } from '../lib/soft-skip.js';
28
28
  import { driver } from '../lib/driver.js';
29
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
29
30
  import { pollUntilTerminal } from '../lib/polling.js';
30
31
  import { isFixtureAdvertised } from '../lib/fixtures.js';
31
32
  import { getCollector, waitForRunSpans } from '../lib/otel-collector.js';
@@ -43,7 +44,7 @@ const FIXTURE = 'conformance-noop';
43
44
  async function isObservabilityAdvertised(): Promise<boolean> {
44
45
  try {
45
46
  const disco = await driver.get('/.well-known/openwop');
46
- const caps = (disco.json as { capabilities?: { observability?: unknown } }).capabilities ?? {};
47
+ const caps = discoveryFamilies(disco.json) as { observability?: unknown };
47
48
  return caps.observability !== undefined;
48
49
  } catch {
49
50
  return false;
@@ -36,6 +36,7 @@
36
36
  import { describe, it, expect } from 'vitest';
37
37
  import { softSkip } from '../lib/soft-skip.js';
38
38
  import { driver } from '../lib/driver.js';
39
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
39
40
  import { pollUntilTerminal } from '../lib/polling.js';
40
41
  import { isFixtureAdvertised } from '../lib/fixtures.js';
41
42
  import { isScenarioOptedOut } from '../lib/env.js';
@@ -70,7 +71,7 @@ function makeTraceparent(): { header: string; traceId: string } {
70
71
  async function isObservabilityAdvertised(): Promise<boolean> {
71
72
  try {
72
73
  const disco = await driver.get('/.well-known/openwop');
73
- const caps = (disco.json as { capabilities?: { observability?: unknown } }).capabilities ?? {};
74
+ const caps = discoveryFamilies(disco.json) as { observability?: unknown };
74
75
  return caps.observability !== undefined;
75
76
  } catch {
76
77
  return false;
@@ -20,6 +20,7 @@
20
20
  import { describe, it, expect } from 'vitest';
21
21
  import { softSkip } from '../lib/soft-skip.js';
22
22
  import { driver } from '../lib/driver.js';
23
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
23
24
  import { pollUntilTerminal } from '../lib/polling.js';
24
25
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
26
  import { getCollector, waitForRunSpans } from '../lib/otel-collector.js';
@@ -45,7 +46,7 @@ function makeTraceparent(): { header: string; traceId: string } {
45
46
  async function isObservabilityAdvertised(): Promise<boolean> {
46
47
  try {
47
48
  const disco = await driver.get('/.well-known/openwop');
48
- const caps = (disco.json as { capabilities?: { observability?: unknown } }).capabilities ?? {};
49
+ const caps = discoveryFamilies(disco.json) as { observability?: unknown };
49
50
  return caps.observability !== undefined;
50
51
  } catch {
51
52
  return false;
@@ -22,6 +22,7 @@
22
22
  import { describe, it, expect } from 'vitest';
23
23
  import { softSkip } from '../lib/soft-skip.js';
24
24
  import { driver } from '../lib/driver.js';
25
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
25
26
 
26
27
  interface DiscoveryDoc {
27
28
  capabilities?: Record<string, unknown>;
@@ -35,7 +36,7 @@ interface TestModeAdvertisement {
35
36
  async function getTestModeAdvertisement(): Promise<TestModeAdvertisement | null> {
36
37
  const res = await driver.get('/.well-known/openwop');
37
38
  const body = res.json as DiscoveryDoc | undefined;
38
- const top = body?.capabilities as Record<string, unknown> | undefined;
39
+ const top = discoveryFamilies(body);
39
40
  const packs = top && typeof top === 'object' ? (top['packs'] as Record<string, unknown> | undefined) : undefined;
40
41
  const testMode = packs && typeof packs === 'object' ? (packs['testMode'] as Record<string, unknown> | undefined) : undefined;
41
42
  if (!testMode || typeof testMode !== 'object') return null;
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { describe, it, expect } from 'vitest';
24
24
  import { driver } from '../lib/driver.js';
25
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
25
26
  import { recordRequirement } from '../lib/requirement-ledger.js';
26
27
  import { requirementIdForScenario } from '../lib/requirement-registry.js';
27
28
 
@@ -50,7 +51,7 @@ async function isTestModeAdvertised(): Promise<boolean> {
50
51
  const res = await driver.get('/.well-known/openwop');
51
52
  const body = res.json as DiscoveryDoc | undefined;
52
53
  const rootPacks = body?.packs && typeof body.packs === 'object' ? body.packs : undefined;
53
- const top = body?.capabilities as Record<string, unknown> | undefined;
54
+ const top = discoveryFamilies(body);
54
55
  const wrappedPacks = top && typeof top === 'object' ? (top['packs'] as Record<string, unknown> | undefined) : undefined;
55
56
  const packs = rootPacks ?? wrappedPacks;
56
57
  const testMode = packs && typeof packs === 'object' ? (packs['testMode'] as Record<string, unknown> | undefined) : undefined;
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { describe, it, expect } from 'vitest';
13
13
  import { driver } from '../lib/driver.js';
14
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
14
15
 
15
16
  interface DiscoveryDoc {
16
17
  capabilities?: Record<string, unknown>;
@@ -19,7 +20,7 @@ interface DiscoveryDoc {
19
20
  async function readCap(): Promise<Record<string, unknown> | null> {
20
21
  const res = await driver.get('/.well-known/openwop');
21
22
  const body = res.json as DiscoveryDoc | undefined;
22
- const top = body?.capabilities as Record<string, unknown> | undefined;
23
+ const top = discoveryFamilies(body);
23
24
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
24
25
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
25
26
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["searchIndex"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { describe, it, expect } from 'vitest';
15
15
  import { driver } from '../lib/driver.js';
16
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
16
17
 
17
18
  interface DiscoveryDoc {
18
19
  capabilities?: Record<string, unknown>;
@@ -21,7 +22,7 @@ interface DiscoveryDoc {
21
22
  async function readCap(): Promise<Record<string, unknown> | null> {
22
23
  const res = await driver.get('/.well-known/openwop');
23
24
  const body = res.json as DiscoveryDoc | undefined;
24
- const top = body?.capabilities as Record<string, unknown> | undefined;
25
+ const top = discoveryFamilies(body);
25
26
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["sql"] : undefined;
26
27
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
27
28
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["sql"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["queueBus"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { describe, it, expect } from 'vitest';
12
12
  import { driver } from '../lib/driver.js';
13
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
13
14
 
14
15
  interface DiscoveryDoc {
15
16
  capabilities?: Record<string, unknown>;
@@ -18,7 +19,7 @@ interface DiscoveryDoc {
18
19
  async function readCap(): Promise<Record<string, unknown> | null> {
19
20
  const res = await driver.get('/.well-known/openwop');
20
21
  const body = res.json as DiscoveryDoc | undefined;
21
- const top = body?.capabilities as Record<string, unknown> | undefined;
22
+ const top = discoveryFamilies(body);
22
23
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["tableStorage"] : undefined;
23
24
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
24
25
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["tableStorage"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -16,6 +16,7 @@
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { readErrorCode } from '../lib/error-envelope.js';
18
18
  import { driver } from '../lib/driver.js';
19
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
19
20
 
20
21
  interface DiscoveryDoc {
21
22
  capabilities?: Record<string, unknown>;
@@ -24,7 +25,7 @@ interface DiscoveryDoc {
24
25
  async function readCap(): Promise<Record<string, unknown> | null> {
25
26
  const res = await driver.get('/.well-known/openwop');
26
27
  const body = res.json as DiscoveryDoc | undefined;
27
- const top = body?.capabilities as Record<string, unknown> | undefined;
28
+ const top = discoveryFamilies(body);
28
29
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["tableStorage"] : undefined;
29
30
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
30
31
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryDoc {
20
21
  capabilities?: Record<string, unknown>;
@@ -23,7 +24,7 @@ interface DiscoveryDoc {
23
24
  async function readCap(): Promise<Record<string, unknown> | null> {
24
25
  const res = await driver.get('/.well-known/openwop');
25
26
  const body = res.json as DiscoveryDoc | undefined;
26
- const top = body?.capabilities as Record<string, unknown> | undefined;
27
+ const top = discoveryFamilies(body);
27
28
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["vectorStore"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -137,6 +137,24 @@ describe('RFC 0153 §A — MCP versioned discovery', () => {
137
137
  ).toBe(true);
138
138
  });
139
139
 
140
+ it('the v1.0 baseline `serverUrls` (required by mcp-discoverability when supported:true) validates — path or absolute URL, non-empty, no duplicates', () => {
141
+ // S27 (2026-08-17): the schema was `additionalProperties: false` and did not
142
+ // list `serverUrls`, while mcp-discoverability.test.ts REQUIRED it — a host
143
+ // could not satisfy the scenario and the schema at once. The second sibling
144
+ // host measured the contradiction on its own advert.
145
+ expect(
146
+ validate({ supported: true, serverUrls: ['/v1/host/openwop-app/mcp'], protocolVersions: ['2026-07-28'] }),
147
+ JSON.stringify(validate.errors),
148
+ ).toBe(true);
149
+ expect(
150
+ validate({ supported: true, serverUrls: ['https://example.test/canvasApi/v1/mcp'], protocolVersions: ['2024-11-05'], preferredVersion: '2024-11-05' }),
151
+ JSON.stringify(validate.errors),
152
+ ).toBe(true);
153
+ expect(validate({ supported: true, serverUrls: [] }), 'empty serverUrls is the bare `supported: true` problem').toBe(false);
154
+ expect(validate({ supported: true, serverUrls: ['/a', '/a'] }), 'duplicates').toBe(false);
155
+ expect(validate({ supported: true, serverUrls: [''] }), 'an empty string is not a mount').toBe(false);
156
+ });
157
+
140
158
  it('versions use MCP date form exactly', () => {
141
159
  // MCP revisions ARE dates. `latest` or a non-padded month would let two
142
160
  // hosts disagree about which revision they share while both validate.
@@ -22,13 +22,13 @@
22
22
 
23
23
  import { describe, it, expect } from 'vitest';
24
24
  import { driver } from '../lib/driver.js';
25
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
25
26
  import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
26
27
 
27
28
  async function isWebhookSupported(): Promise<boolean> {
28
29
  const disco = await driver.get('/.well-known/openwop');
29
- const caps = (disco.json as { capabilities?: { webhooks?: { supported?: boolean } } })
30
- .capabilities;
31
- return caps?.webhooks?.supported === true;
30
+ const caps = discoveryFamilies(disco.json) as { webhooks?: { supported?: boolean } };
31
+ return caps.webhooks?.supported === true;
32
32
  }
33
33
 
34
34
  describe('webhook-negative: SSRF guard rejects private destinations', () => {
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, it, expect } from 'vitest';
17
17
  import { driver } from '../lib/driver.js';
18
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
18
19
 
19
20
  interface DiscoveryWebhooks {
20
21
  webhooks?: {
@@ -26,7 +27,7 @@ interface DiscoveryWebhooks {
26
27
  describe('webhook-sig-algorithm: host advertises supported algorithm set', () => {
27
28
  it('discovery surfaces a webhooks.signatureAlgorithms array including "v1"', async () => {
28
29
  const disco = await driver.get('/.well-known/openwop');
29
- const caps = (disco.json as { capabilities?: DiscoveryWebhooks }).capabilities ?? {};
30
+ const caps = discoveryFamilies(disco.json) as DiscoveryWebhooks;
30
31
  const webhooks = caps.webhooks;
31
32
 
32
33
  if (!webhooks?.supported) {
@@ -28,6 +28,7 @@ import { softSkip } from '../lib/soft-skip.js';
28
28
  import { createHmac } from 'node:crypto';
29
29
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
30
30
  import { driver } from '../lib/driver.js';
31
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
31
32
  import { pollUntilTerminal } from '../lib/polling.js';
32
33
  import { isFixtureAdvertised } from '../lib/fixtures.js';
33
34
  import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
@@ -70,8 +71,8 @@ afterEach(async () => {
70
71
 
71
72
  async function isWebhookSupported(): Promise<boolean> {
72
73
  const disco = await driver.get('/.well-known/openwop');
73
- const caps = (disco.json as { capabilities?: { webhooks?: { supported?: boolean } } }).capabilities;
74
- return caps?.webhooks?.supported === true;
74
+ const caps = discoveryFamilies(disco.json) as { webhooks?: { supported?: boolean } };
75
+ return caps.webhooks?.supported === true;
75
76
  }
76
77
 
77
78
  describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
@@ -46,7 +46,7 @@ import { loadEnv } from '../lib/env.js';
46
46
  import { behaviorGate } from '../lib/behavior-gate.js';
47
47
  import { FIXTURES_DIR } from '../lib/paths.js';
48
48
  import { expandChain, expandChainWithCompensation, type WorkflowChain, type WorkflowChainWithCompensation } from '../lib/workflow-chain-expansion.js';
49
- import { capabilityFamily } from '../lib/discovery-capabilities.js';
49
+ import { capabilityFamily, discoveryFamilies } from '../lib/discovery-capabilities.js';
50
50
  import { softSkip } from '../lib/soft-skip.js';
51
51
  import { readErrorCode } from '../lib/error-envelope.js';
52
52
 
@@ -84,9 +84,7 @@ interface ChainCaps {
84
84
 
85
85
  async function isExpansionAdvertised(): Promise<boolean> {
86
86
  const disco = await driver.get('/.well-known/openwop');
87
- const caps =
88
- (disco.json as { capabilities?: { workflowChainPacks?: ChainCaps } }).capabilities
89
- ?.workflowChainPacks ?? {};
87
+ const caps = (discoveryFamilies(disco.json) as { workflowChainPacks?: ChainCaps }).workflowChainPacks ?? {};
90
88
  return caps.hostExpansionSeam === true;
91
89
  }
92
90
 
@@ -105,8 +103,7 @@ describe('workflow-chain-host-expansion: live host wraps expansion algorithm cor
105
103
  if (!behaviorGate(PROFILE, await isExpansionAdvertised())) return;
106
104
 
107
105
  const disco = await driver.get('/.well-known/openwop');
108
- const caps = (disco.json as { capabilities?: { workflowChainPacks?: ChainCaps } }).capabilities
109
- ?.workflowChainPacks;
106
+ const caps = (discoveryFamilies(disco.json) as { workflowChainPacks?: ChainCaps }).workflowChainPacks;
110
107
  expect(
111
108
  caps,
112
109
  driver.describe(