diff --git a/CHANGELOG.md b/CHANGELOG.md index c0139da..653919e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ ## Unreleased +## 2.1.0 — 2026-10-13 + +- Add verification request and evidence manifest v5 and witness import request v2. A designated + Bun test can run in its package (`workingDirectory: "test-package"`) with the package's + `bunfig.toml`, under `bun test --isolate`, with operator-declared trusted custom matchers. + Repository-internal links are reproduced and bound in the digest, and a `test-closure` snapshot + copies only what the executed tests can load, plus declared `include` paths. V1 to v4 schemas, + parsers and manifests are unchanged. See the + [v5 migration guide](docs/migration-verification-v5.md). +- Harden designated campaigns, v4 included: Bun runs with a generated configuration that puts + AssertLedger's preload before every repository preload, so a built-in matcher that a repository + preload replaced is no longer treated as a built-in assertion; v5 can explicitly trust its + replacement. A `bunfig.toml` whose test preloads cannot + be rewritten refuses the campaign (`BUNFIG_TEST_PRELOAD_UNSUPPORTED`). The designated preflight + adds probes for replaced built-ins, preload and trusted matchers and isolated runs. +- `init` and `doctor` with the `bun:test` framework inventory specs that import `vitest` or + `@jest/globals`, which `bun test` answers itself. + +- Preserve extended compiler settings, package import aliases, multiline test preloads, configured + JSX runtimes and included empty directories in closure snapshots. Retained empty directories are + bound in v5 repository digests. Preserve typed v1 witness-import SDK return compatibility. +- Update MCP server and client to 2.3.0, Biome to 2.5.15, Node types to 22.20.5 and tsx to 4.23.15. + Keep Zod 4.4.3 to preserve frozen schema projections and public validation behavior. + ## 2.0.3 — 2026-10-11 - Reduce container runtime launches through a qualified generic `system dial-stdio` connection, diff --git a/README.fr.md b/README.fr.md index ba51ad3..3fe7150 100644 --- a/README.fr.md +++ b/README.fr.md @@ -14,7 +14,7 @@ Vous obtenez un verdict, les observations qui le justifient et un fichier de pre Installez l’outil dans votre dépôt avec Node.js 22.15 ou une version ultérieure : ```sh -npm install --save-dev assertledger@2.0.3 +npm install --save-dev assertledger@2.1.0 npx assertledger doctor . ``` @@ -60,7 +60,7 @@ flowchart LR Prérequis : **Git**, **Node.js 22.15+** et **pnpm 11.1.2**. L’exemple utilise l’adaptateur intégré `node:test`. ```sh -git clone --branch v2.0.3 https://github.com/hoklims/assertledger.git +git clone --branch v2.1.0 https://github.com/hoklims/assertledger.git cd assertledger pnpm install --frozen-lockfile pnpm build @@ -184,6 +184,8 @@ rejeu. Ce mode exécute du code local de confiance sans bac à sable. Pour vérifier un test `bun:test` existant désigné par son nom, ou rejouer un témoin rouge/vert enregistré, utilisez une requête v4 ou `import-witness`, qui s’exécutent aussi en conteneur ou dans un mode Windows natif déclaré ; voir le [guide de migration v4](docs/migration-verification-v4.md). +Une requête v5 ou un témoin v2 exécute le test dans son paquet, avec le `bunfig.toml` du paquet et +les liens d’une installation isolée ; voir le [guide de migration v5](docs/migration-verification-v5.md). ### Qualifier un test de régression commité diff --git a/README.md b/README.md index d215f3d..23786f1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ You get a verdict, the observations behind it and an evidence file you can repla Install in your repository with Node.js 22.15 or later: ```sh -npm install --save-dev assertledger@2.0.3 +npm install --save-dev assertledger@2.1.0 npx assertledger doctor . ``` @@ -59,7 +59,7 @@ flowchart LR You need **Git**, **Node.js 22.15+** and **pnpm 11.1.2**. The example uses the built-in `node:test` adapter. ```sh -git clone --branch v2.0.3 https://github.com/hoklims/assertledger.git +git clone --branch v2.1.0 https://github.com/hoklims/assertledger.git cd assertledger pnpm install --frozen-lockfile pnpm build @@ -175,7 +175,9 @@ failures remain operational failures and cannot count as target detection. See t contract. The execution mode is explicitly unsandboxed and intended for trusted local code. To verify an existing `bun:test` test by name, or to replay a recorded red/green witness, use a v4 request or `import-witness`, which also run in a container or a declared Windows-native backend; see -the [v4 migration guide](docs/migration-verification-v4.md). +the [v4 migration guide](docs/migration-verification-v4.md). A v5 request or a v2 witness runs the +test in its package with the package's `bunfig.toml`, through the links of an isolated install; see +the [v5 migration guide](docs/migration-verification-v5.md). ### Qualify a committed regression test diff --git a/SECURITY.md b/SECURITY.md index e40837c..37666b2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,8 +18,11 @@ AssertLedger records the achieved isolation level in each manifest. Candidate files are restricted to configured test roots. Absolute paths, traversal segments, path segments ending in a dot or space, and NTFS alternate data stream syntax are rejected. Repository -symlinks are not silently omitted: AssertLedger rejects a symlink unless an excluded path segment keeps -it outside the inventory and snapshot. Overlay writes also reject symlink destinations discovered in +symlinks are not silently omitted: v1 to v3 reject inventoried links; v4 refuses links reached by +the executed test closure and records omitted unrelated links. V5 reproduces repository-internal +links with their relative targets and binds them in its digest, while refusing external, dangling +or escaping links reached by the test closure or explicit include paths. Overlay writes also reject +symlink destinations discovered in the workspace. Commands run from executable and argument arrays with `shell: false`. Environment allowlists and container environment declarations cannot include `NODE_OPTIONS` or diff --git a/conformance/schema-extensions.json b/conformance/schema-extensions.json index b247f4d..afa8912 100644 --- a/conformance/schema-extensions.json +++ b/conformance/schema-extensions.json @@ -32,6 +32,11 @@ "rawSha256": "sha256:56bad50457b84864decbe911d98e3257880e4562d69487b4a820f7d516c3d11b", "$id": "https://testforge.dev/schemas/evidence-manifest.v4.json" }, + { + "path": "schemas/evidence-manifest.v5.json", + "rawSha256": "sha256:e1d8d8e329beec84c7b0a4b9558b4b81079937629c0fda02dea1cd7a6c754231", + "$id": "https://testforge.dev/schemas/evidence-manifest.v5.json" + }, { "path": "schemas/evidence-provider-manifest.v1.json", "rawSha256": "sha256:6100995f772cd24b7369e4428fb1aba570f1ba6e1f61295bc42a1f7ce6b5bde3", @@ -67,10 +72,20 @@ "rawSha256": "sha256:f08389ef40c7d73d7f1c2006aee2efcd2bf53c8b78338883f4bc9f21808db842", "$id": "https://testforge.dev/schemas/verification-request.v4.json" }, + { + "path": "schemas/verification-request.v5.json", + "rawSha256": "sha256:7b267d9305a195ed866b5d8a78047e5d72ce679bdb971f2549dbe20bc3bcf95c", + "$id": "https://testforge.dev/schemas/verification-request.v5.json" + }, { "path": "schemas/witness-import-request.v1.json", "rawSha256": "sha256:ec2aa94fd0d50e6dec7ff6f6b9bea294febf8d7ba4598e01fe89177f97348974", "$id": "https://testforge.dev/schemas/witness-import-request.v1.json" + }, + { + "path": "schemas/witness-import-request.v2.json", + "rawSha256": "sha256:55ee5c06b0b07f452e2120dd9535c6e97994b2ce1caf395645372f24d4d7cdc8", + "$id": "https://testforge.dev/schemas/witness-import-request.v2.json" } ] } diff --git a/docs/migration-verification-v5.md b/docs/migration-verification-v5.md new file mode 100644 index 0000000..f1b95bc --- /dev/null +++ b/docs/migration-verification-v5.md @@ -0,0 +1,103 @@ +# Verification v5: designated Bun tests in their package, with installed links + +Verification request and evidence manifest v5, and witness import request v2, let a designated +`bun:test` campaign run the way a pnpm or Bun workspace runs its own tests: in the test's package, +with that package's `bunfig.toml`, through the links an isolated install creates. V1 to v4 schema +bytes, parsers and historical manifests are unchanged and replayable. + +## Request + +A v5 request is a [v4 request](migration-verification-v4.md) with `schemaVersion: "5.0.0"` and two +groups of new fields. Under `repository`: + +- `snapshot`: `repository` copies every file, as v4 does; `test-closure` copies only the static + module closure of the designated tests, the package, TypeScript and Bun configurations on their + paths, and the `include` paths; +- `include`: repository-relative files or directories added to a `test-closure` snapshot, for files + a test reads at run time. It must be empty with `snapshot: "repository"` + (`REPOSITORY_INCLUDE_REQUIRES_TEST_CLOSURE`), and each entry must exist + (`REPOSITORY_INCLUDE_MISSING`). + +Under the `bun-test-designated` adapter: + +- `workingDirectory`: `repository-root`, as v4, or `test-package`: Bun runs in the nearest directory + above the designated test that holds a `package.json`. Every designated test of a campaign must + belong to the same package (`DESIGNATED_WORKING_DIRECTORY_MIXED`); +- `isolate`: runs `bun test --isolate`. A control that loads several files then runs one Bun + process per file; +- `trustedMatchers`: custom matchers the operator declares trusted, as + `{ "path": "", "matchers": ["", …] }`. + +A witness import request v2 carries the same `repository` and `adapter` fields and is replayed as a +v5 campaign; see [witness import](witness-import.md). + +## Repository links + +Explicit `include` directories retain their empty descendants in both the snapshot and execution +workspaces. The v5 repository digest adds a `D\0path\0` entry for each retained empty leaf directory +to the existing sorted file/link sequence. Snapshots without retained empty directories keep the +previous digest projection byte for byte; historical manifests remain replayable. Adding or removing +a retained empty directory changes the repository digest even when no file bytes change. + +Package `imports` aliases are resolved in the nearest package scope, including wildcard targets +and all conditional branches conservatively. External, recursive, missing or unsupported alias +targets refuse preparation with `TEST_CLOSURE_PACKAGE_IMPORT_UNSUPPORTED`, rather than silently +omitting runtime code. Extended compiler configurations and implicit JSX dependencies are retained +even in repositories with no links. + +A v5 campaign reproduces the links that resolve inside the repository with their relative targets, +and binds each link and its target in the repository digest: every such link for a `repository` +snapshot, those the closure reaches for a `test-closure` snapshot. The closure follows those links, +and also keeps the configuration a `tsconfig` extends and the automatic JSX runtime a `.tsx` or +`.jsx` file imports. A link that leaves the root, dangles or reaches an excluded path still refuses +the campaign when the closure reaches it (`REPOSITORY_LINK_ESCAPES_ROOT`, +`REPOSITORY_LINK_IN_TEST_CLOSURE`); one outside the closure is left out and recorded, at most +10,000 (`REPOSITORY_LINK_LIMIT_EXCEEDED`). When the links found after the copy differ from the +planned ones, the repository changed during the campaign (`REPOSITORY_SNAPSHOT_LINKS_CHANGED`). + +`evidenceContext.repository` records `snapshot`, `include`, `preservedLinkCount` and the closure +edges it could not follow statically (`unboundedClosureEdges`). A test-closure snapshot holds no +file read at run time outside the closure and the include paths; the manifest states this limit. + +Node on Windows cannot read the links that WSL or a Linux container creates. Run AssertLedger from +Linux or WSL for a checkout installed there. + +## Bun configuration and preload order + +Bun runs every designated test with a generated configuration: the working directory's +`bunfig.toml`, rewritten so that AssertLedger's preload runs before every repository preload. The +generated file is passed with `--config` and replaces the `--preload` argument of earlier campaigns. +`[test] preload` must be one string or one array of string literals; a configuration AssertLedger +cannot rewrite refuses the campaign (`BUNFIG_TEST_PRELOAD_UNSUPPORTED`). This also applies to v4 +designated campaigns, which run at the repository root. + +Running first, the preload records the identity of each built-in matcher. A matcher that +`expect.extend` installs or replaces, before or during the test, is not a built-in matcher, so its +failure is never an assertion unless the operator declares it trusted. + +## Trusted matchers + +A declared matcher is trusted only when a module at or below its declared `path` installs it with +`expect.extend` under a declared name. The identity installed is recorded: a later extension under +the same name, from anywhere, is no longer trusted. A failure of a trusted matcher, including a +usage error reported in the matcher message form, counts as an assertion. Each path must exist in +the snapshot (`TRUSTED_MATCHER_PATH_MISSING`); for an installed package, name the directory the +`expect.extend` call resolves to, for example +`node_modules/.pnpm/@/node_modules/`. The declaration is part of the +request and of the decision digest. + +Bun derives a custom matcher's first message line from its parameters after the received value, +for example `expect(received).toHaveTextContent(checkWith, options = { normalizeWhitespace: !0 })` +for jest-dom 7.0.1. Record the expected line from an observed red run. + +## Preflight + +The designated preflight adds probes for each new rule: a built-in replaced inside the test and in a +preload, a custom matcher installed by a preload, a declared trusted matcher, another name from the +same source, and an isolated load and run. Each must produce its expected outcome, or the campaign +stops before any world runs. + +## Not covered + +Tests written for `node:test` and run by Bun, and oracles that are shell commands, are not +designated Bun tests. The 1,000 base-test limit of the other adapters is unchanged. diff --git a/docs/reference.md b/docs/reference.md index 9b3c665..13cf2eb 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -74,7 +74,8 @@ not create a sandbox. `--allow-windows-native-execution` authorizes a v4 request with `windows-native` isolation on a Windows host, recorded as `WINDOWS_NATIVE_UNSANDBOXED`; it cannot be combined with the other backend flags. `import-witness` replays a recorded red/green witness under exactly one operator-chosen backend; see -[witness import](witness-import.md) and the [v4 migration guide](migration-verification-v4.md). +[witness import](witness-import.md) and the [v4](migration-verification-v4.md) and +[v5](migration-verification-v5.md) migration guides. A v2 request with `container` isolation runs without that flag: each execution uses a fresh container from a digest-pinned local image through the operator's `--container-runtime` JSON argv, diff --git a/docs/witness-import.md b/docs/witness-import.md index acdc628..3bab959 100644 --- a/docs/witness-import.md +++ b/docs/witness-import.md @@ -1,7 +1,8 @@ # Witness import -`assertledger import-witness` replays a recorded red/green witness as a v4 campaign and verifies it. -The witness states what was observed; AssertLedger observes it again under its own taxonomy. +`assertledger import-witness` replays a recorded red/green witness as a v4 campaign, or a v5 +campaign for a v2 request, and verifies it. The witness states what was observed; AssertLedger +observes it again under its own taxonomy. ## Request @@ -19,6 +20,39 @@ A `witness-import-request.v1.json` document names: Each target must still hold its base bytes (`WITNESS_TARGET_BASE_MISMATCH`) and its content must differ from them (`WITNESS_TARGET_UNCHANGED`). +A `witness-import-request.v2.json` document (`schemaVersion: "2.0.0"`) adds the v5 fields: +`repository.snapshot` and `repository.include`, and the adapter's `workingDirectory`, `isolate` and +`trustedMatchers`; see the [v5 migration guide](migration-verification-v5.md). A spec of a pnpm +workspace package, run with the package's `bunfig.toml` preloads and a jest-dom matcher, declares +for example: + +```json +{ + "repository": { + "root": "/home/me/checkout", + "exclude": [".pnpm-store", ".npm-token"], + "includeDependencies": true, + "git": "excluded", + "snapshot": "test-closure", + "include": [] + }, + "adapter": { + "kind": "bun-test-designated", + "executable": "bun", + "testTimeoutMs": 15000, + "workingDirectory": "test-package", + "isolate": true, + "trustedMatchers": [ + { + "path": "node_modules/.pnpm/@testing-library+jest-dom@7.0.1_@testing-library+dom@10.4.2_vitest@5.0.3/node_modules/@testing-library/jest-dom", + "matchers": ["toHaveTextContent"] + } + ] + }, + "expectedFailure": "expect(received).toHaveTextContent(checkWith, options = { normalizeWhitespace: !0 })" +} +``` + ## Campaign The repository is the reference world, the targets form the one target world, and the neutral @@ -62,6 +96,10 @@ bun install --frozen-lockfile --linker hoisted --ignore-scripts ``` Set `includeDependencies: true`. Workspace packages stay linked in `node_modules`; a link outside -the designated test's closure is left out and recorded, and one inside it refuses the import. A +the designated test's closure is left out and recorded, and one inside it refuses a v1 import. A test that asks Git for its tracked files needs `git: "synthesized"`. When a snapshot copy is slow to read the first time, for example under on-access scanning, declare a per-test timeout that fits. + +A v2 request replays the installed checkout as it is, isolated linker included: the links the +closure reaches are reproduced, and `snapshot: "test-closure"` keeps the copy to what the test can +load. Run AssertLedger from Linux or WSL when the dependencies were installed there. diff --git a/integrations/bun/driver.d.mts b/integrations/bun/driver.d.mts index f799ecf..dcc701c 100644 --- a/integrations/bun/driver.d.mts +++ b/integrations/bun/driver.d.mts @@ -54,3 +54,9 @@ export function classifyBunInstrumentedEvidence( exitCode: number | null, operationalError?: boolean, ): BunInstrumentedResult; + +/** + * The bunfig a designated run reads: the repository's own with AssertLedger's preload first in + * its [test] preload list. Throws BUNFIG_TEST_PRELOAD_UNSUPPORTED for any other spelling. + */ +export function designatedBunConfig(source: string | undefined, preload: string): string; diff --git a/integrations/bun/driver.mjs b/integrations/bun/driver.mjs index 7be5de2..9bf3084 100644 --- a/integrations/bun/driver.mjs +++ b/integrations/bun/driver.mjs @@ -310,6 +310,120 @@ function escapeRegularExpression(value) { return value.replace(/[\\^$.*+?()[\]{}|/-]/gu, "\\$&"); } +const GENERATED_CONFIG_PREFIX = "__assertledger_bunfig_"; +const TEST_TABLE_HEADER = /^\s*\[\s*test\s*\]\s*(?:#.*)?$/u; +const TABLE_HEADER = /^\s*\[/u; +const TEST_PRELOAD = /^\s*preload\s*=(.*)$/u; +// Spellings that could set test.preload without the plain key inside a [test] table. +const INDIRECT_TEST_PRELOAD = + /^\s*(?:(?:test|"test"|'test')\s*\.\s*(?:preload|"preload"|'preload')\s*=|["']?test["']?\s*=|\[\s*["']test["']\s*\]|["']preload["']\s*=)/u; + +function unsupportedConfig() { + return new Error("BUNFIG_TEST_PRELOAD_UNSUPPORTED"); +} + +/** The strings of a one-line TOML array or string, or undefined when it is anything else. */ +function parsePreloadValue(raw) { + let rest = raw.trim(); + const strings = []; + const array = rest.startsWith("["); + if (array) rest = rest.slice(1).trimStart(); + for (;;) { + if (array && rest.startsWith("]")) { + rest = rest.slice(1); + break; + } + const basic = /^"((?:[^"\\\p{Cc}]|\\["\\btnfr]|\\u[0-9A-Fa-f]{4})*)"/u.exec(rest); + const literal = basic === null ? /^'([^'\p{Cc}]*)'/u.exec(rest) : null; + if (basic === null && literal === null) return undefined; + strings.push(basic === null ? literal[1] : JSON.parse(`"${basic[1]}"`)); + rest = rest.slice((basic ?? literal)[0].length).trimStart(); + if (!array) break; + if (rest.startsWith(",")) rest = rest.slice(1).trimStart(); + else if (!rest.startsWith("]")) return undefined; + } + return /^\s*(?:#.*)?$/u.test(rest) ? strings : undefined; +} + +/** + * The bunfig a designated run uses: the repository's own, read from the working directory, whose + * [test] preload list starts with AssertLedger's preload and then keeps every repository preload in + * order. `bun test --config` reads only this file, so the repository preloads run once, after the + * instrumentation has recorded Bun's own matchers. A preload set any other way is refused. + */ +export function designatedBunConfig(source, preload) { + const entry = JSON.stringify(preload); + if (source === undefined) return `[test]\npreload = [${entry}]\n`; + const lines = source.split(/\r?\n/u); + let inTest = false; + let header = -1; + let assignment = -1; + let assignmentEnd = -1; + let preloads = []; + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index]; + if (INDIRECT_TEST_PRELOAD.test(line)) throw unsupportedConfig(); + if (TABLE_HEADER.test(line)) { + inTest = TEST_TABLE_HEADER.test(line); + if (inTest) { + if (header >= 0) throw unsupportedConfig(); + header = index; + } + continue; + } + const match = inTest ? TEST_PRELOAD.exec(line) : null; + if (match === null) continue; + if (assignment >= 0) throw unsupportedConfig(); + // An array may continue over the following lines; it ends on the first line that parses. + let value = match[1]; + let end = index; + let parsed = parsePreloadValue(value); + while (parsed === undefined && value.trim().startsWith("[") && end + 1 < lines.length) { + end += 1; + if (TABLE_HEADER.test(lines[end]) && !/^\s*\[\s*["']/u.test(lines[end])) break; + value = `${value} ${lines[end].replace(/^\s*#.*$/u, "")}`; + parsed = parsePreloadValue(value); + } + if (parsed === undefined) throw unsupportedConfig(); + assignment = index; + assignmentEnd = end; + preloads = parsed; + index = end; + } + const replacement = `preload = [${[entry, ...preloads.map((value) => JSON.stringify(value))].join(", ")}]`; + if (assignment >= 0) lines.splice(assignment, assignmentEnd - assignment + 1, replacement); + else if (header >= 0) lines.splice(header + 1, 0, replacement); + else { + while (lines.length > 0 && lines.at(-1) === "") lines.pop(); + lines.push(...(lines.length > 0 ? [""] : []), "[test]", replacement); + } + return `${lines.join("\n").replace(/\n*$/u, "")}\n`; +} + +/** A `/`-separated directory relative to the root, "." for the root itself. */ +function normalizeWorkingDirectory(value) { + if (value === ".") return "."; + return normalizeRelativeFile(value); +} + +function validTrustedMatchers(value) { + return ( + Array.isArray(value) && + value.every( + (entry) => + typeof entry === "object" && + entry !== null && + Object.keys(entry).sort().join(",") === "matchers,path" && + normalizeWorkingDirectory(entry.path) !== undefined && + Array.isArray(entry.matchers) && + entry.matchers.length > 0 && + entry.matchers.every( + (name) => typeof name === "string" && /^[A-Za-z_$][\w$]*$/u.test(name), + ), + ) + ); +} + /** Validates a designated selection against the files the driver was asked to load or run. */ function parseDesignatedSelection(value, files) { if (value === undefined) return undefined; @@ -325,9 +439,17 @@ function parseDesignatedSelection(value, files) { const fileSet = (tests) => [...new Set(tests.map((test) => normalizeRelativeFile(test.file)))]; const sameFiles = (tests) => JSON.stringify(fileSet(tests).sort()) === JSON.stringify([...files].sort()); + const validExecution = + typeof selection === "object" && + selection !== null && + normalizeWorkingDirectory(selection.workingDirectory) !== undefined && + typeof selection.isolate === "boolean" && + validTrustedMatchers(selection.trustedMatchers); if ( - selection?.mode === "run" && - Object.keys(selection).sort().join(",") === "expectedFailure,mode,test,testTimeoutMs" && + validExecution && + selection.mode === "run" && + Object.keys(selection).sort().join(",") === + "expectedFailure,isolate,mode,test,testTimeoutMs,trustedMatchers,workingDirectory" && validTest(selection.test) && (selection.expectedFailure === null || typeof selection.expectedFailure === "string") && (selection.testTimeoutMs === null || @@ -339,8 +461,10 @@ function parseDesignatedSelection(value, files) { return selection; } if ( - selection?.mode === "load" && - Object.keys(selection).sort().join(",") === "mode,tests" && + validExecution && + selection.mode === "load" && + Object.keys(selection).sort().join(",") === + "isolate,mode,tests,trustedMatchers,workingDirectory" && Array.isArray(selection.tests) && selection.tests.length > 0 && selection.tests.every(validTest) && @@ -383,8 +507,23 @@ async function installHelper(helperSource, root) { ); } -async function runBun(executable, files, junitFile, root, evidenceKey, extraArguments = []) { - const exactPath = (file) => `./${file.replaceAll(path.sep, "/")}`; +/** + * Runs `bun test` on `files`, relative to the root. Without `config` the preload comes from the + * command line in the root, as for candidate files; with it, Bun runs in `config.cwd` and reads only + * the generated bunfig there, whose first preload is AssertLedger's. + */ +async function runBun( + executable, + files, + junitFile, + root, + evidenceKey, + extraArguments = [], + config = undefined, +) { + const cwd = config?.cwd ?? root; + const exactPath = (file) => + `./${path.relative(cwd, path.join(root, file)).replaceAll(path.sep, "/")}`; const childEnvironment = { ...process.env }; delete childEnvironment.TESTFORGE_RESULT_FILE; delete childEnvironment.TESTFORGE_CANDIDATE_FILES; @@ -395,17 +534,21 @@ async function runBun(executable, files, junitFile, root, evidenceKey, extraArgu executable, [ "test", - "--max-concurrency=1", - "--retry=0", - "--preload", - "./node_modules/assertledger/preload.mjs", + ...(config === undefined + ? [ + "--max-concurrency=1", + "--retry=0", + "--preload", + "./node_modules/assertledger/preload.mjs", + ] + : [`--config=./${config.name}`, "--max-concurrency=1", "--retry=0"]), "--reporter=junit", `--reporter-outfile=${junitFile}`, ...extraArguments, ...files.map(exactPath), ], { - cwd: root, + cwd, env: childEnvironment, shell: false, stdio: ["ignore", "pipe", "pipe", "pipe", "pipe"], @@ -534,14 +677,54 @@ async function runDesignated( root, evidenceKey, ) { - const pattern = - selection.mode === "run" + const cwd = path.resolve(root, selection.workingDirectory); + const relativeCwd = path.relative(root, cwd); + if ( + relativeCwd === ".." || + relativeCwd.startsWith(`..${path.sep}`) || + path.isAbsolute(relativeCwd) + ) + throw new Error("INVALID_DESIGNATED_SELECTION"); + let original; + try { + const bunfig = path.join(cwd, "bunfig.toml"); + if ((await lstat(bunfig)).isFile()) original = await readFile(bunfig, "utf8"); + else throw unsupportedConfig(); + } catch (error) { + if (error?.code !== "ENOENT") { + await emit(infrastructureFailure("BUNFIG_TEST_PRELOAD_UNSUPPORTED")); + return; + } + } + let generated; + try { + generated = designatedBunConfig( + original, + path.join(root, "node_modules", "assertledger", "preload.mjs").replaceAll(path.sep, "/"), + ); + } catch { + await emit(infrastructureFailure("BUNFIG_TEST_PRELOAD_UNSUPPORTED")); + return; + } + const config = { cwd, name: `${GENERATED_CONFIG_PREFIX}${randomUUID()}.toml` }; + await writeFile(path.join(cwd, config.name), generated, { flag: "wx" }); + const pattern = [ + ...(selection.isolate ? ["--isolate"] : []), + ...(selection.mode === "run" ? [ `--test-name-pattern=^${escapeRegularExpression(selection.test.path.join(" "))}$`, ...(selection.testTimeoutMs === null ? [] : [`--timeout=${selection.testTimeoutMs}`]), ] - : []; - const execution = await runBun(executable, testFiles, junitFile, root, evidenceKey, pattern); + : []), + ]; + // An isolated run evaluates the preload again in a fresh global for each further file, and the + // evidence key can be read only once: an isolated load therefore runs one file per process. + const batches = + selection.isolate && selection.mode === "load" ? testFiles.map((file) => [file]) : [testFiles]; + const executions = []; + for (const batch of batches) { + executions.push(await runBun(executable, batch, junitFile, root, evidenceKey, pattern, config)); + } let outcome; try { let junit; @@ -552,21 +735,29 @@ async function runDesignated( if (selection.mode === "run" || error?.code !== "ENOENT") throw error; } // No registration or no matching test leaves the signed channel empty. - const events = - execution.eventContent === undefined - ? undefined - : execution.eventContent === "" - ? [] - : parseEvents(execution.eventContent, root, new Set(files), evidenceKey, true); + let events = []; + for (const execution of executions) { + const parsed = + execution.eventContent === undefined + ? undefined + : execution.eventContent === "" + ? [] + : parseEvents(execution.eventContent, root, new Set(files), evidenceKey, true); + events = events === undefined || parsed === undefined ? undefined : [...events, ...parsed]; + } + const exitCode = executions.some((execution) => execution.exitCode === null) + ? null + : (executions.find((execution) => execution.exitCode !== 0)?.exitCode ?? 0); + const operationalError = executions.some((execution) => execution.operationalError); outcome = selection.mode === "run" - ? classifyBunDesignatedRun(events, junit, execution.exitCode, execution.operationalError) + ? classifyBunDesignatedRun(events, junit, exitCode, operationalError) : classifyBunDesignatedLoad( events, junit, selection.tests.length, - execution.exitCode, - execution.operationalError, + exitCode, + operationalError, ); } catch { outcome = infrastructureFailure("MISSING_OR_INVALID_REPORT"); diff --git a/integrations/bun/preload.mjs b/integrations/bun/preload.mjs index 88c8958..ec5db63 100644 --- a/integrations/bun/preload.mjs +++ b/integrations/bun/preload.mjs @@ -83,20 +83,148 @@ function scopedAssertSame(...arguments_) { } } -// Only a built-in matcher, present before any test file can extend expect, can issue an owned -// failure, and only when its message has Bun's matcher form. Usage errors and custom matchers stay -// ordinary errors. +// Only a built-in matcher can issue an owned failure, and only when its message has Bun's matcher +// form. Usage errors and custom matchers stay ordinary errors. The driver loads this preload before +// any repository preload, so the functions found here are Bun's own; a matcher is built in only +// when Bun 1.4.2 ships it under that name and, when called, it is still that function. expect.extend +// installs a custom matcher, even one reusing a built-in name, as a different native function. +const BUN_BUILT_IN_MATCHERS = [ + "fail", + "lastCalledWith", + "lastReturnedWith", + "nthCalledWith", + "nthReturnedWith", + "pass", + "toBe", + "toBeArray", + "toBeArrayOfSize", + "toBeBoolean", + "toBeCalled", + "toBeCalledTimes", + "toBeCalledWith", + "toBeCloseTo", + "toBeDate", + "toBeDefined", + "toBeEmpty", + "toBeEmptyObject", + "toBeEven", + "toBeFalse", + "toBeFalsy", + "toBeFinite", + "toBeFunction", + "toBeGreaterThan", + "toBeGreaterThanOrEqual", + "toBeInstanceOf", + "toBeInteger", + "toBeLessThan", + "toBeLessThanOrEqual", + "toBeNaN", + "toBeNegative", + "toBeNil", + "toBeNull", + "toBeNumber", + "toBeObject", + "toBeOdd", + "toBeOneOf", + "toBePositive", + "toBeString", + "toBeSymbol", + "toBeTrue", + "toBeTruthy", + "toBeTypeOf", + "toBeUndefined", + "toBeValidDate", + "toBeWithin", + "toContain", + "toContainAllKeys", + "toContainAllValues", + "toContainAnyKeys", + "toContainAnyValues", + "toContainEqual", + "toContainKey", + "toContainKeys", + "toContainValue", + "toContainValues", + "toEndWith", + "toEqual", + "toEqualIgnoringWhitespace", + "toHaveBeenCalled", + "toHaveBeenCalledOnce", + "toHaveBeenCalledTimes", + "toHaveBeenCalledWith", + "toHaveBeenLastCalledWith", + "toHaveBeenNthCalledWith", + "toHaveLastReturnedWith", + "toHaveLength", + "toHaveNthReturnedWith", + "toHaveProperty", + "toHaveReturned", + "toHaveReturnedTimes", + "toHaveReturnedWith", + "toInclude", + "toIncludeRepeated", + "toMatch", + "toMatchInlineSnapshot", + "toMatchObject", + "toMatchSnapshot", + "toReturn", + "toSatisfy", + "toStartWith", + "toStrictEqual", + "toThrow", + "toThrowError", + "toThrowErrorMatchingInlineSnapshot", + "toThrowErrorMatchingSnapshot", +]; const nativeExpect = bunTest.expect; -const builtInMatchers = new Set(); +const builtInMatchers = new Map(); const matcherChains = new Set(["not", "resolves", "rejects"]); const matcherPrototype = designatedSelection === undefined ? null : prototypeOf(nativeExpect(undefined)); -for (const name of matcherPrototype === null ? [] : Reflect.ownKeys(matcherPrototype)) { - const descriptor = ownDescriptor(matcherPrototype, name); - if (typeof name === "string" && name !== "constructor" && typeof descriptor?.value === "function") - builtInMatchers.add(name); +for (const name of matcherPrototype === null ? [] : BUN_BUILT_IN_MATCHERS) { + const value = ownDescriptor(matcherPrototype, name)?.value; + if (typeof value === "function") builtInMatchers.set(name, value); } -const hasBuiltInMatcher = Set.prototype.has.bind(builtInMatchers); +const builtInMatcher = Map.prototype.get.bind(builtInMatchers); + +// Matchers the operator declared trusted: those that a module at or below a declared path installs +// with expect.extend under a declared name. Each one is identified by the function expect.extend +// installed, so a later extension under the same name, from anywhere, is no longer trusted. +const trustedSources = (designatedSelection?.trustedMatchers ?? []).map((entry) => ({ + path: fileKey(path.resolve(root, entry.path)), + matchers: new Set(entry.matchers), +})); +const trustedMatchers = new Map(); +const trustedMatcher = Map.prototype.get.bind(trustedMatchers); +const objectKeys = Object.keys; +function declaredTrusted(file, name) { + if (file === undefined) return false; + const key = fileKey(file); + return trustedSources.some( + (source) => + source.matchers.has(name) && + (key === source.path || safeApply(stringStartsWith, key, [`${source.path}${path.sep}`])), + ); +} +if (matcherPrototype !== null) { + const nativeExtend = nativeExpect.extend; + nativeExpect.extend = function extend(matchers) { + const caller = callerFile(); + const result = safeApply(nativeExtend, this ?? nativeExpect, [matchers]); + for (const name of typeof matchers === "object" && matchers !== null + ? objectKeys(matchers) + : []) { + trustedMatchers.delete(name); + const installed = ownDescriptor(matcherPrototype, name)?.value; + if (typeof installed === "function" && declaredTrusted(caller, name)) + trustedMatchers.set(name, installed); + } + return result; + }; +} +const ownedMatcher = (name, value) => + builtInMatcher(name) === value || + (trustedMatcher(name) !== undefined && trustedMatcher(name) === value); const isMatcherChain = Set.prototype.has.bind(matcherChains); function markMatcherFailure(error, id) { if (id === undefined || !isErrorInstance(error)) return; @@ -110,7 +238,7 @@ function scopedMatchers(matchers) { const value = safeGet(target, property, target); if (typeof property === "string" && isMatcherChain(property)) return scopedMatchers(value); if (typeof value !== "function") return value; - if (typeof property !== "string" || !hasBuiltInMatcher(property)) return value.bind(target); + if (typeof property !== "string" || !ownedMatcher(property, value)) return value.bind(target); return (...arguments_) => { const id = currentTestId(); try { @@ -163,6 +291,13 @@ function record(event) { } function registrationFile() { + const file = callerFile(); + if (file === undefined) throw new Error("ASSERTLEDGER_BUN_REGISTRATION_FILE_UNAVAILABLE"); + return file; +} + +/** The first file outside this preload on the current stack, if any. */ +function callerFile() { const lines = new Error().stack?.split(/\r?\n/u).slice(1) ?? []; for (const line of lines) { let location = line.trim().replace(/^at\s+/u, ""); @@ -184,7 +319,7 @@ function registrationFile() { if (absolute === preloadPath) continue; return absolute; } - throw new Error("ASSERTLEDGER_BUN_REGISTRATION_FILE_UNAVAILABLE"); + return undefined; } // Describe names enclosing the registration in progress. A templated (`each`) or non-string name diff --git a/package.json b/package.json index fda2ab2..0b38394 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assertledger", - "version": "2.0.3", + "version": "2.1.0", "description": "Verify that regression tests detect a declared bug, with deterministic verdicts and replayable evidence.", "type": "module", "private": false, @@ -72,15 +72,15 @@ "prepack": "pnpm run build && pnpm run check:schemas" }, "dependencies": { - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/server": "2.3.0", "typescript": "7.0.2", "zod": "4.4.3" }, "devDependencies": { - "@biomejs/biome": "2.5.14", - "@modelcontextprotocol/client": "2.0.0", - "@types/node": "22.20.4", - "tsx": "4.23.13" + "@biomejs/biome": "2.5.15", + "@modelcontextprotocol/client": "2.3.0", + "@types/node": "22.20.5", + "tsx": "4.23.15" }, "directories": { "doc": "docs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e76c3e..9c2aa18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@modelcontextprotocol/server': - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.3.0 + version: 2.3.0 typescript: specifier: 7.0.2 version: 7.0.2 @@ -19,247 +19,251 @@ importers: version: 4.4.3 devDependencies: '@biomejs/biome': - specifier: 2.5.14 - version: 2.5.14 + specifier: 2.5.15 + version: 2.5.15 '@modelcontextprotocol/client': - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.3.0 + version: 2.3.0 '@types/node': - specifier: 22.20.4 - version: 22.20.4 + specifier: 22.20.5 + version: 22.20.5 tsx: - specifier: 4.23.13 - version: 4.23.13 + specifier: 4.23.15 + version: 4.23.15 packages: - '@biomejs/biome@2.5.14': - resolution: {integrity: sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw==} + '@biomejs/biome@2.5.15': + resolution: {integrity: sha512-WZTW4slm/pdkh92K6t/3aEN++44JD1PQ7squ7RCMLI1flHGl43DVOIGXvCjHiZgBMO1V4uYxoFNtqrIA4qGuIQ==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.5.14': - resolution: {integrity: sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ==} + '@biomejs/cli-darwin-arm64@2.5.15': + resolution: {integrity: sha512-BZVzFhJ/mUvTLMYbc9x6el0o2Uv5zP7bACYyhFb6fSc1giroXm7PL0a5KqMJ9F+/BnkYRjagD/ROSDVOZ6eapg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.5.14': - resolution: {integrity: sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA==} + '@biomejs/cli-darwin-x64@2.5.15': + resolution: {integrity: sha512-V5Kw63V+fVGNFhFrizDxMbGXAzZCh8kYzJpy3GI6gTapWDg7bZK9oYC9CbVgQSEdTnupz0U5Efz8Ev+5vdBBXw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.5.14': - resolution: {integrity: sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ==} + '@biomejs/cli-linux-arm64-musl@2.5.15': + resolution: {integrity: sha512-tGzZUTcJCV7tj5Adh/Gn6nR4MycqA3iohq2LEcrarAgRBkU0h0OKd2UzCRkOsOYOuUGaUJb4YmqRqyy2feGGSw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.5.14': - resolution: {integrity: sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q==} + '@biomejs/cli-linux-arm64@2.5.15': + resolution: {integrity: sha512-XaG7P7eeSLYETD3K9grfB0mQmpLQZjygdbsxekWBJAg4am79fGAtRpfRyNnTvXB2fZFNnJZG3nXm7HBat0+VUw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.5.14': - resolution: {integrity: sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg==} + '@biomejs/cli-linux-x64-musl@2.5.15': + resolution: {integrity: sha512-IlxUcyxilVGPsE008x13pWdkTbU3nQpP2i9b5UrbOYj6goBkKsRX1XB7yJLcV+O1H9LBchIIm4adOaVLZBX0ZQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.5.14': - resolution: {integrity: sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA==} + '@biomejs/cli-linux-x64@2.5.15': + resolution: {integrity: sha512-xE4iEW/3LqlYj9GFgGrFsFSH51dEEpUbYWBfeOv0q87WkUDxK2o/HhreSb7qMJqck70RVM6Lg96hgedcAYcOkA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.5.14': - resolution: {integrity: sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg==} + '@biomejs/cli-win32-arm64@2.5.15': + resolution: {integrity: sha512-2kPKzhNlm8C+Ru3GcO0Me2ODkCBLrVOUNuyi84RJyWDVKAA4+Kjj3jMjL938lF6BRfvltC4vB2nPY9SvN51Ovg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.5.14': - resolution: {integrity: sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ==} + '@biomejs/cli-win32-x64@2.5.15': + resolution: {integrity: sha512-yAzh4UqEImV6Hcy0zjUqNfJAsUhoD64FBQCdTG8Md/Z2wQeVP/ZmJ3XRYAs1g8J9fjTFyl2fCtc5FB5til47+g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@modelcontextprotocol/client@2.0.0': - resolution: {integrity: sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==} + '@modelcontextprotocol/client@2.3.0': + resolution: {integrity: sha512-pbHtGj6EeFCHngFE8gtNPaKrsj8Mb1IMw45BLcaPML/m2WTxI7pyYmeNQvaux88Lw8BSO5r7nyRbUyIkICmGrg==} engines: {node: '>=20'} '@modelcontextprotocol/core@2.0.0': resolution: {integrity: sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==} engines: {node: '>=20'} - '@modelcontextprotocol/server@2.0.0': - resolution: {integrity: sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==} + '@modelcontextprotocol/core@2.3.0': + resolution: {integrity: sha512-09BHFaNVBe5pB2AhzCfLj0cqmsKLERvZQ4FblzP90UMGLaXVqdZPByFjURwsUYCmxonUW2yAwgTTvle+8O2/8w==} engines: {node: '>=20'} - '@types/node@22.20.4': - resolution: {integrity: sha512-zJRE40jpHtKqE/C4fgHrAKQLJuSpzEnP9ff9Y7YtoR3Wd2pwqzlekDeEuUQXjRd+QCYnVnNwuJYmhdk9XV8gvA==} + '@modelcontextprotocol/server@2.3.0': + resolution: {integrity: sha512-+6b0LdsQLmHsvS7J6sQJ4weTpzUn61fU1JKS+321/+IyDYoHW5CR2Tgqcs6GgAAQvlesCo6yX3xttgmx+XGtJw==} + engines: {node: '>=20'} + + '@types/node@22.20.5': + resolution: {integrity: sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==} '@typescript/typescript-aix-ppc64@7.0.2': resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} @@ -385,13 +389,13 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} hasBin: true - eventsource-parser@3.1.0: - resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} engines: {node: '>=18.0.0'} eventsource@3.0.7: @@ -406,8 +410,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jose@6.2.7: - resolution: {integrity: sha512-hq1OB1bALKfydZNoViyg6hPVGV4i93ny9Op+n4zP5RSf7SCZEXa/TsG2O3IEr7+WlHRTPnpqDmHfMH6qXAD60w==} + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -425,8 +429,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - tsx@4.23.13: - resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==} + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} hasBin: true @@ -448,126 +452,126 @@ packages: snapshots: - '@biomejs/biome@2.5.14': + '@biomejs/biome@2.5.15': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.5.14 - '@biomejs/cli-darwin-x64': 2.5.14 - '@biomejs/cli-linux-arm64': 2.5.14 - '@biomejs/cli-linux-arm64-musl': 2.5.14 - '@biomejs/cli-linux-x64': 2.5.14 - '@biomejs/cli-linux-x64-musl': 2.5.14 - '@biomejs/cli-win32-arm64': 2.5.14 - '@biomejs/cli-win32-x64': 2.5.14 + '@biomejs/cli-darwin-arm64': 2.5.15 + '@biomejs/cli-darwin-x64': 2.5.15 + '@biomejs/cli-linux-arm64': 2.5.15 + '@biomejs/cli-linux-arm64-musl': 2.5.15 + '@biomejs/cli-linux-x64': 2.5.15 + '@biomejs/cli-linux-x64-musl': 2.5.15 + '@biomejs/cli-win32-arm64': 2.5.15 + '@biomejs/cli-win32-x64': 2.5.15 - '@biomejs/cli-darwin-arm64@2.5.14': + '@biomejs/cli-darwin-arm64@2.5.15': optional: true - '@biomejs/cli-darwin-x64@2.5.14': + '@biomejs/cli-darwin-x64@2.5.15': optional: true - '@biomejs/cli-linux-arm64-musl@2.5.14': + '@biomejs/cli-linux-arm64-musl@2.5.15': optional: true - '@biomejs/cli-linux-arm64@2.5.14': + '@biomejs/cli-linux-arm64@2.5.15': optional: true - '@biomejs/cli-linux-x64-musl@2.5.14': + '@biomejs/cli-linux-x64-musl@2.5.15': optional: true - '@biomejs/cli-linux-x64@2.5.14': + '@biomejs/cli-linux-x64@2.5.15': optional: true - '@biomejs/cli-win32-arm64@2.5.14': + '@biomejs/cli-win32-arm64@2.5.15': optional: true - '@biomejs/cli-win32-x64@2.5.14': + '@biomejs/cli-win32-x64@2.5.15': optional: true - '@esbuild/aix-ppc64@0.28.1': + '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.28.1': + '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.28.1': + '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.28.1': + '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.28.1': + '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.28.1': + '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': + '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.28.1': + '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.28.1': + '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.28.1': + '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.28.1': + '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.28.1': + '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.28.1': + '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.28.1': + '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.28.1': + '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': + '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.28.1': + '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': + '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.28.1': + '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': + '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.28.1': + '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': + '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.28.1': + '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': + '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/win32-ia32@0.28.1': + '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-x64@0.28.1': + '@esbuild/win32-x64@0.28.2': optional: true - '@modelcontextprotocol/client@2.0.0': + '@modelcontextprotocol/client@2.3.0': dependencies: - '@modelcontextprotocol/core': 2.0.0 + '@modelcontextprotocol/core': 2.3.0 cross-spawn: 7.0.6 eventsource: 3.0.7 - eventsource-parser: 3.1.0 - jose: 6.2.7 + eventsource-parser: 3.1.1 + jose: 6.2.12 pkce-challenge: 5.0.1 zod: 4.4.3 @@ -575,12 +579,16 @@ snapshots: dependencies: zod: 4.4.3 - '@modelcontextprotocol/server@2.0.0': + '@modelcontextprotocol/core@2.3.0': + dependencies: + zod: 4.4.3 + + '@modelcontextprotocol/server@2.3.0': dependencies: - '@modelcontextprotocol/core': 2.0.0 + '@modelcontextprotocol/core': 2.3.0 zod: 4.4.3 - '@types/node@22.20.4': + '@types/node@22.20.5': dependencies: undici-types: 6.21.0 @@ -650,47 +658,47 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - esbuild@0.28.1: + esbuild@0.28.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - - eventsource-parser@3.1.0: {} + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + eventsource-parser@3.1.1: {} eventsource@3.0.7: dependencies: - eventsource-parser: 3.1.0 + eventsource-parser: 3.1.1 fsevents@2.3.3: optional: true isexe@2.0.0: {} - jose@6.2.7: {} + jose@6.2.12: {} path-key@3.1.1: {} @@ -702,9 +710,9 @@ snapshots: shebang-regex@3.0.0: {} - tsx@4.23.13: + tsx@4.23.15: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 diff --git a/schemas/evidence-manifest.v5.json b/schemas/evidence-manifest.v5.json new file mode 100644 index 0000000..1dfdf00 --- /dev/null +++ b/schemas/evidence-manifest.v5.json @@ -0,0 +1,1052 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "schemaVersion": { + "$ref": "#/$defs/__schema0" + }, + "repositoryDigest": { + "$ref": "#/$defs/__schema1" + }, + "evidenceContext": { + "$ref": "#/$defs/__schema3" + }, + "policy": { + "$ref": "#/$defs/__schema14" + }, + "worlds": { + "$ref": "#/$defs/__schema17" + }, + "candidates": { + "$ref": "#/$defs/__schema18" + }, + "observations": { + "$ref": "#/$defs/__schema20" + }, + "decision": { + "$ref": "#/$defs/__schema21" + }, + "decisionDigest": { + "$ref": "#/$defs/__schema2" + }, + "artifactDigest": { + "$ref": "#/$defs/__schema2" + }, + "adapter": { + "$ref": "#/$defs/__schema22" + }, + "isolation": { + "$ref": "#/$defs/__schema23" + }, + "limitations": { + "$ref": "#/$defs/__schema25" + } + }, + "required": [ + "schemaVersion", + "repositoryDigest", + "evidenceContext", + "policy", + "worlds", + "candidates", + "observations", + "decision", + "decisionDigest", + "artifactDigest", + "adapter", + "isolation", + "limitations" + ], + "additionalProperties": false, + "title": "AssertLedger evidence manifest v5", + "description": "A replayable campaign artifact that also records the snapshot scope, the reproduced repository links and the unfollowed closure edges.", + "$defs": { + "__schema0": { + "type": "string", + "const": "5.0.0" + }, + "__schema1": { + "anyOf": [ + { + "$ref": "#/$defs/__schema2" + }, + { + "type": "string", + "const": "invalid" + } + ] + }, + "__schema2": { + "type": "string", + "pattern": "^sha256:[a-f0-9]{64}$" + }, + "__schema3": { + "type": "object", + "properties": { + "engine": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + "required": ["name", "version"], + "additionalProperties": false + }, + "adapter": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "configuration": { + "$ref": "#/$defs/__schema4" + } + }, + "required": ["name", "version", "configuration"], + "additionalProperties": false + }, + "execution": { + "type": "object", + "properties": { + "isolation": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "environmentAllowlist": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema5" + } + }, + "budgets": { + "$ref": "#/$defs/__schema6" + }, + "candidateRoots": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema7" + } + }, + "backend": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "trusted-local" + }, + "level": { + "type": "string", + "const": "UNSANDBOXED" + } + }, + "required": ["kind", "level"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "container" + }, + "level": { + "type": "string", + "const": "CONTAINER" + }, + "image": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "maxLength": 512, + "pattern": "^(?:[A-Za-z0-9][A-Za-z0-9.-]*(?::[0-9]{1,5})?\\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9._-]{0,127})?@sha256:[a-f0-9]{64}$" + }, + "id": { + "$ref": "#/$defs/__schema2" + }, + "os": { + "type": "string", + "const": "linux" + }, + "architecture": { + "type": "string", + "minLength": 1, + "maxLength": 64 + } + }, + "required": ["reference", "id", "os", "architecture"], + "additionalProperties": false + }, + "runtime": { + "type": "object", + "properties": { + "clientVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "serverVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "serverOs": { + "type": "string", + "const": "linux" + }, + "serverArchitecture": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "cgroupVersion": { + "type": "string", + "maxLength": 16 + }, + "securityOptions": { + "maxItems": 64, + "type": "array", + "items": { + "$ref": "#/$defs/__schema8" + } + } + }, + "required": [ + "clientVersion", + "serverVersion", + "serverOs", + "serverArchitecture", + "cgroupVersion", + "securityOptions" + ], + "additionalProperties": false + }, + "controls": { + "type": "object", + "properties": { + "network": { + "type": "string", + "const": "none" + }, + "rootFilesystem": { + "type": "string", + "const": "read-only" + }, + "hostMounts": { + "type": "string", + "const": "none" + }, + "workspace": { + "type": "string", + "const": "anonymous-volume" + }, + "user": { + "type": "string", + "const": "65534:65534" + }, + "capabilities": { + "type": "string", + "const": "none" + }, + "noNewPrivileges": { + "type": "boolean", + "const": true + }, + "imagePull": { + "type": "string", + "const": "never" + }, + "logDriver": { + "type": "string", + "const": "none" + }, + "environment": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema9" + } + }, + "limits": { + "type": "object", + "properties": { + "memoryBytes": { + "type": "integer", + "minimum": 67108864, + "maximum": 68719476736 + }, + "cpuMillicores": { + "type": "integer", + "minimum": 100, + "maximum": 256000 + }, + "pids": { + "type": "integer", + "minimum": 16, + "maximum": 65536 + }, + "temporaryDirectoryBytes": { + "type": "integer", + "minimum": 1048576, + "maximum": 17179869184 + } + }, + "required": [ + "memoryBytes", + "cpuMillicores", + "pids", + "temporaryDirectoryBytes" + ], + "additionalProperties": false + } + }, + "required": [ + "network", + "rootFilesystem", + "hostMounts", + "workspace", + "user", + "capabilities", + "noNewPrivileges", + "imagePull", + "logDriver", + "environment", + "limits" + ], + "additionalProperties": false + } + }, + "required": ["kind", "level", "image", "runtime", "controls"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "windows-native" + }, + "level": { + "type": "string", + "const": "WINDOWS_NATIVE_UNSANDBOXED" + }, + "platform": { + "type": "string", + "const": "win32" + } + }, + "required": ["kind", "level", "platform"], + "additionalProperties": false + } + ] + } + }, + "required": ["isolation", "environmentAllowlist", "budgets", "candidateRoots", "backend"], + "additionalProperties": false + }, + "worlds": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/__schema10" + }, + "provenance": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "digest": { + "$ref": "#/$defs/__schema2" + } + }, + "required": ["id", "provenance", "digest"], + "additionalProperties": false + } + }, + "repository": { + "type": "object", + "properties": { + "includeDependencies": { + "type": "boolean" + }, + "omittedLinks": { + "maxItems": 10000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema11" + } + }, + "git": { + "anyOf": [ + { + "type": "object", + "properties": { + "mode": { + "type": "string", + "const": "synthesized" + }, + "treeId": { + "type": "string", + "pattern": "^(?:[a-f0-9]{40}|[a-f0-9]{64})$" + }, + "gitVersion": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + }, + "required": ["mode", "treeId", "gitVersion"], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "snapshot": { + "type": "string", + "enum": ["repository", "test-closure"] + }, + "include": { + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema12" + } + }, + "preservedLinkCount": { + "type": "integer", + "minimum": 0, + "maximum": 10000000 + }, + "unboundedClosureEdges": { + "maxItems": 10000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema13" + } + } + }, + "required": [ + "includeDependencies", + "omittedLinks", + "git", + "snapshot", + "include", + "preservedLinkCount", + "unboundedClosureEdges" + ], + "additionalProperties": false + } + }, + "required": ["engine", "adapter", "execution", "worlds", "repository"], + "additionalProperties": false + }, + "__schema4": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema4" + } + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/$defs/__schema4" + } + } + ] + }, + "__schema5": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "__schema6": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema6" + } + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/$defs/__schema6" + } + } + ] + }, + "__schema7": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema8": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema9": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^(?!(?:[Nn][Oo][Dd][Ee]_[Oo][Pp][Tt][Ii][Oo][Nn][Ss]$|[Tt][Ee][Ss][Tt][Ff][Oo][Rr][Gg][Ee]_|[Nn][Oo][Dd][Ee]_[Tt][Ee][Ss][Tt]_)).+$" + }, + "value": { + "type": "string", + "maxLength": 4096 + } + }, + "required": ["name", "value"], + "additionalProperties": false + }, + "__schema10": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" + }, + "__schema11": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "__schema12": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema13": { + "type": "object", + "properties": { + "file": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "reason": { + "type": "string", + "enum": [ + "NON_LITERAL_MODULE_SPECIFIER", + "ABSOLUTE_MODULE_SPECIFIER", + "MODULE_OUTSIDE_REPOSITORY", + "TEST_PRELOAD_UNREADABLE" + ] + } + }, + "required": ["file", "reason"], + "additionalProperties": false + }, + "__schema14": { + "anyOf": [ + { + "type": "object", + "properties": { + "policyVersion": { + "type": "string", + "const": "1.0.0" + }, + "requiredAttempts": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "minimumTargetWeightPermille": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "maximumSelectedCandidates": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "acceptedTargetOutcomes": { + "minItems": 1, + "maxItems": 1, + "type": "array", + "items": { + "$ref": "#/$defs/__schema15" + } + } + }, + "required": [ + "policyVersion", + "requiredAttempts", + "minimumTargetWeightPermille", + "maximumSelectedCandidates", + "acceptedTargetOutcomes" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "policyVersion": { + "type": "string", + "const": "invalid" + }, + "requiredAttempts": { + "type": "number", + "const": 1 + }, + "minimumTargetWeightPermille": { + "type": "number", + "const": 1000 + }, + "maximumSelectedCandidates": { + "type": "number", + "const": 0 + }, + "acceptedTargetOutcomes": { + "maxItems": 0, + "type": "array", + "items": { + "$ref": "#/$defs/__schema16" + } + } + }, + "required": [ + "policyVersion", + "requiredAttempts", + "minimumTargetWeightPermille", + "maximumSelectedCandidates", + "acceptedTargetOutcomes" + ], + "additionalProperties": false + } + ] + }, + "__schema15": { + "type": "string", + "const": "ASSERTION_FAILURE" + }, + "__schema16": { + "type": "string", + "const": "ASSERTION_FAILURE" + }, + "__schema17": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/__schema10" + }, + "kind": { + "type": "string", + "enum": ["REFERENCE", "TARGET", "NEUTRAL"] + }, + "required": { + "type": "boolean" + }, + "weight": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + } + }, + "required": ["id", "kind", "required", "weight"], + "additionalProperties": false + } + }, + "__schema18": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/__schema10" + }, + "digest": { + "$ref": "#/$defs/__schema2" + }, + "sizeBytes": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "status": { + "type": "string", + "enum": ["ELIGIBLE", "UNSTABLE", "INCONCLUSIVE", "INVALID", "WEAK_ORACLE"] + }, + "killedTargetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema10" + } + }, + "targetWeightKilled": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "gates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "COMPLETENESS", + "STABILITY", + "DISCOVERY", + "REFERENCE", + "NEUTRAL", + "TARGET_STRENGTH" + ] + }, + "status": { + "type": "string", + "enum": ["PASSED", "FAILED", "NOT_RUN"] + }, + "evidenceRunIds": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "reasonCodes": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema19" + } + } + }, + "required": ["name", "status", "evidenceRunIds", "reasonCodes"], + "additionalProperties": false + } + }, + "reasonCodes": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema19" + } + } + }, + "required": [ + "id", + "digest", + "sizeBytes", + "status", + "killedTargetIds", + "targetWeightKilled", + "gates", + "reasonCodes" + ], + "additionalProperties": false + } + }, + "__schema19": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "__schema20": { + "type": "array", + "items": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "candidateId": { + "anyOf": [ + { + "$ref": "#/$defs/__schema10" + }, + { + "type": "null" + } + ] + }, + "worldId": { + "$ref": "#/$defs/__schema10" + }, + "attempt": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 + }, + "outcome": { + "type": "string", + "enum": [ + "PASS", + "ASSERTION_FAILURE", + "COLLECTION_FAILURE", + "COMPILE_FAILURE", + "PROCESS_CRASH", + "TIMEOUT", + "INFRA_ERROR", + "NO_TEST_DISCOVERED" + ] + }, + "testsDiscovered": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "candidateTestsDiscovered": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "attributed": { + "type": "boolean" + }, + "durationMs": { + "type": "number", + "minimum": 0 + }, + "exitCode": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "stdoutDigest": { + "$ref": "#/$defs/__schema2" + }, + "stderrDigest": { + "$ref": "#/$defs/__schema2" + } + }, + "required": [ + "runId", + "candidateId", + "worldId", + "attempt", + "outcome", + "testsDiscovered", + "candidateTestsDiscovered", + "attributed" + ], + "additionalProperties": false + } + }, + "__schema21": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["VERIFIED", "REJECTED", "INCONCLUSIVE", "ENGINE_ERROR"] + }, + "selectedCandidateIds": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema10" + } + }, + "reasonCodes": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema19" + } + } + }, + "required": ["status", "selectedCandidateIds", "reasonCodes"], + "additionalProperties": false + }, + "__schema22": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "node-test" + } + }, + "required": ["kind"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "bun-test" + } + }, + "required": ["kind"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "bun-test-designated" + } + }, + "required": ["kind"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "testforge-command" + }, + "protocolVersion": { + "type": "string", + "const": "1.0.0" + } + }, + "required": ["kind", "protocolVersion"], + "additionalProperties": false + } + ] + }, + "__schema23": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "trusted-local" + }, + "level": { + "type": "string", + "const": "UNSANDBOXED" + }, + "acknowledgedUnsafeExecution": { + "type": "boolean", + "const": true + } + }, + "required": ["kind", "level", "acknowledgedUnsafeExecution"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "container" + }, + "level": { + "type": "string", + "const": "CONTAINER" + }, + "runtimeCommand": { + "minItems": 1, + "maxItems": 16, + "type": "array", + "items": { + "$ref": "#/$defs/__schema24" + } + } + }, + "required": ["kind", "level", "runtimeCommand"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "windows-native" + }, + "level": { + "type": "string", + "const": "WINDOWS_NATIVE_UNSANDBOXED" + }, + "acknowledgedUnsafeExecution": { + "type": "boolean", + "const": true + } + }, + "required": ["kind", "level", "acknowledgedUnsafeExecution"], + "additionalProperties": false + } + ] + }, + "__schema24": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "__schema25": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + } + }, + "$id": "https://testforge.dev/schemas/evidence-manifest.v5.json" +} diff --git a/schemas/verification-request.v5.json b/schemas/verification-request.v5.json new file mode 100644 index 0000000..5d61d3b --- /dev/null +++ b/schemas/verification-request.v5.json @@ -0,0 +1,653 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "schemaVersion": { + "$ref": "#/$defs/__schema0" + }, + "repository": { + "$ref": "#/$defs/__schema1" + }, + "adapter": { + "$ref": "#/$defs/__schema4" + }, + "isolation": { + "$ref": "#/$defs/__schema11" + }, + "candidateRoots": { + "$ref": "#/$defs/__schema14" + }, + "budgets": { + "$ref": "#/$defs/__schema16" + }, + "policy": { + "$ref": "#/$defs/__schema17" + }, + "worlds": { + "$ref": "#/$defs/__schema19" + }, + "candidates": { + "$ref": "#/$defs/__schema23" + } + }, + "required": [ + "schemaVersion", + "repository", + "adapter", + "isolation", + "candidateRoots", + "budgets", + "policy", + "worlds", + "candidates" + ], + "additionalProperties": false, + "title": "AssertLedger verification request v5", + "description": "A versioned campaign that runs designated Bun tests in their package, preserves repository-internal links and can snapshot only the executed tests' closure.", + "$defs": { + "__schema0": { + "type": "string", + "const": "5.0.0" + }, + "__schema1": { + "type": "object", + "properties": { + "root": { + "type": "string", + "minLength": 1 + }, + "exclude": { + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema2" + } + }, + "includeDependencies": { + "type": "boolean" + }, + "git": { + "type": "string", + "enum": ["excluded", "synthesized"] + }, + "snapshot": { + "type": "string", + "enum": ["repository", "test-closure"] + }, + "include": { + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema3" + } + } + }, + "required": ["root", "exclude", "includeDependencies", "git", "snapshot", "include"], + "additionalProperties": false + }, + "__schema2": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema3": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema4": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "node-test" + }, + "executable": { + "type": "string", + "minLength": 1 + }, + "baseTestFiles": { + "minItems": 1, + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema5" + } + }, + "extraArguments": { + "maxItems": 0, + "type": "array", + "items": { + "$ref": "#/$defs/__schema6" + } + } + }, + "required": ["kind", "executable", "baseTestFiles"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "testforge-command" + }, + "executable": { + "type": "string", + "minLength": 1 + }, + "arguments": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema7" + } + }, + "protocolVersion": { + "type": "string", + "const": "1.0.0" + } + }, + "required": ["kind", "executable", "arguments", "protocolVersion"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "bun-test" + }, + "executable": { + "type": "string", + "minLength": 1 + }, + "baseTestFiles": { + "minItems": 1, + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema8" + } + } + }, + "required": ["kind", "executable", "baseTestFiles"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "bun-test-designated" + }, + "executable": { + "type": "string", + "minLength": 1 + }, + "testTimeoutMs": { + "anyOf": [ + { + "type": "integer", + "minimum": 1, + "maximum": 3600000 + }, + { + "type": "null" + } + ] + }, + "workingDirectory": { + "type": "string", + "enum": ["repository-root", "test-package"] + }, + "isolate": { + "type": "boolean" + }, + "trustedMatchers": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema9" + } + } + }, + "required": [ + "kind", + "executable", + "testTimeoutMs", + "workingDirectory", + "isolate", + "trustedMatchers" + ], + "additionalProperties": false + } + ] + }, + "__schema5": { + "type": "string", + "minLength": 1 + }, + "__schema6": { + "type": "string" + }, + "__schema7": { + "type": "string" + }, + "__schema8": { + "type": "string", + "minLength": 1 + }, + "__schema9": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "matchers": { + "minItems": 1, + "maxItems": 256, + "type": "array", + "items": { + "$ref": "#/$defs/__schema10" + } + } + }, + "required": ["path", "matchers"], + "additionalProperties": false + }, + "__schema10": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z_$][\\w$]*$" + }, + "__schema11": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "trusted-local" + }, + "acknowledgedUnsafeExecution": { + "type": "boolean" + }, + "environmentAllowlist": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema12" + } + } + }, + "required": ["kind", "acknowledgedUnsafeExecution", "environmentAllowlist"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "container" + }, + "image": { + "type": "string", + "maxLength": 512, + "pattern": "^(?:[A-Za-z0-9][A-Za-z0-9.-]*(?::[0-9]{1,5})?\\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9._-]{0,127})?@sha256:[a-f0-9]{64}$" + }, + "environment": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema13" + } + }, + "limits": { + "type": "object", + "properties": { + "memoryBytes": { + "type": "integer", + "minimum": 67108864, + "maximum": 68719476736 + }, + "cpuMillicores": { + "type": "integer", + "minimum": 100, + "maximum": 256000 + }, + "pids": { + "type": "integer", + "minimum": 16, + "maximum": 65536 + }, + "temporaryDirectoryBytes": { + "type": "integer", + "minimum": 1048576, + "maximum": 17179869184 + } + }, + "required": ["memoryBytes", "cpuMillicores", "pids", "temporaryDirectoryBytes"], + "additionalProperties": false + } + }, + "required": ["kind", "image", "environment", "limits"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "windows-native" + }, + "acknowledgedUnsafeExecution": { + "type": "boolean" + }, + "environmentAllowlist": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema12" + } + } + }, + "required": ["kind", "acknowledgedUnsafeExecution", "environmentAllowlist"], + "additionalProperties": false + } + ] + }, + "__schema12": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^(?!(?:[Nn][Oo][Dd][Ee]_[Oo][Pp][Tt][Ii][Oo][Nn][Ss]$|[Tt][Ee][Ss][Tt][Ff][Oo][Rr][Gg][Ee]_|[Nn][Oo][Dd][Ee]_[Tt][Ee][Ss][Tt]_)).+$" + }, + "__schema13": { + "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/__schema12" + }, + "value": { + "type": "string", + "maxLength": 4096 + } + }, + "required": ["name", "value"], + "additionalProperties": false + }, + "__schema14": { + "minItems": 1, + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema15" + } + }, + "__schema15": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema16": { + "type": "object", + "properties": { + "maximumCandidates": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "maximumWorlds": { + "type": "integer", + "minimum": 3, + "maximum": 1000 + }, + "maximumExecutions": { + "type": "integer", + "minimum": 1, + "maximum": 1000000 + }, + "maximumRepositoryFiles": { + "type": "integer", + "minimum": 1, + "maximum": 10000000 + }, + "maximumRepositoryBytes": { + "type": "integer", + "minimum": 1, + "maximum": 1000000000000 + }, + "maximumWorldOverlayBytes": { + "type": "integer", + "minimum": 1, + "maximum": 1000000000000 + }, + "maximumCandidateBytes": { + "type": "integer", + "minimum": 1, + "maximum": 100000000 + }, + "maximumTotalCandidateBytes": { + "type": "integer", + "minimum": 1, + "maximum": 100000000000 + }, + "timeoutMsPerExecution": { + "type": "integer", + "minimum": 10, + "maximum": 86400000 + }, + "maximumOutputBytes": { + "type": "integer", + "minimum": 128, + "maximum": 100000000 + } + }, + "required": [ + "maximumCandidates", + "maximumWorlds", + "maximumExecutions", + "maximumRepositoryFiles", + "maximumRepositoryBytes", + "maximumWorldOverlayBytes", + "maximumCandidateBytes", + "maximumTotalCandidateBytes", + "timeoutMsPerExecution", + "maximumOutputBytes" + ], + "additionalProperties": false + }, + "__schema17": { + "type": "object", + "properties": { + "policyVersion": { + "type": "string", + "const": "1.0.0" + }, + "requiredAttempts": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "minimumTargetWeightPermille": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "maximumSelectedCandidates": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "acceptedTargetOutcomes": { + "minItems": 1, + "maxItems": 1, + "type": "array", + "items": { + "$ref": "#/$defs/__schema18" + } + } + }, + "required": [ + "policyVersion", + "requiredAttempts", + "minimumTargetWeightPermille", + "maximumSelectedCandidates", + "acceptedTargetOutcomes" + ], + "additionalProperties": false + }, + "__schema18": { + "type": "string", + "const": "ASSERTION_FAILURE" + }, + "__schema19": { + "minItems": 1, + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema20" + } + }, + "__schema20": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/__schema21" + }, + "kind": { + "type": "string", + "enum": ["REFERENCE", "TARGET", "NEUTRAL"] + }, + "required": { + "type": "boolean" + }, + "weight": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "provenance": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "files": { + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema22" + } + } + }, + "required": ["id", "kind", "required", "weight", "provenance", "files"], + "additionalProperties": false + }, + "__schema21": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" + }, + "__schema22": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "content": { + "type": "string", + "maxLength": 1048576 + } + }, + "required": ["path", "content"], + "additionalProperties": false + }, + "__schema23": { + "minItems": 1, + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema24" + } + }, + "__schema24": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/__schema21" + }, + "files": { + "minItems": 1, + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema22" + } + } + }, + "required": ["id", "files"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/__schema21" + }, + "test": { + "type": "object", + "properties": { + "file": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "path": { + "minItems": 1, + "maxItems": 32, + "type": "array", + "items": { + "$ref": "#/$defs/__schema25" + } + } + }, + "required": ["file", "path"], + "additionalProperties": false + }, + "expectedFailure": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + { + "type": "null" + } + ] + } + }, + "required": ["id", "test", "expectedFailure"], + "additionalProperties": false + } + ] + }, + "__schema25": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + } + }, + "$id": "https://testforge.dev/schemas/verification-request.v5.json" +} diff --git a/schemas/witness-import-request.v2.json b/schemas/witness-import-request.v2.json new file mode 100644 index 0000000..9acf33c --- /dev/null +++ b/schemas/witness-import-request.v2.json @@ -0,0 +1,294 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "schemaVersion": { + "$ref": "#/$defs/__schema0" + }, + "provenance": { + "$ref": "#/$defs/__schema1" + }, + "repository": { + "$ref": "#/$defs/__schema2" + }, + "adapter": { + "$ref": "#/$defs/__schema5" + }, + "test": { + "$ref": "#/$defs/__schema8" + }, + "expectedFailure": { + "$ref": "#/$defs/__schema10" + }, + "targets": { + "$ref": "#/$defs/__schema11" + }, + "neutral": { + "$ref": "#/$defs/__schema13" + }, + "requiredAttempts": { + "$ref": "#/$defs/__schema15" + }, + "timeoutMsPerExecution": { + "$ref": "#/$defs/__schema16" + } + }, + "required": [ + "schemaVersion", + "provenance", + "repository", + "adapter", + "test", + "expectedFailure", + "targets", + "neutral", + "requiredAttempts", + "timeoutMsPerExecution" + ], + "additionalProperties": false, + "title": "AssertLedger witness import request v2", + "description": "A recorded red/green witness replayed as a v5 campaign: the designated test runs in its package with its own Bun configuration.", + "$defs": { + "__schema0": { + "type": "string", + "const": "2.0.0" + }, + "__schema1": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema2": { + "type": "object", + "properties": { + "root": { + "type": "string", + "minLength": 1 + }, + "exclude": { + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema3" + } + }, + "includeDependencies": { + "type": "boolean" + }, + "git": { + "type": "string", + "enum": ["excluded", "synthesized"] + }, + "snapshot": { + "type": "string", + "enum": ["repository", "test-closure"] + }, + "include": { + "maxItems": 1000, + "type": "array", + "items": { + "$ref": "#/$defs/__schema4" + } + } + }, + "required": ["root", "exclude", "includeDependencies", "git", "snapshot", "include"], + "additionalProperties": false + }, + "__schema3": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema4": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "__schema5": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "bun-test-designated" + }, + "executable": { + "type": "string", + "minLength": 1 + }, + "testTimeoutMs": { + "anyOf": [ + { + "type": "integer", + "minimum": 1, + "maximum": 3600000 + }, + { + "type": "null" + } + ] + }, + "workingDirectory": { + "type": "string", + "enum": ["repository-root", "test-package"] + }, + "isolate": { + "type": "boolean" + }, + "trustedMatchers": { + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema6" + } + } + }, + "required": [ + "kind", + "executable", + "testTimeoutMs", + "workingDirectory", + "isolate", + "trustedMatchers" + ], + "additionalProperties": false + }, + "__schema6": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "matchers": { + "minItems": 1, + "maxItems": 256, + "type": "array", + "items": { + "$ref": "#/$defs/__schema7" + } + } + }, + "required": ["path", "matchers"], + "additionalProperties": false + }, + "__schema7": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z_$][\\w$]*$" + }, + "__schema8": { + "type": "object", + "properties": { + "file": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "path": { + "minItems": 1, + "maxItems": 32, + "type": "array", + "items": { + "$ref": "#/$defs/__schema9" + } + } + }, + "required": ["file", "path"], + "additionalProperties": false + }, + "__schema9": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "__schema10": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "__schema11": { + "minItems": 1, + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema12" + } + }, + "__schema12": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "beforeDigest": { + "type": "string", + "pattern": "^sha256:[a-f0-9]{64}$" + }, + "content": { + "type": "string", + "maxLength": 1048576 + } + }, + "required": ["path", "beforeDigest", "content"], + "additionalProperties": false + }, + "__schema13": { + "anyOf": [ + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "files": { + "minItems": 1, + "maxItems": 100, + "type": "array", + "items": { + "$ref": "#/$defs/__schema14" + } + } + }, + "required": ["reason", "files"], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "__schema14": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "content": { + "type": "string", + "maxLength": 1048576 + } + }, + "required": ["path", "content"], + "additionalProperties": false + }, + "__schema15": { + "type": "integer", + "minimum": 2, + "maximum": 10 + }, + "__schema16": { + "type": "integer", + "minimum": 1000, + "maximum": 3600000 + } + }, + "$id": "https://testforge.dev/schemas/witness-import-request.v2.json" +} diff --git a/scripts/conformance-v1-lock.ts b/scripts/conformance-v1-lock.ts index 1b5925e..ea2cb82 100644 --- a/scripts/conformance-v1-lock.ts +++ b/scripts/conformance-v1-lock.ts @@ -73,4 +73,4 @@ export const CONFORMANCE_V1_PUBLIC_DIGESTS = { // Schemas published after conformance v1 are locked additively: the v1 bundle bytes stay frozen. export const PUBLISHED_SCHEMA_EXTENSIONS_DIGEST = - "sha256:3c7f6d79d07019c21a0a62d7ccf8582c7370f375cef740b61f9c67251cc05b3e"; + "sha256:581cebbfdeb03e13a6d6ac2c4830a6116c09388e429c843f81b8ff0aefa46cbd"; diff --git a/scripts/schema-registry.ts b/scripts/schema-registry.ts index 5e76f6e..a3112ab 100644 --- a/scripts/schema-registry.ts +++ b/scripts/schema-registry.ts @@ -32,6 +32,7 @@ import { evidenceManifestV2JsonSchema, evidenceManifestV3JsonSchema, evidenceManifestV4JsonSchema, + evidenceManifestV5JsonSchema, evidenceProviderManifestJsonSchema, replayResultJsonSchema, repositoryAnalysisJsonSchema, @@ -47,7 +48,9 @@ import { verificationRequestV2JsonSchema, verificationRequestV3JsonSchema, verificationRequestV4JsonSchema, + verificationRequestV5JsonSchema, witnessImportRequestJsonSchema, + witnessImportRequestV2JsonSchema, } from "../src/contracts/index.js"; import { qualificationPlanJsonSchema, @@ -73,6 +76,9 @@ export const POST_CONFORMANCE_V1_SCHEMA_NAMES: ReadonlySet = new Set([ "verification-request.v4.json", "evidence-manifest.v4.json", "witness-import-request.v1.json", + "verification-request.v5.json", + "evidence-manifest.v5.json", + "witness-import-request.v2.json", "repository-init-config.v2.json", "repository-init-lock.v2.json", "repository-init-result.v2.json", @@ -113,7 +119,9 @@ export function publishedSchemas(): ReadonlyArray