@openwop/openwop-conformance 1.136.3 → 1.136.4
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 +38 -1
- package/package.json +1 -1
- package/schemas/CORPUS-STAMP.json +2 -2
- package/src/cli.ts +46 -1
- package/src/scenarios/self-hosted-runner.test.ts +20 -8
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 {
|
|
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,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.
|
|
4
|
-
"corpusCommit": "
|
|
3
|
+
"suiteVersion": "1.136.4",
|
|
4
|
+
"corpusCommit": "abb3c77ccf7f952fbaa66b06463964fa9dd3ad36"
|
|
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 {
|
|
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,
|
|
@@ -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:
|
|
159
|
-
subject:
|
|
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:
|
|
166
|
-
runId:
|
|
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:
|
|
193
|
-
subject:
|
|
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:
|
|
200
|
-
runId:
|
|
211
|
+
subject: SUBJECT_A,
|
|
212
|
+
runId: `run_idem_${NONCE}`,
|
|
201
213
|
stepId: 'step_1',
|
|
202
214
|
seq: 0,
|
|
203
215
|
kind: 'model',
|