@openwop/openwop-conformance 2.45.0 → 2.45.2

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 (64) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/spec-artifacts.lock.json +2 -2
  3. package/package.json +2 -2
  4. package/requirements.json +138 -133
  5. package/schemas/CORPUS-STAMP.json +10 -10
  6. package/src/lib/run-list-walk.ts +65 -0
  7. package/src/scenarios/agentConfidenceEscalation.test.ts +8 -1
  8. package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +8 -1
  9. package/src/scenarios/agentMemoryRedactionContract.test.ts +8 -1
  10. package/src/scenarios/agentMemoryRoundTrip.test.ts +8 -1
  11. package/src/scenarios/agentMemoryTtlExpiry.test.ts +8 -1
  12. package/src/scenarios/agentMessageReducer.test.ts +8 -1
  13. package/src/scenarios/agentMetadata.test.ts +8 -1
  14. package/src/scenarios/agentPackCatalog.test.ts +13 -5
  15. package/src/scenarios/agentPackExport.test.ts +8 -2
  16. package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +17 -2
  17. package/src/scenarios/agentPackInstall.test.ts +8 -2
  18. package/src/scenarios/agentPackProvenance.test.ts +8 -1
  19. package/src/scenarios/agentReasoningEvents.test.ts +9 -1
  20. package/src/scenarios/agentReasoningStreaming.test.ts +14 -1
  21. package/src/scenarios/append-ordering.test.ts +7 -2
  22. package/src/scenarios/bulk-cancel.test.ts +11 -1
  23. package/src/scenarios/cap-breach.test.ts +8 -1
  24. package/src/scenarios/channel-ttl.test.ts +7 -1
  25. package/src/scenarios/conversationCapabilityNegotiation.test.ts +8 -1
  26. package/src/scenarios/conversationLifecycle.test.ts +8 -1
  27. package/src/scenarios/conversationReplayDeterminism.test.ts +8 -2
  28. package/src/scenarios/conversationVsLegacySuspend.test.ts +8 -1
  29. package/src/scenarios/cost-attribution.test.ts +17 -4
  30. package/src/scenarios/debugBundle.test.ts +16 -5
  31. package/src/scenarios/dispatch-cross-worker-handoff.test.ts +10 -2
  32. package/src/scenarios/dispatch-output-mapping.test.ts +20 -4
  33. package/src/scenarios/dispatchLoop.test.ts +7 -1
  34. package/src/scenarios/durability-poison-exhaustion.test.ts +7 -2
  35. package/src/scenarios/highConcurrency.test.ts +11 -1
  36. package/src/scenarios/host-callback-declaration.test.ts +10 -1
  37. package/src/scenarios/identity-passthrough.test.ts +7 -1
  38. package/src/scenarios/interrupt-approval.test.ts +25 -5
  39. package/src/scenarios/interrupt-auth-required-resume.test.ts +9 -3
  40. package/src/scenarios/interrupt-external-event-correlation.test.ts +9 -2
  41. package/src/scenarios/interrupt-parent-child-cascade.test.ts +10 -2
  42. package/src/scenarios/interrupt-quorum-resolution.test.ts +9 -2
  43. package/src/scenarios/interrupt-token-matrix.test.ts +11 -3
  44. package/src/scenarios/interruptRace.test.ts +13 -3
  45. package/src/scenarios/multi-agent-confidence-escalation.test.ts +14 -3
  46. package/src/scenarios/multi-agent-handoff-state-machine.test.ts +15 -3
  47. package/src/scenarios/multi-node-ordering.test.ts +7 -1
  48. package/src/scenarios/node-failed-payload-shape.test.ts +7 -1
  49. package/src/scenarios/orchestratorConservativePath.test.ts +8 -1
  50. package/src/scenarios/orchestratorDispatch.test.ts +9 -1
  51. package/src/scenarios/orchestratorTermination.test.ts +8 -1
  52. package/src/scenarios/owner-subject-echo.test.ts +9 -2
  53. package/src/scenarios/pause-resume.test.ts +19 -8
  54. package/src/scenarios/prompt-all-four-kinds-events.test.ts +9 -2
  55. package/src/scenarios/prompt-end-to-end-events.test.ts +10 -2
  56. package/src/scenarios/replay-fork-arbitrary.test.ts +11 -4
  57. package/src/scenarios/restart-during-run.test.ts +7 -1
  58. package/src/scenarios/secret-leakage-otel-attribute.test.ts +14 -5
  59. package/src/scenarios/stream-text-fixture.test.ts +8 -2
  60. package/src/scenarios/streamReconnect.test.ts +8 -2
  61. package/src/scenarios/subworkflow.test.ts +9 -1
  62. package/src/scenarios/v2-error-registry.test.ts +7 -10
  63. package/src/scenarios/v2-run-list.test.ts +14 -16
  64. package/src/scenarios/version-fold.test.ts +8 -2
@@ -23,6 +23,7 @@ import { driver } from '../lib/driver.js';
23
23
  import { pollUntilTerminal } from '../lib/polling.js';
24
24
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
25
  import { req } from '../lib/requirement-ids.js';
26
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
26
27
 
27
28
  const WORKFLOW_ID = 'conformance-cap-breach';
28
29
  const RECURSION_LIMIT = 3;
@@ -37,8 +38,13 @@ interface RunEvent {
37
38
  readonly payload?: unknown;
38
39
  }
39
40
 
40
- describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fails with recursion_limit_exceeded', () => {
41
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
42
+ const GATE_WHY: readonly [SoftSkipKind, string] =
43
+ (!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
44
+
45
+ describe('cap-breach: conformance-cap-breach fixture fails with recursion_limit_exceeded', () => {
41
46
  it('emits cap.breached + transitions to terminal failed when configurable.recursionLimit is exceeded', async () => {
47
+ if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
42
48
  const create = await driver.post('/v1/runs', {
43
49
  workflowId: WORKFLOW_ID,
44
50
  configurable: { recursionLimit: RECURSION_LIMIT },
@@ -106,6 +112,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
106
112
  });
107
113
 
108
114
  it('cap.breached precedes run.failed in the event sequence (breach detected before over-limit node fires)', async () => {
115
+ if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
109
116
  const create = await driver.post('/v1/runs', {
110
117
  workflowId: WORKFLOW_ID,
111
118
  configurable: { recursionLimit: RECURSION_LIMIT },
@@ -23,6 +23,7 @@ import { driver } from '../lib/driver.js';
23
23
  import { pollUntilTerminal } from '../lib/polling.js';
24
24
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
25
  import { req } from '../lib/requirement-ids.js';
26
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
26
27
 
27
28
  const WORKFLOW_ID = 'conformance-channel-ttl';
28
29
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
@@ -32,8 +33,13 @@ interface ChannelEntry {
32
33
  _ts: number;
33
34
  }
34
35
 
35
- describe.skipIf(SKIP_NO_FIXTURE)('channel-ttl: conformance-channel-ttl drops entries older than ttlMs at write time', () => {
36
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
37
+ const GATE_WHY: readonly [SoftSkipKind, string] =
38
+ (!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
39
+
40
+ describe('channel-ttl: conformance-channel-ttl drops entries older than ttlMs at write time', () => {
36
41
  it('after the post-TTL write, the channel contains exactly one entry with value "d"', async () => {
42
+ if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
37
43
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
38
44
  expect(create.status).toBe(201);
39
45
  const runId = (create.json as { runId: string }).runId;
@@ -22,14 +22,21 @@ import { driver } from '../lib/driver.js';
22
22
  import { isFixtureAdvertised } from '../lib/fixtures.js';
23
23
  import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
24
24
  import { req } from '../lib/requirement-ids.js';
25
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
25
26
 
26
27
  const FIXTURE = 'conformance-conversation-capability-negotiation';
27
28
  // Inverted gate: this scenario runs when host does NOT advertise the
28
29
  // capability, to verify the refusal contract.
29
30
  const SKIP = isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
30
31
 
31
- describe.skipIf(SKIP)('conversationCapabilityNegotiation: refusal contract', () => {
32
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
33
+ const GATE_WHY: readonly [SoftSkipKind, string] =
34
+ (isConversationPrimitiveSupported()) ? ['inapplicable', `the host advertises the conversation primitive; this scenario covers a host that does not`] as const :
35
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
36
+
37
+ describe('conversationCapabilityNegotiation: refusal contract', () => {
32
38
  it('host without conversationPrimitive capability refuses conversation-bearing workflow', async () => {
39
+ if (SKIP) return softSkip(...GATE_WHY);
33
40
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
34
41
  // The host MUST reject — either at workflow registration (404/400)
35
42
  // or at run-create (400). What MUST NOT happen is a successful
@@ -23,12 +23,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
23
23
  import { isFixtureAdvertised } from '../lib/fixtures.js';
24
24
  import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
25
25
  import { req } from '../lib/requirement-ids.js';
26
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
26
27
 
27
28
  const FIXTURE = 'conformance-conversation-lifecycle';
28
29
  const SKIP = !isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
29
30
 
30
- describe.skipIf(SKIP)('conversationLifecycle: open → exchange → close round-trip', () => {
31
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
32
+ const GATE_WHY: readonly [SoftSkipKind, string] =
33
+ (!isConversationPrimitiveSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isConversationPrimitiveSupported() is false)`] as const :
34
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
35
+
36
+ describe('conversationLifecycle: open → exchange → close round-trip', () => {
31
37
  it('emits all three lifecycle events with matching conversationId; no exchanges after close', async () => {
38
+ if (SKIP) return softSkip(...GATE_WHY);
32
39
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
33
40
  expect(create.status, req('openwop.it.conversationLifecycle.emits-all-three-lifecycle-events-with-matching-conversationid-no-exchanges-after', 'RFCS/0005-conversation.md', 'emits all three lifecycle events with matching conversationId; no exchanges after close')).toBe(201);
34
41
  const runId = (create.json as { runId: string }).runId;
@@ -19,15 +19,21 @@ import { driver } from '../lib/driver.js';
19
19
  import { pollUntilTerminal } from '../lib/polling.js';
20
20
  import { isFixtureAdvertised } from '../lib/fixtures.js';
21
21
  import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
22
- import { softSkip } from '../lib/soft-skip.js';
22
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
23
23
  import { req } from '../lib/requirement-ids.js';
24
24
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
25
25
 
26
26
  const FIXTURE = 'conformance-conversation-replay';
27
27
  const SKIP = !isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
28
28
 
29
- describe.skipIf(SKIP)('conversationReplayDeterminism: replay-fork preserves conversation log', () => {
29
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
30
+ const GATE_WHY: readonly [SoftSkipKind, string] =
31
+ (!isConversationPrimitiveSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isConversationPrimitiveSupported() is false)`] as const :
32
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
33
+
34
+ describe('conversationReplayDeterminism: replay-fork preserves conversation log', () => {
30
35
  it('forked run yields byte-equal conversation channel projection', async () => {
36
+ if (SKIP) return softSkip(...GATE_WHY);
31
37
  // unfailable-leg audit wave 2, 2026-09-27: the replay-fork gate used to be
32
38
  // the fork response itself (404/501 → soft-skip 'inapplicable'), reached
33
39
  // only AFTER the create/terminal asserts — a host with no fork surface
@@ -21,12 +21,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
21
21
  import { isFixtureAdvertised } from '../lib/fixtures.js';
22
22
  import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
23
23
  import { req } from '../lib/requirement-ids.js';
24
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
24
25
 
25
26
  const FIXTURE = 'conformance-conversation-vs-clarification';
26
27
  const SKIP = !isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
27
28
 
28
- describe.skipIf(SKIP)('conversationVsLegacySuspend: distinct event surfaces', () => {
29
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
30
+ const GATE_WHY: readonly [SoftSkipKind, string] =
31
+ (!isConversationPrimitiveSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isConversationPrimitiveSupported() is false)`] as const :
32
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
33
+
34
+ describe('conversationVsLegacySuspend: distinct event surfaces', () => {
29
35
  it('conversation suspend emits conversation.* events, not clarification.*', async () => {
36
+ if (SKIP) return softSkip(...GATE_WHY);
30
37
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
31
38
  expect(create.status).toBe(201);
32
39
  const runId = (create.json as { runId: string }).runId;
@@ -37,7 +37,7 @@ import { pollUntilTerminal } from '../lib/polling.js';
37
37
  import { isFixtureAdvertised } from '../lib/fixtures.js';
38
38
  import { getCollector, waitForRunSpans } from '../lib/otel-collector.js';
39
39
  import { req } from '../lib/requirement-ids.js';
40
- import { softSkip } from '../lib/soft-skip.js';
40
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
41
41
 
42
42
  /**
43
43
  * Callback-shaped: the host exports OTLP metrics to the suite's collector.
@@ -75,8 +75,17 @@ const OPENWOP_COST_ATTRIBUTE_NAMES: readonly string[] = [
75
75
  * in snake_case / kebab-case neighbors still match. */
76
76
  const CREDENTIAL_SHAPE_RE = /(?<![A-Za-z0-9_])(?:sk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}|Bearer\s+[A-Za-z0-9._~+/=-]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,})(?![A-Za-z0-9])|CANARY-openwop-CONFORMANCE-NEVER-SECRET[A-Za-z0-9_-]*/g;
77
77
 
78
- describe.skipIf(SKIP_NO_NOOP)('cost-attribution: metrics.openwopCost forward-compat shape (G6)', () => {
78
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
79
+ const GATE_WHY: readonly [SoftSkipKind, string] =
80
+ (!isFixtureAdvertised(NOOP_WORKFLOW_ID)) ? ['blocked', `the \`${NOOP_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
81
+
82
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
83
+ const GATE_WHY_2: readonly [SoftSkipKind, string] =
84
+ (!isFixtureAdvertised(COST_EMIT_WORKFLOW_ID)) ? ['blocked', `the \`${COST_EMIT_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
85
+
86
+ describe('cost-attribution: metrics.openwopCost forward-compat shape (G6)', () => {
79
87
  it('on any run, IF metrics.openwopCost is present, its shape MUST match the spec', async () => {
88
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
80
89
  // Use the noop fixture so we don't depend on AI nodes. The fixture
81
90
  // doesn't emit recordCost, so metrics.openwopCost will typically be
82
91
  // absent — that's allowed. The assertion is forward-compat: when
@@ -132,8 +141,9 @@ describe.skipIf(SKIP_NO_NOOP)('cost-attribution: metrics.openwopCost forward-com
132
141
  // scenarios assert shape conformance + non-negative-integer/number
133
142
  // constraints, not exact numeric equality, so any host-canary works.
134
143
 
135
- describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: end-to-end roundtrip via conformance.cost.emit (G6 / O4)', () => {
144
+ describe('cost-attribution: end-to-end roundtrip via conformance.cost.emit (G6 / O4)', () => {
136
145
  it('metrics.openwopCost MUST carry the canary cost shape after the fixture node runs', async () => {
146
+ if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
137
147
  // Try to start the cost-emit fixture workflow. If the host doesn't
138
148
  // advertise the fixture surface (production deployments don't), we
139
149
  // get 404 / 422 back and skip the scenario.
@@ -221,6 +231,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: end-to-end roundtrip via c
221
231
  });
222
232
 
223
233
  it('cost-emit fixture run MUST emit a node.completed event for the cost-emitting node', async () => {
234
+ if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
224
235
  const create = await driver.post('/v1/runs', {
225
236
  workflowId: 'openwop-smoke-cost-emit',
226
237
  });
@@ -247,7 +258,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: end-to-end roundtrip via c
247
258
  });
248
259
  });
249
260
 
250
- describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: G6 / O4 allowlist + redaction (live OTel spans)', () => {
261
+ describe('cost-attribution: G6 / O4 allowlist + redaction (live OTel spans)', () => {
251
262
  // Drives the `openwop-smoke-cost-emit` fixture, which posts arbitrary
252
263
  // `attrs` into `conformance.cost.emit` — a mix of (a) all 7
253
264
  // allowlisted attribute names, (b) one non-allowlisted key
@@ -260,6 +271,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: G6 / O4 allowlist + redact
260
271
  // collector isn't available, matching `otel-emission.test.ts`).
261
272
 
262
273
  it('only allowlisted openwop.cost.* attributes reach the OTel span (G6 close criteria — allowlist enforcement)', async () => {
274
+ if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
263
275
  if (!getCollector()) {
264
276
  // eslint-disable-next-line no-console
265
277
  console.warn('[cost-attribution] OTel collector not started; set OPENWOP_OTEL_COLLECTOR=true to run');
@@ -299,6 +311,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: G6 / O4 allowlist + redact
299
311
  });
300
312
 
301
313
  it('credential-shaped canaries do NOT leak to any OTel attribute (G6 close criteria — redaction)', async () => {
314
+ if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
302
315
  if (!getCollector()) {
303
316
  // eslint-disable-next-line no-console
304
317
  console.warn('[cost-attribution] OTel collector not started; set OPENWOP_OTEL_COLLECTOR=true to run');
@@ -43,7 +43,7 @@ import { pollUntilTerminal } from '../lib/polling.js';
43
43
  import { CANARY_MARKER, getCanary } from '../lib/canaries.js';
44
44
  import { isFixtureAdvertised } from '../lib/fixtures.js';
45
45
  import { req } from '../lib/requirement-ids.js';
46
- import { blockedDespiteAssertions, softSkip } from '../lib/soft-skip.js';
46
+ import { blockedDespiteAssertions, softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
47
47
  import { makeTraceparent } from '../lib/trace-context.js';
48
48
 
49
49
  const NOOP_WORKFLOW_ID = 'conformance-noop';
@@ -69,8 +69,13 @@ async function isAdvertised(): Promise<boolean> {
69
69
  return body.debugBundle?.supported === true;
70
70
  }
71
71
 
72
- describe.skipIf(SKIP_NO_NOOP)('debug-bundle: GET /v1/runs/{runId}/debug-bundle response shape', () => {
72
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
73
+ const GATE_WHY: readonly [SoftSkipKind, string] =
74
+ (!isFixtureAdvertised(NOOP_WORKFLOW_ID)) ? ['blocked', `the \`${NOOP_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
75
+
76
+ describe('debug-bundle: GET /v1/runs/{runId}/debug-bundle response shape', () => {
73
77
  it('host advertising capabilities.debugBundle.supported returns 200 with valid bundle', async () => {
78
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
74
79
  if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early (skip-equivalent)'); // skip-equivalent
75
80
 
76
81
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
@@ -125,6 +130,7 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: GET /v1/runs/{runId}/debug-bundle r
125
130
  });
126
131
 
127
132
  it('hosts not advertising debugBundle return 404 on the endpoint', async () => {
133
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
128
134
  if (await isAdvertised()) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `await isAdvertised()` returned early (skip-equivalent for hosts that DO advertise)'); // skip-equivalent for hosts that DO advertise
129
135
 
130
136
  // Use any runId — even a synthetic one — since the host should 404
@@ -137,8 +143,9 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: GET /v1/runs/{runId}/debug-bundle r
137
143
  });
138
144
  });
139
145
 
140
- describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', () => {
146
+ describe('debug-bundle: invariants per debug-bundle.md', () => {
141
147
  it('metrics.eventCount equals events.length', async () => {
148
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
142
149
  if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
143
150
 
144
151
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
@@ -159,6 +166,7 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', ()
159
166
  });
160
167
 
161
168
  it('redactionApplied=true is incompatible with redactionMode=passthrough', async () => {
169
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
162
170
  if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
163
171
 
164
172
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
@@ -179,6 +187,7 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', ()
179
187
  });
180
188
 
181
189
  it('bundle events agree with /events/poll for the same run', async () => {
190
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
182
191
  if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
183
192
 
184
193
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
@@ -202,8 +211,9 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', ()
202
211
  });
203
212
  });
204
213
 
205
- describe.skipIf(SKIP_NO_NOOP)('debug-bundle: redaction inheritance per SECURITY/invariants.yaml secret-leakage-debug-bundle', () => {
214
+ describe('debug-bundle: redaction inheritance per SECURITY/invariants.yaml secret-leakage-debug-bundle', () => {
206
215
  it('canary in workflow inputs MUST NOT appear verbatim in the bundle response', async () => {
216
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
207
217
  if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
208
218
 
209
219
  const canary = getCanary('byok-credential-ref').value;
@@ -233,11 +243,12 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: redaction inheritance per SECURITY/
233
243
  });
234
244
  });
235
245
 
236
- describe.skipIf(SKIP_NO_NOOP)('debug-bundle: spans join the trace (RFC 0207 §C, v1 only, non-gating)', () => {
246
+ describe('debug-bundle: spans join the trace (RFC 0207 §C, v1 only, non-gating)', () => {
237
247
  const LEG = 'openwop.it.debugBundle.spans-join-the-trace';
238
248
  const KINDS = new Set(['internal', 'server', 'client', 'producer', 'consumer']);
239
249
  const CODES = new Set(['unset', 'ok', 'error']);
240
250
  it('span traceId / kind / status have the RFC 0207 shape and the run span joins the caller trace', async () => {
251
+ if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
241
252
  if (!(await isAdvertised())) return softSkip('inapplicable', 'debugBundle not advertised by this host');
242
253
  const tp = makeTraceparent();
243
254
  // Run create and the bundle read are asserted by the legs above; here they
@@ -26,7 +26,7 @@ import { driver } from '../lib/driver.js';
26
26
  import { pollUntilTerminal } from '../lib/polling.js';
27
27
  import { isFixtureAdvertised } from '../lib/fixtures.js';
28
28
  import { req } from '../lib/requirement-ids.js';
29
- import { softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
29
+ import { softSkip, blockedDespiteAssertions, type SoftSkipKind } from '../lib/soft-skip.js';
30
30
 
31
31
  const PARENT = 'conformance-dispatch-cross-worker-handoff';
32
32
  const CHILD_A = 'conformance-dispatch-cross-worker-handoff-child-a';
@@ -48,8 +48,15 @@ interface RunSnapshot {
48
48
  readonly variables?: Record<string, unknown>;
49
49
  }
50
50
 
51
- describe.skipIf(SKIP)('dispatch-cross-worker-handoff: sequential child→parent→child variable flow (RFC 0022 §A + §D)', () => {
51
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
52
+ const GATE_WHY: readonly [SoftSkipKind, string] =
53
+ (!isFixtureAdvertised(PARENT)) ? ['blocked', `the \`${PARENT}\` fixture is not advertised`] as const :
54
+ (!isFixtureAdvertised(CHILD_A)) ? ['blocked', `the \`${CHILD_A}\` fixture is not advertised`] as const :
55
+ (!isFixtureAdvertised(CHILD_B)) ? ['blocked', `the \`${CHILD_B}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
56
+
57
+ describe('dispatch-cross-worker-handoff: sequential child→parent→child variable flow (RFC 0022 §A + §D)', () => {
52
58
  it('HVMAP-1c: child-a writes via perWorkerOutputMappings; child-b reads via perWorkerInputMappings; shared parent bag is the handoff channel', async () => {
59
+ if (SKIP) return softSkip(...GATE_WHY);
53
60
  const create = await driver.post('/v1/runs', { workflowId: PARENT });
54
61
  expect(create.status).toBe(201);
55
62
  const parentRunId = (create.json as { runId: string }).runId;
@@ -95,6 +102,7 @@ describe.skipIf(SKIP)('dispatch-cross-worker-handoff: sequential child→parent
95
102
  });
96
103
 
97
104
  it('HVMAP-1c-override: per-worker mapping overrides default mapping per §A effectiveInputMapping precedence', async () => {
105
+ if (SKIP) return softSkip(...GATE_WHY);
98
106
  const PARENT_OVERRIDE = 'conformance-dispatch-per-worker-override';
99
107
  if (!isFixtureAdvertised(PARENT_OVERRIDE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(PARENT_OVERRIDE)` returned early (fixture not seeded — soft-skip)'); // fixture not seeded — soft-skip
100
108
  const create = await driver.post('/v1/runs', { workflowId: PARENT_OVERRIDE });
@@ -23,7 +23,7 @@ import { driver } from '../lib/driver.js';
23
23
  import { pollUntilTerminal } from '../lib/polling.js';
24
24
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
25
  import { req } from '../lib/requirement-ids.js';
26
- import { softSkip } from '../lib/soft-skip.js';
26
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
27
27
 
28
28
  const PARENT = 'conformance-dispatch-output-mapping';
29
29
  const CHILD = 'conformance-dispatch-output-mapping-child';
@@ -34,8 +34,22 @@ interface RunSnapshot {
34
34
  readonly variables?: Record<string, unknown>;
35
35
  }
36
36
 
37
- describe.skipIf(SKIP)('dispatch-output-mapping: child → parent variable harvest (RFC 0022 §A)', () => {
37
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
38
+ const GATE_WHY: readonly [SoftSkipKind, string] =
39
+ (!isFixtureAdvertised(PARENT)) ? ['blocked', `the \`${PARENT}\` fixture is not advertised`] as const :
40
+ (!isFixtureAdvertised(CHILD)) ? ['blocked', `the \`${CHILD}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
41
+
42
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
43
+ const GATE_WHY_2: readonly [SoftSkipKind, string] =
44
+ (!isFixtureAdvertised('conformance-dispatch-deterministic-fail-child')) ? ['blocked', `the \`conformance-dispatch-deterministic-fail-child\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
45
+
46
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
47
+ const GATE_WHY_3: readonly [SoftSkipKind, string] =
48
+ (!isFixtureAdvertised('conformance-dispatch-cancellable-child')) ? ['blocked', `the \`conformance-dispatch-cancellable-child\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
49
+
50
+ describe('dispatch-output-mapping: child → parent variable harvest (RFC 0022 §A)', () => {
38
51
  it('HVMAP-1b: outputMapping harvests child variables into parent variables on terminal completed', async () => {
52
+ if (SKIP) return softSkip(...GATE_WHY);
39
53
  const create = await driver.post('/v1/runs', { workflowId: PARENT });
40
54
  expect(create.status).toBe(201);
41
55
  const parentRunId = (create.json as { runId: string }).runId;
@@ -98,8 +112,9 @@ async function registerParent(childFixtureId: string): Promise<string | null> {
98
112
  return workflowId;
99
113
  }
100
114
 
101
- describe.skipIf(!isFixtureAdvertised('conformance-dispatch-deterministic-fail-child'))('dispatch-output-mapping: HVMAP-1b-failed (RFC 0022 §B)', () => {
115
+ describe('dispatch-output-mapping: HVMAP-1b-failed (RFC 0022 §B)', () => {
102
116
  it('child terminates `failed` → outputMapping MUST be skipped; parent.parentResult stays at sentinel', async () => {
117
+ if (!isFixtureAdvertised('conformance-dispatch-deterministic-fail-child')) return softSkip(...GATE_WHY_2);
103
118
  const parentId = await registerParent('conformance-dispatch-deterministic-fail-child');
104
119
  if (!parentId) return softSkip('blocked', 'precondition not met — `!parentId` returned early (workflow-register seam not exposed — soft-skip) (seam, prior step, or fixture unavailable)'); // workflow-register seam not exposed — soft-skip
105
120
  const create = await driver.post('/v1/runs', { workflowId: parentId });
@@ -119,8 +134,9 @@ describe.skipIf(!isFixtureAdvertised('conformance-dispatch-deterministic-fail-ch
119
134
  });
120
135
  });
121
136
 
122
- describe.skipIf(!isFixtureAdvertised('conformance-dispatch-cancellable-child'))('dispatch-output-mapping: HVMAP-1b-cancelled (RFC 0022 §B)', () => {
137
+ describe('dispatch-output-mapping: HVMAP-1b-cancelled (RFC 0022 §B)', () => {
123
138
  it('child terminates `cancelled` → outputMapping MUST be skipped; parent.parentResult stays at sentinel', async () => {
139
+ if (!isFixtureAdvertised('conformance-dispatch-cancellable-child')) return softSkip(...GATE_WHY_3);
124
140
  const parentId = await registerParent('conformance-dispatch-cancellable-child');
125
141
  if (!parentId) return softSkip('blocked', 'precondition not met — `!parentId` returned early (soft-skip) (seam, prior step, or fixture unavailable)'); // soft-skip
126
142
  const create = await driver.post('/v1/runs', { workflowId: parentId });
@@ -16,12 +16,18 @@ import { driver } from '../lib/driver.js';
16
16
  import { pollUntilTerminal } from '../lib/polling.js';
17
17
  import { isFixtureAdvertised } from '../lib/fixtures.js';
18
18
  import { req } from '../lib/requirement-ids.js';
19
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
19
20
 
20
21
  const DISPATCH_LOOP_WORKFLOW_ID = 'conformance-dispatch-loop';
21
22
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(DISPATCH_LOOP_WORKFLOW_ID);
22
23
 
23
- describe.skipIf(SKIP_NO_FIXTURE)('dispatchLoop: core.dispatch consumes OrchestratorDecision', () => {
24
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
25
+ const GATE_WHY: readonly [SoftSkipKind, string] =
26
+ (!isFixtureAdvertised(DISPATCH_LOOP_WORKFLOW_ID)) ? ['blocked', `the \`${DISPATCH_LOOP_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
27
+
28
+ describe('dispatchLoop: core.dispatch consumes OrchestratorDecision', () => {
24
29
  it('host correctly processes orchestrator decisions and terminates', async () => {
30
+ if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
25
31
  // 1. Create the run
26
32
  const create = await driver.post('/v1/runs', { workflowId: DISPATCH_LOOP_WORKFLOW_ID });
27
33
  expect(create.status).toBe(201);
@@ -47,7 +47,7 @@ import { describe, expect, it } from 'vitest';
47
47
  import { driver } from '../lib/driver.js';
48
48
  import { pollUntilTerminal, scaledTimeoutMs } from '../lib/polling.js';
49
49
  import { isFixtureAdvertised } from '../lib/fixtures.js';
50
- import { blockedDespiteAssertions } from '../lib/soft-skip.js';
50
+ import { blockedDespiteAssertions, softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
51
51
  import { queryTestEvents, requireEvents } from '../lib/event-log-query.js';
52
52
  import { recordRequirement } from '../lib/requirement-ledger.js';
53
53
  import { requirementIdForFile } from '../lib/scenario-disposition.js';
@@ -88,8 +88,13 @@ if (SKIP_NO_FIXTURE) {
88
88
  recordRequirement(REQ, 'inapplicable', `fixture ${WORKFLOW_ID} not advertised`);
89
89
  }
90
90
 
91
- describe.skipIf(SKIP_NO_FIXTURE)('RFC 0158 §C.8 — poison work terminates within a bounded number of attempts', () => {
91
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
92
+ const GATE_WHY: readonly [SoftSkipKind, string] =
93
+ (!isFixtureAdvertised(WORKFLOW_ID)) ? ['inapplicable', `fixture ${WORKFLOW_ID} not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
94
+
95
+ describe('RFC 0158 §C.8 — poison work terminates within a bounded number of attempts', () => {
92
96
  it('reaches terminal AND stops being retried, asserted on the log rather than on the status alone', async () => {
97
+ if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
93
98
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
94
99
  expect(create.status, req('openwop.it.durability-poison-exhaustion.reaches-terminal-and-stops-being-retried-asserted-on-the-log-rather-than-on-the',
95
100
  'rest-endpoints.md',
@@ -26,6 +26,7 @@ import { describe, it, expect } from 'vitest';
26
26
  import { driver } from '../lib/driver.js';
27
27
  import { isFixtureAdvertised } from '../lib/fixtures.js';
28
28
  import { req } from '../lib/requirement-ids.js';
29
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
29
30
 
30
31
  const WORKFLOW_ID = 'conformance-idempotent';
31
32
  const SKIP_SCALE = process.env.OPENWOP_SKIP_SCALE_PRODUCTION === '1';
@@ -64,12 +65,18 @@ async function createRun(body: unknown, key?: string): Promise<RunCreateResult>
64
65
  };
65
66
  }
66
67
 
67
- describe.skipIf(SKIP)(
68
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
69
+ const GATE_WHY: readonly [SoftSkipKind, string] =
70
+ (SKIP_SCALE) ? ['skipped', `the operator opted out of the scale scenario (OPENWOP_SKIP_SCALE_PRODUCTION=1)`] as const :
71
+ (!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
72
+
73
+ describe(
68
74
  'high-concurrency: parallel POST /v1/runs per scale-profiles.md §"Conformance scenarios"',
69
75
  () => {
70
76
  it(
71
77
  '10 parallel requests with same key yield ONE runId and 9 replays',
72
78
  async () => {
79
+ if (SKIP) return softSkip(...GATE_WHY);
73
80
  const key = freshKey('parallel-same-key');
74
81
  const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'parallel-1' } };
75
82
 
@@ -124,6 +131,7 @@ describe.skipIf(SKIP)(
124
131
  it(
125
132
  '10 parallel requests with distinct keys yield 10 distinct runIds',
126
133
  async () => {
134
+ if (SKIP) return softSkip(...GATE_WHY);
127
135
  const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'parallel-2' } };
128
136
 
129
137
  const results = await Promise.all(
@@ -173,6 +181,7 @@ describe.skipIf(SKIP)(
173
181
  it(
174
182
  '5 sequential retries with same key 100ms apart all succeed (idempotency cache survives retry storm)',
175
183
  async () => {
184
+ if (SKIP) return softSkip(...GATE_WHY);
176
185
  const key = freshKey('retry-storm');
177
186
  const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'retry-storm' } };
178
187
 
@@ -227,6 +236,7 @@ describe.skipIf(SKIP)(
227
236
  it(
228
237
  'concurrent distinct-key requests respect advertised idempotency cache retention',
229
238
  async () => {
239
+ if (SKIP) return softSkip(...GATE_WHY);
230
240
  // This is a structural assertion against /.well-known/openwop, NOT a
231
241
  // wall-clock test. We can't realistically wait 24h for cache
232
242
  // expiration in a conformance run. Instead we assert the host
@@ -20,6 +20,7 @@ import { join } from 'node:path';
20
20
  import { SCENARIOS_DIR } from '../lib/paths.js';
21
21
  import { HARNESS_DOUBLE_MODULES } from '../lib/host-callback.js';
22
22
  import { req } from '../lib/requirement-ids.js';
23
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
23
24
 
24
25
  const DECLARATION = /export\s+const\s+REQUIRES_HOST_CALLBACK\s*[:=]/;
25
26
  /**
@@ -57,10 +58,15 @@ function scan(dir: string): Scenario[] {
57
58
  });
58
59
  }
59
60
 
60
- describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/README §"Where the suite runs")', () => {
61
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
62
+ const GATE_WHY: readonly [SoftSkipKind, string] =
63
+ (SCENARIOS_DIR === null) ? ['inapplicable', `the published package does not bundle the scenario sources this check reads, so nothing about the host is exercised`] as const : ['blocked', 'the gate held for no named reason'] as const;
64
+
65
+ describe('host-callback declaration (conformance/README §"Where the suite runs")', () => {
61
66
  const all = SCENARIOS_DIR === null ? [] : scan(SCENARIOS_DIR);
62
67
 
63
68
  it('the scan reaches the scenario corpus', () => {
69
+ if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
64
70
  // Guard: an empty scan makes every leg below vacuously true, which is the
65
71
  // shape RFC 0148 §C found in the floor verifier. A gate that passes by
66
72
  // having looked at nothing is worse than no gate, because it reports clean.
@@ -74,6 +80,7 @@ describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/
74
80
  });
75
81
 
76
82
  it('every scenario driving a harness double declares the callback', () => {
83
+ if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
77
84
  const undeclared = all
78
85
  .filter((s) => s.doubles.length > 0 && !s.declared && !s.optedOut)
79
86
  .map((s) => `${s.file} (imports ${s.doubles.join(', ')})`);
@@ -91,6 +98,7 @@ describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/
91
98
  });
92
99
 
93
100
  it('the declaration states a reason rather than a bare flag', () => {
101
+ if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
94
102
  // A boolean records that somebody ticked a box. A sentence records what a
95
103
  // consumer must route, and is checkable against the scenario body by anyone
96
104
  // reading the diff — the same annotated-vs-bare rule RFC 0149 §D applies to
@@ -110,6 +118,7 @@ describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/
110
118
  });
111
119
 
112
120
  it('nothing declares a callback it does not make', () => {
121
+ if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
113
122
  // The reverse direction, and the one that keeps the list honest as the
114
123
  // corpus moves. A declaration left behind after the double was removed
115
124
  // would tell a consumer to route something nobody needs — a stale claim,
@@ -14,12 +14,18 @@ import { driver } from '../lib/driver.js';
14
14
  import { pollUntilTerminal } from '../lib/polling.js';
15
15
  import { isFixtureAdvertised } from '../lib/fixtures.js';
16
16
  import { req } from '../lib/requirement-ids.js';
17
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
17
18
 
18
19
  const WORKFLOW_ID = 'conformance-identity';
19
20
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
20
21
 
21
- describe.skipIf(SKIP_NO_FIXTURE)('identity: conformance-identity fixture echoes payload input to variables', () => {
22
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
23
+ const GATE_WHY: readonly [SoftSkipKind, string] =
24
+ (!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
25
+
26
+ describe('identity: conformance-identity fixture echoes payload input to variables', () => {
22
27
  it('arbitrary nested JSON payload round-trips through inputs → variables', async () => {
28
+ if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
23
29
  const payload = {
24
30
  stringField: 'hello',
25
31
  intField: 42,