@openwop/openwop-conformance 1.106.1 → 1.124.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.
- package/README.md +3 -3
- package/coverage.md +6 -0
- package/dist/cli.js +89 -45
- package/dist/lib/certification-bundle-verify.js +274 -0
- package/dist/lib/profiles.js +102 -19
- package/dist/lib/requirement-ledger.js +182 -0
- package/dist/lib/requirement-registry.js +110 -0
- package/dist/lib/scenario-disposition.js +249 -0
- package/dist/lib/soft-skip.js +82 -0
- package/fixtures/conformance-a2a-task-roundtrip.json +1 -1
- package/fixtures.md +1 -1
- package/package.json +1 -1
- package/schemas/CORPUS-STAMP.json +2 -2
- package/schemas/README.md +1 -0
- package/schemas/capabilities.schema.json +2 -2
- package/schemas/compensation-policy.schema.json +64 -0
- package/schemas/run-snapshot.schema.json +5 -0
- package/schemas/workflow-chain-pack-manifest.schema.json +125 -0
- package/schemas/workflow-definition.schema.json +4 -0
- package/src/cli.ts +99 -45
- package/src/lib/a2a-fake-peer.ts +290 -110
- package/src/lib/certification-bundle-verify.ts +356 -0
- package/src/lib/error-envelope.ts +77 -0
- package/src/lib/llm-cache-key-recipe.ts +6 -5
- package/src/lib/mcp-fake-server.ts +235 -72
- package/src/lib/profiles.ts +132 -19
- package/src/lib/requirement-ledger.ts +98 -1
- package/src/lib/requirement-registry.ts +47 -4
- package/src/lib/scenario-disposition.ts +291 -0
- package/src/lib/soft-skip.ts +89 -0
- package/src/lib/workflow-chain-expansion.ts +189 -0
- package/src/scenarios/a2a-1-0-agent-card.test.ts +179 -0
- package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +109 -0
- package/src/scenarios/a2a-card-runtime-consistency.test.ts +167 -0
- package/src/scenarios/a2a-peer-authority.test.ts +99 -0
- package/src/scenarios/a2a-task-roundtrip.test.ts +18 -6
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -2
- package/src/scenarios/agent-loop-stateful-resume.test.ts +3 -2
- package/src/scenarios/agent-loop-version5-shape.test.ts +2 -1
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +4 -3
- package/src/scenarios/approval-gate-flow.test.ts +5 -4
- package/src/scenarios/artifact-type-store-without-render.test.ts +2 -1
- package/src/scenarios/auth-scim-profile.test.ts +5 -4
- package/src/scenarios/authorization-fail-closed.test.ts +4 -3
- package/src/scenarios/certification-bundle-non-vacuous.test.ts +249 -0
- package/src/scenarios/certification-bundle-redaction.test.ts +182 -0
- package/src/scenarios/certification-floor-enforcement.test.ts +30 -6
- package/src/scenarios/chain-compensation-expansion.test.ts +243 -0
- package/src/scenarios/commitment-fired.test.ts +3 -2
- package/src/scenarios/compensation-behavior.test.ts +63 -0
- package/src/scenarios/compensation-profile.test.ts +144 -1
- package/src/scenarios/conformance-advertised-seam-required.test.ts +118 -0
- package/src/scenarios/conformance-execution-witness.test.ts +104 -0
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +3 -2
- package/src/scenarios/core-manifest-and-extension-registry.test.ts +26 -0
- package/src/scenarios/credential-payload-redaction.test.ts +4 -3
- package/src/scenarios/credentials-capability-shape.test.ts +4 -3
- package/src/scenarios/cross-engine-append-behavior.test.ts +5 -4
- package/src/scenarios/cross-engine-append-ordering.test.ts +3 -2
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +5 -4
- package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
- package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
- package/src/scenarios/data-residency-admission.test.ts +6 -6
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +5 -4
- package/src/scenarios/distillation-index-roundtrip.test.ts +3 -2
- package/src/scenarios/distillation-secret-carryforward.test.ts +3 -2
- package/src/scenarios/distillation-shape.test.ts +2 -1
- package/src/scenarios/distillation-stable-archive.test.ts +4 -3
- package/src/scenarios/distillation-token-budget.test.ts +4 -3
- package/src/scenarios/envelope-recovery-applied.test.ts +3 -2
- package/src/scenarios/envelope-refusal-shape.test.ts +4 -3
- package/src/scenarios/error-envelope-canonical-shape.test.ts +64 -0
- package/src/scenarios/feedback-correction-redaction.test.ts +4 -3
- package/src/scenarios/feedback-cross-tenant-isolation.test.ts +4 -3
- package/src/scenarios/feedback-fork-not-copied.test.ts +6 -5
- package/src/scenarios/feedback-on-terminal-run.test.ts +4 -3
- package/src/scenarios/feedback-record-and-list.test.ts +4 -3
- package/src/scenarios/feedback-unsupported-501.test.ts +3 -2
- package/src/scenarios/fixtures-valid.test.ts +16 -18
- package/src/scenarios/fs-path-traversal.test.ts +3 -2
- package/src/scenarios/heartbeat-capability-shape.test.ts +2 -1
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +4 -3
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +6 -5
- package/src/scenarios/heartbeat-runtime-bound.test.ts +4 -3
- package/src/scenarios/mcp-2026-07-28-discover.test.ts +207 -0
- package/src/scenarios/mcp-cache-tenant-scope.test.ts +66 -0
- package/src/scenarios/mcp-current-auth-boundary.test.ts +55 -0
- package/src/scenarios/mcp-extension-opacity.test.ts +79 -0
- package/src/scenarios/mcp-mrtr-roundtrip.test.ts +140 -0
- package/src/scenarios/mcp-stateless-request.test.ts +77 -0
- package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
- package/src/scenarios/mcp-toolcall-redaction.test.ts +2 -1
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +6 -5
- package/src/scenarios/memory-attribution-no-content.test.ts +4 -3
- package/src/scenarios/memory-attribution-replay-stable.test.ts +6 -5
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +4 -3
- package/src/scenarios/memory-compaction-event-emitted.test.ts +3 -2
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +6 -5
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +4 -3
- package/src/scenarios/memory-consolidation-idempotent.test.ts +4 -3
- package/src/scenarios/memory-injection-budget.test.ts +4 -3
- package/src/scenarios/metric-emission.test.ts +4 -3
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +4 -3
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +5 -4
- package/src/scenarios/multi-party-conversation-behavioral.test.ts +3 -2
- package/src/scenarios/multi-region-idempotency-behavior.test.ts +7 -6
- package/src/scenarios/multi-region-idempotency.test.ts +4 -3
- package/src/scenarios/normative-example-extraction.test.ts +242 -0
- package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +4 -3
- package/src/scenarios/oauth-capability-shape.test.ts +4 -3
- package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
- package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +309 -0
- package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -1
- package/src/scenarios/otel-emission-grpc.test.ts +4 -3
- package/src/scenarios/otel-emission.test.ts +4 -3
- package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +5 -4
- package/src/scenarios/otel-trace-propagation.test.ts +4 -3
- package/src/scenarios/pack-registry-isolation.test.ts +4 -3
- package/src/scenarios/pack-registry-publish.test.ts +65 -3
- package/src/scenarios/pack-registry.test.ts +22 -0
- package/src/scenarios/profile-discovery-core-alias.test.ts +137 -0
- package/src/scenarios/profileDerivation.test.ts +2 -1
- package/src/scenarios/provider-usage.test.ts +2 -2
- package/src/scenarios/rate-limit-envelope.test.ts +2 -1
- package/src/scenarios/registry-public.test.ts +6 -5
- package/src/scenarios/replay-divergence-at-refusal.test.ts +9 -8
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +19 -13
- package/src/scenarios/replay-llm-cache-key.test.ts +120 -47
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +5 -4
- package/src/scenarios/replay-side-effect-suppression.test.ts +20 -1
- package/src/scenarios/requirement-ledger.test.ts +29 -6
- package/src/scenarios/runner-ledger.test.ts +279 -0
- package/src/scenarios/runtime-requires-install-gate.test.ts +6 -5
- package/src/scenarios/sandbox-memory-cap.test.ts +3 -2
- package/src/scenarios/sandbox-mvp-behavior.test.ts +11 -10
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
- package/src/scenarios/sandbox-timeout-cap.test.ts +3 -2
- package/src/scenarios/stream-text-fixture.test.ts +5 -4
- package/src/scenarios/subrun-approval-fail-closed.test.ts +3 -2
- package/src/scenarios/subrun-approval-gate.test.ts +4 -3
- package/src/scenarios/subrun-attestation-shape.test.ts +2 -1
- package/src/scenarios/subrun-checksum-stable.test.ts +4 -3
- package/src/scenarios/table-schema-enforcement.test.ts +2 -2
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -2
- package/src/scenarios/tool-hooks-content-free.test.ts +3 -2
- package/src/scenarios/tool-hooks-rate-limit.test.ts +3 -2
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +3 -2
- package/src/scenarios/tool-hooks-shape.test.ts +2 -1
- package/src/scenarios/trigger-bridge-delivery.test.ts +3 -2
- package/src/scenarios/version-fold.test.ts +5 -4
- package/src/scenarios/voice-streamref-tenant-bound.test.ts +2 -1
- package/src/scenarios/voice-transcription-streaming.test.ts +2 -1
- package/src/scenarios/voice-transcription-unadvertised.test.ts +2 -1
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +5 -4
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
- package/src/scenarios/wasm-pack-load.test.ts +4 -3
- package/src/scenarios/wasm-pack-memory-cap.test.ts +4 -3
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +3 -2
- package/src/scenarios/webhook-signed-delivery.test.ts +4 -3
- package/src/scenarios/workflow-primary-output-annotation.test.ts +13 -23
- package/src/scenarios/workload-identity-behavior.test.ts +7 -4
- package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +3 -2
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +3 -2
- package/src/setup.ts +95 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 0153 §B — the host as a 2026-07-28 MCP server answers a core request
|
|
3
|
+
* with NO prior `initialize` and NO session header (invariant
|
|
4
|
+
* `mcp-header-body-consistent`, named by RFC 0153 §E — the agreement half is
|
|
5
|
+
* witnessed in `mcp-2026-07-28-discover.test.ts`).
|
|
6
|
+
*
|
|
7
|
+
* `mcp-integration.md` §B: under the current profile a host MUST NOT require
|
|
8
|
+
* `initialize` / `notifications/initialized` or any `Mcp-Session-Id` for a core
|
|
9
|
+
* request; every request self-describes in `_meta`; list results are stable per
|
|
10
|
+
* caller (not per connection) and carry `resultType` + cache hints (§D).
|
|
11
|
+
*
|
|
12
|
+
* Gate: `mcp.profiles ∋ mcp-2026-07-28` and `serverMount.supported`. A
|
|
13
|
+
* 2025-06-18 host soft-skips (`blocked`). Hard-fails under
|
|
14
|
+
* `OPENWOP_REQUIRE_BEHAVIOR=true`. Non-vacuity: the leg issues `tools/list`
|
|
15
|
+
* twice on two independent connections and asserts byte-equal tool lists —
|
|
16
|
+
* per-connection state would show up as drift.
|
|
17
|
+
*
|
|
18
|
+
* @see spec/v1/mcp-integration.md §"MCP 2026-07-28 versioned composition" §B, §D
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, it, expect } from 'vitest';
|
|
22
|
+
import { driver } from '../lib/driver.js';
|
|
23
|
+
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
24
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
25
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
26
|
+
|
|
27
|
+
const PROFILE = 'mcp-2026-07-28';
|
|
28
|
+
const META_V = 'io.modelcontextprotocol/protocolVersion';
|
|
29
|
+
const META_C = 'io.modelcontextprotocol/clientCapabilities';
|
|
30
|
+
const META_I = 'io.modelcontextprotocol/clientInfo';
|
|
31
|
+
|
|
32
|
+
interface McpCaps { readonly supported?: boolean; readonly profiles?: readonly string[]; readonly serverMount?: { supported?: boolean } }
|
|
33
|
+
async function claimsCurrent(): Promise<boolean> {
|
|
34
|
+
const disco = await driver.get('/.well-known/openwop');
|
|
35
|
+
const caps = capabilityFamily<McpCaps>(disco.json, 'mcp');
|
|
36
|
+
return caps?.supported === true && (caps.profiles ?? []).includes(PROFILE) && caps.serverMount?.supported === true;
|
|
37
|
+
}
|
|
38
|
+
async function list() {
|
|
39
|
+
const res = await driver.post(
|
|
40
|
+
'/v1/host/sample/mcp',
|
|
41
|
+
{ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: { [META_V]: '2026-07-28', [META_C]: {}, [META_I]: { name: 'openwop-conformance', version: 'suite' } } } },
|
|
42
|
+
{ headers: { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/list' } },
|
|
43
|
+
);
|
|
44
|
+
return { status: res.status, body: res.json as { result?: { resultType?: string; tools?: unknown[]; ttlMs?: number; cacheScope?: string }; error?: { code: number } } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §B — mcp-header-body-consistent, the method/name half (host as server, gated on mcp.profiles ∋ mcp-2026-07-28)', () => {
|
|
48
|
+
it('Mcp-Method ≠ body method, and Mcp-Name ≠ params.name, are refused 400 + -32020 (HeaderMismatchError) — the same fail-closed rule as the version header', async () => {
|
|
49
|
+
if (!behaviorGate(PROFILE, await claimsCurrent())) return;
|
|
50
|
+
const meta = { [META_V]: '2026-07-28', [META_C]: {}, [META_I]: { name: 'openwop-conformance', version: 'suite' } };
|
|
51
|
+
// (a) Mcp-Method header disagrees with the JSON-RPC method
|
|
52
|
+
const m = await driver.post('/v1/host/sample/mcp', { jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } }, { headers: { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'resources/list' } });
|
|
53
|
+
if (m.status === 404 || m.status === 403) return softSkip('blocked', `MCP server mount /v1/host/sample/mcp answered ${m.status}`);
|
|
54
|
+
expect(m.status, driver.describe('mcp-integration.md §B', 'Mcp-Method MUST equal the body method; disagreement MUST be refused 400 (mcp-header-body-consistent)')).toBe(400);
|
|
55
|
+
expect((m.json as { error?: { code?: number } }).error?.code, driver.describe('mcp-integration.md §B', 'the refusal is HeaderMismatchError -32020')).toBe(-32020);
|
|
56
|
+
// (b) Mcp-Name header disagrees with params.name on tools/call
|
|
57
|
+
const n = await driver.post('/v1/host/sample/mcp', { jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'echo', arguments: {}, _meta: meta } }, { headers: { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/call', 'Mcp-Name': 'not-echo' } });
|
|
58
|
+
expect(n.status, driver.describe('mcp-integration.md §B', 'Mcp-Name MUST equal params.name; disagreement MUST be refused 400 (mcp-header-body-consistent)')).toBe(400);
|
|
59
|
+
expect((n.json as { error?: { code?: number } }).error?.code, driver.describe('mcp-integration.md §B', 'the refusal is HeaderMismatchError -32020')).toBe(-32020);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §B — mcp-stateless-request (host as server, gated on mcp.profiles ∋ mcp-2026-07-28)', () => {
|
|
64
|
+
it('tools/list succeeds with no initialize and no session; result carries resultType + cache hints; two connections agree', async () => {
|
|
65
|
+
if (!behaviorGate(PROFILE, await claimsCurrent())) return;
|
|
66
|
+
const a = await list();
|
|
67
|
+
if (a.status === 404 || a.status === 403) return; // mount not at the sample path — covered by mcp-server-* legs
|
|
68
|
+
expect(a.status, driver.describe('mcp-integration.md §B', 'a core request MUST succeed without a prior initialize or a session header')).toBe(200);
|
|
69
|
+
expect(a.body.error, driver.describe('mcp-integration.md §B', `stateless tools/list MUST NOT error: ${JSON.stringify(a.body.error)}`)).toBeUndefined();
|
|
70
|
+
expect(a.body.result?.resultType, driver.describe('mcp-integration.md §B', 'every current-revision result carries resultType')).toBe('complete');
|
|
71
|
+
expect(typeof a.body.result?.ttlMs, driver.describe('mcp-integration.md §D', 'tools/list MUST carry ttlMs (>= 0)')).toBe('number');
|
|
72
|
+
expect((a.body.result?.ttlMs ?? -1) >= 0).toBe(true);
|
|
73
|
+
expect(['public', 'private'], driver.describe('mcp-integration.md §D', 'cacheScope MUST be public|private')).toContain(a.body.result?.cacheScope);
|
|
74
|
+
const b = await list();
|
|
75
|
+
expect(JSON.stringify(b.body.result?.tools), driver.describe('mcp-integration.md §B', 'list results MUST NOT vary per connection — two independent requests from the same caller MUST agree')).toBe(JSON.stringify(a.body.result?.tools));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
63
|
import { describe, it, expect } from 'vitest';
|
|
64
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
64
65
|
import { driver } from '../lib/driver.js';
|
|
65
66
|
import { getMcpFakeServer } from '../lib/mcp-fake-server.js';
|
|
66
67
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
@@ -186,7 +187,7 @@ describe('mcp-tool-roundtrip: server wire shape', () => {
|
|
|
186
187
|
'[mcp-tool-roundtrip] no MCP endpoint configured; set OPENWOP_MCP_FAKE_SERVER=true ' +
|
|
187
188
|
'or OPENWOP_MCP_REAL_SERVER_URL=<base-url>',
|
|
188
189
|
);
|
|
189
|
-
return;
|
|
190
|
+
return softSkip('blocked', 'precondition not met — `!probe` returned early (seam, prior step, or fixture unavailable)');
|
|
190
191
|
}
|
|
191
192
|
if (!probe.isReal) getMcpFakeServer()!.reset();
|
|
192
193
|
|
|
@@ -282,14 +283,14 @@ describe('mcp-tool-roundtrip: host-mediated tool invocation', () => {
|
|
|
282
283
|
if (!server) {
|
|
283
284
|
// eslint-disable-next-line no-console
|
|
284
285
|
console.warn('[mcp-tool-roundtrip] fake server not started; skipping host-mediated test');
|
|
285
|
-
return;
|
|
286
|
+
return softSkip('blocked', '[mcp-tool-roundtrip] fake server not started; skipping host-mediated test');
|
|
286
287
|
}
|
|
287
288
|
if (!isFixtureAdvertised(ROUNDTRIP_FIXTURE)) {
|
|
288
289
|
// eslint-disable-next-line no-console
|
|
289
290
|
console.warn(
|
|
290
291
|
`[mcp-tool-roundtrip] fixture ${ROUNDTRIP_FIXTURE} not advertised; skipping`,
|
|
291
292
|
);
|
|
292
|
-
return;
|
|
293
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(ROUNDTRIP_FIXTURE)` returned early');
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
server.reset();
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { describe, it, expect } from 'vitest';
|
|
23
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
23
24
|
import { driver } from '../lib/driver.js';
|
|
24
25
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
25
26
|
|
|
@@ -35,7 +36,7 @@ describe('mcp-toolcall-redaction: capability advertisement contract', () => {
|
|
|
35
36
|
if (!(await isMcpClientSupported())) {
|
|
36
37
|
// eslint-disable-next-line no-console
|
|
37
38
|
console.warn('[mcp-toolcall-redaction] host does not advertise mcpClient; skipping');
|
|
38
|
-
return;
|
|
39
|
+
return softSkip('inapplicable', '[mcp-toolcall-redaction] host does not advertise mcpClient; skipping');
|
|
39
40
|
}
|
|
40
41
|
const disco = await driver.get('/.well-known/openwop');
|
|
41
42
|
const cap = capabilityFamily((disco.json as {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
12
13
|
import { driver } from '../lib/driver.js';
|
|
13
14
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
14
15
|
import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvents } from '../lib/memoryAttribution.js';
|
|
@@ -16,16 +17,16 @@ import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvent
|
|
|
16
17
|
describe('memory-attribution-emits-on-write (RFC 0057 §A/§B)', () => {
|
|
17
18
|
it('an advertised host emits memory.written carrying a stable memoryId', async () => {
|
|
18
19
|
const cap = await readMemoryAttributionCap();
|
|
19
|
-
if (!emitsWriteEvents(cap)) return;
|
|
20
|
+
if (!emitsWriteEvents(cap)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!emitsWriteEvents(cap)` returned early');
|
|
20
21
|
const runId = await seedRun('mem-attr-emit');
|
|
21
|
-
if (!runId) return;
|
|
22
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
22
23
|
try {
|
|
23
24
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
24
25
|
} catch {
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
28
|
const events = await memoryWrittenEvents(runId);
|
|
28
|
-
if (events.length === 0) return
|
|
29
|
+
if (events.length === 0) return softSkip('blocked', 'run wrote no memory — soft-skip (events.length === 0)');
|
|
29
30
|
for (const e of events) {
|
|
30
31
|
const memoryId = (e.payload as { memoryId?: unknown } | undefined)?.memoryId;
|
|
31
32
|
expect(
|
|
@@ -37,9 +38,9 @@ describe('memory-attribution-emits-on-write (RFC 0057 §A/§B)', () => {
|
|
|
37
38
|
|
|
38
39
|
it('a host without the capability emits no memory.written', async () => {
|
|
39
40
|
const cap = await readMemoryAttributionCap();
|
|
40
|
-
if (emitsWriteEvents(cap)) return
|
|
41
|
+
if (emitsWriteEvents(cap)) return softSkip('inapplicable', 'advertised — N/A');
|
|
41
42
|
const runId = await seedRun('mem-attr-absent');
|
|
42
|
-
if (!runId) return;
|
|
43
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
43
44
|
try {
|
|
44
45
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
45
46
|
} catch {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { describe, it, expect } from 'vitest';
|
|
15
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
15
16
|
import { driver } from '../lib/driver.js';
|
|
16
17
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
17
18
|
import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvents } from '../lib/memoryAttribution.js';
|
|
@@ -19,16 +20,16 @@ import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvent
|
|
|
19
20
|
describe('memory-attribution-no-content (RFC 0057 §C)', () => {
|
|
20
21
|
it('memory.written payloads carry no entry content', async () => {
|
|
21
22
|
const cap = await readMemoryAttributionCap();
|
|
22
|
-
if (!emitsWriteEvents(cap)) return;
|
|
23
|
+
if (!emitsWriteEvents(cap)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!emitsWriteEvents(cap)` returned early');
|
|
23
24
|
const runId = await seedRun('mem-attr-no-content');
|
|
24
|
-
if (!runId) return;
|
|
25
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
25
26
|
try {
|
|
26
27
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
27
28
|
} catch {
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
const events = await memoryWrittenEvents(runId);
|
|
31
|
-
if (events.length === 0) return
|
|
32
|
+
if (events.length === 0) return softSkip('blocked', 'run wrote no memory — soft-skip (events.length === 0)');
|
|
32
33
|
for (const e of events) {
|
|
33
34
|
const payload = e.payload ?? {};
|
|
34
35
|
expect(
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
17
18
|
import { driver } from '../lib/driver.js';
|
|
18
19
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
19
20
|
import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvents } from '../lib/memoryAttribution.js';
|
|
@@ -26,22 +27,22 @@ function memoryIdOf(payload: Record<string, unknown> | undefined): string | null
|
|
|
26
27
|
describe('memory-attribution-replay-stable (RFC 0057 §D)', () => {
|
|
27
28
|
it('a replay-mode fork introduces no memory.written with a new memoryId', async () => {
|
|
28
29
|
const cap = await readMemoryAttributionCap();
|
|
29
|
-
if (!emitsWriteEvents(cap)) return;
|
|
30
|
+
if (!emitsWriteEvents(cap)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!emitsWriteEvents(cap)` returned early');
|
|
30
31
|
const runId = await seedRun('mem-attr-replay');
|
|
31
|
-
if (!runId) return;
|
|
32
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
32
33
|
try {
|
|
33
34
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
34
35
|
} catch {
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
const original = await memoryWrittenEvents(runId);
|
|
38
|
-
if (original.length === 0) return
|
|
39
|
+
if (original.length === 0) return softSkip('blocked', 'run wrote no memory — nothing to test');
|
|
39
40
|
const recordedIds = new Set(original.map((e) => memoryIdOf(e.payload)).filter((x): x is string => x !== null));
|
|
40
41
|
|
|
41
42
|
const fork = await driver.post(`/v1/runs/${runId}:fork`, { fromSeq: 0, mode: 'replay' });
|
|
42
|
-
if (fork.status !== 200 && fork.status !== 201) return
|
|
43
|
+
if (fork.status !== 200 && fork.status !== 201) return softSkip('inapplicable', 'replay fork unsupported — soft-skip (fork.status !== 200 && fork.status !== 201)');
|
|
43
44
|
const forkId = (fork.json as { runId?: string } | undefined)?.runId;
|
|
44
|
-
if (!forkId) return;
|
|
45
|
+
if (!forkId) return softSkip('blocked', 'precondition not met — `!forkId` returned early (seam, prior step, or fixture unavailable)');
|
|
45
46
|
try {
|
|
46
47
|
await pollUntilTerminal(forkId, { timeoutMs: 10_000 });
|
|
47
48
|
} catch {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { describe, it, expect } from 'vitest';
|
|
16
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
16
17
|
import { driver } from '../lib/driver.js';
|
|
17
18
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
18
19
|
import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvents } from '../lib/memoryAttribution.js';
|
|
@@ -20,16 +21,16 @@ import { readMemoryAttributionCap, emitsWriteEvents, seedRun, memoryWrittenEvent
|
|
|
20
21
|
describe('memory-attribution-tenant-scoped (RFC 0057 §C)', () => {
|
|
21
22
|
it("a run's memory.written events appear only on that run's stream", async () => {
|
|
22
23
|
const cap = await readMemoryAttributionCap();
|
|
23
|
-
if (!emitsWriteEvents(cap)) return;
|
|
24
|
+
if (!emitsWriteEvents(cap)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!emitsWriteEvents(cap)` returned early');
|
|
24
25
|
const runId = await seedRun('mem-attr-cti');
|
|
25
|
-
if (!runId) return;
|
|
26
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
26
27
|
try {
|
|
27
28
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
28
29
|
} catch {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
const events = await memoryWrittenEvents(runId);
|
|
32
|
-
if (events.length === 0) return;
|
|
33
|
+
if (events.length === 0) return softSkip('blocked', 'precondition not met — `events.length === 0` returned early (seam, prior step, or fixture unavailable)');
|
|
33
34
|
// Every memory.written we read came from THIS run's /events stream; if the
|
|
34
35
|
// host echoes a runId in the event it MUST be this run's (no cross-run leak).
|
|
35
36
|
for (const e of events) {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { describe, it, expect } from 'vitest';
|
|
28
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
28
29
|
import { driver } from '../lib/driver.js';
|
|
29
30
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
30
31
|
|
|
@@ -50,12 +51,12 @@ describe('memory-compaction-event-emitted: canonical memory.compacted payload sh
|
|
|
50
51
|
if (!(await isCompactionAdvertised())) {
|
|
51
52
|
// eslint-disable-next-line no-console
|
|
52
53
|
console.warn('[rfc0012-event] capabilities.memory.compaction.supported not advertised; skipping');
|
|
53
|
-
return;
|
|
54
|
+
return softSkip('inapplicable', '[rfc0012-event] capabilities.memory.compaction.supported not advertised; skipping');
|
|
54
55
|
}
|
|
55
56
|
if (!(await isTestSeamReachable())) {
|
|
56
57
|
// eslint-disable-next-line no-console
|
|
57
58
|
console.warn('[rfc0012-event] test seam unreachable; skipping');
|
|
58
|
-
return;
|
|
59
|
+
return softSkip('blocked', '[rfc0012-event] test seam unreachable; skipping');
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
const seed = await driver.post('/v1/test/memory/seed', {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { describe, it, expect } from 'vitest';
|
|
22
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
22
23
|
import { driver } from '../lib/driver.js';
|
|
23
24
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
24
25
|
|
|
@@ -49,12 +50,12 @@ describe('memory-compaction-provenance-tag: compacted-from:<id> tag follows §C
|
|
|
49
50
|
if (!(await isCompactionAdvertised())) {
|
|
50
51
|
// eslint-disable-next-line no-console
|
|
51
52
|
console.warn('[rfc0012-tag] capabilities.memory.compaction.supported not advertised; skipping');
|
|
52
|
-
return;
|
|
53
|
+
return softSkip('inapplicable', '[rfc0012-tag] capabilities.memory.compaction.supported not advertised; skipping');
|
|
53
54
|
}
|
|
54
55
|
if (!(await isTestSeamReachable())) {
|
|
55
56
|
// eslint-disable-next-line no-console
|
|
56
57
|
console.warn('[rfc0012-tag] test seam unreachable; skipping');
|
|
57
|
-
return;
|
|
58
|
+
return softSkip('blocked', '[rfc0012-tag] test seam unreachable; skipping');
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// Seed + compact.
|
|
@@ -88,7 +89,7 @@ describe('memory-compaction-provenance-tag: compacted-from:<id> tag follows §C
|
|
|
88
89
|
if (listRes.status === 404) {
|
|
89
90
|
// eslint-disable-next-line no-console
|
|
90
91
|
console.warn('[rfc0012-tag] host does not expose memory:list at /v1/memory/{ref}; skipping tag inspection (canonical provenance signal remains the memory.compacted event itself)');
|
|
91
|
-
return;
|
|
92
|
+
return softSkip('blocked', '[rfc0012-tag] host does not expose memory:list at /v1/memory/{ref}; skipping tag inspection (canonical provenance signal remains the memory.compacted event itself)');
|
|
92
93
|
}
|
|
93
94
|
expect(listRes.status, 'memory:list MUST return 200 when reachable').toBe(200);
|
|
94
95
|
|
|
@@ -98,7 +99,7 @@ describe('memory-compaction-provenance-tag: compacted-from:<id> tag follows §C
|
|
|
98
99
|
if (!output) {
|
|
99
100
|
// eslint-disable-next-line no-console
|
|
100
101
|
console.warn(`[rfc0012-tag] outputId ${outputId} not visible via memory:list; cannot inspect tags`);
|
|
101
|
-
return;
|
|
102
|
+
return softSkip('blocked', '[rfc0012-tag] outputId … not visible via memory:list; cannot inspect tags');
|
|
102
103
|
}
|
|
103
104
|
const tags = output.tags ?? [];
|
|
104
105
|
|
|
@@ -107,7 +108,7 @@ describe('memory-compaction-provenance-tag: compacted-from:<id> tag follows §C
|
|
|
107
108
|
if (provenance === undefined) {
|
|
108
109
|
// eslint-disable-next-line no-console
|
|
109
110
|
console.warn('[rfc0012-tag] output entry has no compacted-from:<id> tag — RFC 0012 §C is SHOULD, not MUST; pass with warning');
|
|
110
|
-
return;
|
|
111
|
+
return softSkip('inapplicable', '[rfc0012-tag] output entry has no compacted-from:<id> tag — RFC 0012 §C is SHOULD, not MUST; pass with warning');
|
|
111
112
|
}
|
|
112
113
|
expect(provenance, driver.describe(
|
|
113
114
|
'RFC 0012 §C',
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { describe, it, expect } from 'vitest';
|
|
30
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
30
31
|
import { driver } from '../lib/driver.js';
|
|
31
32
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
32
33
|
|
|
@@ -54,12 +55,12 @@ describe('memory-compaction-sr1-carry-forward: derived content passes the BYOK r
|
|
|
54
55
|
if (!(await isCompactionAdvertised())) {
|
|
55
56
|
// eslint-disable-next-line no-console
|
|
56
57
|
console.warn('[rfc0012-sr1] capabilities.memory.compaction.supported not advertised; skipping');
|
|
57
|
-
return;
|
|
58
|
+
return softSkip('inapplicable', '[rfc0012-sr1] capabilities.memory.compaction.supported not advertised; skipping');
|
|
58
59
|
}
|
|
59
60
|
if (!(await isTestSeamReachable())) {
|
|
60
61
|
// eslint-disable-next-line no-console
|
|
61
62
|
console.warn('[rfc0012-sr1] test seam /v1/test/memory/compact unreachable; skipping (set host\'s OPENWOP_TEST_TRIGGER_COMPACTION=true)');
|
|
62
|
-
return;
|
|
63
|
+
return softSkip('blocked', '[rfc0012-sr1] test seam /v1/test/memory/compact unreachable; skipping (set host\'s OPENWOP_TEST_TRIGGER_COMPACTION=true)');
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// 1. Seed source entries containing:
|
|
@@ -99,7 +100,7 @@ describe('memory-compaction-sr1-carry-forward: derived content passes the BYOK r
|
|
|
99
100
|
if (typeof event.outputContent !== 'string') {
|
|
100
101
|
// eslint-disable-next-line no-console
|
|
101
102
|
console.warn('[rfc0012-sr1] test seam did not return outputContent; the wire-level memory.compacted shape does not surface content so without a host-side seam we cannot verify §D end-to-end. Skipping.');
|
|
102
|
-
return;
|
|
103
|
+
return softSkip('blocked', '[rfc0012-sr1] test seam did not return outputContent; the wire-level memory.compacted shape does not surface content so without a host-side seam we cannot verify §D end-to-end. Skipping.');
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
// The load-bearing assertion: the PERSISTED entry content (what
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { describe, it, expect } from 'vitest';
|
|
25
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
25
26
|
import { driver } from '../lib/driver.js';
|
|
26
27
|
|
|
27
28
|
interface ConsolidationCaps {
|
|
@@ -41,13 +42,13 @@ async function consolidationSupported(): Promise<boolean> {
|
|
|
41
42
|
|
|
42
43
|
describe('memory-consolidation-idempotent: pass contract (RFC 0068 §D, capability-gated)', () => {
|
|
43
44
|
it('a consolidation pass reduces or holds entry count and is idempotent on a stable corpus', async () => {
|
|
44
|
-
if (!(await consolidationSupported())) return
|
|
45
|
+
if (!(await consolidationSupported())) return softSkip('inapplicable', 'capability absent — gated skip');
|
|
45
46
|
|
|
46
47
|
const first = await driver.post('/v1/host/sample/memory/consolidate', {
|
|
47
48
|
memoryRef: 'mem://conformance/consolidation',
|
|
48
49
|
includeSecretCanary: true,
|
|
49
50
|
});
|
|
50
|
-
if (first.status === 404 || first.status === 501) return
|
|
51
|
+
if (first.status === 404 || first.status === 501) return softSkip('blocked', 'seam not wired — soft-skip');
|
|
51
52
|
|
|
52
53
|
expect(first.status, driver.describe('RFC 0068 §D', 'an advertised consolidation seam MUST succeed')).toBe(200);
|
|
53
54
|
const r1 = first.json as ConsolidateResult;
|
|
@@ -59,7 +60,7 @@ describe('memory-consolidation-idempotent: pass contract (RFC 0068 §D, capabili
|
|
|
59
60
|
const second = await driver.post('/v1/host/sample/memory/consolidate', {
|
|
60
61
|
memoryRef: 'mem://conformance/consolidation',
|
|
61
62
|
});
|
|
62
|
-
if (second.status === 404 || second.status === 501) return;
|
|
63
|
+
if (second.status === 404 || second.status === 501) return softSkip('blocked', 'precondition not met — `second.status === 404 || second.status === 501` returned early (seam, prior step, or fixture unavailable)');
|
|
63
64
|
const r2 = second.json as ConsolidateResult;
|
|
64
65
|
expect(
|
|
65
66
|
r2.event?.inputCount,
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { describe, it, expect } from 'vitest';
|
|
31
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
31
32
|
import { driver } from '../lib/driver.js';
|
|
32
33
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
33
34
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
@@ -110,7 +111,7 @@ describe('memory-injection-budget (RFC 0113)', () => {
|
|
|
110
111
|
it('token-bounds the injection read, omits the over-budget entry, and preserves SR-1 + CTI-1', async () => {
|
|
111
112
|
const mem = await readCapabilityFamily<MemoryCap>('memory');
|
|
112
113
|
if (!behaviorGate(PROFILE, mem?.injectionBudget?.supported === true)) return;
|
|
113
|
-
if (!isFixtureAdvertised(FIXTURE)) return
|
|
114
|
+
if (!isFixtureAdvertised(FIXTURE)) return softSkip('inapplicable', `host does not advertise the ${FIXTURE} conformance fixture`);
|
|
114
115
|
|
|
115
116
|
const v = await driveFixtureVariables();
|
|
116
117
|
expect(v, 'fixture MUST surface run variables').toBeDefined();
|
|
@@ -169,9 +170,9 @@ describe('memory-injection-budget (RFC 0113)', () => {
|
|
|
169
170
|
if (!advertisesSemanticSearch(mem)) {
|
|
170
171
|
// eslint-disable-next-line no-console
|
|
171
172
|
console.warn(`[${PROFILE}] memory.search semantic not advertised; relevance leg soft-skipped`);
|
|
172
|
-
return;
|
|
173
|
+
return softSkip('inapplicable', 'host does not advertise memory.search semantic ranking — the relevance leg does not apply');
|
|
173
174
|
}
|
|
174
|
-
if (!isFixtureAdvertised(FIXTURE)) return;
|
|
175
|
+
if (!isFixtureAdvertised(FIXTURE)) return softSkip('inapplicable', `host does not advertise the ${FIXTURE} conformance fixture`);
|
|
175
176
|
|
|
176
177
|
const v = await driveFixtureVariables();
|
|
177
178
|
expect(v, 'fixture MUST surface run variables').toBeDefined();
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { describe, it, expect } from 'vitest';
|
|
22
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
22
23
|
import { driver } from '../lib/driver.js';
|
|
23
24
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
24
25
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
@@ -69,7 +70,7 @@ describe('metric-emission: canonical openwop.* metrics arrive at the collector',
|
|
|
69
70
|
console.warn(
|
|
70
71
|
'[metric-emission] collector not started; set OPENWOP_OTEL_COLLECTOR=true to run',
|
|
71
72
|
);
|
|
72
|
-
return;
|
|
73
|
+
return softSkip('blocked', 'precondition not met — `!getCollector()` returned early (seam, prior step, or fixture unavailable)');
|
|
73
74
|
}
|
|
74
75
|
const metricsCaps = await metricsAdvertised();
|
|
75
76
|
if (!metricsCaps?.supported) {
|
|
@@ -77,12 +78,12 @@ describe('metric-emission: canonical openwop.* metrics arrive at the collector',
|
|
|
77
78
|
console.warn(
|
|
78
79
|
'[metric-emission] host does not advertise observability.metrics.supported; skipping',
|
|
79
80
|
);
|
|
80
|
-
return;
|
|
81
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!metricsCaps?.supported` returned early');
|
|
81
82
|
}
|
|
82
83
|
if (!isFixtureAdvertised(FIXTURE)) {
|
|
83
84
|
// eslint-disable-next-line no-console
|
|
84
85
|
console.warn(`[metric-emission] ${FIXTURE} not advertised; skipping`);
|
|
85
|
-
return;
|
|
86
|
+
return softSkip('inapplicable', '[metric-emission] … not advertised; skipping');
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
const collector = getCollector()!;
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import { describe, it, expect } from 'vitest';
|
|
50
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
50
51
|
import { driver } from '../lib/driver.js';
|
|
51
52
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
52
53
|
import { pollUntil } from '../lib/polling.js';
|
|
@@ -84,11 +85,11 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
84
85
|
describe.skipIf(HTTP_SKIP)('multi-agent-confidence-escalation: capability shape (RFC 0039 §A)', () => {
|
|
85
86
|
it('confidenceEscalationFloor (when advertised) MUST be in [0.5, 1.0]', async () => {
|
|
86
87
|
const d = await readDiscovery();
|
|
87
|
-
if (d === null) return;
|
|
88
|
+
if (d === null) return softSkip('blocked', 'precondition not met — `d === null` returned early (seam, prior step, or fixture unavailable)');
|
|
88
89
|
const em = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
89
|
-
if (em === undefined) return;
|
|
90
|
+
if (em === undefined) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `em === undefined` returned early');
|
|
90
91
|
const floor = em.confidenceEscalationFloor;
|
|
91
|
-
if (floor === undefined) return;
|
|
92
|
+
if (floor === undefined) return softSkip('blocked', 'precondition not met — `floor === undefined` returned early (seam, prior step, or fixture unavailable)');
|
|
92
93
|
expect(
|
|
93
94
|
typeof floor === 'number' && Number.isFinite(floor) && floor >= 0.5 && floor <= 1.0,
|
|
94
95
|
driver.describe(
|
|
@@ -105,7 +106,7 @@ describe.skipIf(BEHAVIORAL_SKIP)('multi-agent-confidence-escalation: behavioral
|
|
|
105
106
|
const supported = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.supported === true;
|
|
106
107
|
const versionRaw = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.version;
|
|
107
108
|
const version = typeof versionRaw === 'number' ? versionRaw : 0;
|
|
108
|
-
if (!supported || version < 2) return
|
|
109
|
+
if (!supported || version < 2) return softSkip('inapplicable', 'soft-skip — `version: 1` hosts pass via this absence (!supported || version < 2)');
|
|
109
110
|
|
|
110
111
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
111
112
|
expect(create.status).toBe(201);
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { describe, it, expect } from 'vitest';
|
|
34
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
34
35
|
import { driver } from '../lib/driver.js';
|
|
35
36
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
36
37
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
@@ -62,9 +63,9 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
62
63
|
describe.skipIf(HTTP_SKIP)('multi-agent-handoff-state-machine: advertisement shape (RFC 0037 §C)', () => {
|
|
63
64
|
it('capabilities.multiAgent.executionModel (when present) conforms to RFC 0037 §C', async () => {
|
|
64
65
|
const d = await readDiscovery();
|
|
65
|
-
if (d === null) return
|
|
66
|
+
if (d === null) return softSkip('blocked', 'discovery unavailable — skip (d === null)');
|
|
66
67
|
const executionModel = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
67
|
-
if (executionModel === undefined) return
|
|
68
|
+
if (executionModel === undefined) return softSkip('inapplicable', 'host doesn\'t advertise — soft-skip');
|
|
68
69
|
expect(
|
|
69
70
|
typeof executionModel.supported,
|
|
70
71
|
driver.describe(
|
|
@@ -106,7 +107,7 @@ describe.skipIf(BEHAVIORAL_SKIP)('multi-agent-handoff-state-machine: behavioral
|
|
|
106
107
|
it('happy-path: dispatch.began → dispatch.succeeded → child.completed → output.harvested fire in causation order', async () => {
|
|
107
108
|
const d = await readDiscovery();
|
|
108
109
|
const advertised = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.supported === true;
|
|
109
|
-
if (!advertised) return
|
|
110
|
+
if (!advertised) return softSkip('inapplicable', 'soft-skip — host honest about not implementing');
|
|
110
111
|
|
|
111
112
|
const create = await driver.post('/v1/runs', { workflowId: PARENT_FIXTURE });
|
|
112
113
|
expect(create.status).toBe(201);
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import { describe, it, expect } from 'vitest';
|
|
50
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
50
51
|
import { driver } from '../lib/driver.js';
|
|
51
52
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
52
53
|
|
|
@@ -80,12 +81,12 @@ describe.skipIf(HTTP_SKIP)('multi-agent-memory-lifecycle: advertisement shape (R
|
|
|
80
81
|
const d = await readDiscovery();
|
|
81
82
|
if (d === null) {
|
|
82
83
|
ctx.skip();
|
|
83
|
-
return;
|
|
84
|
+
return softSkip('blocked', 'precondition not met — `d === null` returned early (seam, prior step, or fixture unavailable)');
|
|
84
85
|
}
|
|
85
86
|
const ccmc = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.crossChildMemoryConcurrency;
|
|
86
87
|
if (ccmc === undefined) {
|
|
87
88
|
ctx.skip(); // optional advertisement — host hasn't opted in
|
|
88
|
-
return;
|
|
89
|
+
return softSkip('blocked', 'precondition not met — `ccmc === undefined` returned early (seam, prior step, or fixture unavailable)');
|
|
89
90
|
}
|
|
90
91
|
expect(
|
|
91
92
|
ccmc === 'strict' || ccmc === 'advisory',
|
|
@@ -134,7 +135,7 @@ describe.skipIf(HTTP_SKIP)('multi-agent-memory-lifecycle: behavioral (RFC 0039
|
|
|
134
135
|
const d = await readDiscovery();
|
|
135
136
|
if (d === null) {
|
|
136
137
|
ctx.skip();
|
|
137
|
-
return;
|
|
138
|
+
return softSkip('blocked', 'precondition not met — `d === null` returned early (seam, prior step, or fixture unavailable)');
|
|
138
139
|
}
|
|
139
140
|
const v = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.version;
|
|
140
141
|
const memorySupported = capabilityFamily<{ supported?: unknown }>(d, 'memory')?.supported;
|
|
@@ -142,7 +143,7 @@ describe.skipIf(HTTP_SKIP)('multi-agent-memory-lifecycle: behavioral (RFC 0039
|
|
|
142
143
|
const expiredRunId = process.env.OPENWOP_TEST_EXPIRED_REPLAY_RUN_ID;
|
|
143
144
|
if (!phase2OrLater || memorySupported !== true || !expiredRunId) {
|
|
144
145
|
ctx.skip();
|
|
145
|
-
return;
|
|
146
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!phase2OrLater || memorySupported !== true || !expiredRunId` returned early');
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
const fromSeq = 0;
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
import { describe, it, expect } from 'vitest';
|
|
41
|
+
import { seamAbsent } from '../lib/soft-skip.js';
|
|
41
42
|
import { driver } from '../lib/driver.js';
|
|
42
43
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
43
44
|
import { isMultiPartyConversationSupported } from '../lib/multi-agent-capabilities.js';
|
|
@@ -80,7 +81,7 @@ describe('multi-party-conversation-behavioral (RFC 0101 §Conformance)', () => {
|
|
|
80
81
|
// ---- Open a 3-agent council via the conformance seam ------------------
|
|
81
82
|
const convId = 'conf:multi-party:council-q1';
|
|
82
83
|
const opened = await openMultiPartyConversation({ conversationId: convId, participants: ROSTER });
|
|
83
|
-
if (opened.unwired) return
|
|
84
|
+
if (opened.unwired) return seamAbsent('host advertises multiPartyConversation but the open-conversation seam is not wired');
|
|
84
85
|
expect(
|
|
85
86
|
opened.status === 200,
|
|
86
87
|
driver.describe('RFC 0101 §Spec', 'a conforming 3-agent council MUST open (≤ maxParticipants)'),
|
|
@@ -88,7 +89,7 @@ describe('multi-party-conversation-behavioral (RFC 0101 §Conformance)', () => {
|
|
|
88
89
|
|
|
89
90
|
// ---- MUST 1: POSITIVE — a roster-valid attributed turn is accepted ----
|
|
90
91
|
const ok = await exchangeMultiPartyTurn({ conversationId: convId, turn: agentTurn('host:advisor-cfo', 1) });
|
|
91
|
-
if (ok.unwired) return;
|
|
92
|
+
if (ok.unwired) return seamAbsent('host advertises multiPartyConversation but the conversation seam is not wired');
|
|
92
93
|
expect(
|
|
93
94
|
ok.status === 200,
|
|
94
95
|
driver.describe('RFC 0101 §Spec', "a role:'agent' turn with an in-roster speakerId MUST be accepted"),
|