@openwop/openwop-conformance 1.106.1 → 1.123.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 (153) 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/llm-cache-key-recipe.ts +6 -5
  24. package/src/lib/mcp-fake-server.ts +235 -72
  25. package/src/lib/profiles.ts +132 -19
  26. package/src/lib/requirement-ledger.ts +98 -1
  27. package/src/lib/requirement-registry.ts +47 -4
  28. package/src/lib/scenario-disposition.ts +291 -0
  29. package/src/lib/soft-skip.ts +89 -0
  30. package/src/lib/workflow-chain-expansion.ts +189 -0
  31. package/src/scenarios/a2a-1-0-agent-card.test.ts +179 -0
  32. package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +109 -0
  33. package/src/scenarios/a2a-card-runtime-consistency.test.ts +167 -0
  34. package/src/scenarios/a2a-peer-authority.test.ts +99 -0
  35. package/src/scenarios/a2a-task-roundtrip.test.ts +18 -6
  36. package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -2
  37. package/src/scenarios/agent-loop-stateful-resume.test.ts +3 -2
  38. package/src/scenarios/agent-loop-version5-shape.test.ts +2 -1
  39. package/src/scenarios/agent-loop-workspace-snapshot.test.ts +4 -3
  40. package/src/scenarios/approval-gate-flow.test.ts +5 -4
  41. package/src/scenarios/artifact-type-store-without-render.test.ts +2 -1
  42. package/src/scenarios/auth-scim-profile.test.ts +5 -4
  43. package/src/scenarios/authorization-fail-closed.test.ts +4 -3
  44. package/src/scenarios/certification-bundle-non-vacuous.test.ts +249 -0
  45. package/src/scenarios/certification-bundle-redaction.test.ts +182 -0
  46. package/src/scenarios/certification-floor-enforcement.test.ts +30 -6
  47. package/src/scenarios/chain-compensation-expansion.test.ts +243 -0
  48. package/src/scenarios/commitment-fired.test.ts +3 -2
  49. package/src/scenarios/compensation-behavior.test.ts +63 -0
  50. package/src/scenarios/compensation-profile.test.ts +144 -1
  51. package/src/scenarios/conformance-advertised-seam-required.test.ts +118 -0
  52. package/src/scenarios/conformance-execution-witness.test.ts +104 -0
  53. package/src/scenarios/core-manifest-and-extension-registry.test.ts +26 -0
  54. package/src/scenarios/credential-payload-redaction.test.ts +4 -3
  55. package/src/scenarios/credentials-capability-shape.test.ts +4 -3
  56. package/src/scenarios/cross-engine-append-behavior.test.ts +5 -4
  57. package/src/scenarios/cross-engine-append-ordering.test.ts +3 -2
  58. package/src/scenarios/cross-host-ancestry-endpoint.test.ts +5 -4
  59. package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
  60. package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
  61. package/src/scenarios/deadletter-retry-exhaustion.test.ts +5 -4
  62. package/src/scenarios/distillation-index-roundtrip.test.ts +3 -2
  63. package/src/scenarios/distillation-secret-carryforward.test.ts +3 -2
  64. package/src/scenarios/distillation-shape.test.ts +2 -1
  65. package/src/scenarios/distillation-stable-archive.test.ts +4 -3
  66. package/src/scenarios/distillation-token-budget.test.ts +4 -3
  67. package/src/scenarios/feedback-correction-redaction.test.ts +4 -3
  68. package/src/scenarios/feedback-cross-tenant-isolation.test.ts +4 -3
  69. package/src/scenarios/feedback-fork-not-copied.test.ts +6 -5
  70. package/src/scenarios/feedback-on-terminal-run.test.ts +4 -3
  71. package/src/scenarios/feedback-record-and-list.test.ts +4 -3
  72. package/src/scenarios/feedback-unsupported-501.test.ts +3 -2
  73. package/src/scenarios/fixtures-valid.test.ts +16 -18
  74. package/src/scenarios/heartbeat-capability-shape.test.ts +2 -1
  75. package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +4 -3
  76. package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +6 -5
  77. package/src/scenarios/heartbeat-runtime-bound.test.ts +4 -3
  78. package/src/scenarios/mcp-2026-07-28-discover.test.ts +207 -0
  79. package/src/scenarios/mcp-cache-tenant-scope.test.ts +66 -0
  80. package/src/scenarios/mcp-current-auth-boundary.test.ts +55 -0
  81. package/src/scenarios/mcp-extension-opacity.test.ts +79 -0
  82. package/src/scenarios/mcp-mrtr-roundtrip.test.ts +140 -0
  83. package/src/scenarios/mcp-stateless-request.test.ts +77 -0
  84. package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
  85. package/src/scenarios/mcp-toolcall-redaction.test.ts +2 -1
  86. package/src/scenarios/memory-attribution-emits-on-write.test.ts +6 -5
  87. package/src/scenarios/memory-attribution-no-content.test.ts +4 -3
  88. package/src/scenarios/memory-attribution-replay-stable.test.ts +6 -5
  89. package/src/scenarios/memory-attribution-tenant-scoped.test.ts +4 -3
  90. package/src/scenarios/memory-compaction-event-emitted.test.ts +3 -2
  91. package/src/scenarios/memory-compaction-provenance-tag.test.ts +6 -5
  92. package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +4 -3
  93. package/src/scenarios/memory-consolidation-idempotent.test.ts +4 -3
  94. package/src/scenarios/memory-injection-budget.test.ts +4 -3
  95. package/src/scenarios/metric-emission.test.ts +4 -3
  96. package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
  97. package/src/scenarios/multi-agent-handoff-state-machine.test.ts +4 -3
  98. package/src/scenarios/multi-agent-memory-lifecycle.test.ts +5 -4
  99. package/src/scenarios/multi-party-conversation-behavioral.test.ts +3 -2
  100. package/src/scenarios/multi-region-idempotency-behavior.test.ts +7 -6
  101. package/src/scenarios/multi-region-idempotency.test.ts +4 -3
  102. package/src/scenarios/normative-example-extraction.test.ts +242 -0
  103. package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +4 -3
  104. package/src/scenarios/oauth-capability-shape.test.ts +4 -3
  105. package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
  106. package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +309 -0
  107. package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -1
  108. package/src/scenarios/otel-emission-grpc.test.ts +4 -3
  109. package/src/scenarios/otel-emission.test.ts +4 -3
  110. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +5 -4
  111. package/src/scenarios/otel-trace-propagation.test.ts +4 -3
  112. package/src/scenarios/pack-registry-isolation.test.ts +4 -3
  113. package/src/scenarios/pack-registry-publish.test.ts +65 -3
  114. package/src/scenarios/pack-registry.test.ts +22 -0
  115. package/src/scenarios/profile-discovery-core-alias.test.ts +137 -0
  116. package/src/scenarios/profileDerivation.test.ts +2 -1
  117. package/src/scenarios/rate-limit-envelope.test.ts +2 -1
  118. package/src/scenarios/registry-public.test.ts +6 -5
  119. package/src/scenarios/replay-divergence-at-refusal.test.ts +9 -8
  120. package/src/scenarios/replay-llm-cache-key-portable.test.ts +19 -13
  121. package/src/scenarios/replay-llm-cache-key.test.ts +120 -47
  122. package/src/scenarios/replay-observable-sequence-determinism.test.ts +5 -4
  123. package/src/scenarios/replay-side-effect-suppression.test.ts +20 -1
  124. package/src/scenarios/requirement-ledger.test.ts +29 -6
  125. package/src/scenarios/runner-ledger.test.ts +279 -0
  126. package/src/scenarios/runtime-requires-install-gate.test.ts +6 -5
  127. package/src/scenarios/sandbox-memory-cap.test.ts +3 -2
  128. package/src/scenarios/sandbox-mvp-behavior.test.ts +11 -10
  129. package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
  130. package/src/scenarios/sandbox-timeout-cap.test.ts +3 -2
  131. package/src/scenarios/stream-text-fixture.test.ts +5 -4
  132. package/src/scenarios/subrun-approval-fail-closed.test.ts +3 -2
  133. package/src/scenarios/subrun-approval-gate.test.ts +4 -3
  134. package/src/scenarios/subrun-attestation-shape.test.ts +2 -1
  135. package/src/scenarios/subrun-checksum-stable.test.ts +4 -3
  136. package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -2
  137. package/src/scenarios/tool-hooks-content-free.test.ts +3 -2
  138. package/src/scenarios/tool-hooks-rate-limit.test.ts +3 -2
  139. package/src/scenarios/tool-hooks-secret-redaction.test.ts +3 -2
  140. package/src/scenarios/tool-hooks-shape.test.ts +2 -1
  141. package/src/scenarios/trigger-bridge-delivery.test.ts +3 -2
  142. package/src/scenarios/version-fold.test.ts +5 -4
  143. package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +5 -4
  144. package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
  145. package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
  146. package/src/scenarios/wasm-pack-load.test.ts +4 -3
  147. package/src/scenarios/wasm-pack-memory-cap.test.ts +4 -3
  148. package/src/scenarios/wasm-pack-replay-determinism.test.ts +3 -2
  149. package/src/scenarios/webhook-signed-delivery.test.ts +4 -3
  150. package/src/scenarios/workflow-primary-output-annotation.test.ts +13 -23
  151. package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +3 -2
  152. package/src/scenarios/workspace-cross-tenant-isolation.test.ts +3 -2
  153. package/src/setup.ts +95 -1
@@ -10,23 +10,24 @@
10
10
  */
11
11
 
12
12
  import { describe, it, expect } from 'vitest';
13
+ import { softSkip } from '../lib/soft-skip.js';
13
14
  import { driver } from '../lib/driver.js';
14
15
  import { readSubRunAttestationCap, invokeSubRunAttest } from '../lib/subRunAttestation.js';
15
16
 
16
17
  describe('subrun-approval-gate (RFC 0063 §C)', () => {
17
18
  it('accept merges the child outputs; reject does not', async () => {
18
- if ((await readSubRunAttestationCap()) !== true) return;
19
+ if ((await readSubRunAttestationCap()) !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `(await readSubRunAttestationCap()) !== true` returned early');
19
20
  const base = { childOutputs: { artifact: 'x' }, outputAttestation: { requireApproval: true } };
20
21
 
21
22
  const accepted = await invokeSubRunAttest({ ...base, approvalAction: 'accept' });
22
- if (accepted === null) return; // seam absent — soft-skip
23
+ if (accepted === null) return softSkip('blocked', 'seam absent — soft-skip');
23
24
  expect(
24
25
  accepted.merged,
25
26
  driver.describe('RFC 0063 §C', 'an `accept` approval MUST merge the child outputs'),
26
27
  ).toBe(true);
27
28
 
28
29
  const rejected = await invokeSubRunAttest({ ...base, approvalAction: 'reject' });
29
- if (rejected === null) return;
30
+ if (rejected === null) return softSkip('blocked', 'precondition not met — `rejected === null` returned early (seam, prior step, or fixture unavailable)');
30
31
  expect(
31
32
  rejected.merged,
32
33
  driver.describe('RFC 0063 §C', 'a `reject` approval MUST NOT merge the child outputs'),
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
  import { driver } from '../lib/driver.js';
15
16
  import { readSubRunAttestationCap } from '../lib/subRunAttestation.js';
16
17
 
@@ -18,7 +19,7 @@ describe('subrun-attestation-shape: advertisement (RFC 0063 §A)', () => {
18
19
  it('capabilities.agents.subRunAttestation is absent or a boolean', async () => {
19
20
  const cap = await readSubRunAttestationCap();
20
21
  // null = unadvertised (no agents block OR flag omitted) — valid.
21
- if (cap === null) return;
22
+ if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
22
23
  expect(
23
24
  typeof cap,
24
25
  driver.describe(
@@ -11,21 +11,22 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
  import { driver } from '../lib/driver.js';
15
16
  import { readSubRunAttestationCap, invokeSubRunAttest } from '../lib/subRunAttestation.js';
16
17
 
17
18
  describe('subrun-checksum-stable (RFC 0063 §B)', () => {
18
19
  it('identical child outputs produce an identical sha256 attestation checksum', async () => {
19
- if ((await readSubRunAttestationCap()) !== true) return;
20
+ if ((await readSubRunAttestationCap()) !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `(await readSubRunAttestationCap()) !== true` returned early');
20
21
  const childOutputs = { report: 'done', score: 0.9, tags: ['a', 'b'] };
21
22
  const a = await invokeSubRunAttest({ childOutputs, outputAttestation: { checksum: true } });
22
- if (a === null) return; // seam absent — soft-skip
23
+ if (a === null) return softSkip('blocked', 'seam absent — soft-skip');
23
24
  // Key-reordered but value-identical: JCS canonicalization MUST yield the same hash.
24
25
  const b = await invokeSubRunAttest({
25
26
  childOutputs: { tags: ['a', 'b'], score: 0.9, report: 'done' },
26
27
  outputAttestation: { checksum: true },
27
28
  });
28
- if (b === null) return;
29
+ if (b === null) return softSkip('blocked', 'precondition not met — `b === null` returned early (seam, prior step, or fixture unavailable)');
29
30
  const att = a.attestation ?? {};
30
31
  expect(
31
32
  typeof att.checksum === 'string' && (att.checksum as string).length > 0,
@@ -12,13 +12,14 @@
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 { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
17
18
 
18
19
  describe('tool-hooks-authorization-fail-closed (RFC 0064 §C)', () => {
19
20
  it('a principal lacking a tool scope is denied and the tool is not invoked', async () => {
20
21
  const cap = await readToolHooksCap();
21
- if (cap?.perToolAuthorization !== true) return;
22
+ if (cap?.perToolAuthorization !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.perToolAuthorization !== true` returned early');
22
23
  // A principal with no scopes against a tool requiring one MUST be denied.
23
24
  const res = await invokeToolHook({
24
25
  principal: 'conformance-unprivileged',
@@ -26,7 +27,7 @@ describe('tool-hooks-authorization-fail-closed (RFC 0064 §C)', () => {
26
27
  requiredScopes: ['db:write'],
27
28
  args: {},
28
29
  });
29
- if (res === null) return; // seam absent — soft-skip
30
+ if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
30
31
  expect(
31
32
  (res.toolReturned ?? {}).status,
32
33
  driver.describe('RFC 0064 §C', 'a missing/unevaluable tool scope MUST fail closed → status:"forbidden"'),
@@ -11,15 +11,16 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
  import { driver } from '../lib/driver.js';
15
16
  import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
16
17
 
17
18
  describe('tool-hooks-content-free (RFC 0064 §B)', () => {
18
19
  it('toolCalled carries argsHash; toolReturned carries status + durationMs', async () => {
19
20
  const cap = await readToolHooksCap();
20
- if (cap?.prePostEvents !== true) return;
21
+ if (cap?.prePostEvents !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.prePostEvents !== true` returned early');
21
22
  const res = await invokeToolHook({ principal: 'core.system', toolName: 'web.search', args: { q: 'openwop' } });
22
- if (res === null) return; // seam absent — soft-skip
23
+ if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
23
24
  const called = res.toolCalled ?? {};
24
25
  const returned = res.toolReturned ?? {};
25
26
  expect(
@@ -10,20 +10,21 @@
10
10
  */
11
11
 
12
12
  import { describe, it, expect } from 'vitest';
13
+ import { softSkip } from '../lib/soft-skip.js';
13
14
  import { driver } from '../lib/driver.js';
14
15
  import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
15
16
 
16
17
  describe('tool-hooks-rate-limit (RFC 0064 §D)', () => {
17
18
  it('an exhausted (principal, tool) bucket yields status:"rate_limited"', async () => {
18
19
  const cap = await readToolHooksCap();
19
- if (cap?.perToolRateLimit !== true) return;
20
+ if (cap?.perToolRateLimit !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.perToolRateLimit !== true` returned early');
20
21
  const res = await invokeToolHook({
21
22
  principal: 'core.system',
22
23
  toolName: 'web.search',
23
24
  args: { q: 'x' },
24
25
  simulateRateLimitExhausted: true,
25
26
  });
26
- if (res === null) return; // seam absent — soft-skip
27
+ if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
27
28
  expect(
28
29
  (res.toolReturned ?? {}).status,
29
30
  driver.describe('RFC 0064 §D', 'an exhausted token bucket MUST yield status:"rate_limited" without invoking the tool'),
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
  import { driver } from '../lib/driver.js';
15
16
  import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
16
17
 
@@ -19,13 +20,13 @@ const CANARY = 'sk-canary-rfc0064-do-not-leak-xyz789';
19
20
  describe('tool-hooks-secret-redaction (RFC 0064 §E)', () => {
20
21
  it('a secret-shaped tool arg never appears in the emitted events', async () => {
21
22
  const cap = await readToolHooksCap();
22
- if (cap?.prePostEvents !== true) return;
23
+ if (cap?.prePostEvents !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.prePostEvents !== true` returned early');
23
24
  const res = await invokeToolHook({
24
25
  principal: 'core.system',
25
26
  toolName: 'web.search',
26
27
  args: { apiKey: CANARY, q: 'openwop' },
27
28
  });
28
- if (res === null) return; // seam absent — soft-skip
29
+ if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
29
30
  expect(
30
31
  JSON.stringify(res).includes(CANARY),
31
32
  driver.describe('RFC 0064 §B', 'a resolved secret MUST be redacted before hashing; the raw value MUST NOT appear in argsHash or any emitted field (SR-1)'),
@@ -11,13 +11,14 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
  import { driver } from '../lib/driver.js';
15
16
  import { readToolHooksCap } from '../lib/toolHooks.js';
16
17
 
17
18
  describe('tool-hooks-shape: advertisement (RFC 0064 §A)', () => {
18
19
  it('capabilities.toolHooks is absent or a well-formed object', async () => {
19
20
  const cap = await readToolHooksCap();
20
- if (cap === null) return; // not advertised — valid
21
+ if (cap === null) return softSkip('inapplicable', 'not advertised — valid');
21
22
  expect(
22
23
  typeof cap.supported,
23
24
  driver.describe('capabilities.schema.json §toolHooks', 'toolHooks.supported MUST be a boolean when the block is present'),
@@ -28,6 +28,7 @@
28
28
  */
29
29
 
30
30
  import { describe, it, expect } from 'vitest';
31
+ import { seamAbsent } from '../lib/soft-skip.js';
31
32
  import { driver } from '../lib/driver.js';
32
33
  import { behaviorGate } from '../lib/behavior-gate.js';
33
34
  import {
@@ -52,11 +53,11 @@ function expectContentFree(payload: Record<string, unknown>, where: string): voi
52
53
  describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
53
54
  it('de-dups by dedupKey, retries to dead-letter, and links delivery→run causation', async () => {
54
55
  if (!behaviorGate('openwop-trigger-bridge', await isTriggerBridgeProfileAdvertised())) return;
55
- if (!(await isEventLogSeamAvailable())) return; // event-log seam absent — soft-skip
56
+ if (!(await isEventLogSeamAvailable())) return seamAbsent('host advertises openwop-trigger-bridge but the event-log seam is absent');
56
57
 
57
58
  // ---- Leg 1: dedup → effectively-once (§C-1) ---------------------------
58
59
  const dedup = await driveDelivery({ scenario: 'dedup', dedupKey: 'conformance-dedup-key', source: 'queue' });
59
- if (dedup === null) return; // delivery seam unwired — soft-skip the whole behavioral suite
60
+ if (dedup === null) return seamAbsent('host advertises openwop-trigger-bridge but the delivery seam is unwired');
60
61
 
61
62
  // The profile is derived AND the seam is wired — missing evidence is a
62
63
  // FAILURE, not a soft-skip. A repeated dedupKey MUST be effectively-once:
@@ -34,6 +34,7 @@
34
34
  */
35
35
 
36
36
  import { describe, it, expect } from 'vitest';
37
+ import { softSkip } from '../lib/soft-skip.js';
37
38
  import { driver, type OpenWOPResponse } from '../lib/driver.js';
38
39
  import { pollUntilTerminal } from '../lib/polling.js';
39
40
  import { isFixtureAdvertised } from '../lib/fixtures.js';
@@ -84,7 +85,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('version-fold: forced engine versions fold-best
84
85
  '[version-fold] host does not advertise Capabilities.testing.forceEngineVersionRange; ' +
85
86
  'skipping (the X-Force-Engine-Version seam is test-keys-only and optional)',
86
87
  );
87
- return;
88
+ return softSkip('blocked', 'precondition not met — `seam === null` returned early (seam, prior step, or fixture unavailable)');
88
89
  }
89
90
 
90
91
  const candidates = [seam.range.min, ...(seam.current === null ? [] : [seam.current]), seam.range.max];
@@ -104,7 +105,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('version-fold: forced engine versions fold-best
104
105
  if (create.status === 403 && errCode(create.json) === 'force_engine_version_forbidden') {
105
106
  // eslint-disable-next-line no-console
106
107
  console.warn('[version-fold] API key is production-scoped (403 force_engine_version_forbidden); skipping');
107
- return;
108
+ return softSkip('blocked', '[version-fold] API key is production-scoped (403 force_engine_version_forbidden); skipping (create.status === 403 && errCode(create.json) === \'force_engine_version_forbidden\')');
108
109
  }
109
110
  expect(
110
111
  create.status,
@@ -165,7 +166,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('version-fold: forced engine versions fold-best
165
166
  if (seam === null) {
166
167
  // eslint-disable-next-line no-console
167
168
  console.warn('[version-fold] forceEngineVersionRange not advertised; skipping negative leg');
168
- return;
169
+ return softSkip('inapplicable', '[version-fold] forceEngineVersionRange not advertised; skipping negative leg');
169
170
  }
170
171
 
171
172
  const outOfRange = seam.range.max + 1;
@@ -173,7 +174,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('version-fold: forced engine versions fold-best
173
174
  if (create.status === 403 && errCode(create.json) === 'force_engine_version_forbidden') {
174
175
  // eslint-disable-next-line no-console
175
176
  console.warn('[version-fold] API key is production-scoped; skipping negative leg');
176
- return;
177
+ return softSkip('blocked', '[version-fold] API key is production-scoped; skipping negative leg (create.status === 403 && errCode(create.json) === \'force_engine_version_forbidden\')');
177
178
  }
178
179
  expect(
179
180
  create.status,
@@ -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
 
@@ -37,7 +38,7 @@ describe('wasm-pack-abi-version-rejection: host advertises supported ABI version
37
38
  const wasm =
38
39
  capabilityFamily<{ wasm?: Record<string, unknown> }>(disco.json, 'nodePackRuntimes')?.wasm;
39
40
 
40
- if (!wasm?.supported) return;
41
+ if (!wasm?.supported) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!wasm?.supported` returned early');
41
42
 
42
43
  expect(Array.isArray(wasm.abiVersions), driver.describe(
43
44
  'RFCS/0008-wasm-abi.md §H',
@@ -63,7 +64,7 @@ describe('wasm-pack-abi-version-rejection: positive path via misbehaving pack',
63
64
  const wasm =
64
65
  capabilityFamily<{ wasm?: Record<string, unknown> }>(disco.json, 'nodePackRuntimes')?.wasm;
65
66
 
66
- if (!wasm?.supported) return;
67
+ if (!wasm?.supported) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!wasm?.supported` returned early');
67
68
 
68
69
  if (!Array.isArray(wasm.loadedPacks)) {
69
70
  // eslint-disable-next-line no-console
@@ -71,7 +72,7 @@ describe('wasm-pack-abi-version-rejection: positive path via misbehaving pack',
71
72
  '[wasm-pack-abi-version-rejection] host does not advertise capabilities.nodePackRuntimes.wasm.loadedPacks[]; skipping positive path. ' +
72
73
  'Hosts MAY advertise loadedPacks[] to surface ABI rejection observably (RFC 0008 §H + Track 7).',
73
74
  );
74
- return;
75
+ return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!Array.isArray(wasm.loadedPacks)` returned early');
75
76
  }
76
77
 
77
78
  // Soft-skip when the well-behaved reference pack isn't loaded. If
@@ -84,7 +85,7 @@ describe('wasm-pack-abi-version-rejection: positive path via misbehaving pack',
84
85
  `[wasm-pack-abi-version-rejection] reference pack ${WELL_BEHAVED_PACK_NAME} not in loadedPacks[]; ` +
85
86
  'skipping positive path (build examples/packs/rust-hello first or run against a host that ships it).',
86
87
  );
87
- return;
88
+ return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!wasm.loadedPacks.includes(WELL_BEHAVED_PACK_NAME)` returned early');
88
89
  }
89
90
 
90
91
  // Core assertion: ABI 999 pack MUST NOT be in loadedPacks[]
@@ -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 { isFixtureAdvertised } from '../lib/fixtures.js';
@@ -29,11 +30,11 @@ async function isWasmSupported(): Promise<boolean> {
29
30
 
30
31
  describe('wasm-pack-invoke-completed: round-trip output', () => {
31
32
  it('greet node returns Hello, <name>! and run reaches completed', async () => {
32
- if (!isFixtureAdvertised(FIXTURE)) return;
33
+ if (!isFixtureAdvertised(FIXTURE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(FIXTURE)` returned early');
33
34
  if (!(await isWasmSupported())) {
34
35
  // eslint-disable-next-line no-console
35
36
  console.warn('[wasm-pack-invoke-completed] WASM not advertised; skipping');
36
- return;
37
+ return softSkip('inapplicable', '[wasm-pack-invoke-completed] WASM not advertised; skipping');
37
38
  }
38
39
 
39
40
  const create = await driver.post('/v1/runs', {
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { describe, it, expect } from 'vitest';
20
+ import { softSkip } from '../lib/soft-skip.js';
20
21
  import { driver } from '../lib/driver.js';
21
22
  import { pollUntilTerminal } from '../lib/polling.js';
22
23
  import { isFixtureAdvertised } from '../lib/fixtures.js';
@@ -33,8 +34,8 @@ async function isWasmSupported(): Promise<boolean> {
33
34
 
34
35
  describe('wasm-pack-invoke-suspended: suspend → resume round-trip', () => {
35
36
  it('host either suspends the run or explicitly reports wasm_suspend_not_implemented', async () => {
36
- if (!isFixtureAdvertised(FIXTURE)) return;
37
- if (!(await isWasmSupported())) return;
37
+ if (!isFixtureAdvertised(FIXTURE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(FIXTURE)` returned early');
38
+ if (!(await isWasmSupported())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isWasmSupported())` returned early');
38
39
 
39
40
  // The reference rust-hello pack does NOT itself suspend (it always
40
41
  // returns `completed`), so against that pack this scenario can only
@@ -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 { isFixtureAdvertised } from '../lib/fixtures.js';
18
19
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
@@ -39,7 +40,7 @@ describe('wasm-pack-load: discovery surfaces WASM runtime support', () => {
39
40
  if (!wasm?.supported) {
40
41
  // eslint-disable-next-line no-console
41
42
  console.warn('[wasm-pack-load] host does not advertise WASM support; skipping');
42
- return;
43
+ return softSkip('inapplicable', '[wasm-pack-load] host does not advertise WASM support; skipping');
43
44
  }
44
45
  expect(Array.isArray(wasm.abiVersions), driver.describe(
45
46
  'RFCS/0008-wasm-abi.md §H',
@@ -55,11 +56,11 @@ describe('wasm-pack-load: discovery surfaces WASM runtime support', () => {
55
56
  describe('wasm-pack-load: loaded pack typeIds are dispatchable', () => {
56
57
  it('host accepts a workflow whose node typeId is provided by a loaded WASM pack', async () => {
57
58
  const wasm = await getWasmCaps();
58
- if (!wasm?.supported) return;
59
+ if (!wasm?.supported) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!wasm?.supported` returned early');
59
60
  if (!isFixtureAdvertised(FIXTURE)) {
60
61
  // eslint-disable-next-line no-console
61
62
  console.warn(`[wasm-pack-load] fixture ${FIXTURE} not advertised; skipping`);
62
- return;
63
+ return softSkip('inapplicable', '[wasm-pack-load] fixture … not advertised; skipping');
63
64
  }
64
65
  // Creating a run against the fixture proves the host knows about the
65
66
  // WASM-provided typeId. A host that loaded the pack accepts the
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import { describe, it, expect } from 'vitest';
26
+ import { softSkip } from '../lib/soft-skip.js';
26
27
  import { driver } from '../lib/driver.js';
27
28
  import { pollUntilTerminal } from '../lib/polling.js';
28
29
  import { isFixtureAdvertised } from '../lib/fixtures.js';
@@ -36,7 +37,7 @@ describe('wasm-pack-memory-cap: host advertises maxMemoryBytes', () => {
36
37
  const wasm =
37
38
  capabilityFamily<{ wasm?: Record<string, unknown> }>(disco.json, 'nodePackRuntimes')?.wasm;
38
39
 
39
- if (!wasm?.supported) return;
40
+ if (!wasm?.supported) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!wasm?.supported` returned early');
40
41
 
41
42
  // The cap is REQUIRED to enforce §K. Hosts MUST surface the configured
42
43
  // ceiling so clients can size their packs accordingly.
@@ -59,12 +60,12 @@ describe('wasm-pack-memory-cap: positive path via misbehaving pack', () => {
59
60
  `[wasm-pack-memory-cap] fixture ${CAP_BREACH_FIXTURE} not advertised; skipping positive path. ` +
60
61
  'Build the misbehaving pack at examples/packs/rust-misbehaving-memory/ and ensure the host serves the fixture.',
61
62
  );
62
- return;
63
+ return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(CAP_BREACH_FIXTURE)` returned early');
63
64
  }
64
65
  const disco = await driver.get('/.well-known/openwop');
65
66
  const wasm =
66
67
  capabilityFamily<{ wasm?: Record<string, unknown> }>(disco.json, 'nodePackRuntimes')?.wasm;
67
- if (!wasm?.supported) return;
68
+ if (!wasm?.supported) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!wasm?.supported` returned early');
68
69
 
69
70
  const create = await driver.post('/v1/runs', {
70
71
  workflowId: CAP_BREACH_FIXTURE,
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
  import { driver } from '../lib/driver.js';
15
16
  import { pollUntilTerminal } from '../lib/polling.js';
16
17
  import { isFixtureAdvertised } from '../lib/fixtures.js';
@@ -33,8 +34,8 @@ function extractGreeting(events: Array<{ type: string; data?: unknown }>): strin
33
34
 
34
35
  describe('wasm-pack-replay-determinism: same inputs → same output', () => {
35
36
  it('two independent runs with same inputs produce same WASM output', async () => {
36
- if (!isFixtureAdvertised(FIXTURE)) return;
37
- if (!(await isWasmSupported())) return;
37
+ if (!isFixtureAdvertised(FIXTURE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(FIXTURE)` returned early');
38
+ if (!(await isWasmSupported())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isWasmSupported())` returned early');
38
39
 
39
40
  const inputs = { name: 'determinism-probe' };
40
41
 
@@ -24,6 +24,7 @@
24
24
  */
25
25
 
26
26
  import { afterEach, describe, expect, it } from 'vitest';
27
+ import { softSkip } from '../lib/soft-skip.js';
27
28
  import { createHmac } from 'node:crypto';
28
29
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
29
30
  import { driver } from '../lib/driver.js';
@@ -78,12 +79,12 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
78
79
  if (!(await isWebhookSupported())) {
79
80
  // eslint-disable-next-line no-console
80
81
  console.warn('[webhook-signed-delivery] host does not advertise webhook support; skipping');
81
- return;
82
+ return softSkip('inapplicable', '[webhook-signed-delivery] host does not advertise webhook support; skipping');
82
83
  }
83
84
  if (!isFixtureAdvertised('conformance-noop')) {
84
85
  // eslint-disable-next-line no-console
85
86
  console.warn('[webhook-signed-delivery] conformance-noop not advertised; skipping');
86
- return;
87
+ return softSkip('inapplicable', '[webhook-signed-delivery] conformance-noop not advertised; skipping');
87
88
  }
88
89
 
89
90
  const receiver = await startReceiver();
@@ -114,7 +115,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
114
115
  '[webhook-signed-delivery] host SSRF guard rejected the loopback receiver; ' +
115
116
  'set OPENWOP_WEBHOOK_ALLOW_PRIVATE=true on the host (or equivalent) to run',
116
117
  );
117
- return;
118
+ return softSkip('blocked', 'precondition not met — `body.error === \'webhook_url_rejected\'` returned early (seam, prior step, or fixture unavailable)');
118
119
  }
119
120
  }
120
121
 
@@ -20,35 +20,25 @@
20
20
  import { describe, it, expect } from 'vitest';
21
21
  import Ajv2020 from 'ajv/dist/2020.js';
22
22
  import addFormats from 'ajv-formats';
23
- import { readFileSync } from 'node:fs';
23
+ import { readFileSync, readdirSync } from 'node:fs';
24
24
  import { join } from 'node:path';
25
25
  import { SCHEMAS_DIR } from '../lib/paths.js';
26
26
 
27
27
  function compileWorkflowDefinition(): ReturnType<Ajv2020['compile']> {
28
28
  const ajv = new Ajv2020({ strict: false, allErrors: true });
29
29
  addFormats(ajv);
30
- // Register cross-file `$ref` targets — same pattern as
31
- // `fixtures-valid.test.ts`. Without these, Ajv throws
32
- // `missingRef` when compiling `workflow-definition.schema.json`
33
- // because it references agent-ref + prompt-ref by URL.
34
- const agentRefSchema = JSON.parse(
35
- readFileSync(join(SCHEMAS_DIR, 'agent-ref.schema.json'), 'utf8'),
36
- ) as Record<string, unknown>;
37
- const promptRefSchema = JSON.parse(
38
- readFileSync(join(SCHEMAS_DIR, 'prompt-ref.schema.json'), 'utf8'),
39
- ) as Record<string, unknown>;
40
- const promptKindSchema = JSON.parse(
41
- readFileSync(join(SCHEMAS_DIR, 'prompt-kind.schema.json'), 'utf8'),
42
- ) as Record<string, unknown>;
43
- ajv.addSchema(agentRefSchema, 'agent-ref.schema.json');
44
- ajv.addSchema(promptRefSchema, 'prompt-ref.schema.json');
45
- ajv.addSchema(promptRefSchema, './prompt-ref.schema.json');
46
- ajv.addSchema(promptKindSchema, 'prompt-kind.schema.json');
47
- ajv.addSchema(promptKindSchema, './prompt-kind.schema.json');
48
- const schema = JSON.parse(
49
- readFileSync(join(SCHEMAS_DIR, 'workflow-definition.schema.json'), 'utf8'),
50
- ) as Record<string, unknown>;
51
- return ajv.compile(schema);
30
+ // Register EVERY schema in the corpus (S14, 2026-08-16) — a fixed ref list is
31
+ // a claim about the schema graph that nothing keeps true; the fourth cross-file
32
+ // `$ref` (#1009) broke the previous list at describe time.
33
+ for (const file of readdirSync(SCHEMAS_DIR).filter((f) => f.endsWith('.schema.json'))) {
34
+ const s = JSON.parse(readFileSync(join(SCHEMAS_DIR, file), 'utf8')) as Record<string, unknown>;
35
+ ajv.addSchema(s, file);
36
+ ajv.addSchema(s, `./${file}`);
37
+ }
38
+ return (
39
+ ajv.getSchema('workflow-definition.schema.json') ??
40
+ ajv.compile(JSON.parse(readFileSync(join(SCHEMAS_DIR, 'workflow-definition.schema.json'), 'utf8')) as Record<string, unknown>)
41
+ );
52
42
  }
53
43
 
54
44
  /** Build the minimal-required shape of a WorkflowDefinition. Tests
@@ -24,6 +24,7 @@
24
24
  * @see SECURITY/invariants.yaml workspace-cross-tenant-isolation
25
25
  */
26
26
  import { describe, it, expect } from 'vitest';
27
+ import { softSkip } from '../lib/soft-skip.js';
27
28
  import { randomUUID } from 'node:crypto';
28
29
  import { driver } from '../lib/driver.js';
29
30
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
@@ -42,11 +43,11 @@ const asTenantB = { headers: { Authorization: `Bearer ${tenantBKey ?? ''}` } };
42
43
 
43
44
  describe('workspace-cross-tenant-isolation (black-box): a §C file MUST NOT leak across owners (RFC 0059 §E WCT-1)', () => {
44
45
  it('a file written by owner A is unreadable + un-enumerable by a second-tenant credential', async () => {
45
- if (!(await workspaceSupported())) return; // capability not advertised — skip
46
+ if (!(await workspaceSupported())) return softSkip('inapplicable', 'capability not advertised — skip');
46
47
  if (!tenantBKey) {
47
48
  // eslint-disable-next-line no-console
48
49
  console.warn('[workspace-cross-tenant-isolation-blackbox] OPENWOP_TEST_TENANT_B_API_KEY not supplied; skipping the production-path cross-tenant assertion');
49
- return;
50
+ return softSkip('blocked', '[workspace-cross-tenant-isolation-blackbox] OPENWOP_TEST_TENANT_B_API_KEY not supplied; skipping the production-path cross-tenant assertion');
50
51
  }
51
52
 
52
53
  const path = `wct-blackbox-${randomUUID()}.md`;
@@ -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
 
@@ -40,11 +41,11 @@ const SEAM_PATH = 'WCT1-SECRET.md';
40
41
 
41
42
  describe('workspace-cross-tenant-isolation: a workspace file MUST NOT leak across owners (RFC 0059 §E WCT-1)', () => {
42
43
  it('a file written under {tenant A, workspace A} is not readable under a different owner', async () => {
43
- if (!(await workspaceSupported())) return; // capability not advertised — skip
44
+ if (!(await workspaceSupported())) return softSkip('inapplicable', 'capability not advertised — skip');
44
45
 
45
46
  // Owner A writes a file.
46
47
  const put = await seam({ tenant: 'wct1-tenant-a', workspace: 'ws-a', op: 'put', path: SEAM_PATH, content: 'A-only secret body' });
47
- if (put.status === 404) return; // seam unwired — soft-skip the behavioral probe
48
+ if (put.status === 404) return softSkip('blocked', 'seam unwired — soft-skip the behavioral probe');
48
49
  expect(put.status, driver.describe('agent-workspace.md §C PUT', 'seam put MUST succeed for the owning workspace')).toBe(200);
49
50
 
50
51
  // A DIFFERENT workspace (same tenant) MUST NOT read it.