@openwop/openwop-conformance 1.136.3 → 1.136.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * openwop-conformance --filter discovery # category filter
13
13
  * openwop-conformance --base-url ... --api-key ... --filter "interrupt|cancellation"
14
14
  * openwop-conformance --base-url ... --api-key ... --certify out.json # RFC 0089 bundle
15
+ * openwop-conformance --base-url ... --api-key ... --max-workers 4 # cap parallel scenario files
15
16
  *
16
17
  * Environment variables override flags (per the conformance harness's
17
18
  * existing convention):
@@ -46,6 +47,7 @@ function parseArgs(argv) {
46
47
  let implVersion;
47
48
  let certify;
48
49
  let bundleVersion = '1';
50
+ let maxWorkers = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
49
51
  for (let i = 0; i < argv.length; i++) {
50
52
  const arg = argv[i] ?? '';
51
53
  if (arg === '-h' || arg === '--help') {
@@ -99,13 +101,42 @@ function parseArgs(argv) {
99
101
  case '--certify':
100
102
  certify = nextValue();
101
103
  break;
104
+ case '--max-workers':
105
+ maxWorkers = parseMaxWorkers(nextValue(), '--max-workers');
106
+ break;
102
107
  default:
103
108
  if (arg.startsWith('-')) {
104
109
  // Unknown flag — pass through to vitest by ignoring here.
105
110
  }
106
111
  }
107
112
  }
108
- return { baseUrl, apiKey, offline, filter, help, impl, implVersion, certify, bundleVersion };
113
+ return {
114
+ baseUrl,
115
+ apiKey,
116
+ offline,
117
+ filter,
118
+ help,
119
+ impl,
120
+ implVersion,
121
+ certify,
122
+ bundleVersion,
123
+ maxWorkers,
124
+ };
125
+ }
126
+ /** Parse a `--max-workers` / `OPENWOP_MAX_WORKERS` value: a positive integer, else exit 2. */
127
+ function parseMaxWorkers(raw, source) {
128
+ if (raw === undefined || raw === '')
129
+ return undefined;
130
+ const n = Number(raw);
131
+ if (!Number.isInteger(n) || n < 1) {
132
+ process.stderr.write(`${source} must be a positive integer (got '${raw}')\n`);
133
+ process.exit(2);
134
+ }
135
+ return n;
136
+ }
137
+ /** The vitest argv fragment for the resolved worker cap (empty when uncapped). */
138
+ function maxWorkersArgs(maxWorkers) {
139
+ return maxWorkers === undefined ? [] : ['--maxWorkers', String(maxWorkers)];
109
140
  }
110
141
  const HELP_TEXT = `openwop-conformance — run the openwop conformance suite against a server
111
142
 
@@ -129,6 +160,10 @@ Certification (RFC 0089):
129
160
  §C) records per-requirement DISPOSITIONS instead of pass/fail/skip
130
161
  file lists, so "we could not check" stops being indistinguishable
131
162
  from "checked and it holds". See the note it prints.
163
+ --max-workers <n> Cap concurrently running scenario files (vitest --maxWorkers).
164
+ Default: one worker per CPU. Use a small number against a
165
+ rate-limited production origin so 429s don't read as failures.
166
+ (env: OPENWOP_MAX_WORKERS)
132
167
  --certify <out.json> Generate a machine-readable conformance certification
133
168
  bundle: fetch /.well-known/openwop (captured verbatim +
134
169
  SHA-256), derive claimedProfiles from it, run the suite
@@ -271,6 +306,7 @@ async function runCertify(args, baseUrl, apiKey) {
271
306
  resolvePath(conformanceRoot, 'vitest.config.ts'),
272
307
  '--reporter=json',
273
308
  `--outputFile=${reportFile}`,
309
+ ...maxWorkersArgs(args.maxWorkers),
274
310
  ];
275
311
  const runResult = spawnSync('npx', vitestArgs, { cwd: conformanceRoot, env, stdio: 'inherit' });
276
312
  if (runResult.error) {
@@ -528,6 +564,7 @@ async function main() {
528
564
  if (args.filter) {
529
565
  vitestArgs.push('--testNamePattern', args.filter);
530
566
  }
567
+ vitestArgs.push(...maxWorkersArgs(args.maxWorkers));
531
568
  const result = spawnSync('npx', ['vitest', ...vitestArgs], {
532
569
  cwd: conformanceRoot,
533
570
  env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.136.3",
3
+ "version": "1.136.5",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
- "suiteVersion": "1.136.3",
4
- "corpusCommit": "54f29548af7c1b388553b982a6819ecd7d7ae4d9"
3
+ "suiteVersion": "1.136.5",
4
+ "corpusCommit": "d1107230a2b68b61a854b340a054f3770b40f783"
5
5
  }
package/src/cli.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  * openwop-conformance --filter discovery # category filter
13
13
  * openwop-conformance --base-url ... --api-key ... --filter "interrupt|cancellation"
14
14
  * openwop-conformance --base-url ... --api-key ... --certify out.json # RFC 0089 bundle
15
+ * openwop-conformance --base-url ... --api-key ... --max-workers 4 # cap parallel scenario files
15
16
  *
16
17
  * Environment variables override flags (per the conformance harness's
17
18
  * existing convention):
@@ -55,6 +56,13 @@ interface ParsedArgs {
55
56
  /** RFC 0089 — emit a conformance certification bundle to this path. */
56
57
  readonly certify: string | undefined;
57
58
  readonly bundleVersion: '1' | '2';
59
+ /**
60
+ * S43 (2026-08-18) — cap on concurrently running scenario FILES, forwarded to
61
+ * vitest `--maxWorkers`. Unset = vitest's default (one worker per CPU), which
62
+ * hammers a rate-limited production origin with ~460 files at once and turns
63
+ * `429`s into spurious reds. Env: `OPENWOP_MAX_WORKERS`.
64
+ */
65
+ readonly maxWorkers: number | undefined;
58
66
  }
59
67
 
60
68
  function parseArgs(argv: readonly string[]): ParsedArgs {
@@ -67,6 +75,7 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
67
75
  let implVersion: string | undefined;
68
76
  let certify: string | undefined;
69
77
  let bundleVersion: '1' | '2' = '1';
78
+ let maxWorkers: number | undefined = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
70
79
 
71
80
  for (let i = 0; i < argv.length; i++) {
72
81
  const arg = argv[i] ?? '';
@@ -121,6 +130,9 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
121
130
  case '--certify':
122
131
  certify = nextValue();
123
132
  break;
133
+ case '--max-workers':
134
+ maxWorkers = parseMaxWorkers(nextValue(), '--max-workers');
135
+ break;
124
136
  default:
125
137
  if (arg.startsWith('-')) {
126
138
  // Unknown flag — pass through to vitest by ignoring here.
@@ -128,7 +140,34 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
128
140
  }
129
141
  }
130
142
 
131
- return { baseUrl, apiKey, offline, filter, help, impl, implVersion, certify, bundleVersion };
143
+ return {
144
+ baseUrl,
145
+ apiKey,
146
+ offline,
147
+ filter,
148
+ help,
149
+ impl,
150
+ implVersion,
151
+ certify,
152
+ bundleVersion,
153
+ maxWorkers,
154
+ };
155
+ }
156
+
157
+ /** Parse a `--max-workers` / `OPENWOP_MAX_WORKERS` value: a positive integer, else exit 2. */
158
+ function parseMaxWorkers(raw: string | undefined, source: string): number | undefined {
159
+ if (raw === undefined || raw === '') return undefined;
160
+ const n = Number(raw);
161
+ if (!Number.isInteger(n) || n < 1) {
162
+ process.stderr.write(`${source} must be a positive integer (got '${raw}')\n`);
163
+ process.exit(2);
164
+ }
165
+ return n;
166
+ }
167
+
168
+ /** The vitest argv fragment for the resolved worker cap (empty when uncapped). */
169
+ function maxWorkersArgs(maxWorkers: number | undefined): string[] {
170
+ return maxWorkers === undefined ? [] : ['--maxWorkers', String(maxWorkers)];
132
171
  }
133
172
 
134
173
  const HELP_TEXT = `openwop-conformance — run the openwop conformance suite against a server
@@ -153,6 +192,10 @@ Certification (RFC 0089):
153
192
  §C) records per-requirement DISPOSITIONS instead of pass/fail/skip
154
193
  file lists, so "we could not check" stops being indistinguishable
155
194
  from "checked and it holds". See the note it prints.
195
+ --max-workers <n> Cap concurrently running scenario files (vitest --maxWorkers).
196
+ Default: one worker per CPU. Use a small number against a
197
+ rate-limited production origin so 429s don't read as failures.
198
+ (env: OPENWOP_MAX_WORKERS)
156
199
  --certify <out.json> Generate a machine-readable conformance certification
157
200
  bundle: fetch /.well-known/openwop (captured verbatim +
158
201
  SHA-256), derive claimedProfiles from it, run the suite
@@ -306,6 +349,7 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
306
349
  resolvePath(conformanceRoot, 'vitest.config.ts'),
307
350
  '--reporter=json',
308
351
  `--outputFile=${reportFile}`,
352
+ ...maxWorkersArgs(args.maxWorkers),
309
353
  ];
310
354
  const runResult = spawnSync('npx', vitestArgs, { cwd: conformanceRoot, env, stdio: 'inherit' });
311
355
  if (runResult.error) {
@@ -605,6 +649,7 @@ async function main(): Promise<never> {
605
649
  if (args.filter) {
606
650
  vitestArgs.push('--testNamePattern', args.filter);
607
651
  }
652
+ vitestArgs.push(...maxWorkersArgs(args.maxWorkers));
608
653
 
609
654
  const result = spawnSync('npx', ['vitest', ...vitestArgs], {
610
655
  cwd: conformanceRoot,
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import { describe, it, expect } from 'vitest';
24
+ import { gzipSync } from 'node:zlib';
24
25
  import { driver } from '../lib/driver.js';
25
26
  import { discoveryFamilies } from '../lib/discovery-capabilities.js';
26
27
  import { recordRequirement } from '../lib/requirement-ledger.js';
@@ -81,6 +82,14 @@ function freshPackName(scope: string = 'core'): string {
81
82
  * impl PR will likely extend the driver with an octet-stream variant.
82
83
  * The shape-only error-catalog tests below only need the host's first
83
84
  * validation step (URL pattern, body-presence, etc.) to fire. */
85
+ /**
86
+ * S45 — a VALID gzip stream whose decompressed size (64 MiB of zeros) exceeds
87
+ * the recommended registry cap (50 MB) while the wire body stays ~64 KB, so no
88
+ * proxy body limit is reached and the host's own decompressed-bytes cap is the
89
+ * only thing that can refuse it. Built once per file.
90
+ */
91
+ const OVERSIZED_GZIP: Buffer = gzipSync(Buffer.alloc(64 * 1024 * 1024, 0));
92
+
84
93
  async function putTest(name: string, version: string, body: unknown, extraHeaders: Record<string, string> = {}) {
85
94
  const res = await driver.put(`/v1/packs-test/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.tgz`, body, {
86
95
  headers: { 'Content-Type': 'application/octet-stream', ...extraHeaders },
@@ -196,14 +205,29 @@ describe('pack-registry-publish: tarball extraction error catalog (RFC 0025)', (
196
205
 
197
206
  it('PUT with decompressed bytes exceeding the registry\'s cap MUST return 400 tarball_too_large', async () => {
198
207
  if (!(await isTestModeAdvertised())) return;
199
- // A real test would build a huge gzip; for shape-only assertion we
200
- // send a body large enough that any reasonable cap fires.
201
- const big = Buffer.alloc(60 * 1024 * 1024, 0x1f); // 60MB
202
- big[0] = 0x1f; big[1] = 0x8b; // gzip magic so it gets past body-shape check
203
- const res = await putTest(freshPackName(), '1.0.0', big);
208
+ // S45 (2026-08-18): the cap is on DECOMPRESSED bytes (node-packs.md
209
+ // §"Tarball extraction", recommended default 50 MB), so the probe is a
210
+ // small VALID gzip stream that inflates past it 64 MiB of zeros gzips
211
+ // to ~64 KB on the wire. The previous probe was a 60 MB body of fake
212
+ // gzip magic: a load balancer in front of the host (Cloud Run's front
213
+ // end caps HTTP/1 bodies at 32 MiB) answered its own HTML 413 before the
214
+ // host saw the request, the leg blew the 30 s budget on a normal uplink,
215
+ // and — because `tarball_gunzip_failed` was also accepted — a host with
216
+ // NO cap at all passed it. Measured by MyndHyve on `api.myndhyve.ai`.
217
+ // Only `tarball_too_large` is accepted now: a real gzip that inflates to
218
+ // 64 MiB either trips the cap or reaches the tar layer, and reaching the
219
+ // tar layer means the cap is absent or above 64 MiB (> the recommended
220
+ // default) — say so in the run record if that is a deliberate host choice.
221
+ const res = await putTest(freshPackName(), '1.0.0', OVERSIZED_GZIP);
204
222
  if (res.status === 404) return;
205
- expect(res.status).toBe(400);
206
- expect(['tarball_too_large', 'tarball_gunzip_failed'].includes(errorCode(res.json) ?? '')).toBe(true);
223
+ expect(
224
+ res.status,
225
+ driver.describe('node-packs.md §"Tarball extraction"', 'a valid gzip inflating past the decompressed cap MUST be refused with 400 (a 413 here means a proxy refused the wire body — this probe is ~64 KB on the wire, so that is a host/proxy misconfiguration, not the cap)'),
226
+ ).toBe(400);
227
+ expect(
228
+ errorCode(res.json),
229
+ driver.describe('node-packs.md §"Tarball extraction"', 'decompressed bytes exceeding the registry cap MUST surface tarball_too_large (recommended default cap 50 MB; this probe inflates to 64 MiB)'),
230
+ ).toBe('tarball_too_large');
207
231
  });
208
232
 
209
233
  it('PUT with no `pack.json` at the tarball root MUST return 400 tarball_manifest_missing', async () => {
@@ -152,7 +152,7 @@ describe('redaction: 401 response MUST NOT echo invalid Bearer token (NFR-7)', (
152
152
  // are observable surfaces and MUST be canary-clean.
153
153
  const res = await driver.post(
154
154
  '/v1/runs',
155
- { workflowId: NOOP_WORKFLOW_ID, tenantId: 'conformance-tenant' },
155
+ { workflowId: NOOP_WORKFLOW_ID },
156
156
  {
157
157
  authenticated: false,
158
158
  headers: { Authorization: `Bearer ${canaryValue}` },
@@ -170,7 +170,7 @@ describe('redaction: 401 response MUST NOT echo invalid Bearer token (NFR-7)', (
170
170
  it('the marker substring alone never appears in a 401 body (universal)', async () => {
171
171
  const res = await driver.post(
172
172
  '/v1/runs',
173
- { workflowId: NOOP_WORKFLOW_ID, tenantId: 'conformance-tenant' },
173
+ { workflowId: NOOP_WORKFLOW_ID },
174
174
  {
175
175
  authenticated: false,
176
176
  headers: { Authorization: `Bearer ${CANARY_MARKER}-direct-marker` },
@@ -204,9 +204,11 @@ describe.skipIf(SKIP_NO_NOOP)('redaction: credentialRef value MUST NOT appear in
204
204
  // §configurable echo) — but per capabilities.md §"aiProviders"
205
205
  // it MUST NOT appear in any RunEvent payload.
206
206
  const c = getCanary('byok-credential-ref');
207
+ // S46 (2026-08-18): no fabricated `tenantId` — the run belongs to the
208
+ // credential's own tenant (S41 rule). A tenant-enforcing host answered
209
+ // 403 to the made-up id and this leg then `return`ed, vacuously.
207
210
  const create = await driver.post('/v1/runs', {
208
211
  workflowId: NOOP_WORKFLOW_ID,
209
- tenantId: 'conformance-tenant',
210
212
  configurable: { ai: { credentialRef: c.value } },
211
213
  });
212
214
  if (create.status !== 201) {
@@ -30,6 +30,7 @@
30
30
 
31
31
  import { describe, it, expect } from 'vitest';
32
32
  import { readFileSync } from 'node:fs';
33
+ import { randomUUID } from 'node:crypto';
33
34
  import { join } from 'node:path';
34
35
  import { driver } from '../lib/driver.js';
35
36
  import { behaviorGate } from '../lib/behavior-gate.js';
@@ -39,6 +40,17 @@ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
39
40
 
40
41
  const GATE = 'openwop-self-hosted-runner';
41
42
 
43
+ // S40 (2026-08-18): every registration / dispatch id carries a per-run nonce. The
44
+ // seam drives the host's REAL runner registry + `{runId, stepId}` result store
45
+ // (host-sample-test-seams.md §19), so fixed ids (`runner_a_1`, `run_idem`/`step_1`)
46
+ // collide with the previous certification run on any host whose store outlives
47
+ // the process: the second run's FIRST dispatch is already `deduped:true` and the
48
+ // at-most-once leg no longer proves anything, and re-registering a fixed runnerId
49
+ // may be refused. Fresh ids per run keep both legs non-vacuous on a durable host.
50
+ const NONCE = randomUUID().slice(0, 8);
51
+ const SUBJECT_A = `subject_A_${NONCE}`;
52
+ const SUBJECT_B = `subject_B_${NONCE}`;
53
+
42
54
  interface JsonSchema {
43
55
  properties?: Record<string, JsonSchema>;
44
56
  required?: string[];
@@ -155,15 +167,15 @@ describe('self-hosted-runner: behavioral (seam-gated, soft-skip 404)', () => {
155
167
  // MUST NOT fall back to B's runner (subject-first isolation); with no runner
156
168
  // for A the dispatch MUST fail with the retriable `runner_unavailable`.
157
169
  const reg = await driver.post(REGISTER, {
158
- runnerId: 'runner_b_1',
159
- subject: 'subject_B',
170
+ runnerId: `runner_b_${NONCE}`,
171
+ subject: SUBJECT_B,
160
172
  capabilities: { providers: ['anthropic'] },
161
173
  });
162
174
  if (reg.status === 404) return; // seam unwired — soft-skip
163
175
 
164
176
  const res = await driver.post(DISPATCH, {
165
- subject: 'subject_A',
166
- runId: 'run_iso',
177
+ subject: SUBJECT_A,
178
+ runId: `run_iso_${NONCE}`,
167
179
  stepId: 'step_0',
168
180
  seq: 0,
169
181
  kind: 'model',
@@ -189,15 +201,15 @@ describe('self-hosted-runner: behavioral (seam-gated, soft-skip 404)', () => {
189
201
 
190
202
  it('a redelivered {runId, stepId} dispatch is dropped, not re-executed (at-most-once)', async () => {
191
203
  const reg = await driver.post(REGISTER, {
192
- runnerId: 'runner_a_1',
193
- subject: 'subject_A',
204
+ runnerId: `runner_a_${NONCE}`,
205
+ subject: SUBJECT_A,
194
206
  capabilities: { providers: ['anthropic'] },
195
207
  });
196
208
  if (reg.status === 404) return;
197
209
 
198
210
  const frame = {
199
- subject: 'subject_A',
200
- runId: 'run_idem',
211
+ subject: SUBJECT_A,
212
+ runId: `run_idem_${NONCE}`,
201
213
  stepId: 'step_1',
202
214
  seq: 0,
203
215
  kind: 'model',