@qvac/harness 1.0.0-rc.2 → 1.0.0-rc.3

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 (58) hide show
  1. package/README.md +12 -12
  2. package/bench/README.md +94 -0
  3. package/bench/bfcl/checker.ts +313 -0
  4. package/bench/bfcl/data.ts +93 -0
  5. package/bench/bfcl/observe.ts +93 -0
  6. package/bench/bfcl/run.ts +213 -0
  7. package/bench/bfcl/tools.ts +72 -0
  8. package/bench/bridge/bridge.ts +392 -0
  9. package/bench/bridge/openai.ts +150 -0
  10. package/bench/bridge/python/qvac_bench/__init__.py +3 -0
  11. package/bench/bridge/python/qvac_bench/airline.py +42 -0
  12. package/bench/bridge/python/qvac_bench/approval.py +26 -0
  13. package/bench/bridge/python/qvac_bench/bfcl_multi_turn.py +152 -0
  14. package/bench/bridge/python/qvac_bench/client.py +88 -0
  15. package/bench/bridge/python/qvac_bench/harbor_agent.py +85 -0
  16. package/bench/bridge/python/qvac_bench/session.py +107 -0
  17. package/bench/bridge/python/qvac_bench/speed.py +30 -0
  18. package/bench/bridge/python/qvac_bench/tau2_agent.py +106 -0
  19. package/bench/bridge/python/qvac_bench/tools.py +11 -0
  20. package/bench/bridge/python/run_bfcl_multi_turn.py +95 -0
  21. package/bench/bridge/python/run_tau2.py +150 -0
  22. package/bench/bridge/python/smoke.py +47 -0
  23. package/bench/bridge/python/test_airline.py +28 -0
  24. package/bench/bridge/python/test_approval.py +57 -0
  25. package/bench/bridge/python/test_bfcl_multi_turn.py +185 -0
  26. package/bench/bridge/python/test_harbor_agent.py +107 -0
  27. package/bench/bridge/serve.ts +52 -0
  28. package/bench/bridge/transcript.ts +52 -0
  29. package/bench/bun-tsconfig.json +15 -0
  30. package/bench/cpu-main.mjs +216 -0
  31. package/bench/cpu.ts +457 -0
  32. package/bench/evidence/2026-09-28-review15.md +225 -0
  33. package/bench/evidence/redact-bun1.3.14.json +92 -0
  34. package/bench/evidence/redact-node24.11.json +92 -0
  35. package/bench/graph-metrics.ts +43 -0
  36. package/bench/harness.ts +73 -0
  37. package/bench/issue129-matrix.mjs +722 -0
  38. package/bench/redact-adversarial.ts +256 -0
  39. package/bench/repeat-cpu.mjs +281 -0
  40. package/bench/report.ts +1075 -0
  41. package/bench/skill-eval/harness.ts +30 -0
  42. package/bench/skill-eval/laws.ts +37 -0
  43. package/bench/skill-eval/mermaid/client.ts +132 -0
  44. package/bench/skill-eval/mermaid/validate.mjs +115 -0
  45. package/bench/skill-eval/pack.ts +61 -0
  46. package/bench/skill-eval/packs/diagrams/cases.ts +281 -0
  47. package/bench/skill-eval/packs/diagrams/index.ts +50 -0
  48. package/bench/skill-eval/packs/diagrams/score.ts +74 -0
  49. package/bench/skill-eval/packs/index.ts +5 -0
  50. package/bench/skill-eval/packs/weather/cases.ts +279 -0
  51. package/bench/skill-eval/packs/weather/fixtures/wttr.json +566 -0
  52. package/bench/skill-eval/packs/weather/fixtures.ts +84 -0
  53. package/bench/skill-eval/packs/weather/index.ts +55 -0
  54. package/bench/skill-eval/packs/weather/score.ts +92 -0
  55. package/bench/skill-eval/report.ts +194 -0
  56. package/bench/skill-eval/run.ts +316 -0
  57. package/bench/speed.ts +80 -0
  58. package/package.json +4 -13
@@ -0,0 +1,722 @@
1
+ // Reproduce the 33-cell Issue 129 matrix; --dry-run and --prepare-only never measure.
2
+ import { execFileSync, spawnSync } from 'node:child_process'
3
+ import { createHash } from 'node:crypto'
4
+ import {
5
+ cpSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ mkdtempSync,
9
+ readFileSync,
10
+ readdirSync,
11
+ realpathSync,
12
+ renameSync,
13
+ symlinkSync,
14
+ writeFileSync
15
+ } from 'node:fs'
16
+ import os from 'node:os'
17
+ import path from 'node:path'
18
+ import { fileURLToPath } from 'node:url'
19
+
20
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
21
+ const baselineRevisions = [
22
+ '7d4a1ec282ed29b2480b551e6a4670d448441e63',
23
+ '8cbf419e15e6b13aee118f601b3282749cdf7e01'
24
+ ]
25
+ const nodeDefault = '/tmp/node-v24.11.0-darwin-arm64/bin/node'
26
+ const omittedDirectories = new Set([
27
+ '.git',
28
+ '.turbo',
29
+ '.cache',
30
+ 'node_modules',
31
+ 'coverage',
32
+ 'dist',
33
+ 'dist-lib'
34
+ ])
35
+ const options = parseOptions(process.argv.slice(2))
36
+ const schedule = makeSchedule(options.seed)
37
+ const nodeModules = realpathSync(
38
+ options.nodeModules ?? process.env.QVAC_MATRIX_NODE_MODULES ?? path.join(root, 'node_modules')
39
+ )
40
+ const nodeBinary = path.resolve(options.node ?? process.env.QVAC_NODE24_BIN ?? nodeDefault)
41
+ const bunBinary = options.bun ?? process.env.QVAC_BUN_BIN ?? 'bun'
42
+ const bareRuntimeBinary = path.resolve(
43
+ options.bareRuntime ??
44
+ process.env.QVAC_BARE_RUNTIME_BIN ??
45
+ path.join(nodeModules, 'bare-runtime/bin/bare')
46
+ )
47
+
48
+ if (options.dryRun) {
49
+ console.log(JSON.stringify({ seed: options.seed, jobCount: schedule.length, schedule }))
50
+ process.exit(0)
51
+ }
52
+
53
+ const scratch = mkdtempSync(path.join(os.tmpdir(), 'qvac-issue129-matrix-'))
54
+ const candidateRoot = path.join(scratch, 'candidate')
55
+ const baselineRoots = new Map()
56
+ prepareCandidate(candidateRoot)
57
+ for (const revision of baselineRevisions) {
58
+ const baselineRoot = path.join(scratch, `baseline-${revision.slice(0, 7)}`)
59
+ prepareBaseline(revision, baselineRoot)
60
+ baselineRoots.set(revision, baselineRoot)
61
+ }
62
+
63
+ const runtimes = {
64
+ node: { executable: nodeBinary, version: versionOf(nodeBinary, ['--version']) },
65
+ bun: { executable: bunBinary, version: versionOf(bunBinary, ['--version']) },
66
+ bare: {
67
+ executable: bareRuntimeBinary,
68
+ version: versionOf(bareRuntimeBinary, ['--version'])
69
+ }
70
+ }
71
+ const sourceManifests = {
72
+ candidate: sourceManifest(candidateRoot),
73
+ ...Object.fromEntries(
74
+ [...baselineRoots].map(([revision, baselineRoot]) => [revision, sourceManifest(baselineRoot)])
75
+ )
76
+ }
77
+
78
+ if (options.prepareOnly) {
79
+ console.log(
80
+ JSON.stringify({
81
+ scratch,
82
+ runtimes,
83
+ dependencyGraph: {
84
+ nodeModules,
85
+ packageManifestSha256: hashFile(path.resolve(nodeModules, 'package.json')),
86
+ lockfileSha256: hashFile(path.resolve(nodeModules, '../bun.lock')),
87
+ installedPackages: installedPackageVersions(nodeModules, [
88
+ 'xstate',
89
+ '@qvac/sdk',
90
+ '@qvac/inference',
91
+ 'b4a',
92
+ 'bare-runtime',
93
+ 'typescript'
94
+ ])
95
+ },
96
+ sourceManifests
97
+ })
98
+ )
99
+ process.exit(0)
100
+ }
101
+
102
+ const outputPath = path.resolve(options.output)
103
+ const partialPath = `${outputPath}.partial.json`
104
+ mkdirSync(path.dirname(outputPath), { recursive: true })
105
+
106
+ const report = {
107
+ name: 'Issue 129 reproducible benchmark matrix',
108
+ status: 'running',
109
+ createdAt: new Date().toISOString(),
110
+ method: {
111
+ unit: 'process CPU milliseconds',
112
+ workload:
113
+ 'tools: 101 messages (~200 KB), 3 rounds x 2 read tools x 2 KB results then answer; stream: 2,000 one-character tokens, 110 ms first-token gap, immediate setImmediate or timeout(0) spacing',
114
+ warmup: 'one untimed selected workload pass per process before three timed samples',
115
+ processReplicates: 3,
116
+ timedSamplesPerProcess: 3,
117
+ aggregate: 'pooled median of nine raw cpuSamplesMs values; process medians retained separately',
118
+ interleaving: `deterministic Fisher-Yates shuffle with seed ${options.seed}`
119
+ },
120
+ host: { platform: process.platform, architecture: process.arch, controller: process.version },
121
+ runtimes,
122
+ dependencyGraph: {
123
+ nodeModules,
124
+ packageManifestSha256: hashFile(path.resolve(nodeModules, 'package.json')),
125
+ lockfileSha256: hashFile(path.resolve(nodeModules, '../bun.lock')),
126
+ installedPackages: installedPackageVersions(nodeModules, [
127
+ 'xstate',
128
+ '@qvac/sdk',
129
+ '@qvac/inference',
130
+ 'b4a',
131
+ 'bare-runtime',
132
+ 'typescript'
133
+ ]),
134
+ note: 'All snapshots and runtimes use the same selected frozen dependency graph, symlinked into each snapshot. Baseline source is historical, but baseline dependencies are not reinstalled from each historical lockfile.'
135
+ },
136
+ snapshots: {
137
+ directory: scratch,
138
+ candidate: sourceManifests.candidate,
139
+ baselines: Object.fromEntries(
140
+ baselineRevisions.map((revision) => [revision, sourceManifests[revision]])
141
+ ),
142
+ overlay: [
143
+ 'bench/cpu-main.mjs',
144
+ 'bench/repeat-cpu.mjs',
145
+ 'bench/bun-tsconfig.json',
146
+ "candidate-only generated Bare entrypoint: import 'bare-encoding/global'; import('./cpu.ts')"
147
+ ]
148
+ },
149
+ schedule,
150
+ execution: [],
151
+ cells: []
152
+ }
153
+
154
+ writePartial()
155
+ for (let index = 0; index < schedule.length; index++) {
156
+ const job = schedule[index]
157
+ const result = runJob(job, index)
158
+ report.execution.push(result)
159
+ report.cells = aggregateCells(report.execution)
160
+ writePartial()
161
+ }
162
+
163
+ if (
164
+ report.cells.length !== 33 ||
165
+ report.cells.some((cell) => cell.processCount !== 3 || cell.rawSampleCount !== 9)
166
+ ) {
167
+ throw new Error(
168
+ 'The matrix is incomplete: expected 33 cells with three processes and nine raw samples each'
169
+ )
170
+ }
171
+
172
+ report.status = 'complete'
173
+ report.completedAt = new Date().toISOString()
174
+ writePartial()
175
+ renameSync(partialPath, outputPath)
176
+ console.log(
177
+ JSON.stringify({ status: report.status, output: outputPath, jobCount: schedule.length })
178
+ )
179
+
180
+ function writePartial() {
181
+ writeFileSync(partialPath, `${JSON.stringify(report, null, 2)}\n`)
182
+ }
183
+
184
+ function prepareCandidate(destination) {
185
+ mkdirSync(destination, { recursive: true })
186
+ cpSync(root, destination, {
187
+ recursive: true,
188
+ filter(source) {
189
+ const relative = path.relative(root, source)
190
+ if (relative === '') {
191
+ return true
192
+ }
193
+
194
+ const parts = relative.split(path.sep)
195
+
196
+ return (
197
+ !parts.some((part) => omittedDirectories.has(part) || part.startsWith('.')) &&
198
+ !/\.(?:pem|key)$/i.test(relative)
199
+ )
200
+ }
201
+ })
202
+ writeFileSync(
203
+ path.join(destination, 'bench/cpu-bare-runner.mjs'),
204
+ "import 'bare-encoding/global'\nawait import('./cpu.ts')\n"
205
+ )
206
+ linkNodeModules(destination, nodeModules)
207
+ }
208
+
209
+ function prepareBaseline(revision, destination) {
210
+ mkdirSync(destination, { recursive: true })
211
+ const archive = execFileSync('git', ['archive', '--format=tar', revision], {
212
+ cwd: root,
213
+ maxBuffer: 512 * 1024 * 1024
214
+ })
215
+ const extracted = spawnSync('tar', ['-xf', '-', '-C', destination], {
216
+ input: archive,
217
+ encoding: 'utf8'
218
+ })
219
+ if (extracted.error) {
220
+ throw extracted.error
221
+ }
222
+
223
+ if (extracted.status !== 0) {
224
+ throw new Error(`Could not extract ${revision}: ${extracted.stderr}`)
225
+ }
226
+
227
+ for (const relative of [
228
+ 'bench/cpu-main.mjs',
229
+ 'bench/repeat-cpu.mjs',
230
+ 'bench/bun-tsconfig.json'
231
+ ]) {
232
+ const target = path.join(destination, relative)
233
+ mkdirSync(path.dirname(target), { recursive: true })
234
+ cpSync(path.join(root, relative), target)
235
+ }
236
+
237
+ linkNodeModules(destination, nodeModules)
238
+ }
239
+
240
+ function linkNodeModules(destination, graphNodeModules) {
241
+ const target = path.join(destination, 'node_modules')
242
+ if (!existsSync(target)) {
243
+ symlinkSync(graphNodeModules, target, 'dir')
244
+ }
245
+ }
246
+
247
+ function sourceManifest(directory) {
248
+ const files = []
249
+ listFiles(directory, '', files)
250
+ files.sort((left, right) => left.localeCompare(right))
251
+ const hash = createHash('sha256')
252
+ let byteCount = 0
253
+ const entries = []
254
+ for (const relative of files) {
255
+ const content = readFileSync(path.join(directory, relative))
256
+ const sha256 = createHash('sha256').update(content).digest('hex')
257
+ hash.update(relative).update('\0').update(sha256).update('\n')
258
+ byteCount += content.byteLength
259
+ if (relative.startsWith('bench/') || relative === 'package.json' || relative === 'bun.lock') {
260
+ entries.push({ path: relative, sha256 })
261
+ }
262
+ }
263
+
264
+ return {
265
+ revision: directory === candidateRoot ? currentRevision() : null,
266
+ treeSha256: hash.digest('hex'),
267
+ fileCount: files.length,
268
+ byteCount,
269
+ benchmarkFiles: entries
270
+ }
271
+ }
272
+
273
+ function listFiles(directory, relative, output) {
274
+ for (const entry of readdirSync(path.join(directory, relative), { withFileTypes: true })) {
275
+ if (entry.name.startsWith('.')) {
276
+ continue
277
+ }
278
+
279
+ const childRelative = path.join(relative, entry.name)
280
+ if (entry.isDirectory()) {
281
+ if (!omittedDirectories.has(entry.name)) {
282
+ listFiles(directory, childRelative, output)
283
+ }
284
+ } else if (entry.isFile()) {
285
+ output.push(childRelative.split(path.sep).join('/'))
286
+ }
287
+ }
288
+ }
289
+
290
+ function currentRevision() {
291
+ return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim()
292
+ }
293
+
294
+ function hashFile(filename) {
295
+ return createHash('sha256').update(readFileSync(filename)).digest('hex')
296
+ }
297
+
298
+ function makeSchedule(seed) {
299
+ const cells = [...baselineCells(), ...candidateCells(), ...bareCells()]
300
+
301
+ if (cells.length !== 33) {
302
+ throw new Error(`Expected 33 cells, got ${cells.length}`)
303
+ }
304
+
305
+ return shuffleJobs(
306
+ cells.flatMap((cell) => [1, 2, 3].map((replicate) => ({ ...cell, replicate }))),
307
+ seed
308
+ )
309
+ }
310
+
311
+ function baselineCells() {
312
+ const cells = []
313
+ for (const revision of baselineRevisions) {
314
+ for (const runtime of ['node', 'bun']) {
315
+ cells.push({
316
+ kind: 'baseline',
317
+ revision,
318
+ runtime,
319
+ mode: 'main',
320
+ workload: 'tools',
321
+ spacing: null
322
+ })
323
+ for (const spacing of ['immediate', 'timeout']) {
324
+ cells.push({
325
+ kind: 'baseline',
326
+ revision,
327
+ runtime,
328
+ mode: 'main',
329
+ workload: 'stream',
330
+ spacing
331
+ })
332
+ }
333
+ }
334
+ }
335
+
336
+ return cells
337
+ }
338
+
339
+ function candidateCells() {
340
+ const cells = []
341
+ for (const runtime of ['node', 'bun']) {
342
+ for (const mode of ['plain', 'bench', 'production']) {
343
+ cells.push({
344
+ kind: 'candidate',
345
+ revision: 'candidate',
346
+ runtime,
347
+ mode,
348
+ workload: 'tools',
349
+ spacing: null
350
+ })
351
+ for (const spacing of ['immediate', 'timeout']) {
352
+ cells.push({
353
+ kind: 'candidate',
354
+ revision: 'candidate',
355
+ runtime,
356
+ mode,
357
+ workload: 'stream',
358
+ spacing
359
+ })
360
+ }
361
+ }
362
+ }
363
+
364
+ return cells
365
+ }
366
+
367
+ function bareCells() {
368
+ const cells = []
369
+ for (const workload of ['tools', 'stream']) {
370
+ const spacings = workload === 'tools' ? [null] : ['immediate', 'timeout']
371
+ for (const spacing of spacings) {
372
+ cells.push({
373
+ kind: 'candidate',
374
+ revision: 'candidate',
375
+ runtime: 'bare',
376
+ mode: 'production',
377
+ workload,
378
+ spacing
379
+ })
380
+ }
381
+ }
382
+
383
+ return cells
384
+ }
385
+
386
+ function shuffleJobs(jobs, seed) {
387
+ let state = seedHash(seed)
388
+ for (let index = jobs.length - 1; index > 0; index--) {
389
+ state ^= state << 13
390
+ state ^= state >>> 17
391
+ state ^= state << 5
392
+ const swapIndex = (state >>> 0) % (index + 1)
393
+ ;[jobs[index], jobs[swapIndex]] = [jobs[swapIndex], jobs[index]]
394
+ }
395
+
396
+ return jobs
397
+ }
398
+
399
+ function seedHash(value) {
400
+ let hash = 2166136261
401
+ for (const character of value) {
402
+ hash = Math.imul(hash ^ character.charCodeAt(0), 16777619)
403
+ }
404
+
405
+ return hash >>> 0 || 1
406
+ }
407
+
408
+ function runJob(job, scheduleIndex) {
409
+ const directory = job.kind === 'candidate' ? candidateRoot : baselineRoots.get(job.revision)
410
+ const invocation = createInvocation(job, directory)
411
+ const startedAt = new Date().toISOString()
412
+ const started = process.hrtime.bigint()
413
+ const child = spawnSync(invocation.executable, invocation.args, {
414
+ cwd: invocation.cwd,
415
+ encoding: 'utf8',
416
+ env: {
417
+ ...process.env,
418
+ PATH: prependExecutableDirectory(job.runtime, process.env.PATH ?? ''),
419
+ QVAC_NO_SKILL_BIN_PROBE: '1'
420
+ },
421
+ maxBuffer: 32 * 1024 * 1024
422
+ })
423
+ const wallMs = Number(process.hrtime.bigint() - started) / 1e6
424
+ if (child.error) {
425
+ throw child.error
426
+ }
427
+
428
+ if (child.status !== 0) {
429
+ throw new Error(
430
+ `${invocation.executable} exited ${child.status} for ${cellKey(job)}:\n${child.stderr || child.stdout}`
431
+ )
432
+ }
433
+
434
+ const parsed = parseLastJson(child.stdout)
435
+ const result =
436
+ job.runtime === 'bare' ? readBareResult(parsed, job) : readRepeatResult(parsed, job)
437
+ assertRuntimeVersion(parsed, job)
438
+
439
+ return reportJob({ job, invocation, scheduleIndex, startedAt, wallMs, parsed, result })
440
+ }
441
+
442
+ function createInvocation(job, directory) {
443
+ const script = path.join(directory, 'bench/repeat-cpu.mjs')
444
+ if (job.runtime === 'bare') {
445
+ return {
446
+ executable: bareRuntimeBinary,
447
+ args: [
448
+ 'bench/cpu-bare-runner.mjs',
449
+ '3',
450
+ `--mode=${job.mode}`,
451
+ `--spacing=${job.spacing ?? 'immediate'}`,
452
+ `--workload=${job.workload}`,
453
+ '--json'
454
+ ],
455
+ cwd: directory
456
+ }
457
+ }
458
+
459
+ const args = [
460
+ `--runtime=${job.runtime}`,
461
+ `--driver=${job.kind === 'baseline' ? 'main' : 'engine'}`,
462
+ '--processes=1',
463
+ '--runs=3',
464
+ `--spacing=${job.spacing ?? 'immediate'}`,
465
+ `--workload=${job.workload}`,
466
+ `--revision=${job.revision}`
467
+ ]
468
+ if (job.kind !== 'baseline') {
469
+ args.push(`--mode=${job.mode}`)
470
+ }
471
+
472
+ return {
473
+ executable: nodeBinary,
474
+ args: ['--experimental-strip-types', script, ...args],
475
+ cwd: directory
476
+ }
477
+ }
478
+
479
+ function assertRuntimeVersion(report, job) {
480
+ if (report.version !== runtimes[job.runtime].version) {
481
+ throw new Error(`Runtime version changed during ${cellKey(job)}: ${report.version}`)
482
+ }
483
+ }
484
+
485
+ function reportJob({ job, invocation, scheduleIndex, startedAt, wallMs, parsed, result }) {
486
+ return {
487
+ order: scheduleIndex + 1,
488
+ startedAt,
489
+ wallMs,
490
+ key: cellKey(job),
491
+ replicate: job.replicate,
492
+ command: { executable: invocation.executable, args: invocation.args, cwd: invocation.cwd },
493
+ runtime: parsed.runtime,
494
+ version: parsed.version,
495
+ rawCpuSamplesMs: result.cpuSamplesMs,
496
+ processMedianMs: result.cpuMs,
497
+ logBytes: result.logBytes ?? null,
498
+ records: result.records ?? null,
499
+ contentEvents: result.contentEvents
500
+ }
501
+ }
502
+
503
+ function readRepeatResult(report, job) {
504
+ if (
505
+ report.runtime !== job.runtime ||
506
+ !Array.isArray(report.samples) ||
507
+ report.samples.length !== 1
508
+ ) {
509
+ throw new Error(`Unexpected repeat-cpu report for ${cellKey(job)}`)
510
+ }
511
+
512
+ const process = report.samples[0]
513
+ const expectedMode = job.kind === 'baseline' ? 'main' : job.mode
514
+ const result = process?.results?.[0]
515
+ validateResult(result, job, expectedMode)
516
+ if (!Array.isArray(result.cpuSamplesMs) || result.cpuSamplesMs.length !== 3) {
517
+ throw new Error(`Expected three raw samples in ${cellKey(job)}`)
518
+ }
519
+
520
+ validateSamples(result, cellKey(job))
521
+
522
+ return result
523
+ }
524
+
525
+ function readBareResult(report, job) {
526
+ if (report.runtime !== 'bare' || !Array.isArray(report.results) || report.results.length !== 1) {
527
+ throw new Error(`Unexpected Bare report for ${cellKey(job)}`)
528
+ }
529
+
530
+ const result = report.results[0]
531
+ validateResult(result, job, job.mode)
532
+ if (!Array.isArray(result.cpuSamplesMs) || result.cpuSamplesMs.length !== 3) {
533
+ throw new Error(`Expected three raw Bare samples in ${cellKey(job)}`)
534
+ }
535
+
536
+ validateSamples(result, cellKey(job))
537
+
538
+ return result
539
+ }
540
+
541
+ function validateSamples(result, key) {
542
+ if (
543
+ !Array.isArray(result.cpuSamplesMs) ||
544
+ !result.cpuSamplesMs.every((sample) => Number.isFinite(sample) && sample >= 0) ||
545
+ result.cpuMs !== median(result.cpuSamplesMs)
546
+ ) {
547
+ throw new Error(`Invalid per-process raw CPU samples in ${key}`)
548
+ }
549
+ }
550
+
551
+ function validateResult(result, job, mode) {
552
+ const carriesLogMetrics = job.kind !== 'baseline'
553
+ if (
554
+ !result ||
555
+ result.workload !== job.workload ||
556
+ result.mode !== mode ||
557
+ result.runs !== 3 ||
558
+ !Number.isFinite(result.cpuMs) ||
559
+ (carriesLogMetrics &&
560
+ (!Number.isFinite(result.logBytes) || !Number.isFinite(result.records))) ||
561
+ (!carriesLogMetrics && (result.logBytes !== undefined || result.records !== undefined))
562
+ ) {
563
+ throw new Error(`Incomplete benchmark result for ${cellKey(job)}`)
564
+ }
565
+
566
+ if (
567
+ job.workload === 'stream' &&
568
+ (result.contentEvents !== 2000 || result.spacing !== job.spacing)
569
+ ) {
570
+ throw new Error(`Stream workload did not preserve events/spacing for ${cellKey(job)}`)
571
+ }
572
+ }
573
+
574
+ function parseLastJson(stdout) {
575
+ for (const line of stdout.trim().split(/\r?\n/).reverse()) {
576
+ try {
577
+ const value = JSON.parse(line)
578
+ if (value && typeof value === 'object') {
579
+ return value
580
+ }
581
+ } catch {}
582
+ }
583
+
584
+ throw new Error('Benchmark process did not emit a JSON report')
585
+ }
586
+
587
+ function aggregateCells(execution) {
588
+ const cells = new Map()
589
+ for (const sample of execution) {
590
+ let cell = cells.get(sample.key)
591
+ if (!cell) {
592
+ cell = { key: sample.key, processes: [] }
593
+ cells.set(sample.key, cell)
594
+ }
595
+
596
+ cell.processes.push(sample)
597
+ }
598
+
599
+ return [...cells.values()].map((cell) => {
600
+ if (cell.processes.length > 3) {
601
+ throw new Error(`Too many process samples for ${cell.key}`)
602
+ }
603
+
604
+ const raw = cell.processes.flatMap((process) => process.rawCpuSamplesMs)
605
+
606
+ return {
607
+ key: cell.key,
608
+ processCount: cell.processes.length,
609
+ rawSampleCount: raw.length,
610
+ rawCpuSamplesMs: raw,
611
+ cpuMs: raw.length === 9 ? median(raw) : null,
612
+ processMedianMs: cell.processes.map((process) => process.processMedianMs),
613
+ logBytes: medianOrNull(cell.processes.map((process) => process.logBytes)),
614
+ records: medianOrNull(cell.processes.map((process) => process.records)),
615
+ contentEvents: cell.processes[0]?.contentEvents
616
+ }
617
+ })
618
+ }
619
+
620
+ function cellKey(job) {
621
+ return [job.kind, job.revision, job.runtime, job.mode, job.workload, job.spacing ?? ''].join(':')
622
+ }
623
+
624
+ function median(values) {
625
+ const sorted = [...values].sort((left, right) => left - right)
626
+ return sorted[Math.floor(sorted.length / 2)]
627
+ }
628
+
629
+ function medianOrNull(values) {
630
+ const finite = values.filter(Number.isFinite)
631
+ return finite.length ? median(finite) : null
632
+ }
633
+
634
+ function installedPackageVersions(graphNodeModules, names) {
635
+ return Object.fromEntries(
636
+ names.map((name) => {
637
+ const manifestPath = path.join(graphNodeModules, name, 'package.json')
638
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
639
+ return [name, manifest.version]
640
+ })
641
+ )
642
+ }
643
+
644
+ function prependExecutableDirectory(runtime, existingPath) {
645
+ if (runtime === 'node') {
646
+ return existingPath
647
+ }
648
+
649
+ const executable = runtime === 'bun' ? bunBinary : bareRuntimeBinary
650
+ if (!executable.includes(path.sep)) {
651
+ return existingPath
652
+ }
653
+
654
+ return `${path.dirname(path.resolve(executable))}${path.delimiter}${existingPath}`
655
+ }
656
+
657
+ function versionOf(executable, args) {
658
+ const result = spawnSync(executable, args, { encoding: 'utf8', maxBuffer: 1024 * 1024 })
659
+ if (result.error) {
660
+ throw result.error
661
+ }
662
+
663
+ if (result.status !== 0) {
664
+ throw new Error(`Could not read runtime version from ${executable}`)
665
+ }
666
+
667
+ return result.stdout.trim().split(/\r?\n/)[0]
668
+ }
669
+
670
+ function parseOptions(args) {
671
+ const result = {
672
+ seed: 'issue129-final-2026-09',
673
+ node: undefined,
674
+ bun: undefined,
675
+ nodeModules: undefined,
676
+ bareRuntime: undefined,
677
+ output: path.join(root, 'output/benchmarks/issue-129/final-matrix.json'),
678
+ dryRun: false,
679
+ prepareOnly: false
680
+ }
681
+ for (const arg of args) {
682
+ const [key, value] = arg.split('=', 2)
683
+ if (arg === '--dry-run') {
684
+ result.dryRun = true
685
+ } else if (arg === '--prepare-only') {
686
+ result.prepareOnly = true
687
+ } else if (key === '--seed' && value) {
688
+ result.seed = value
689
+ } else if (key === '--node' && value) {
690
+ result.node = value
691
+ } else if (key === '--bun' && value) {
692
+ result.bun = value
693
+ } else if (key === '--node-modules' && value) {
694
+ result.nodeModules = path.resolve(value)
695
+ } else if (key === '--bare-runtime' && value) {
696
+ result.bareRuntime = value
697
+ } else if (key === '--output' && value) {
698
+ result.output = value
699
+ } else {
700
+ throw new Error(`Unknown option: ${arg}`)
701
+ }
702
+ }
703
+
704
+ if (!existsSync(optionsNodePath(result))) {
705
+ throw new Error(`Node 24 binary not found: ${optionsNodePath(result)}`)
706
+ }
707
+
708
+ const graphNodeModules = path.resolve(
709
+ result.nodeModules ?? process.env.QVAC_MATRIX_NODE_MODULES ?? path.join(root, 'node_modules')
710
+ )
711
+ if (!existsSync(graphNodeModules)) {
712
+ throw new Error(`Validated dependency graph not found: ${graphNodeModules}`)
713
+ }
714
+
715
+ result.nodeModules = realpathSync(graphNodeModules)
716
+
717
+ return result
718
+ }
719
+
720
+ function optionsNodePath(result) {
721
+ return path.resolve(result.node ?? process.env.QVAC_NODE24_BIN ?? nodeDefault)
722
+ }