@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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/schemas/CORPUS-STAMP.json +2 -2
- package/schemas/capabilities.schema.json +10 -0
- package/src/lib/discovery-capabilities.ts +21 -0
- package/src/lib/feedback.ts +2 -1
- package/src/lib/heartbeat.ts +2 -5
- package/src/lib/memoryAttribution.ts +2 -5
- package/src/lib/toolHooks.ts +2 -5
- package/src/scenarios/aiEnvelope.contractRefusal.test.ts +2 -1
- package/src/scenarios/aiEnvelope.correlationReplay.test.ts +2 -1
- package/src/scenarios/aiEnvelope.redaction.test.ts +2 -1
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +3 -3
- package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +2 -1
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -2
- package/src/scenarios/blob-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/blob-presign-expiry.test.ts +2 -1
- package/src/scenarios/blob-roundtrip.test.ts +0 -0
- package/src/scenarios/cache-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/cache-ttl-expiry.test.ts +2 -1
- package/src/scenarios/discovery-families-view.test.ts +38 -0
- package/src/scenarios/discovery.test.ts +6 -9
- package/src/scenarios/kv-atomic-increment.test.ts +2 -1
- package/src/scenarios/kv-cas.test.ts +2 -1
- package/src/scenarios/kv-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/kv-ttl-expiry.test.ts +2 -1
- package/src/scenarios/mcp-server-elicitation-bridge.test.ts +2 -1
- package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +2 -1
- package/src/scenarios/mcp-server-resource-roundtrip.test.ts +2 -1
- package/src/scenarios/mcp-server-sampling-bridge.test.ts +2 -1
- package/src/scenarios/mcp-server-tool-roundtrip.test.ts +2 -1
- package/src/scenarios/mcp-server-untrusted-args.test.ts +2 -1
- package/src/scenarios/mcp-toolcall-redaction.test.ts +3 -4
- package/src/scenarios/media-url-inline-cap.test.ts +3 -5
- package/src/scenarios/metric-emission.test.ts +3 -4
- package/src/scenarios/node-module-required-capabilities-shape.test.ts +2 -1
- package/src/scenarios/otel-emission-grpc.test.ts +3 -6
- package/src/scenarios/otel-emission.test.ts +2 -1
- package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +2 -1
- package/src/scenarios/otel-trace-propagation.test.ts +2 -1
- package/src/scenarios/pack-registry-isolation.test.ts +2 -1
- package/src/scenarios/pack-registry-publish.test.ts +2 -1
- package/src/scenarios/queue-ack-nack-dlq.test.ts +2 -1
- package/src/scenarios/queue-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/queue-publish-consume-roundtrip.test.ts +2 -1
- package/src/scenarios/search-bm25-roundtrip.test.ts +2 -1
- package/src/scenarios/sql-injection-rejection.test.ts +2 -1
- package/src/scenarios/sql-transaction-atomicity.test.ts +2 -1
- package/src/scenarios/stream-subscribe-from-beginning.test.ts +2 -1
- package/src/scenarios/table-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/table-cursor-pagination.test.ts +2 -1
- package/src/scenarios/table-schema-enforcement.test.ts +2 -1
- package/src/scenarios/vector-knn-roundtrip.test.ts +2 -1
- package/src/scenarios/versioned-composition-profiles.test.ts +18 -0
- package/src/scenarios/webhook-negative.test.ts +3 -3
- package/src/scenarios/webhook-sig-algorithm.test.ts +2 -1
- package/src/scenarios/webhook-signed-delivery.test.ts +3 -2
- package/src/scenarios/workflow-chain-host-expansion.test.ts +3 -6
package/package.json
CHANGED
|
@@ -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.
|
|
4
|
-
"corpusCommit": "
|
|
3
|
+
"suiteVersion": "1.135.1",
|
|
4
|
+
"corpusCommit": "821921ce3b4a6cbbc20fe1aafe81a7413e10c9d9"
|
|
5
5
|
}
|
|
@@ -3332,6 +3332,16 @@
|
|
|
3332
3332
|
"type": "boolean",
|
|
3333
3333
|
"description": "Host advertises a client-side MCP surface (ctx.mcp.*). See spec/v1/mcp-integration.md."
|
|
3334
3334
|
},
|
|
3335
|
+
"serverUrls": {
|
|
3336
|
+
"type": "array",
|
|
3337
|
+
"minItems": 1,
|
|
3338
|
+
"uniqueItems": true,
|
|
3339
|
+
"items": {
|
|
3340
|
+
"type": "string",
|
|
3341
|
+
"minLength": 1
|
|
3342
|
+
},
|
|
3343
|
+
"description": "Where the host's MCP server mount(s) answer JSON-RPC \u2014 an absolute URL or a path relative to the discovery origin (S25: a path is joined to the base URL, an absolute URL is used as-is). The v1.0 conformance baseline (`mcp-discoverability.test.ts`, spec/v1/mcp-integration.md \u00a7Conformance + interop) REQUIRES a non-empty `serverUrls` whenever `supported: true`, and since suite 1.134.0 every server-side scenario POSTs to `serverUrls[0]`. Required by the scenario and absent from this schema until 2026-08-17 (S27) \u2014 the second sibling host emitted it and measured the contradiction."
|
|
3344
|
+
},
|
|
3335
3345
|
"serverMount": {
|
|
3336
3346
|
"type": "object",
|
|
3337
3347
|
"description": "Server-side MCP composition (workflow IS an MCP server). When supported, the host mounts an MCP endpoint and routes inbound tools/call, resources/read, prompts/get into workflows. Inbound MUST be treated as `trustBoundary: 'untrusted'`.",
|
|
@@ -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/feedback.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/lib/heartbeat.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
|
@@ -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
|
|
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;
|
package/src/lib/toolHooks.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
|
Binary file
|
|
@@ -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
|
|
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
|
|
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', () => {
|
|
@@ -182,8 +183,7 @@ const AUTH_SCOPED_PROFILE = 'openwop-discovery-auth-scoped';
|
|
|
182
183
|
|
|
183
184
|
async function readDiscoveryCaps(): Promise<DiscoveryCaps | undefined> {
|
|
184
185
|
const disco = await driver.get('/.well-known/openwop', { authenticated: false });
|
|
185
|
-
return (disco.json as {
|
|
186
|
-
?.discovery;
|
|
186
|
+
return (discoveryFamilies(disco.json) as { discovery?: DiscoveryCaps }).discovery;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function isAuthScopedAdvertised(disc: DiscoveryCaps | undefined): boolean {
|
|
@@ -306,13 +306,10 @@ describe('discovery: auth-scoped is not an authorization oracle', () => {
|
|
|
306
306
|
}
|
|
307
307
|
expect(unauthorized.status).toBe(200);
|
|
308
308
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
);
|
|
312
|
-
const unauthorizedCaps = Object.keys(
|
|
313
|
-
(unauthorized.json as { capabilities?: Record<string, unknown> })?.capabilities ??
|
|
314
|
-
{},
|
|
315
|
-
);
|
|
309
|
+
// Root-first (RFC 0073); the deprecated `capabilities` wrapper is folded in
|
|
310
|
+
// so a root-only host and a wrapper-only host are compared the same way (S26).
|
|
311
|
+
const primaryCaps = Object.keys(discoveryFamilies(primary.json));
|
|
312
|
+
const unauthorizedCaps = Object.keys(discoveryFamilies(unauthorized.json));
|
|
316
313
|
|
|
317
314
|
// Spec annex line 69: "Hosts MUST NOT let scoped discovery become
|
|
318
315
|
// 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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -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
|
import { seamAbsent } from '../lib/soft-skip.js';
|
|
17
18
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
18
19
|
|
|
@@ -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
|
|
27
|
+
const top = discoveryFamilies(body);
|
|
27
28
|
const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
|
|
28
29
|
const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
|
|
29
30
|
return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
|
|
@@ -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 { seamAbsent } from '../lib/soft-skip.js';
|
|
12
13
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
13
14
|
|
|
@@ -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
|
|
22
|
+
const top = discoveryFamilies(body);
|
|
22
23
|
const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
|
|
23
24
|
const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
|
|
24
25
|
return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
|
|
@@ -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
|
import { seamAbsent } from '../lib/soft-skip.js';
|
|
14
15
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
15
16
|
|
|
@@ -20,7 +21,7 @@ interface DiscoveryDoc {
|
|
|
20
21
|
async function readCap(): Promise<Record<string, unknown> | null> {
|
|
21
22
|
const res = await driver.get('/.well-known/openwop');
|
|
22
23
|
const body = res.json as DiscoveryDoc | undefined;
|
|
23
|
-
const top = body
|
|
24
|
+
const top = discoveryFamilies(body);
|
|
24
25
|
const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
|
|
25
26
|
const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
|
|
26
27
|
return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { describe, it, expect } from 'vitest';
|
|
18
18
|
import { driver } from '../lib/driver.js';
|
|
19
|
+
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
19
20
|
import { seamAbsent } from '../lib/soft-skip.js';
|
|
20
21
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
21
22
|
|
|
@@ -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
|
|
30
|
+
const top = discoveryFamilies(body);
|
|
30
31
|
const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
|
|
31
32
|
const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
|
|
32
33
|
return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { describe, it, expect } from 'vitest';
|
|
16
16
|
import { driver } from '../lib/driver.js';
|
|
17
|
+
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
17
18
|
import { seamAbsent } from '../lib/soft-skip.js';
|
|
18
19
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
19
20
|
|
|
@@ -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
|
|
28
|
+
const top = discoveryFamilies(body);
|
|
28
29
|
const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
|
|
29
30
|
const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
|
|
30
31
|
return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
14
|
import { driver } from '../lib/driver.js';
|
|
15
|
+
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
15
16
|
import { seamAbsent } from '../lib/soft-skip.js';
|
|
16
17
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
17
18
|
|
|
@@ -22,7 +23,7 @@ interface DiscoveryDoc {
|
|
|
22
23
|
async function readCap(): Promise<Record<string, unknown> | null> {
|
|
23
24
|
const res = await driver.get('/.well-known/openwop');
|
|
24
25
|
const body = res.json as DiscoveryDoc | undefined;
|
|
25
|
-
const top = body
|
|
26
|
+
const top = discoveryFamilies(body);
|
|
26
27
|
const cur = (top && typeof top === 'object') ? (top as Record<string, unknown>)["mcp"] : undefined;
|
|
27
28
|
const final = (cur && typeof cur === 'object') ? (cur as Record<string, unknown>)["serverMount"] : undefined;
|
|
28
29
|
return (final && typeof final === 'object' ? (final as Record<string, unknown>) : null);
|
|
@@ -22,13 +22,12 @@
|
|
|
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 { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
25
|
+
import { capabilityFamily, discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
26
26
|
|
|
27
27
|
async function isMcpClientSupported(): Promise<boolean> {
|
|
28
28
|
const disco = await driver.get('/.well-known/openwop');
|
|
29
|
-
const caps = (disco.json as {
|
|
30
|
-
|
|
31
|
-
return caps?.mcpClient?.supported === true;
|
|
29
|
+
const caps = discoveryFamilies(disco.json) as { mcpClient?: { supported?: boolean } };
|
|
30
|
+
return caps.mcpClient?.supported === true;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
describe('mcp-toolcall-redaction: capability advertisement contract', () => {
|
|
@@ -32,7 +32,7 @@ import { readFileSync } from 'node:fs';
|
|
|
32
32
|
import { join } from 'node:path';
|
|
33
33
|
import { driver } from '../lib/driver.js';
|
|
34
34
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
35
|
-
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
35
|
+
import { capabilityFamily, discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
36
36
|
|
|
37
37
|
const MEDIA_KINDS = ['media.image', 'media.audio', 'media.file'] as const;
|
|
38
38
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
@@ -136,10 +136,8 @@ describe.skipIf(HTTP_SKIP)('media-url-inline-cap: advertisement shape (RFC 0055
|
|
|
136
136
|
// that emits a media.* envelope into a run (so no media payload appears).
|
|
137
137
|
const disc = await driver.get('/.well-known/openwop');
|
|
138
138
|
if (disc.status !== 200) return;
|
|
139
|
-
const caps = (disc.json as {
|
|
140
|
-
|
|
141
|
-
}).capabilities;
|
|
142
|
-
if (caps?.aiProviders?.maxInlineMediaBytes === undefined || caps.debugBundle?.supported !== true) {
|
|
139
|
+
const caps = discoveryFamilies(disc.json) as { aiProviders?: { maxInlineMediaBytes?: unknown }; debugBundle?: { supported?: unknown } };
|
|
140
|
+
if (caps.aiProviders?.maxInlineMediaBytes === undefined || caps.debugBundle?.supported !== true) {
|
|
143
141
|
return; // host doesn't serve media + export debug bundles — contract not exercisable
|
|
144
142
|
}
|
|
145
143
|
// Find a recent run and inspect its debug bundle for any media.* event.
|