@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.
Files changed (166) hide show
  1. package/README.md +3 -3
  2. package/coverage.md +6 -0
  3. package/dist/cli.js +89 -45
  4. package/dist/lib/certification-bundle-verify.js +274 -0
  5. package/dist/lib/profiles.js +102 -19
  6. package/dist/lib/requirement-ledger.js +182 -0
  7. package/dist/lib/requirement-registry.js +110 -0
  8. package/dist/lib/scenario-disposition.js +249 -0
  9. package/dist/lib/soft-skip.js +82 -0
  10. package/fixtures/conformance-a2a-task-roundtrip.json +1 -1
  11. package/fixtures.md +1 -1
  12. package/package.json +1 -1
  13. package/schemas/CORPUS-STAMP.json +2 -2
  14. package/schemas/README.md +1 -0
  15. package/schemas/capabilities.schema.json +2 -2
  16. package/schemas/compensation-policy.schema.json +64 -0
  17. package/schemas/run-snapshot.schema.json +5 -0
  18. package/schemas/workflow-chain-pack-manifest.schema.json +125 -0
  19. package/schemas/workflow-definition.schema.json +4 -0
  20. package/src/cli.ts +99 -45
  21. package/src/lib/a2a-fake-peer.ts +290 -110
  22. package/src/lib/certification-bundle-verify.ts +356 -0
  23. package/src/lib/error-envelope.ts +77 -0
  24. package/src/lib/llm-cache-key-recipe.ts +6 -5
  25. package/src/lib/mcp-fake-server.ts +235 -72
  26. package/src/lib/profiles.ts +132 -19
  27. package/src/lib/requirement-ledger.ts +98 -1
  28. package/src/lib/requirement-registry.ts +47 -4
  29. package/src/lib/scenario-disposition.ts +291 -0
  30. package/src/lib/soft-skip.ts +89 -0
  31. package/src/lib/workflow-chain-expansion.ts +189 -0
  32. package/src/scenarios/a2a-1-0-agent-card.test.ts +179 -0
  33. package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +109 -0
  34. package/src/scenarios/a2a-card-runtime-consistency.test.ts +167 -0
  35. package/src/scenarios/a2a-peer-authority.test.ts +99 -0
  36. package/src/scenarios/a2a-task-roundtrip.test.ts +18 -6
  37. package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -2
  38. package/src/scenarios/agent-loop-stateful-resume.test.ts +3 -2
  39. package/src/scenarios/agent-loop-version5-shape.test.ts +2 -1
  40. package/src/scenarios/agent-loop-workspace-snapshot.test.ts +4 -3
  41. package/src/scenarios/approval-gate-flow.test.ts +5 -4
  42. package/src/scenarios/artifact-type-store-without-render.test.ts +2 -1
  43. package/src/scenarios/auth-scim-profile.test.ts +5 -4
  44. package/src/scenarios/authorization-fail-closed.test.ts +4 -3
  45. package/src/scenarios/certification-bundle-non-vacuous.test.ts +249 -0
  46. package/src/scenarios/certification-bundle-redaction.test.ts +182 -0
  47. package/src/scenarios/certification-floor-enforcement.test.ts +30 -6
  48. package/src/scenarios/chain-compensation-expansion.test.ts +243 -0
  49. package/src/scenarios/commitment-fired.test.ts +3 -2
  50. package/src/scenarios/compensation-behavior.test.ts +63 -0
  51. package/src/scenarios/compensation-profile.test.ts +144 -1
  52. package/src/scenarios/conformance-advertised-seam-required.test.ts +118 -0
  53. package/src/scenarios/conformance-execution-witness.test.ts +104 -0
  54. package/src/scenarios/conversationCapabilityNegotiation.test.ts +3 -2
  55. package/src/scenarios/core-manifest-and-extension-registry.test.ts +26 -0
  56. package/src/scenarios/credential-payload-redaction.test.ts +4 -3
  57. package/src/scenarios/credentials-capability-shape.test.ts +4 -3
  58. package/src/scenarios/cross-engine-append-behavior.test.ts +5 -4
  59. package/src/scenarios/cross-engine-append-ordering.test.ts +3 -2
  60. package/src/scenarios/cross-host-ancestry-endpoint.test.ts +5 -4
  61. package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
  62. package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
  63. package/src/scenarios/data-residency-admission.test.ts +6 -6
  64. package/src/scenarios/deadletter-retry-exhaustion.test.ts +5 -4
  65. package/src/scenarios/distillation-index-roundtrip.test.ts +3 -2
  66. package/src/scenarios/distillation-secret-carryforward.test.ts +3 -2
  67. package/src/scenarios/distillation-shape.test.ts +2 -1
  68. package/src/scenarios/distillation-stable-archive.test.ts +4 -3
  69. package/src/scenarios/distillation-token-budget.test.ts +4 -3
  70. package/src/scenarios/envelope-recovery-applied.test.ts +3 -2
  71. package/src/scenarios/envelope-refusal-shape.test.ts +4 -3
  72. package/src/scenarios/error-envelope-canonical-shape.test.ts +64 -0
  73. package/src/scenarios/feedback-correction-redaction.test.ts +4 -3
  74. package/src/scenarios/feedback-cross-tenant-isolation.test.ts +4 -3
  75. package/src/scenarios/feedback-fork-not-copied.test.ts +6 -5
  76. package/src/scenarios/feedback-on-terminal-run.test.ts +4 -3
  77. package/src/scenarios/feedback-record-and-list.test.ts +4 -3
  78. package/src/scenarios/feedback-unsupported-501.test.ts +3 -2
  79. package/src/scenarios/fixtures-valid.test.ts +16 -18
  80. package/src/scenarios/fs-path-traversal.test.ts +3 -2
  81. package/src/scenarios/heartbeat-capability-shape.test.ts +2 -1
  82. package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +4 -3
  83. package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +6 -5
  84. package/src/scenarios/heartbeat-runtime-bound.test.ts +4 -3
  85. package/src/scenarios/mcp-2026-07-28-discover.test.ts +207 -0
  86. package/src/scenarios/mcp-cache-tenant-scope.test.ts +66 -0
  87. package/src/scenarios/mcp-current-auth-boundary.test.ts +55 -0
  88. package/src/scenarios/mcp-extension-opacity.test.ts +79 -0
  89. package/src/scenarios/mcp-mrtr-roundtrip.test.ts +140 -0
  90. package/src/scenarios/mcp-stateless-request.test.ts +77 -0
  91. package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
  92. package/src/scenarios/mcp-toolcall-redaction.test.ts +2 -1
  93. package/src/scenarios/memory-attribution-emits-on-write.test.ts +6 -5
  94. package/src/scenarios/memory-attribution-no-content.test.ts +4 -3
  95. package/src/scenarios/memory-attribution-replay-stable.test.ts +6 -5
  96. package/src/scenarios/memory-attribution-tenant-scoped.test.ts +4 -3
  97. package/src/scenarios/memory-compaction-event-emitted.test.ts +3 -2
  98. package/src/scenarios/memory-compaction-provenance-tag.test.ts +6 -5
  99. package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +4 -3
  100. package/src/scenarios/memory-consolidation-idempotent.test.ts +4 -3
  101. package/src/scenarios/memory-injection-budget.test.ts +4 -3
  102. package/src/scenarios/metric-emission.test.ts +4 -3
  103. package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
  104. package/src/scenarios/multi-agent-handoff-state-machine.test.ts +4 -3
  105. package/src/scenarios/multi-agent-memory-lifecycle.test.ts +5 -4
  106. package/src/scenarios/multi-party-conversation-behavioral.test.ts +3 -2
  107. package/src/scenarios/multi-region-idempotency-behavior.test.ts +7 -6
  108. package/src/scenarios/multi-region-idempotency.test.ts +4 -3
  109. package/src/scenarios/normative-example-extraction.test.ts +242 -0
  110. package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +4 -3
  111. package/src/scenarios/oauth-capability-shape.test.ts +4 -3
  112. package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
  113. package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +309 -0
  114. package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -1
  115. package/src/scenarios/otel-emission-grpc.test.ts +4 -3
  116. package/src/scenarios/otel-emission.test.ts +4 -3
  117. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +5 -4
  118. package/src/scenarios/otel-trace-propagation.test.ts +4 -3
  119. package/src/scenarios/pack-registry-isolation.test.ts +4 -3
  120. package/src/scenarios/pack-registry-publish.test.ts +65 -3
  121. package/src/scenarios/pack-registry.test.ts +22 -0
  122. package/src/scenarios/profile-discovery-core-alias.test.ts +137 -0
  123. package/src/scenarios/profileDerivation.test.ts +2 -1
  124. package/src/scenarios/provider-usage.test.ts +2 -2
  125. package/src/scenarios/rate-limit-envelope.test.ts +2 -1
  126. package/src/scenarios/registry-public.test.ts +6 -5
  127. package/src/scenarios/replay-divergence-at-refusal.test.ts +9 -8
  128. package/src/scenarios/replay-llm-cache-key-portable.test.ts +19 -13
  129. package/src/scenarios/replay-llm-cache-key.test.ts +120 -47
  130. package/src/scenarios/replay-observable-sequence-determinism.test.ts +5 -4
  131. package/src/scenarios/replay-side-effect-suppression.test.ts +20 -1
  132. package/src/scenarios/requirement-ledger.test.ts +29 -6
  133. package/src/scenarios/runner-ledger.test.ts +279 -0
  134. package/src/scenarios/runtime-requires-install-gate.test.ts +6 -5
  135. package/src/scenarios/sandbox-memory-cap.test.ts +3 -2
  136. package/src/scenarios/sandbox-mvp-behavior.test.ts +11 -10
  137. package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
  138. package/src/scenarios/sandbox-timeout-cap.test.ts +3 -2
  139. package/src/scenarios/stream-text-fixture.test.ts +5 -4
  140. package/src/scenarios/subrun-approval-fail-closed.test.ts +3 -2
  141. package/src/scenarios/subrun-approval-gate.test.ts +4 -3
  142. package/src/scenarios/subrun-attestation-shape.test.ts +2 -1
  143. package/src/scenarios/subrun-checksum-stable.test.ts +4 -3
  144. package/src/scenarios/table-schema-enforcement.test.ts +2 -2
  145. package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -2
  146. package/src/scenarios/tool-hooks-content-free.test.ts +3 -2
  147. package/src/scenarios/tool-hooks-rate-limit.test.ts +3 -2
  148. package/src/scenarios/tool-hooks-secret-redaction.test.ts +3 -2
  149. package/src/scenarios/tool-hooks-shape.test.ts +2 -1
  150. package/src/scenarios/trigger-bridge-delivery.test.ts +3 -2
  151. package/src/scenarios/version-fold.test.ts +5 -4
  152. package/src/scenarios/voice-streamref-tenant-bound.test.ts +2 -1
  153. package/src/scenarios/voice-transcription-streaming.test.ts +2 -1
  154. package/src/scenarios/voice-transcription-unadvertised.test.ts +2 -1
  155. package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +5 -4
  156. package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
  157. package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
  158. package/src/scenarios/wasm-pack-load.test.ts +4 -3
  159. package/src/scenarios/wasm-pack-memory-cap.test.ts +4 -3
  160. package/src/scenarios/wasm-pack-replay-determinism.test.ts +3 -2
  161. package/src/scenarios/webhook-signed-delivery.test.ts +4 -3
  162. package/src/scenarios/workflow-primary-output-annotation.test.ts +13 -23
  163. package/src/scenarios/workload-identity-behavior.test.ts +7 -4
  164. package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +3 -2
  165. package/src/scenarios/workspace-cross-tenant-isolation.test.ts +3 -2
  166. 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; // run wrote no memory — soft-skip
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; // advertised — N/A
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; // run wrote no memory — soft-skip
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; // run wrote no memory — nothing to test
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; // replay fork unsupported — soft-skip
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; // capability absent — gated skip
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; // seam not wired — soft-skip
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; // fixture-gated soft-skip
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; // soft-skip — `version: 1` hosts pass via this absence
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; // discovery unavailable — skip
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; // host doesn't advertise — soft-skip
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; // soft-skip — host honest about not implementing
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; // seam not wired on this host soft-skip the behavioral leg
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"),