@openwop/openwop-conformance 2.45.0 → 2.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/spec-artifacts.lock.json +2 -2
  3. package/package.json +2 -2
  4. package/requirements.json +132 -127
  5. package/schemas/CORPUS-STAMP.json +9 -9
  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-run-list.test.ts +14 -16
  63. package/src/scenarios/version-fold.test.ts +8 -2
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
3
3
  "package": "@openwop/spec-artifacts",
4
- "version": "2.45.0",
5
- "corpusTag": "v2.45.0",
4
+ "version": "2.45.1",
5
+ "corpusTag": "v2.45.1",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "69464bc0e1e71ef3b543a73d4fa4f67202343d2603c80af8033f6c3619ef1b30",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "92744ab58f83798c63b96700475bd55c201e7af14c3fd230c31ab0bbd86f6a60",
13
- "api/v2/asyncapi.yaml": "4f00d2847c3ffb526af9c294f9e318b762f197e86cfd607934e677f12a1fc8bc",
14
- "api/v2/openapi.yaml": "396fe10fc350cbae59190c4bd0e4cd24e18b16b4aac9db2f6c78e54344db4709",
12
+ "api/seams-v2.yaml": "c2d7c5d68c7358229149d2552da95e32798b9c21b21a56e19d561f29eab3633e",
13
+ "api/v2/asyncapi.yaml": "e9c0e3cc27f8d72a6f7704de24cb81f39175b8e6bc3ae5e4ad87d46fb0be6b0c",
14
+ "api/v2/openapi.yaml": "622817c49d5977b51bb83685e6cb0fe27a5e087b63c3a31621959670d6829a2d",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -204,7 +204,7 @@
204
204
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
205
205
  "spec/v1/alias-detectors.json": "0b2f28808ffe0b9d98a187775038c3406bb70919747a788a6e4f866e293ae6d3",
206
206
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
207
- "spec/v1/core-standard-manifest.json": "684dff535d3536673129ef594b97717cbc30fd066a6ea4d2c52d9fbbde69836d",
207
+ "spec/v1/core-standard-manifest.json": "4aa1dbeec20dc51fb727ad85e2012af1235bac608e41836db256e7816916f898",
208
208
  "spec/v1/deprecations.json": "a3293072ea951857395d05f0437264e88965f1e9019ff61c55bacfa9c1aaa17c",
209
209
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
210
210
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -303,9 +303,9 @@
303
303
  "spec/v2/path-manifest.json": "27c2307b84376b2f22bea4e2213fc7e33ef3bf8fd1d18039f7955edb00dc9c9a",
304
304
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
305
305
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
306
- "spec/v2/release.json": "89c093ee04d4aaceef641782e53fbc368e21123e244246f1727fa8ac3b151101",
306
+ "spec/v2/release.json": "e6a344ecc48e172a585fafeb83631c26629373e847b144a86cc01628693ea3ad",
307
307
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
308
- "spec/v2/surface-baseline.json": "85c44cc57bfc9f8242e77dc27d7b28a1b239c694ef761cce4748ab15d6a3290f"
308
+ "spec/v2/surface-baseline.json": "b70b9f22313a99ea4853f8ffee1c4f6659b89e8cbba7317c52771fe9dfd40330"
309
309
  },
310
- "corpusCommit": "1ac68fd60069d0b9729312976de4946171281f87"
310
+ "corpusCommit": "11a23de713fd4d56d9514a753d25229b029fb461"
311
311
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The `GET /runs` walk the RFC 0182 run-list legs use (`v2-run-list.test.ts`).
3
+ *
4
+ * The list is newest first (`runs.md` §List), so the runs a leg just created
5
+ * sit on the first page. The walk therefore stops once every wanted id has
6
+ * been seen, then follows `nextCursor` for a bounded tail (`tailPages`, or
7
+ * until the list ends) so a host-minted cursor is still exercised. It never
8
+ * walks the whole history: on a long-lived host the conformance tenant holds
9
+ * thousands of runs, and a full walk outran the test timeout (openwop-app,
10
+ * 2026-09-29), failing a leg on the tenant's age rather than the host's
11
+ * behaviour.
12
+ *
13
+ * Pure: the page fetch is injected, so the walk is self-tested without a host.
14
+ */
15
+
16
+ export type RunListPage = { runs?: unknown; nextCursor?: unknown };
17
+
18
+ export type FetchPage = (cursor: string | undefined) => Promise<{ page: RunListPage } | { reason: string }>;
19
+
20
+ export interface WalkResult {
21
+ /** Every runId seen, in list order. */
22
+ readonly ids: string[];
23
+ readonly pages: RunListPage[];
24
+ /** True when every wanted id was seen. */
25
+ readonly foundAll: boolean;
26
+ /** Why the walk stopped early (fetch failure or the page cap), if it did. */
27
+ readonly reason?: string;
28
+ }
29
+
30
+ export interface WalkOptions {
31
+ /** Ids the leg created and expects to find. */
32
+ readonly want: readonly string[];
33
+ /** Pages still followed after every wanted id is seen (default 2). */
34
+ readonly tailPages?: number;
35
+ /** Hard cap on pages fetched while searching (default 50). */
36
+ readonly maxPages?: number;
37
+ }
38
+
39
+ export async function walkRunList(fetchPage: FetchPage, opts: WalkOptions): Promise<WalkResult> {
40
+ const tailPages = opts.tailPages ?? 2;
41
+ const maxPages = opts.maxPages ?? 50;
42
+ const wanted = new Set(opts.want);
43
+ const ids: string[] = [];
44
+ const pages: RunListPage[] = [];
45
+ let cursor: string | undefined;
46
+ let tailLeft: number | undefined; // set once every wanted id is seen
47
+ for (let i = 0; i < maxPages + tailPages; i++) {
48
+ if (tailLeft === undefined && i >= maxPages) {
49
+ return { ids, pages, foundAll: false, reason: `the wanted id(s) did not appear within ${maxPages} page(s)` };
50
+ }
51
+ const got = await fetchPage(cursor);
52
+ if ('reason' in got) return { ids, pages, foundAll: [...wanted].every((w) => ids.includes(w)), reason: got.reason };
53
+ pages.push(got.page);
54
+ for (const r of Array.isArray(got.page.runs) ? got.page.runs : []) {
55
+ const id = (r as { runId?: unknown }).runId;
56
+ if (typeof id === 'string') ids.push(id);
57
+ }
58
+ if (tailLeft === undefined && [...wanted].every((w) => ids.includes(w))) tailLeft = tailPages;
59
+ else if (tailLeft !== undefined) tailLeft -= 1;
60
+ if (typeof got.page.nextCursor !== 'string') break;
61
+ if (tailLeft !== undefined && tailLeft <= 0) break;
62
+ cursor = got.page.nextCursor;
63
+ }
64
+ return { ids, pages, foundAll: [...wanted].every((w) => ids.includes(w)) };
65
+ }
@@ -22,12 +22,19 @@ import { driver } from '../lib/driver.js';
22
22
  import { isFixtureAdvertised } from '../lib/fixtures.js';
23
23
  import { isAgentSupported } 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-agent-low-confidence';
27
28
  const SKIP = !isAgentSupported() || !isFixtureAdvertised(FIXTURE);
28
29
 
29
- describe.skipIf(SKIP)('agentConfidenceEscalation: confidence < threshold → low-confidence suspend', () => {
30
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
31
+ const GATE_WHY: readonly [SoftSkipKind, string] =
32
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const :
33
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
34
+
35
+ describe('agentConfidenceEscalation: confidence < threshold → low-confidence suspend', () => {
30
36
  it('low-confidence agent.decided suspends with reason=low-confidence and run reaches waiting-approval', async () => {
37
+ if (SKIP) return softSkip(...GATE_WHY);
31
38
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
32
39
  expect(create.status).toBe(201);
33
40
  const runId = (create.json as { runId: string }).runId;
@@ -21,12 +21,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
21
21
  import { isFixtureAdvertised } from '../lib/fixtures.js';
22
22
  import { hasLongTermMemory } 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-agent-memory-cross-tenant';
26
27
  const SKIP = !hasLongTermMemory() || !isFixtureAdvertised(FIXTURE);
27
28
 
28
- describe.skipIf(SKIP)('agentMemoryCrossTenantIsolation: CTI-1 invariant', () => {
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
+ (!hasLongTermMemory()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (hasLongTermMemory() 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('agentMemoryCrossTenantIsolation: CTI-1 invariant', () => {
29
35
  it('cross-tenant memoryRef returns empty / null — no leak across tenant boundary', 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;
@@ -21,12 +21,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
21
21
  import { isFixtureAdvertised } from '../lib/fixtures.js';
22
22
  import { hasLongTermMemory } 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-agent-memory-redaction';
26
27
  const SKIP = !hasLongTermMemory() || !isFixtureAdvertised(FIXTURE);
27
28
 
28
- describe.skipIf(SKIP)('agentMemoryRedactionContract: SR-1 invariant', () => {
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
+ (!hasLongTermMemory()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (hasLongTermMemory() 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('agentMemoryRedactionContract: SR-1 invariant', () => {
29
35
  it('BYOK plaintext is redacted to [REDACTED:<secretId>] at the read surface', async () => {
36
+ if (SKIP) return softSkip(...GATE_WHY);
30
37
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
31
38
  expect(create.status, req('openwop.it.agentMemoryRedactionContract.byok-plaintext-is-redacted-to-redacted-secretid-at-the-read-surface', 'RFCS/0004-memory-layer.md', 'BYOK plaintext is redacted to [REDACTED:<secretId>] at the read surface')).toBe(201);
32
39
  const runId = (create.json as { runId: string }).runId;
@@ -20,12 +20,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
20
20
  import { isFixtureAdvertised } from '../lib/fixtures.js';
21
21
  import { hasLongTermMemory } from '../lib/multi-agent-capabilities.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 FIXTURE = 'conformance-agent-memory-roundtrip';
25
26
  const SKIP = !hasLongTermMemory() || !isFixtureAdvertised(FIXTURE);
26
27
 
27
- describe.skipIf(SKIP)('agentMemoryRoundTrip: write → read via MemoryAdapter', () => {
28
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
29
+ const GATE_WHY: readonly [SoftSkipKind, string] =
30
+ (!hasLongTermMemory()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (hasLongTermMemory() is false)`] as const :
31
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
32
+
33
+ describe('agentMemoryRoundTrip: write → read via MemoryAdapter', () => {
28
34
  it('memory entries written during a run are readable via the resolved memoryRef', async () => {
35
+ if (SKIP) return softSkip(...GATE_WHY);
29
36
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
30
37
  expect(create.status, req('openwop.it.agentMemoryRoundTrip.memory-entries-written-during-a-run-are-readable-via-the-resolved-memoryref', 'RFCS/0004-memory-layer.md', 'memory entries written during a run are readable via the resolved memoryRef')).toBe(201);
31
38
  const runId = (create.json as { runId: string }).runId;
@@ -19,12 +19,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
19
19
  import { isFixtureAdvertised } from '../lib/fixtures.js';
20
20
  import { hasLongTermMemory } from '../lib/multi-agent-capabilities.js';
21
21
  import { req } from '../lib/requirement-ids.js';
22
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
22
23
 
23
24
  const FIXTURE = 'conformance-agent-memory-ttl';
24
25
  const SKIP = !hasLongTermMemory() || !isFixtureAdvertised(FIXTURE);
25
26
 
26
- describe.skipIf(SKIP)('agentMemoryTtlExpiry: expired entries are excluded from list/get', () => {
27
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
28
+ const GATE_WHY: readonly [SoftSkipKind, string] =
29
+ (!hasLongTermMemory()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (hasLongTermMemory() is false)`] as const :
30
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
31
+
32
+ describe('agentMemoryTtlExpiry: expired entries are excluded from list/get', () => {
27
33
  it('list() excludes entries whose expiresAt is in the past', async () => {
34
+ if (SKIP) return softSkip(...GATE_WHY);
28
35
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
29
36
  expect(create.status).toBe(201);
30
37
  const runId = (create.json as { runId: string }).runId;
@@ -24,12 +24,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
24
24
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
25
  import { isAgentSupported } from '../lib/multi-agent-capabilities.js';
26
26
  import { req } from '../lib/requirement-ids.js';
27
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
27
28
 
28
29
  const FIXTURE = 'conformance-message-reducer';
29
30
  const SKIP = !isAgentSupported() || !isFixtureAdvertised(FIXTURE);
30
31
 
31
- describe.skipIf(SKIP)('agentMessageReducer: message reducer idempotency + append-only invariant', () => {
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
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] 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('agentMessageReducer: message reducer idempotency + append-only invariant', () => {
32
38
  it('duplicate messageId emissions fold to a single channel entry', async () => {
39
+ if (SKIP) return softSkip(...GATE_WHY);
33
40
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
34
41
  expect(create.status).toBe(201);
35
42
  const runId = (create.json as { runId: string }).runId;
@@ -24,12 +24,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
24
24
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
25
  import { isAgentSupported } from '../lib/multi-agent-capabilities.js';
26
26
  import { req } from '../lib/requirement-ids.js';
27
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
27
28
 
28
29
  const FIXTURE = 'conformance-agent-identity';
29
30
  const SKIP = !isAgentSupported() || !isFixtureAdvertised(FIXTURE);
30
31
 
31
- describe.skipIf(SKIP)('agentMetadata: RunSnapshot.agent + runOrchestrator wire-shape', () => {
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
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] 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('agentMetadata: RunSnapshot.agent + runOrchestrator wire-shape', () => {
32
38
  it('host populates AgentRef-shaped agent identity on RunSnapshot', async () => {
39
+ if (SKIP) return softSkip(...GATE_WHY);
33
40
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
34
41
  expect(create.status).toBe(201);
35
42
  const runId = (create.json as { runId: string }).runId;
@@ -32,7 +32,7 @@ import { describe, it, expect } from 'vitest';
32
32
  import { driver } from '../lib/driver.js';
33
33
  import { isAgentSupported } from '../lib/multi-agent-capabilities.js';
34
34
  import { req } from '../lib/requirement-ids.js';
35
- import { softSkip } from '../lib/soft-skip.js';
35
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
36
36
 
37
37
  interface PackList {
38
38
  packs?: Array<{
@@ -132,8 +132,13 @@ function assertAgentManifestShape(requirementId: string,
132
132
 
133
133
  const SKIP = !isAgentSupported();
134
134
 
135
- describe.skipIf(SKIP)('core.openwop.agents.deep-research — pack catalog evidence', () => {
135
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
136
+ const GATE_WHY: readonly [SoftSkipKind, string] =
137
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const : ['blocked', 'the gate held for no named reason'] as const;
138
+
139
+ describe('core.openwop.agents.deep-research — pack catalog evidence', () => {
136
140
  it('host pack-list includes deep-research with a well-formed AgentManifest', async () => {
141
+ if (SKIP) return softSkip(...GATE_WHY);
137
142
  const pack = await findPack('core.openwop.agents.deep-research');
138
143
  if (pack === null) return softSkip('blocked', 'precondition not met — `pack === null` returned early (host doesn\'t expose /v1/packs or doesn\'t have this pack) (seam, prior step, or fixture unavailable)'); // host doesn't expose /v1/packs or doesn't have this pack
139
144
  expect(pack.version, req('openwop.it.agentPackCatalog.host-pack-list-includes-deep-research-with-a-well-formed-agentmanifest', 'RFCS/0003-agent-packs.md', 'pack version MUST be present')).toBeDefined();
@@ -156,8 +161,9 @@ describe.skipIf(SKIP)('core.openwop.agents.deep-research — pack catalog eviden
156
161
  });
157
162
  });
158
163
 
159
- describe.skipIf(SKIP)('core.openwop.agents.react — pack catalog evidence', () => {
164
+ describe('core.openwop.agents.react — pack catalog evidence', () => {
160
165
  it('host pack-list includes react with a well-formed AgentManifest', async () => {
166
+ if (SKIP) return softSkip(...GATE_WHY);
161
167
  const pack = await findPack('core.openwop.agents.react');
162
168
  if (pack === null) return softSkip('blocked', 'precondition not met — `pack === null` returned early (seam, prior step, or fixture unavailable)');
163
169
  expect(pack.version).toBeDefined();
@@ -173,8 +179,9 @@ describe.skipIf(SKIP)('core.openwop.agents.react — pack catalog evidence', ()
173
179
  });
174
180
  });
175
181
 
176
- describe.skipIf(SKIP)('core.openwop.agents.supervisor — pack catalog evidence', () => {
182
+ describe('core.openwop.agents.supervisor — pack catalog evidence', () => {
177
183
  it('host pack-list includes supervisor with a well-formed AgentManifest', async () => {
184
+ if (SKIP) return softSkip(...GATE_WHY);
178
185
  const pack = await findPack('core.openwop.agents.supervisor');
179
186
  if (pack === null) return softSkip('blocked', 'precondition not met — `pack === null` returned early (seam, prior step, or fixture unavailable)');
180
187
  expect(pack.version).toBeDefined();
@@ -194,8 +201,9 @@ describe.skipIf(SKIP)('core.openwop.agents.supervisor — pack catalog evidence'
194
201
  });
195
202
  });
196
203
 
197
- describe.skipIf(SKIP)('agent-pack catalog summary', () => {
204
+ describe('agent-pack catalog summary', () => {
198
205
  it('all three 2026-05-17 reference agent packs are catalog-reachable', async () => {
206
+ if (SKIP) return softSkip(...GATE_WHY);
199
207
  const names = [
200
208
  'core.openwop.agents.deep-research',
201
209
  'core.openwop.agents.react',
@@ -23,7 +23,7 @@ import { SCHEMAS_DIR } from '../lib/paths.js';
23
23
  import { driver } from '../lib/driver.js';
24
24
  import { isFixtureAdvertised } from '../lib/fixtures.js';
25
25
  import { isAgentSupported } from '../lib/multi-agent-capabilities.js';
26
- import { softSkip } from '../lib/soft-skip.js';
26
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
27
27
  import { req } from '../lib/requirement-ids.js';
28
28
 
29
29
  const FIXTURE = 'conformance-agent-pack-export';
@@ -49,8 +49,14 @@ function agentManifestValidator(): (doc: unknown) => { ok: boolean; errors: stri
49
49
  return (doc: unknown) => ({ ok: validate(doc) as boolean, errors: ajv.errorsText(validate.errors, { separator: '; ' }) });
50
50
  }
51
51
 
52
- describe.skipIf(SKIP)('agentPackExport: workspace agents project to AgentManifest', () => {
52
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
53
+ const GATE_WHY: readonly [SoftSkipKind, string] =
54
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const :
55
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
56
+
57
+ describe('agentPackExport: workspace agents project to AgentManifest', () => {
53
58
  it('exported manifests contain required AgentManifest fields', async () => {
59
+ if (SKIP) return softSkip(...GATE_WHY);
54
60
  const res = await driver.get('/v1/packs/export');
55
61
  if (res.status === 404 || res.status === 501) {
56
62
  // Host doesn't expose pack-export over REST; treated as skip.
@@ -52,6 +52,7 @@ import { driver } from '../lib/driver.js';
52
52
  import { isFixtureAdvertised } from '../lib/fixtures.js';
53
53
  import { isAgentSupported, hasHandoffValidation } from '../lib/multi-agent-capabilities.js';
54
54
  import { req } from '../lib/requirement-ids.js';
55
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
55
56
 
56
57
  const FIXTURE = 'conformance-agent-pack-handoff-schema-validation';
57
58
  const SKIP = !isAgentSupported() || !isFixtureAdvertised(FIXTURE);
@@ -67,8 +68,18 @@ async function settle(runId: string, terminal = ['completed', 'failed', 'waiting
67
68
  return undefined;
68
69
  }
69
70
 
70
- describe.skipIf(SKIP)('agentPackHandoffSchemaValidation: handoff schema enforcement at dispatch', () => {
71
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
72
+ const GATE_WHY: readonly [SoftSkipKind, string] =
73
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const :
74
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
75
+
76
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
77
+ const GATE_WHY_2: readonly [SoftSkipKind, string] =
78
+ (!hasHandoffValidation()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (hasHandoffValidation() is false)`] as const : ['blocked', 'the gate held for no named reason'] as const;
79
+
80
+ describe('agentPackHandoffSchemaValidation: handoff schema enforcement at dispatch', () => {
71
81
  it('HV-1a: valid task payload that matches taskSchemaRef is dispatched and completes', async () => {
82
+ if (SKIP) return softSkip(...GATE_WHY);
72
83
  // Valid inputs for `structured-fixture` task schema (required: text,
73
84
  // extractionFields). No `scenario` key → the gate takes the task probe,
74
85
  // `validateTask(inputs)` passes, the gate is ok, the no-op node runs.
@@ -88,8 +99,10 @@ describe.skipIf(SKIP)('agentPackHandoffSchemaValidation: handoff schema enforcem
88
99
 
89
100
  // HV-1b / HV-1c assert a REJECTION, which only a host performing handoff
90
101
  // validation can produce — gate on the capability that declares that behavior.
91
- describe.skipIf(!hasHandoffValidation())('rejection legs (agents.manifestRuntime.handoffValidation)', () => {
102
+ describe('rejection legs (agents.manifestRuntime.handoffValidation)', () => {
92
103
  it('HV-1b: invalid task payload (missing required field) fails before dispatch with a structured violation', async () => {
104
+ if (!hasHandoffValidation()) return softSkip(...GATE_WHY_2);
105
+ if (SKIP) return softSkip(...GATE_WHY);
93
106
  // HV-1a's inputs minus the required `extractionFields` — the paired
94
107
  // sabotage. `validateTask` fails `required` → the run MUST fail.
95
108
  const create = await driver.post('/v1/runs', {
@@ -121,6 +134,8 @@ describe.skipIf(SKIP)('agentPackHandoffSchemaValidation: handoff schema enforcem
121
134
  });
122
135
 
123
136
  it('HV-1c: agent return payload that fails returnSchemaRef surfaces a structured violation before persistence', async () => {
137
+ if (!hasHandoffValidation()) return softSkip(...GATE_WHY_2);
138
+ if (SKIP) return softSkip(...GATE_WHY);
124
139
  // `scenario: 'mock-return-violation'` routes to the return probe, which
125
140
  // validates `{}` against the return schema's success-XOR-error `oneOf`
126
141
  // (satisfies neither branch) → a return-schema violation.
@@ -24,7 +24,7 @@ import { SCHEMAS_DIR } from '../lib/paths.js';
24
24
  import { driver } from '../lib/driver.js';
25
25
  import { isFixtureAdvertised } from '../lib/fixtures.js';
26
26
  import { isAgentSupported } from '../lib/multi-agent-capabilities.js';
27
- import { softSkip } from '../lib/soft-skip.js';
27
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
28
28
  import { req } from '../lib/requirement-ids.js';
29
29
 
30
30
  const FIXTURE = 'conformance-agent-pack-install';
@@ -50,8 +50,14 @@ function agentManifestValidator(): (doc: unknown) => { ok: boolean; errors: stri
50
50
  return (doc: unknown) => ({ ok: validate(doc) as boolean, errors: ajv.errorsText(validate.errors, { separator: '; ' }) });
51
51
  }
52
52
 
53
- describe.skipIf(SKIP)('agentPackInstall: pack agents[] entries surface as AgentManifest', () => {
53
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
54
+ const GATE_WHY: readonly [SoftSkipKind, string] =
55
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const :
56
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
57
+
58
+ describe('agentPackInstall: pack agents[] entries surface as AgentManifest', () => {
54
59
  it('host exposes installed agent manifests with required AgentManifest fields', async () => {
60
+ if (SKIP) return softSkip(...GATE_WHY);
55
61
  // Host-specific pack-listing endpoint. The conformance suite probes
56
62
  // common paths; hosts that don't expose pack listings via REST mark
57
63
  // this scenario as skip via their own capability advertisement.
@@ -21,12 +21,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
21
21
  import { isFixtureAdvertised } from '../lib/fixtures.js';
22
22
  import { isAgentSupported } 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-agent-pack-provenance';
26
27
  const SKIP = !isAgentSupported() || !isFixtureAdvertised(FIXTURE);
27
28
 
28
- describe.skipIf(SKIP)('agentPackProvenance: sourceManifestId survives install + run', () => {
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
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() 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('agentPackProvenance: sourceManifestId survives install + run', () => {
29
35
  it('run-level AgentRef carries sourceManifestId pointing back to the install source', 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;
@@ -23,6 +23,7 @@ import {
23
23
  getReasoningVerbosity,
24
24
  } 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-agent-reasoning';
28
29
  const SKIP =
@@ -38,8 +39,15 @@ const REASONING_EVENT_TYPES = new Set([
38
39
  'agent.decided',
39
40
  ]);
40
41
 
41
- describe.skipIf(SKIP)('agentReasoningEvents: agent.* event family emission', () => {
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: readonly [SoftSkipKind, string] =
44
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const :
45
+ (getReasoningVerbosity() === 'off') ? ['inapplicable', `the host advertises reasoning verbosity \`off\`, so agent reasoning events do not apply`] as const :
46
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
47
+
48
+ describe('agentReasoningEvents: agent.* event family emission', () => {
42
49
  it('host emits at least one canonical agent.* event during a reasoning-fixture run', async () => {
50
+ if (SKIP) return softSkip(...GATE_WHY);
43
51
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
44
52
  expect(create.status).toBe(201);
45
53
  const runId = (create.json as { runId: string }).runId;
@@ -30,6 +30,7 @@ import {
30
30
  getReasoningVerbosity,
31
31
  } from '../lib/multi-agent-capabilities.js';
32
32
  import { req } from '../lib/requirement-ids.js';
33
+ import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
33
34
 
34
35
  const FIXTURE = 'conformance-agent-reasoning-streaming';
35
36
  /** Expected concatenation of the fixture's `streamChunks` — kept in sync
@@ -48,8 +49,16 @@ const SKIP =
48
49
  getReasoningVerbosity() === 'off' ||
49
50
  !isFixtureAdvertised(FIXTURE);
50
51
 
51
- describe.skipIf(SKIP)('agentReasoningStreaming: RFC 0024 incremental + closing event contract', () => {
52
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
53
+ const GATE_WHY: readonly [SoftSkipKind, string] =
54
+ (!isAgentSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isAgentSupported() is false)`] as const :
55
+ (!isReasoningStreamingSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isReasoningStreamingSupported() is false)`] as const :
56
+ (getReasoningVerbosity() === 'off') ? ['inapplicable', `the host advertises reasoning verbosity \`off\`, so agent reasoning events do not apply`] as const :
57
+ (!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
58
+
59
+ describe('agentReasoningStreaming: RFC 0024 incremental + closing event contract', () => {
52
60
  it('emits N agent.reasoning.delta events followed by exactly one closing agent.reasoned', async () => {
61
+ if (SKIP) return softSkip(...GATE_WHY);
53
62
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
54
63
  expect(create.status).toBe(201);
55
64
  const runId = (create.json as { runId: string }).runId;
@@ -80,6 +89,7 @@ describe.skipIf(SKIP)('agentReasoningStreaming: RFC 0024 incremental + closing e
80
89
  });
81
90
 
82
91
  it('agent.reasoning.delta `sequence` starts at 0 and increments by 1 within the block', async () => {
92
+ if (SKIP) return softSkip(...GATE_WHY);
83
93
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
84
94
  expect(create.status).toBe(201);
85
95
  const runId = (create.json as { runId: string }).runId;
@@ -104,6 +114,7 @@ describe.skipIf(SKIP)('agentReasoningStreaming: RFC 0024 incremental + closing e
104
114
  });
105
115
 
106
116
  it('closing agent.reasoned.reasoning is the concatenation of the deltas (authoritative)', async () => {
117
+ if (SKIP) return softSkip(...GATE_WHY);
107
118
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
108
119
  expect(create.status).toBe(201);
109
120
  const runId = (create.json as { runId: string }).runId;
@@ -130,6 +141,7 @@ describe.skipIf(SKIP)('agentReasoningStreaming: RFC 0024 incremental + closing e
130
141
  });
131
142
 
132
143
  it('agentId is consistent across all streaming + closing events in a block', async () => {
144
+ if (SKIP) return softSkip(...GATE_WHY);
133
145
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
134
146
  expect(create.status).toBe(201);
135
147
  const runId = (create.json as { runId: string }).runId;
@@ -158,6 +170,7 @@ describe.skipIf(SKIP)('agentReasoningStreaming: RFC 0024 incremental + closing e
158
170
  });
159
171
 
160
172
  it('all agent.reasoning.delta events arrive BEFORE the closing agent.reasoned', async () => {
173
+ if (SKIP) return softSkip(...GATE_WHY);
161
174
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
162
175
  expect(create.status).toBe(201);
163
176
  const runId = (create.json as { runId: string }).runId;
@@ -19,7 +19,7 @@ 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 { req } from '../lib/requirement-ids.js';
22
- import { softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
22
+ import { softSkip, blockedDespiteAssertions, type SoftSkipKind } from '../lib/soft-skip.js';
23
23
 
24
24
  const CANDIDATES = ['conformance-append-ordering', 'conformance-multi-node'] as const;
25
25
  const FIXTURE = CANDIDATES.find((id) => isFixtureAdvertised(id)) ?? null;
@@ -46,8 +46,13 @@ interface RunEvent {
46
46
  payload?: ChannelWrittenPayload;
47
47
  }
48
48
 
49
- describe.skipIf(SKIP)('append-ordering: folded channel reflects event sequence', () => {
49
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
50
+ const GATE_WHY: readonly [SoftSkipKind, string] =
51
+ (!FIXTURE) ? ['blocked', `none of the candidate fixtures (${CANDIDATES.join(', ')}) is advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
52
+
53
+ describe('append-ordering: folded channel reflects event sequence', () => {
50
54
  it('append-reducer channels project entries in event-sequence order', async () => {
55
+ if (SKIP) return softSkip(...GATE_WHY);
51
56
  const create = await driver.post('/v1/runs', { workflowId: FIXTURE! });
52
57
  const runId = (create.json as { runId?: string } | undefined)?.runId;
53
58
 
@@ -21,6 +21,7 @@ import { describe, it, expect } from 'vitest';
21
21
  import { driver } from '../lib/driver.js';
22
22
  import { isFixtureAdvertised } from '../lib/fixtures.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 CANCELLABLE = 'conformance-cancellable';
26
27
  const NOOP = 'conformance-noop';
@@ -34,8 +35,14 @@ interface BulkResult {
34
35
  error?: { code?: string; message?: string };
35
36
  }
36
37
 
37
- describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
38
+ /** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
39
+ const GATE_WHY: readonly [SoftSkipKind, string] =
40
+ (!isFixtureAdvertised(CANCELLABLE)) ? ['blocked', `the \`${CANCELLABLE}\` fixture is not advertised`] as const :
41
+ (!isFixtureAdvertised(NOOP)) ? ['blocked', `the \`${NOOP}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
42
+
43
+ describe('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
38
44
  it('mixed-outcome request returns per-id results in order', async () => {
45
+ if (SKIP) return softSkip(...GATE_WHY);
39
46
  // Spin up a long-running cancellable run + observe a known-bad id
40
47
  // alongside it. The host MUST handle each independently.
41
48
  const create = await driver.post('/v1/runs', {
@@ -71,6 +78,7 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
71
78
  });
72
79
 
73
80
  it('empty runIds array returns 400 validation_error', async () => {
81
+ if (SKIP) return softSkip(...GATE_WHY);
74
82
  const res = await driver.post('/v1/runs:bulk-cancel', { runIds: [] });
75
83
  expect(res.status, req('openwop.it.bulk-cancel.empty-runids-array-returns-400-validation-error', 'rest-endpoints.md §Open', 'empty runIds array returns 400 validation_error')).toBe(400);
76
84
  const body = res.json as { error?: string };
@@ -78,6 +86,7 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
78
86
  });
79
87
 
80
88
  it('oversized runIds array returns 400 with details.maxRunIds', async () => {
89
+ if (SKIP) return softSkip(...GATE_WHY);
81
90
  // 101 entries — exceeds the recommended 100-entry cap.
82
91
  const ids = Array.from({ length: 101 }, (_, i) => `run-overflow-${i}`);
83
92
  const res = await driver.post('/v1/runs:bulk-cancel', { runIds: ids });
@@ -92,6 +101,7 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
92
101
  });
93
102
 
94
103
  it('re-bulk-cancel after first cancel is idempotent', async () => {
104
+ if (SKIP) return softSkip(...GATE_WHY);
95
105
  const create = await driver.post('/v1/runs', {
96
106
  workflowId: CANCELLABLE,
97
107
  inputs: { delaySeconds: 30 },