@openwop/openwop-conformance 1.133.0 → 1.135.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/README.md +2 -2
  2. package/package.json +1 -1
  3. package/schemas/CORPUS-STAMP.json +2 -2
  4. package/src/lib/discovery-capabilities.ts +21 -0
  5. package/src/lib/driver.ts +3 -1
  6. package/src/lib/feedback.ts +2 -1
  7. package/src/lib/heartbeat.ts +2 -5
  8. package/src/lib/mcp-mount.ts +39 -0
  9. package/src/lib/memoryAttribution.ts +2 -5
  10. package/src/lib/toolHooks.ts +2 -5
  11. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +2 -1
  12. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +2 -1
  13. package/src/scenarios/aiEnvelope.redaction.test.ts +2 -1
  14. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +3 -3
  15. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +2 -1
  16. package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -2
  17. package/src/scenarios/blob-cross-tenant-isolation.test.ts +2 -1
  18. package/src/scenarios/blob-presign-expiry.test.ts +2 -1
  19. package/src/scenarios/blob-roundtrip.test.ts +0 -0
  20. package/src/scenarios/cache-cross-tenant-isolation.test.ts +2 -1
  21. package/src/scenarios/cache-ttl-expiry.test.ts +2 -1
  22. package/src/scenarios/discovery-families-view.test.ts +38 -0
  23. package/src/scenarios/discovery.test.ts +5 -7
  24. package/src/scenarios/kv-atomic-increment.test.ts +2 -1
  25. package/src/scenarios/kv-cas.test.ts +2 -1
  26. package/src/scenarios/kv-cross-tenant-isolation.test.ts +2 -1
  27. package/src/scenarios/kv-ttl-expiry.test.ts +2 -1
  28. package/src/scenarios/mcp-2026-07-28-discover.test.ts +5 -3
  29. package/src/scenarios/mcp-cache-tenant-scope.test.ts +4 -2
  30. package/src/scenarios/mcp-current-auth-boundary.test.ts +5 -3
  31. package/src/scenarios/mcp-extension-opacity.test.ts +2 -1
  32. package/src/scenarios/mcp-mrtr-roundtrip.test.ts +4 -3
  33. package/src/scenarios/mcp-server-elicitation-bridge.test.ts +6 -3
  34. package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +6 -3
  35. package/src/scenarios/mcp-server-resource-roundtrip.test.ts +6 -3
  36. package/src/scenarios/mcp-server-sampling-bridge.test.ts +6 -3
  37. package/src/scenarios/mcp-server-tool-roundtrip.test.ts +6 -3
  38. package/src/scenarios/mcp-server-untrusted-args.test.ts +7 -4
  39. package/src/scenarios/mcp-stateless-request.test.ts +6 -5
  40. package/src/scenarios/mcp-version-negotiation.test.ts +3 -3
  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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.133.0",
3
+ "version": "1.135.0",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
- "suiteVersion": "1.133.0",
4
- "corpusCommit": "01d2c45b0cc72ac953bc3c996f9bc3004d3ec297"
3
+ "suiteVersion": "1.135.0",
4
+ "corpusCommit": "1b8327c673b8a11c004c17b848ba0a8d2128c2b8"
5
5
  }
@@ -47,3 +47,24 @@ export async function readCapabilityFamily<T = Record<string, unknown>>(
47
47
  if (res.status !== 200) return undefined;
48
48
  return capabilityFamily<T>(res.json, name);
49
49
  }
50
+
51
+ /**
52
+ * A root-first VIEW of the discovery document's capability families for
53
+ * readers that were written against the deprecated top-level `capabilities`
54
+ * wrapper (RFC 0073: root is the MUST; the wrapper is a v1.x-tolerated
55
+ * mirror that retires at v2). Root families win; a family that exists only in
56
+ * the wrapper is still visible so a legacy host does not lose coverage. Until
57
+ * suite 1.135.0 forty-four readers consulted the wrapper ONLY, which meant a
58
+ * root-only host — the shape RFC 0073 asks for — silently lost their legs
59
+ * (S26, found by the second sibling host, which had to keep its wrapper as a
60
+ * mirror because of it).
61
+ */
62
+ export function discoveryFamilies(doc: unknown): Record<string, unknown> {
63
+ if (!doc || typeof doc !== 'object') return {};
64
+ const root = doc as Record<string, unknown>;
65
+ const wrapper = root['capabilities'];
66
+ const merged: Record<string, unknown> = {};
67
+ if (wrapper && typeof wrapper === 'object' && !Array.isArray(wrapper)) Object.assign(merged, wrapper as Record<string, unknown>);
68
+ for (const [k, v] of Object.entries(root)) if (k !== 'capabilities') merged[k] = v;
69
+ return merged;
70
+ }
package/src/lib/driver.ts CHANGED
@@ -35,7 +35,9 @@ class OpenWOPDriver {
35
35
  init: OpenWOPRequestInit = {},
36
36
  ): Promise<OpenWOPResponse> {
37
37
  const env = loadEnv();
38
- const url = `${env.baseUrl}${path}`;
38
+ // An absolute URL is used as-is (a host may advertise its MCP server mount or
39
+ // an A2A endpoint on another origin); a path is joined to the base URL.
40
+ const url = /^https?:\/\//i.test(path) ? path : `${env.baseUrl}${path}`;
39
41
 
40
42
  const headers: Record<string, string> = {
41
43
  Accept: 'application/json',
@@ -4,6 +4,7 @@
4
4
  * standard `../lib/feedback.js` path.
5
5
  */
6
6
  import { driver } from './driver.js';
7
+ import { discoveryFamilies } from './discovery-capabilities.js';
7
8
  import { isFixtureAdvertised } from './fixtures.js';
8
9
 
9
10
  interface DiscoveryDoc {
@@ -14,7 +15,7 @@ interface DiscoveryDoc {
14
15
  export async function readFeedbackCap(): Promise<Record<string, unknown> | null> {
15
16
  const res = await driver.get('/.well-known/openwop');
16
17
  const body = res.json as DiscoveryDoc | undefined;
17
- const top = body?.capabilities as Record<string, unknown> | undefined;
18
+ const top = discoveryFamilies(body);
18
19
  const fb = top && typeof top === 'object' ? (top as Record<string, unknown>)['feedback'] : undefined;
19
20
  return fb && typeof fb === 'object' ? (fb as Record<string, unknown>) : null;
20
21
  }
@@ -3,15 +3,12 @@
3
3
  * Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/heartbeat.js`.
4
4
  */
5
5
  import { driver } from './driver.js';
6
-
7
- interface DiscoveryDoc {
8
- capabilities?: Record<string, unknown>;
9
- }
6
+ import { discoveryFamilies } from './discovery-capabilities.js';
10
7
 
11
8
  /** Reads `capabilities.heartbeat` from discovery; null when unadvertised. */
12
9
  export async function readHeartbeatCap(): Promise<Record<string, unknown> | null> {
13
10
  const res = await driver.get('/.well-known/openwop');
14
- const caps = (res.json as DiscoveryDoc | undefined)?.capabilities;
11
+ const caps = discoveryFamilies(res.json);
15
12
  const hb = caps && typeof caps === 'object' ? (caps as Record<string, unknown>)['heartbeat'] : undefined;
16
13
  return hb && typeof hb === 'object' ? (hb as Record<string, unknown>) : null;
17
14
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Where a host mounts its MCP server (RFC 0153 §B / `mcp-integration.md`
3
+ * §"MCP server composition").
4
+ *
5
+ * The reference boots mount it at the sample-seam path
6
+ * `/v1/host/sample/mcp`, and until suite 1.134.0 every server-side MCP
7
+ * scenario hard-coded that path. The v1.0 discovery baseline has always let a
8
+ * host SAY where its mount is — `capabilities.mcp.serverUrls: string[]`
9
+ * (`mcp-discoverability.test.ts`) — and the first deployed host to advertise
10
+ * `mcp-2026-07-28` mounted at `/v1/host/openwop-app/mcp`, advertised exactly
11
+ * that, and watched ten `server/discover` legs 404 against the sample path and
12
+ * return early (S25, openwop-app H38, 2026-08-17). So: read the advert first,
13
+ * fall back to the sample path, and never treat "the mount answered 404" as a
14
+ * pass — that is `seamAbsent` (the host advertised a server mount it does not
15
+ * serve where it said).
16
+ */
17
+ import { driver } from './driver.js';
18
+ import { capabilityFamily } from './discovery-capabilities.js';
19
+
20
+ export const SAMPLE_MCP_MOUNT = '/v1/host/sample/mcp';
21
+
22
+ interface McpAdvert {
23
+ readonly supported?: boolean;
24
+ readonly serverUrls?: unknown;
25
+ readonly serverMount?: { readonly supported?: boolean };
26
+ }
27
+
28
+ /**
29
+ * The MCP server mount to POST JSON-RPC to: `capabilities.mcp.serverUrls[0]`
30
+ * when the host advertises one (a path is joined to the base URL by the driver;
31
+ * an absolute URL is used as-is), else the sample-seam path.
32
+ */
33
+ export async function mcpServerMount(): Promise<string> {
34
+ const disco = await driver.get('/.well-known/openwop');
35
+ const mcp = capabilityFamily<McpAdvert>(disco.json, 'mcp');
36
+ const urls = Array.isArray(mcp?.serverUrls) ? (mcp?.serverUrls as unknown[]) : [];
37
+ const first = urls.find((u): u is string => typeof u === 'string' && u.length > 0);
38
+ return first ?? SAMPLE_MCP_MOUNT;
39
+ }
@@ -3,16 +3,13 @@
3
3
  * Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/memoryAttribution.js`.
4
4
  */
5
5
  import { driver } from './driver.js';
6
+ import { discoveryFamilies } from './discovery-capabilities.js';
6
7
  import { isFixtureAdvertised } from './fixtures.js';
7
8
 
8
- interface DiscoveryDoc {
9
- capabilities?: Record<string, unknown>;
10
- }
11
-
12
9
  /** Reads `capabilities.memory.attribution` from discovery; null when unadvertised. */
13
10
  export async function readMemoryAttributionCap(): Promise<Record<string, unknown> | null> {
14
11
  const res = await driver.get('/.well-known/openwop');
15
- const caps = (res.json as DiscoveryDoc | undefined)?.capabilities;
12
+ const caps = discoveryFamilies(res.json);
16
13
  const mem = caps && typeof caps === 'object' ? (caps as Record<string, unknown>)['memory'] : undefined;
17
14
  const attr = mem && typeof mem === 'object' ? (mem as Record<string, unknown>)['attribution'] : undefined;
18
15
  return attr && typeof attr === 'object' ? (attr as Record<string, unknown>) : null;
@@ -3,15 +3,12 @@
3
3
  * Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/toolHooks.js`.
4
4
  */
5
5
  import { driver } from './driver.js';
6
-
7
- interface DiscoveryDoc {
8
- capabilities?: Record<string, unknown>;
9
- }
6
+ import { discoveryFamilies } from './discovery-capabilities.js';
10
7
 
11
8
  /** Reads `capabilities.toolHooks` from discovery; null when unadvertised. */
12
9
  export async function readToolHooksCap(): Promise<Record<string, unknown> | null> {
13
10
  const res = await driver.get('/.well-known/openwop');
14
- const caps = (res.json as DiscoveryDoc | undefined)?.capabilities;
11
+ const caps = discoveryFamilies(res.json);
15
12
  const th = caps && typeof caps === 'object' ? (caps as Record<string, unknown>)['toolHooks'] : undefined;
16
13
  return th && typeof th === 'object' ? (th as Record<string, unknown>) : null;
17
14
  }
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { describe, it, expect, afterEach } from 'vitest';
24
24
  import { driver } from '../lib/driver.js';
25
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
25
26
  import { setHostCapability, resetHostCapabilities, isToggleAvailable } from '../lib/host-toggle.js';
26
27
 
27
28
  interface DiscoveryDoc {
@@ -32,7 +33,7 @@ describe('aiEnvelope.contractRefusal: advertisement shape (FINAL v1.1)', () => {
32
33
  it('opted-in hosts advertise envelopeContracts.advertised as a boolean', async () => {
33
34
  const res = await driver.get('/.well-known/openwop');
34
35
  const body = res.json as DiscoveryDoc | undefined;
35
- const top = body?.capabilities as Record<string, unknown> | undefined;
36
+ const top = discoveryFamilies(body);
36
37
  const block = top && typeof top === 'object' ? top['envelopeContracts'] : undefined;
37
38
  if (block === undefined) return; // absent — skip
38
39
  expect(
@@ -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
 
26
27
  interface DiscoveryDoc {
27
28
  capabilities?: Record<string, unknown>;
@@ -30,7 +31,7 @@ interface DiscoveryDoc {
30
31
  async function isEnvelopeContractsAdvertised(): Promise<boolean> {
31
32
  const res = await driver.get('/.well-known/openwop');
32
33
  const body = res.json as DiscoveryDoc | undefined;
33
- const top = body?.capabilities as Record<string, unknown> | undefined;
34
+ const top = discoveryFamilies(body);
34
35
  const block = top && typeof top === 'object' ? (top['envelopeContracts'] as Record<string, unknown> | undefined) : undefined;
35
36
  return Boolean(block && block['advertised'] === true);
36
37
  }
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { describe, it, expect } from 'vitest';
26
26
  import { driver } from '../lib/driver.js';
27
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
27
28
 
28
29
  interface DiscoveryDoc {
29
30
  capabilities?: Record<string, unknown>;
@@ -32,7 +33,7 @@ interface DiscoveryDoc {
32
33
  async function isBYOKAdvertised(): Promise<boolean> {
33
34
  const res = await driver.get('/.well-known/openwop');
34
35
  const body = res.json as DiscoveryDoc | undefined;
35
- const top = body?.capabilities as Record<string, unknown> | undefined;
36
+ const top = discoveryFamilies(body);
36
37
  const secrets = top && typeof top === 'object' ? top['secrets'] : undefined;
37
38
  return Boolean(secrets && typeof secrets === 'object');
38
39
  }
@@ -29,7 +29,7 @@ interface DiscoveryDoc {
29
29
  async function isEnvelopeContractsAdvertised(): Promise<boolean> {
30
30
  const res = await driver.get('/.well-known/openwop');
31
31
  const body = res.json as DiscoveryDoc | undefined;
32
- const top = body?.capabilities as Record<string, unknown> | undefined;
32
+ const top = discoveryFamilies(body);
33
33
  const block = top && typeof top === 'object' ? (top['envelopeContracts'] as Record<string, unknown> | undefined) : undefined;
34
34
  return Boolean(block && block['advertised'] === true);
35
35
  }
@@ -38,7 +38,7 @@ describe('aiEnvelope.schemaDrift: advertisement shape (FINAL v1.1)', () => {
38
38
  it('capabilities.envelopeStrictness is either absent (treated as "warn") or "warn" | "strict"', async () => {
39
39
  const res = await driver.get('/.well-known/openwop');
40
40
  const body = res.json as DiscoveryDoc | undefined;
41
- const top = body?.capabilities as Record<string, unknown> | undefined;
41
+ const top = discoveryFamilies(body);
42
42
  const val = top && typeof top === 'object' ? top['envelopeStrictness'] : undefined;
43
43
  if (val === undefined) return; // absent → treated as 'warn'; skip
44
44
  expect(
@@ -189,7 +189,7 @@ describe('aiEnvelope.schemaDrift: behavioral strictness gate (FINAL v1.1)', () =
189
189
  // E.2 OTel scrape seam.
190
190
  import { queryTestSpans, isOtelSeamAvailable } from '../lib/otel-scrape.js';
191
191
  import { resetTestSeam } from '../lib/event-log-query.js';
192
- import { capabilityFamily } from '../lib/discovery-capabilities.js';
192
+ import { capabilityFamily, discoveryFamilies } from '../lib/discovery-capabilities.js';
193
193
 
194
194
  describe('aiEnvelope.schemaDrift: OTel drift attribute projection (E.2)', () => {
195
195
  it('below-floor + strictness:warn → OTel span MUST carry envelope_schema_version_drift attribute', async () => {
@@ -23,6 +23,7 @@
23
23
 
24
24
  import { describe, it, expect } from 'vitest';
25
25
  import { driver } from '../lib/driver.js';
26
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
26
27
 
27
28
  interface DiscoveryDoc {
28
29
  capabilities?: Record<string, unknown>;
@@ -31,7 +32,7 @@ interface DiscoveryDoc {
31
32
  async function readMcpTrustBoundary(): Promise<string | null> {
32
33
  const res = await driver.get('/.well-known/openwop');
33
34
  const body = res.json as DiscoveryDoc | undefined;
34
- const top = body?.capabilities as Record<string, unknown> | undefined;
35
+ const top = discoveryFamilies(body);
35
36
  const mcp = top && typeof top === 'object' ? (top['mcpClient'] as Record<string, unknown> | undefined) : undefined;
36
37
  if (!mcp || typeof mcp !== 'object') return null;
37
38
  const tb = mcp['trustBoundary'];
@@ -29,7 +29,7 @@ const UNIVERSALS = ['clarification.request', 'schema.request', 'schema.response'
29
29
  async function readEnvelopeContracts(): Promise<{ advertised: boolean } | null> {
30
30
  const res = await driver.get('/.well-known/openwop');
31
31
  const body = res.json as DiscoveryDoc | undefined;
32
- const top = body?.capabilities as Record<string, unknown> | undefined;
32
+ const top = discoveryFamilies(body);
33
33
  const block = top && typeof top === 'object' ? (top['envelopeContracts'] as Record<string, unknown> | undefined) : undefined;
34
34
  if (!block || typeof block !== 'object') return null;
35
35
  return { advertised: block['advertised'] === true };
@@ -166,7 +166,7 @@ describe('aiEnvelope.universalKinds: behavioral accept via /v1/host/sample/envel
166
166
 
167
167
  // E.1 engine-projection via the test-only event-log seam.
168
168
  import { queryTestEvents, isEventLogSeamAvailable, resetTestSeam } from '../lib/event-log-query.js';
169
- import { capabilityFamily } from '../lib/discovery-capabilities.js';
169
+ import { capabilityFamily, discoveryFamilies } from '../lib/discovery-capabilities.js';
170
170
 
171
171
  describe('aiEnvelope.universalKinds: engine projection via event-log seam', () => {
172
172
  it('clarification.request MUST be lifted to interrupt.requested { kind: "clarification" } per interrupt.md', async () => {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { describe, it, expect } from 'vitest';
11
11
  import { driver } from '../lib/driver.js';
12
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
12
13
 
13
14
  interface DiscoveryDoc {
14
15
  capabilities?: Record<string, unknown>;
@@ -17,7 +18,7 @@ interface DiscoveryDoc {
17
18
  async function readCap(): Promise<Record<string, unknown> | null> {
18
19
  const res = await driver.get('/.well-known/openwop');
19
20
  const body = res.json as DiscoveryDoc | undefined;
20
- const top = body?.capabilities as Record<string, unknown> | undefined;
21
+ const top = discoveryFamilies(body);
21
22
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["blobStorage"] : undefined;
22
23
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
23
24
  }
@@ -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>)["blobStorage"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { describe, it, expect } from 'vitest';
11
11
  import { driver } from '../lib/driver.js';
12
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
12
13
 
13
14
  interface DiscoveryDoc {
14
15
  capabilities?: Record<string, unknown>;
@@ -17,7 +18,7 @@ interface DiscoveryDoc {
17
18
  async function readCap(): Promise<Record<string, unknown> | null> {
18
19
  const res = await driver.get('/.well-known/openwop');
19
20
  const body = res.json as DiscoveryDoc | undefined;
20
- const top = body?.capabilities as Record<string, unknown> | undefined;
21
+ const top = discoveryFamilies(body);
21
22
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["cache"] : undefined;
22
23
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
23
24
  }
@@ -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>)["cache"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * RFC 0073 — root is the normative layout; the top-level `capabilities`
3
+ * wrapper is a v1.x-tolerated mirror. This pins `discoveryFamilies`, the view
4
+ * every reader that was written against the wrapper now goes through (S26,
5
+ * suite 1.135.0): root families win, a wrapper-only family is still visible,
6
+ * and a root-only host loses nothing.
7
+ *
8
+ * Why it exists: forty-four readers consulted the wrapper ONLY, so a host that
9
+ * did what RFC 0073 asks — root families, no wrapper — silently lost their
10
+ * legs, and the second sibling host had to keep its wrapper as a mirror
11
+ * because of it. Server-free, always-on.
12
+ */
13
+
14
+ import { describe, it, expect } from 'vitest';
15
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
16
+
17
+ describe('RFC 0073 — discoveryFamilies is root-first with the deprecated wrapper as fallback', () => {
18
+ it('a root-only document is read as-is (the RFC 0073 shape)', () => {
19
+ expect(discoveryFamilies({ protocolVersion: '1.0', kvStorage: { supported: true } })).toEqual({ protocolVersion: '1.0', kvStorage: { supported: true } });
20
+ });
21
+
22
+ it('a wrapper-only family is still visible, and the wrapper key itself is not a family', () => {
23
+ const v = discoveryFamilies({ protocolVersion: '1.0', capabilities: { heartbeat: { supported: true } } });
24
+ expect(v['heartbeat']).toEqual({ supported: true });
25
+ expect('capabilities' in v).toBe(false);
26
+ });
27
+
28
+ it('when both carry a family, the root wins', () => {
29
+ const v = discoveryFamilies({ feedback: { supported: true, root: true }, capabilities: { feedback: { supported: false, root: false } } });
30
+ expect(v['feedback']).toEqual({ supported: true, root: true });
31
+ });
32
+
33
+ it('a non-object document reads as empty', () => {
34
+ expect(discoveryFamilies(null)).toEqual({});
35
+ expect(discoveryFamilies('nope')).toEqual({});
36
+ expect(discoveryFamilies({ capabilities: ['not', 'an', 'object'] })).toEqual({});
37
+ });
38
+ });
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { describe, it, expect } from 'vitest';
10
10
  import { driver } from '../lib/driver.js';
11
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
11
12
  import { behaviorGate } from '../lib/behavior-gate.js';
12
13
 
13
14
  describe('discovery: /.well-known/openwop', () => {
@@ -306,13 +307,10 @@ describe('discovery: auth-scoped is not an authorization oracle', () => {
306
307
  }
307
308
  expect(unauthorized.status).toBe(200);
308
309
 
309
- const primaryCaps = Object.keys(
310
- (primary.json as { capabilities?: Record<string, unknown> })?.capabilities ?? {},
311
- );
312
- const unauthorizedCaps = Object.keys(
313
- (unauthorized.json as { capabilities?: Record<string, unknown> })?.capabilities ??
314
- {},
315
- );
310
+ // Root-first (RFC 0073); the deprecated `capabilities` wrapper is folded in
311
+ // so a root-only host and a wrapper-only host are compared the same way (S26).
312
+ const primaryCaps = Object.keys(discoveryFamilies(primary.json));
313
+ const unauthorizedCaps = Object.keys(discoveryFamilies(unauthorized.json));
316
314
 
317
315
  // Spec annex line 69: "Hosts MUST NOT let scoped discovery become
318
316
  // an authorization oracle. A caller should learn only about
@@ -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>)["kvStorage"] : undefined;
23
24
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
24
25
  }
@@ -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>)["kvStorage"] : undefined;
23
24
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
24
25
  }
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { describe, it, expect } from 'vitest';
20
20
  import { driver } from '../lib/driver.js';
21
+ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
21
22
 
22
23
  interface DiscoveryDoc {
23
24
  capabilities?: Record<string, unknown>;
@@ -26,7 +27,7 @@ interface DiscoveryDoc {
26
27
  async function readCap(): Promise<Record<string, unknown> | null> {
27
28
  const res = await driver.get('/.well-known/openwop');
28
29
  const body = res.json as DiscoveryDoc | undefined;
29
- const top = body?.capabilities as Record<string, unknown> | undefined;
30
+ const top = discoveryFamilies(body);
30
31
  const final = (top && typeof top === 'object') ? (top as Record<string, unknown>)["kvStorage"] : undefined;
31
32
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
32
33
  }
@@ -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>)["kvStorage"] : undefined;
28
29
  return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
29
30
  }
@@ -25,6 +25,8 @@
25
25
 
26
26
  import { describe, it, expect, beforeAll, afterAll } from 'vitest';
27
27
  import { driver } from '../lib/driver.js';
28
+ import { seamAbsent } from '../lib/soft-skip.js';
29
+ import { mcpServerMount } from '../lib/mcp-mount.js';
28
30
  import { behaviorGate } from '../lib/behavior-gate.js';
29
31
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
30
32
  import { McpFakeServer, MCP_ERR } from '../lib/mcp-fake-server.js';
@@ -170,7 +172,7 @@ async function claimsCurrent(): Promise<boolean> {
170
172
  }
171
173
  async function hostRpc(method: string, params: Record<string, unknown>, headers: Record<string, string>) {
172
174
  // The reference host mounts its MCP server at /v1/host/sample/mcp (mcp-server-* scenarios).
173
- const res = await driver.post('/v1/host/sample/mcp', { jsonrpc: '2.0', id: 1, method, params }, { headers });
175
+ const res = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 1, method, params }, { headers });
174
176
  return { status: res.status, body: res.json as { result?: Record<string, unknown>; error?: { code: number; data?: Record<string, unknown> } } };
175
177
  }
176
178
 
@@ -179,7 +181,7 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §B — host as MCP ser
179
181
  if (!behaviorGate(PROFILE, await claimsCurrent())) return;
180
182
  const caps = (await mcp())!;
181
183
  const r = await hostRpc('server/discover', { _meta: { [META_V]: '2026-07-28', [META_C]: {} } }, { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'server/discover' });
182
- if (r.status === 404 || r.status === 403) return; // mount not wired at the sample path — the mcp-server-* legs cover that gap
184
+ if (r.status === 404 || r.status === 403) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${r.status} RFC 0153 §B is unobservable at the path the host itself advertised`);
183
185
  expect(r.status, driver.describe('mcp-integration.md §B', 'server/discover is a server MUST under 2026-07-28')).toBe(200);
184
186
  expect(r.body.result?.['resultType']).toBe('complete');
185
187
  expect([...((r.body.result?.['supportedVersions'] as string[] | undefined) ?? [])].sort(), driver.describe('mcp-integration.md §B', 'server/discover.supportedVersions MUST equal capabilities.mcp.protocolVersions — two documents, one fact')).toEqual([...(caps.protocolVersions ?? [])].sort());
@@ -196,7 +198,7 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §B — host as MCP ser
196
198
  // answered -32022 — the leg was testing an unstated precedence
197
199
  // (mcp-integration.md §B: agreement is checked before selection).
198
200
  const mismatch = await hostRpc('tools/list', { _meta: { [META_V]: '2025-06-18', [META_C]: {} } }, { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/list' });
199
- if (mismatch.status === 404 || mismatch.status === 403) return;
201
+ if (mismatch.status === 404 || mismatch.status === 403) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${mismatch.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
200
202
  expect(mismatch.status, driver.describe('mcp-integration.md §B', 'header ≠ body MUST be refused 400 (HeaderMismatchError -32020) — fail closed')).toBe(400);
201
203
  expect(mismatch.body.error?.code).toBe(MCP_ERR.HEADER_MISMATCH);
202
204
  const unsupported = await hostRpc('tools/list', { _meta: { [META_V]: '1999-01-01', [META_C]: {} } }, { 'MCP-Protocol-Version': '1999-01-01', 'Mcp-Method': 'tools/list' });
@@ -22,6 +22,8 @@
22
22
 
23
23
  import { describe, it, expect } from 'vitest';
24
24
  import { driver } from '../lib/driver.js';
25
+ import { seamAbsent } from '../lib/soft-skip.js';
26
+ import { mcpServerMount } from '../lib/mcp-mount.js';
25
27
  import { behaviorGate } from '../lib/behavior-gate.js';
26
28
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
27
29
 
@@ -38,7 +40,7 @@ async function claimsCurrent(): Promise<boolean> {
38
40
  async function listAs(bearer?: string) {
39
41
  const headers: Record<string, string> = { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/list' };
40
42
  if (bearer) headers['authorization'] = `Bearer ${bearer}`;
41
- const res = await driver.post('/v1/host/sample/mcp', { jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: { [META_V]: '2026-07-28', [META_C]: {} } } }, { headers });
43
+ const res = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: { [META_V]: '2026-07-28', [META_C]: {} } } }, { headers });
42
44
  return { status: res.status, body: res.json as { result?: { tools?: unknown[]; cacheScope?: string; ttlMs?: number } } };
43
45
  }
44
46
 
@@ -46,7 +48,7 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §D — mcp-cache-tenan
46
48
  it('a per-caller list is cacheScope private; a public list is byte-identical across callers', async () => {
47
49
  if (!behaviorGate(PROFILE, await claimsCurrent())) return;
48
50
  const mine = await listAs();
49
- if (mine.status === 404 || mine.status === 403) return;
51
+ if (mine.status === 404 || mine.status === 403) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${mine.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
50
52
  expect(['public', 'private'], driver.describe('mcp-integration.md §D', 'cacheScope MUST be present on tools/list')).toContain(mine.body.result?.cacheScope);
51
53
  const other = process.env.OPENWOP_TEST_SECONDARY_API_KEY;
52
54
  if (!other) {
@@ -18,6 +18,8 @@
18
18
 
19
19
  import { describe, it, expect } from 'vitest';
20
20
  import { driver } from '../lib/driver.js';
21
+ import { seamAbsent } from '../lib/soft-skip.js';
22
+ import { mcpServerMount } from '../lib/mcp-mount.js';
21
23
  import { behaviorGate } from '../lib/behavior-gate.js';
22
24
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
23
25
 
@@ -38,10 +40,10 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §E — mcp-current-aut
38
40
  const { mcp, anon } = await disco();
39
41
  const claims = mcp?.supported === true && (mcp.profiles ?? []).includes(PROFILE) && mcp.serverMount?.supported === true;
40
42
  if (!behaviorGate(PROFILE, claims)) return;
41
- const authed = await driver.post('/v1/host/sample/mcp', REQ, { headers: HDR });
42
- if (authed.status === 404 || authed.status === 403) return; // mount not at the sample path
43
+ const authed = await driver.post(await mcpServerMount(), REQ, { headers: HDR });
44
+ if (authed.status === 404 || authed.status === 403) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${authed.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
43
45
  expect(authed.status, driver.describe('mcp-integration.md §E', 'the authenticated call MUST succeed at the same path, so a refusal below is not a wrong path')).toBe(200);
44
- const anonymous = await driver.post('/v1/host/sample/mcp', REQ, { headers: HDR, authenticated: false });
46
+ const anonymous = await driver.post(await mcpServerMount(), REQ, { headers: HDR, authenticated: false });
45
47
  if (anon?.supported === true) {
46
48
  // Anonymous is permitted only through the RFC 0132 surface; a 200 here is that surface answering.
47
49
  expect([200, 401, 403]).toContain(anonymous.status);
@@ -23,6 +23,7 @@
23
23
  import { describe, it, expect } from 'vitest';
24
24
  import { softSkip, seamAbsent } from '../lib/soft-skip.js';
25
25
  import { driver } from '../lib/driver.js';
26
+ import { mcpServerMount } from '../lib/mcp-mount.js';
26
27
  import { behaviorGate } from '../lib/behavior-gate.js';
27
28
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
28
29
  import { getMcpFakeServer } from '../lib/mcp-fake-server.js';
@@ -66,7 +67,7 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §D — mcp-extension-o
66
67
  const caps = await mcp();
67
68
  const claims = caps?.supported === true && (caps.profiles ?? []).includes('mcp-2026-07-28') && caps.serverMount?.supported === true;
68
69
  if (!behaviorGate('mcp-2026-07-28', claims)) return;
69
- const res = await driver.post('/v1/host/sample/mcp', {
70
+ const res = await driver.post(await mcpServerMount(), {
70
71
  jsonrpc: '2.0', id: 1, method: 'tools/list',
71
72
  params: { _meta: { [META_V]: '2026-07-28', [META_C]: { extensions: { 'io.example/authority': { admin: true } } }, 'io.example/authority': { grantScopes: ['*'] } } },
72
73
  }, { headers: { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/list' } });