-
Notifications
You must be signed in to change notification settings - Fork 0
api.qa/vitest@1 — executable suites runner (Worker Loader, flag-held) #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9e97e2f
dfbe915
9511d9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,7 +57,9 @@ import { | |
| import { verifyAttestation } from '../src/attest.js' | ||
| import { sha256Hex } from '../src/digest.js' | ||
| import { runMcpServer } from '../src/mcp.js' | ||
| import type { VerificationReport } from '../src/types.js' | ||
| import { localExecRunner, type ExecRunOutcome } from '../src/exec/dialect.js' | ||
| import { parseExecSuiteDocument } from '../src/suite-doc.js' | ||
| import type { Suite, VerificationReport } from '../src/types.js' | ||
|
|
||
| interface Flags { | ||
| /** Last value wins (single-valued reads). */ | ||
|
|
@@ -246,6 +248,121 @@ async function main(): Promise<number> { | |
| return emit(report, suiteMarkdown(report), flags) | ||
| } | ||
|
|
||
| if (cmd === 'vitest') { | ||
| // vitest <suite.json|module.mjs> — run an `api.qa/vitest@1` artifact | ||
| // LOCALLY through the SAME shared subset harness the hosted verifier | ||
| // executes (AXP A.8.6.2 parity is by construction: one module, byte- | ||
| // identical, never a reimplementation). Local runs are ADVISORY (never | ||
| // attested); the definition of done is the SAME digest passing hosted. | ||
| // | ||
| // npx autonomous-qa vitest suite.json --target https://api.example | ||
| // [--env <name>] [--expect-digest sha256:<64hex>] [--seed <n>] [--json] | ||
| // npx autonomous-qa vitest dist/index.mjs --target https://api.example | ||
| // [--export suite] [--expect-digest sha256:<64hex>] | ||
| // | ||
| // Artifact KIND follows the card rule (A.8.5): a path ending `.mjs` (or | ||
| // `--module`) is a module artifact; anything else is a suite document. | ||
| // The digest gate is FAIL-CLOSED: with --expect-digest, bytes that do not | ||
| // hash to the pin never instantiate. | ||
| const file = rest[0] | ||
| if (!file) return die('vitest needs an artifact: a suite document (.json) or a module (.mjs)') | ||
| const text = readFileSync(file, 'utf8') | ||
| const digest = `sha256:${await sha256Hex(text)}` | ||
| const expect = flags.get('expect-digest') | ||
| if (expect !== undefined && expect !== digest) { | ||
| console.error( | ||
| `autonomous-qa: vitest artifact digest mismatch: expected ${expect}, ${file} hashes to ${digest}. ` + | ||
| 'NOTHING was instantiated (digest fail-closed, A.8.6.3).', | ||
| ) | ||
| return 1 | ||
| } | ||
| const isModule = flags.has('module') || /\.mjs$/i.test(file) | ||
| const envName = flags.get('env') ?? 'public' | ||
|
|
||
| let doc: Suite | undefined | ||
| if (!isModule) { | ||
| try { | ||
| doc = parseExecSuiteDocument(text) | ||
| } catch (err) { | ||
| console.error(`autonomous-qa: ${err instanceof Error ? err.message : String(err)}`) | ||
| return 1 | ||
| } | ||
| if (!Object.hasOwn(doc.environments, envName)) { | ||
| return die( | ||
| `vitest: environment "${envName}" is not defined by the suite (it defines ${Object.keys(doc.environments).join(', ') || 'none'})`, | ||
| ) | ||
| } | ||
| } else if (flags.has('env') && envName !== 'public') { | ||
| return die('vitest: a module artifact defines no environments — only the implicit "public" exists (A.8.6.4)') | ||
| } | ||
| const env = doc?.environments[envName] | ||
| const vars = { ...(env?.vars ?? {}) } | ||
| const target = flags.get('target') ?? (typeof vars.baseUrl === 'string' ? (vars.baseUrl as string) : undefined) | ||
| if (!target) return die('vitest needs a target: --target <origin> (or a string `baseUrl` var in the selected environment)') | ||
| const runSeed = seed ?? (Math.floor(Math.random() * 0xffffffff) >>> 0) | ||
|
|
||
| const outcome: ExecRunOutcome = await localExecRunner().run({ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The local CLI starts executable tests here and only runs declarative rows afterward, whereas Useful? React with 👍 / 👎. |
||
| artifactKind: isModule ? 'module' : 'document', | ||
| testsSource: isModule ? text : doc!.tests!, | ||
| ...(doc?.module !== undefined && { moduleSource: doc.module }), | ||
| ...(flags.has('export') && { exportName: flags.get('export') }), | ||
| origin: new URL(/^https?:\/\//.test(target) ? target : `https://${target}`).origin, | ||
| vars, | ||
| environment: envName, | ||
| sandbox: env?.sandbox === true, | ||
| seed: runSeed, | ||
| declarativeRows: doc?.requirements.length ?? 0, | ||
| digest, | ||
| }) | ||
|
|
||
| // Declarative rows (document form) keep unchanged suite@1 engine | ||
| // semantics — run them through the SAME verifySuite door the `suite` verb | ||
| // uses, folded into one exit code (A.8.6.5's one result set). | ||
| let rowsPassed = true | ||
| let rowLines: string[] = [] | ||
| if (doc !== undefined && doc.requirements.length > 0) { | ||
| const rowSuite = JSON.stringify({ | ||
| $type: 'Suite', | ||
| name: doc.name, | ||
| version: doc.version, | ||
| environments: doc.environments, | ||
| requirements: doc.requirements, | ||
| }) | ||
| const rowReport = await verifySuite(rowSuite, envName, { | ||
| mode: 'local', | ||
| seed: runSeed, | ||
| target, | ||
| delayMs: isLocalTarget(target) ? 0 : 150, | ||
| }) | ||
| rowsPassed = rowReport.passed | ||
| rowLines = rowReport.requirements.map( | ||
| (r) => ` ${r.verdict === 'pass' ? 'PASS' : 'FAIL'} row ${r.id}${r.verdict === 'pass' ? '' : ` — ${r.detail}`}`, | ||
| ) | ||
| } | ||
|
|
||
| if (flags.get('json') === 'true') { | ||
| console.log(JSON.stringify({ runner: 'api.qa/vitest@1', digest, seed: runSeed, environment: envName, outcome, rowsPassed }, null, 2)) | ||
| } else { | ||
| console.log(`# api.qa/vitest@1 — local run (advisory, never attested)`) | ||
| console.log(`artifact ${file} (${isModule ? 'module' : 'document'}), digest ${digest}`) | ||
| console.log(`target ${target}, environment "${envName}"${env?.sandbox === true ? ' (sandbox)' : ''}, seed ${runSeed}`) | ||
| for (const line of rowLines) console.log(line) | ||
| if (outcome.status === 'ran') { | ||
| for (const r of outcome.results) { | ||
| console.log(` ${r.status === 'pass' ? 'PASS' : 'FAIL'} ${r.name} (${r.durationMs} ms)${r.reason ? ` — ${r.reason}` : ''}`) | ||
| } | ||
| console.log( | ||
| `${outcome.results.filter((r) => r.status === 'pass').length}/${outcome.registered} tests passed ` + | ||
| `(${outcome.elapsedWallMs} ms wall, limits ${outcome.appliedLimits.wallMs} ms wall / ${outcome.appliedLimits.cpuMs} ms CPU)`, | ||
| ) | ||
| } else { | ||
| console.log(`RUN ${outcome.status.toUpperCase()}: ${outcome.reason}`) | ||
| } | ||
| } | ||
| const testsPassed = outcome.status === 'ran' && outcome.results.every((r) => r.status === 'pass') | ||
| return testsPassed && rowsPassed ? 0 : 1 | ||
| } | ||
|
|
||
| if (cmd === 'contract-diff') { | ||
| // contract-diff <openapi-spec> <target> (ax-gyh): the highest-value CI gate | ||
| // — "did this deploy break the published contract?". The spec is LOCAL (a | ||
|
|
@@ -464,6 +581,10 @@ function usage(): string { | |
| [--iteration-data <dataset.csv|.json>] run once per dataset row (data-driven) | ||
| [--target <target>] [--expect-digest <sha256>] [--seed <n>] | ||
| (target defaults to the selected environment's baseUrl var) | ||
| npx autonomous-qa vitest <suite.json|module.mjs> run an api.qa/vitest@1 executable suite LOCALLY | ||
| --target <origin> [--env <name>] [--export <n>] through the SAME shared subset harness the hosted | ||
| [--expect-digest <sha256:…>] [--seed <n>] verifier executes (local==hosted by construction); | ||
| [--module] [--json] EXITS NON-ZERO iff any test or row fails | ||
| npx autonomous-qa contract-diff <spec> <target> did this deploy break the published contract? | ||
| [--json] [--reporter ...] [--seed <n>] EXITS NON-ZERO on any breaking operation diff | ||
| (spec is the published OpenAPI file; target is fetched live, SSRF-gated) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Regenerate `src/exec/vitest-subset-source.ts` from the canonical harness | ||
| * `src/exec/vitest-subset.mjs`. | ||
| * | ||
| * node scripts/gen-vitest-subset.mjs | ||
| * | ||
| * WHY A GENERATED STRING CONSTANT EXISTS AT ALL. AXP A.8.6.2 requires the | ||
| * hosted verifier and the local CLI to share ONE implementation of the | ||
| * `api.qa/vitest@1` subset. The hosted runner must inject the harness into a | ||
| * Worker Loader isolate's module map as SOURCE TEXT (an isolate takes strings, | ||
| * not in-memory objects), and a Worker has no filesystem to read the .mjs from | ||
| * — so the exact bytes are carried as a TypeScript string constant, exactly | ||
| * like `scripts/gen-assets.mjs` inlines the raster assets. The local runner | ||
| * instantiates the SAME constant via a `data:text/javascript` import, so both | ||
| * hosts execute byte-identical harness code by construction. | ||
| * | ||
| * The constant is committed; a normal build does NOT run this. A pinning test | ||
| * (test/vitest-subset.test.ts) fails when the generated bytes drift from the | ||
| * canonical file, naming this script as the fix. | ||
| */ | ||
|
|
||
| import { readFileSync, writeFileSync } from 'node:fs' | ||
|
|
||
| const canonicalUrl = new URL('../src/exec/vitest-subset.mjs', import.meta.url) | ||
| const outUrl = new URL('../src/exec/vitest-subset-source.ts', import.meta.url) | ||
|
|
||
| const source = readFileSync(canonicalUrl, 'utf8') | ||
|
|
||
| const generated = `/** | ||
| * GENERATED — do not edit. Regenerate with: node scripts/gen-vitest-subset.mjs | ||
| * | ||
| * The exact bytes of the canonical \`api.qa/vitest@1\` subset harness | ||
| * (src/exec/vitest-subset.mjs), carried as a string so the hosted Worker | ||
| * Loader runner can inject them into an isolate's module map and the local | ||
| * runner can instantiate them via a data: import — ONE implementation for | ||
| * both hosts (AXP A.8.6.2), pinned byte-identical by test/vitest-subset.test.ts. | ||
| */ | ||
|
|
||
| export const VITEST_SUBSET_SOURCE: string = ${JSON.stringify(source)} | ||
| ` | ||
|
|
||
| writeFileSync(outUrl, generated) | ||
| console.log(`gen-vitest-subset: wrote ${outUrl.pathname} (${source.length} bytes of harness source)`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For document artifacts, this path only calls
parseExecSuiteDocument()and never appliesgateVitestSuiteDocument(), while the hosted path rejects non-endpoint rows and any declarative method other than GET/HEAD. The later localverifySuite()call is consent mode and permits writes, so a document containing a POST row can mutate the target and pass locally even though hosted verification refuses the artifact before execution. Reuse the same document gate before starting either part of the local run.Useful? React with 👍 / 👎.