From e536b4321f787c4202f9a3a1812e27b66b70dc73 Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 23 Oct 2026 12:54:43 +0200 Subject: [PATCH 1/4] fix: verify current source artifacts before release --- .github/workflows/publish.yml | 12 +++ .github/workflows/test.yml | 79 +++++++++++++++++++ scripts/README.md | 2 +- scripts/native-helper-workflows.test.ts | 35 ++++++++ ...urce-provenance-bundle.integration.test.ts | 51 ++++++++++-- 5 files changed, 170 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8e37d837..2a5fc654 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -74,10 +74,22 @@ jobs: - name: Build run: npm run build + - name: Resolve exact Daimon provenance fixture revision + id: daimon-provenance + run: | + node --input-type=module <<'NODE' >> "$GITHUB_OUTPUT" + import { readFileSync } from 'node:fs'; + import { parse } from 'yaml'; + const ref = parse(readFileSync('runtimes.yaml', 'utf8')).runtimes.daimon.ref; + if (!/^[a-f0-9]{40}$/.test(ref)) throw Error('Daimon provenance requires a full commit pin'); + console.log(`ref=${ref}`); + NODE + - name: Check out Daimon provenance fixture uses: actions/checkout@v4 with: repository: noopolis/daimon-harness + ref: ${{ steps.daimon-provenance.outputs.ref }} path: daimon-fixture # The shipped runtime refuses any Grok executable but the manifest-pinned diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e476053e..cf0e2677 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -162,3 +162,82 @@ jobs: env -u TYPESAFE_API_KEY src/training/dspy/.venv/bin/python -c 'import json, subprocess; result=subprocess.run(["src/training/dspy/.venv/bin/spawnfile-training-dspy"], input="{}\n", capture_output=True, text=True, check=False); terminal=json.loads(result.stdout); assert result.returncode == 1 and terminal["schema"] == "spawnfile.training.dspy.v2" and terminal["error"]["code"] == "invalid_request"' - name: Test the bridge and enforce per-module Python coverage run: env -u TYPESAFE_API_KEY npm run test:training-python + + # Run the release gate on every PR/main revision: ~6 runner minutes in + # parallel with coverage, with no path filter that can miss an upstream move. + source-provenance: + name: offline source provenance (linux/amd64) + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - name: Check out Spawnfile + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: env -u TYPESAFE_API_KEY npm ci + + # Uses the checked-in native helpers, as the shipped package does. + # The root job separately rebuilds and syscall-tests both architectures. + - name: Build + run: env -u TYPESAFE_API_KEY npm run build + + - name: Resolve exact Daimon provenance fixture revision + id: daimon-provenance + run: | + node --input-type=module <<'NODE' >> "$GITHUB_OUTPUT" + import { readFileSync } from 'node:fs'; + import { parse } from 'yaml'; + const ref = parse(readFileSync('runtimes.yaml', 'utf8')).runtimes.daimon.ref; + if (!/^[a-f0-9]{40}$/.test(ref)) throw Error('Daimon provenance requires a full commit pin'); + console.log(`ref=${ref}`); + NODE + + - name: Check out Daimon provenance fixture + uses: actions/checkout@v4 + with: + repository: noopolis/daimon-harness + ref: ${{ steps.daimon-provenance.outputs.ref }} + path: daimon-fixture + + # The shipped runtime refuses any Grok executable but the manifest-pinned + # build, so the source build needs the real linux-x86_64 binary. Its URL, + # size, and digest come from the vendored Daimon contract manifest, so a + # re-vendored Daimon bump moves this download with no CI edit. + - name: Fetch the pinned linux-x86_64 Grok CLI + id: grok-cli + shell: bash + run: | + set -euo pipefail + + manifest=src/runtime/daimon/contract-manifest.json + read -r url bytes sha256 < <(node -e ' + const artifact = require("./" + process.argv[1]).grokEngineBroker?.grokCliArtifacts?.x64; + if (typeof artifact?.url !== "string" || !Number.isInteger(artifact.bytes) || !/^[a-f0-9]{64}$/.test(artifact.sha256 ?? "")) { + throw new Error("vendored Daimon contract manifest has no pinned linux-x86_64 Grok CLI artifact"); + } + process.stdout.write(`${artifact.url} ${artifact.bytes} ${artifact.sha256}\n`); + ' "${manifest}") + + file="${RUNNER_TEMP}/grok" + curl --fail --location --silent --show-error --output "${file}" -- "${url}" + test "$(stat -c %s -- "${file}")" -eq "${bytes}" + echo "${sha256} ${file}" | sha256sum --check --strict - + chmod 0755 -- "${file}" + echo "file=${file}" >> "${GITHUB_OUTPUT}" + + - name: Verify Git-free offline linux/amd64 source build + env: + SPAWNFILE_GROK_CLI_FILE: ${{ steps.grok-cli.outputs.file }} + SPAWNFILE_TEST_DAIMON_SOURCE: ${{ github.workspace }}/daimon-fixture + run: env -u TYPESAFE_API_KEY npm run test:source-provenance-docker + + - name: Remove Daimon provenance fixture and Grok CLI + if: always() + run: rm -rf -- daimon-fixture "${RUNNER_TEMP}/grok" diff --git a/scripts/README.md b/scripts/README.md index 49ba5496..723cd183 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -21,7 +21,7 @@ in the npm package. Scripts run as native TypeScript on Node 22.19+. | `verify-package-closure.ts` | `npm run verify:package-closure` | Verify the packed CLI and runtime closure | | `test-training-python.ts` | `npm run test:training-python`, CI | Run the real DSPy bridge pytest suite and enforce 90% lines and branches for every module and the total; install `src/training/dspy/requirements.lock` and the editable bridge in `src/training/dspy/.venv` first | | `product-state-volume-integration.test.ts` | `npm run test:product-state-volume`, CI | Test real volume preseed; Docker and host volume access required | -| `source-provenance-bundle.integration.test.ts` | `npm run test:source-provenance-docker` | Test the real offline Daimon archive build | +| `source-provenance-bundle.integration.test.ts` | `npm run test:source-provenance-docker` | PR/main and publish workflows: real offline Daimon archive build and tamper rejection; Docker, Daimon checkout (`SPAWNFILE_TEST_DAIMON_SOURCE`), pinned Linux Grok CLI (`SPAWNFILE_GROK_CLI_FILE`) | | `moltnet-source-provenance.integration.test.ts` | `npm run test:moltnet-source-provenance-docker` | Test the real offline Moltnet archive build | `source-provenance-bundle.ts`, `native-helper-artifacts.ts`, and diff --git a/scripts/native-helper-workflows.test.ts b/scripts/native-helper-workflows.test.ts index dc6e5dd8..d9af8057 100644 --- a/scripts/native-helper-workflows.test.ts +++ b/scripts/native-helper-workflows.test.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; +import { parse } from "yaml"; const gate = "node --experimental-strip-types --test scripts/native-helper-artifacts.test.ts scripts/native-helper-integration.test.ts"; @@ -22,3 +23,37 @@ test("normal package build copies shipped helpers without invoking the native re assert.doesNotMatch(packageJson.scripts.build, /native\/build\.ts|docker/u); assert.equal(packageJson.scripts["build:native"], "node --experimental-strip-types ./src/deployment/native/build.ts"); }); + +test("PR/main runs the same offline source provenance gate and inputs as release", async () => { + type Step = { name: string; run?: string; env?: Record; uses?: string; with?: Record }; + type Job = { if?: string; "timeout-minutes": number; steps: Step[] }; + const ci = parse(await readFile(".github/workflows/test.yml", "utf8")) as { + on: { pull_request: { paths?: string[]; "paths-ignore"?: string[] } | null; push: { branches: string[]; paths?: string[]; "paths-ignore"?: string[] } }; + jobs: Record; + }; + const release = parse(await readFile(".github/workflows/publish.yml", "utf8")) as { jobs: { publish: Job } }; + assert.ok(Object.hasOwn(ci.on, "pull_request")); + assert.ok(ci.on.push.branches.includes("main")); + assert.equal(ci.on.push.paths, undefined); + assert.equal(ci.on.push["paths-ignore"], undefined); + assert.equal(ci.on.pull_request?.paths, undefined); + assert.equal(ci.on.pull_request?.["paths-ignore"], undefined); + const job = ci.jobs["source-provenance"]; + assert.ok(job); + assert.equal(job.if, undefined, "the gate must run on every PR/main revision"); + assert.ok(job["timeout-minutes"] >= 15 && job["timeout-minutes"] <= 30); + for (const name of ["Resolve exact Daimon provenance fixture revision", "Check out Daimon provenance fixture", "Fetch the pinned linux-x86_64 Grok CLI", + "Verify Git-free offline linux/amd64 source build"]) { + const actual = job.steps.find((step) => step.name === name); + const expected = release.jobs.publish.steps.find((step) => step.name === name); + assert.ok(actual && expected, name); + assert.deepEqual(actual.with, expected.with, name); + assert.deepEqual(actual.env, expected.env, name); + assert.equal(actual.run?.replace("env -u TYPESAFE_API_KEY ", ""), expected.run, name); + } + assert.equal(job.steps.find((step) => step.name === "Check out Daimon provenance fixture")?.with?.ref, + "${{ steps.daimon-provenance.outputs.ref }}"); + const gate = job.steps.findIndex((step) => step.run?.includes("npm run test:source-provenance-docker")); + const build = job.steps.findIndex((step) => step.run?.includes("npm run build")); + assert.ok(build >= 0 && gate > build, "the gate requires compiled materializer output"); +}); diff --git a/scripts/source-provenance-bundle.integration.test.ts b/scripts/source-provenance-bundle.integration.test.ts index fde28c2d..cff1a873 100644 --- a/scripts/source-provenance-bundle.integration.test.ts +++ b/scripts/source-provenance-bundle.integration.test.ts @@ -25,6 +25,13 @@ const pinnedGrokCli = (): string => { }; const sha512 = (file: string): string => `sha512:${createHash("sha512").update(readFileSync(file)).digest("hex")}`; +const assertRejected = (result: ReturnType, label: string, diagnostic: RegExp): void => { + assert.ifError(result.error); + const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`; + assert.ok(result.status !== null && result.status > 0, `${label} must be rejected (status=${result.status})\n${output}`); + assert.match(output, diagnostic, `${label} must fail at its verifier`); +}; + // A full pass — offline closure, shipped artifact, wrapper image, literal org // container, and four tamper rejections — measured 328 s on a GitHub-hosted // runner, so the budget leaves room for a slower one rather than 32 s. @@ -82,7 +89,7 @@ test("actual Daimon lock produces a real offline linux/amd64 shipped artifact an const port = mapped.slice(mapped.lastIndexOf(":") + 1); const identityPath = path.join(repository, ".local-daimon-runtime-identity.json"), priorIdentity = existsSync(identityPath) ? readFileSync(identityPath) : null; try { - execFileSync("npm", ["run", "--silent", "build:local-daimon"], { cwd: repository, env: { ...process.env, AGY_CLI_SHA256: agySha, AGY_CLI_SHA512: sha512(agyTar), AGY_CLI_URL: "https://invalid.example/agy", AGY_CLI_VERSION: "fixture", CODEX_CLI_SHA256: codexSha, GROK_CLI_SHA256: grokSha, GROK_CLI_URL: pinnedManifest.grokEngineBroker.grokCliArtifacts.x64.url, GROK_CLI_VERSION: pinnedManifest.grokEngineBroker.grokCliVersion, SPAWNFILE_AGY_CLI_ARCHIVE: agyTar, SPAWNFILE_DAIMON_DEPENDENCY_BUNDLE: dependencyTar, SPAWNFILE_DAIMON_LOCAL_IMAGE_TAG: `127.0.0.1:${port}/noopolis/spawnfile-runtime-daimon:archive-wrapper`, SPAWNFILE_DAIMON_SOURCE_BUNDLE: sourceTar, SPAWNFILE_GROK_CLI_FILE: grok }, stdio: "ignore" }); + execFileSync("npm", ["run", "--silent", "build:local-daimon"], { cwd: repository, env: { ...process.env, SPAWNFILE_DAIMON_TARGET_ARCH: "amd64", AGY_CLI_SHA256: agySha, AGY_CLI_SHA512: sha512(agyTar), AGY_CLI_URL: "https://invalid.example/agy", AGY_CLI_VERSION: "fixture", CODEX_CLI_SHA256: codexSha, GROK_CLI_SHA256: grokSha, GROK_CLI_URL: pinnedManifest.grokEngineBroker.grokCliArtifacts.x64.url, GROK_CLI_VERSION: pinnedManifest.grokEngineBroker.grokCliVersion, SPAWNFILE_AGY_CLI_ARCHIVE: agyTar, SPAWNFILE_DAIMON_DEPENDENCY_BUNDLE: dependencyTar, SPAWNFILE_DAIMON_LOCAL_IMAGE_TAG: `127.0.0.1:${port}/noopolis/spawnfile-runtime-daimon:archive-wrapper`, SPAWNFILE_DAIMON_SOURCE_BUNDLE: sourceTar, SPAWNFILE_GROK_CLI_FILE: grok }, stdio: "inherit" }); const wrapperIdentity = JSON.parse(readFileSync(identityPath, "utf8")); assert.equal(wrapperIdentity.image_architecture, "amd64"); assert.match(wrapperIdentity.image_reference, new RegExp(`^127\\.0\\.0\\.1:${port}/noopolis/spawnfile-runtime-daimon@sha256:[a-f0-9]{64}$`, "u")); } finally { if (priorIdentity) writeFileSync(identityPath, priorIdentity); else rmSync(identityPath, { force: true }); } const receipt = { architecture: "amd64", daimon: { package_sha256: packageSha, source_inputs: sourceInputs, source_sha256: digest(path.join(packageContext, "source-inputs.json")) }, engines: { agy: { executable_sha256: agySha }, codex: { executable_sha256: codexSha }, grok: { executable_sha256: grokSha } }, manifest_sha256: manifestSha, provenance: { agy: { archive: { format: "tar.gz", sha512: sha512(agyTar), url: "https://invalid.example/agy", version: "fixture" } }, grok: { executable: { sha256: grokSha, url: pinnedManifest.grokEngineBroker.grokCliArtifacts.x64.url, version: pinnedManifest.grokEngineBroker.grokCliVersion } } }, version: "spawnfile.daimon-runtime-capability-receipt.v1" }; @@ -138,13 +145,41 @@ test("actual Daimon lock produces a real offline linux/amd64 shipped artifact an const fakeReceipt = validateSourceBundle(readFileSync(fakeTar)); mkdirSync(fakeContext); mkdirSync(fakeOutput); cpSync(fakeTar, path.join(fakeContext, "dependencies.tar")); const fakeArgs = args.map((value) => value === `dependency_bundle=${dependencyContext}` ? `dependency_bundle=${fakeContext}` : value === `DEPENDENCY_ARCHIVE_SHA256=${dependencyReceipt.archive_sha256}` ? `DEPENDENCY_ARCHIVE_SHA256=${fakeReceipt.archive_sha256}` : value === `DEPENDENCY_MANIFEST_SHA256=${dependencyReceipt.manifest_sha256}` ? `DEPENDENCY_MANIFEST_SHA256=${fakeReceipt.manifest_sha256}` : value === `type=local,dest=${output}` ? `type=local,dest=${fakeOutput}` : value); - assert.notEqual(spawnSync("docker", fakeArgs, { stdio: "ignore" }).status, 0); - for (const fault of ["missing", "wrong"]) { - const badRoot=path.join(temporary,`bad-native-${fault}`),badTar=path.join(temporary,`bad-native-${fault}.tar`),badContext=path.join(temporary,`bad-native-${fault}-context`),badOutput=path.join(temporary,`bad-native-${fault}-output`); - mkdirSync(badRoot);execFileSync("tar",["-xf",sourceTar,"-C",badRoot]);rmSync(path.join(badRoot,".spawnfile-source-manifest.json"),{force:true});const artifact=path.join(badRoot,"src","runtime","native","artifacts","daimon-engine-broker-x64");if(fault==="missing")rmSync(artifact);else writeFileSync(artifact,"wrong-native-artifact\n",{mode:0o755}); - const badReceipt=createSourceBundle(badRoot,badTar);mkdirSync(badContext);mkdirSync(badOutput);cpSync(badTar,path.join(badContext,"source.tar"));const badArgs=args.map((value)=>value===`source_bundle=${sourceContext}`?`source_bundle=${badContext}`:value===`SOURCE_ARCHIVE_SHA256=${sourceReceipt.archive_sha256}`?`SOURCE_ARCHIVE_SHA256=${badReceipt.archive_sha256}`:value===`SOURCE_MANIFEST_SHA256=${sourceReceipt.manifest_sha256}`?`SOURCE_MANIFEST_SHA256=${badReceipt.manifest_sha256}`:value===`type=local,dest=${output}`?`type=local,dest=${badOutput}`:value);assert.notEqual(spawnSync("docker",badArgs,{stdio:"ignore"}).status,0); + assertRejected(spawnSync("docker", fakeArgs, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }), "false Codex lock integrity", /npm error code (?:EINTEGRITY|ENOTCACHED)/u); + for (const fault of ["missing", "wrong"] as const) { + const badRoot = path.join(temporary, `bad-native-${fault}`), badTar = path.join(temporary, `bad-native-${fault}.tar`), + badContext = path.join(temporary, `bad-native-${fault}-context`), badOutput = path.join(temporary, `bad-native-${fault}-output`); + mkdirSync(badRoot); + execFileSync("tar", ["-xf", sourceTar, "-C", badRoot]); + rmSync(path.join(badRoot, ".spawnfile-source-manifest.json")); + // Daimon's engine ownership move relocated the source artifact; the pack + // and installed runtime still ship this exact x64 broker's bytes. + const artifact = path.join(badRoot, "src", "engines", "grok", "broker", "native", "artifacts", "daimon-engine-broker-x64"); + assert.deepEqual(readFileSync(artifact), packedBroker, "tamper target must be the shipped broker"); + execFileSync("npm", ["run", "verify:native"], { cwd: badRoot, stdio: "pipe" }); + if (fault === "missing") rmSync(artifact); + else writeFileSync(artifact, "wrong-native-artifact\n", { mode: 0o755 }); + const diagnostic = fault === "missing" ? /ENOENT[^\n]*daimon-engine-broker-x64/u : /native broker provenance mismatch/u; + // Check the verifier directly as well: a missing file breaking a later + // copy/build must not hide a verifier that silently ignores that file. + assertRejected(spawnSync("npm", ["run", "verify:native"], { cwd: badRoot, encoding: "utf8" }), `${fault} native verifier`, diagnostic); + const badReceipt = createSourceBundle(badRoot, badTar); + mkdirSync(badContext); mkdirSync(badOutput); cpSync(badTar, path.join(badContext, "source.tar")); + const badArgs = args.map((value) => value === `source_bundle=${sourceContext}` ? `source_bundle=${badContext}` + : value === `SOURCE_ARCHIVE_SHA256=${sourceReceipt.archive_sha256}` ? `SOURCE_ARCHIVE_SHA256=${badReceipt.archive_sha256}` + : value === `SOURCE_MANIFEST_SHA256=${sourceReceipt.manifest_sha256}` ? `SOURCE_MANIFEST_SHA256=${badReceipt.manifest_sha256}` + : value === `type=local,dest=${output}` ? `type=local,dest=${badOutput}` : value); + assertRejected(spawnSync("docker", badArgs, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }), `${fault} native source build`, diagnostic); } - const tampered = readFileSync(path.join(dependencyContext, "dependencies.tar")); const tamperOffset = tampered.length - 1025; tampered[tamperOffset] = (tampered[tamperOffset] ?? 0) ^ 1; writeFileSync(path.join(dependencyContext, "dependencies.tar"), tampered); - assert.notEqual(spawnSync("docker", args, { stdio: "ignore" }).status, 0); + // Alter whitespace in the real bundled lock, preserving valid JSON and + // dependency semantics. Only the archive digest should reject this case. + const tampered = readFileSync(path.join(dependencyContext, "dependencies.tar")); + const lockBytes = execFileSync("tar", ["-xOf", dependencyTar, "package-lock.json"]); + const lockOffset = tampered.indexOf(lockBytes), whitespace = lockBytes.indexOf(" "); + assert.ok(lockOffset >= 0 && whitespace >= 0, "archive tamper target must exist"); + tampered[lockOffset + whitespace] = 0x09; + writeFileSync(path.join(dependencyContext, "dependencies.tar"), tampered); + assert.notEqual(digest(path.join(dependencyContext, "dependencies.tar")), dependencyReceipt.archive_sha256); + assertRejected(spawnSync("docker", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }), "changed dependency archive", /did not complete successfully.*exit code: 1/u); } finally { if (registry) spawnSync("docker", ["rm", "--force", registry], { stdio: "ignore" }); rmSync(temporary, { force: true, recursive: true }); } }); From e02301fbb1a056e6499e8752662d3d4aaa35e2f7 Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 23 Oct 2026 12:54:48 +0200 Subject: [PATCH 2/4] chore: release 0.1.27 --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f63c5018..776c0c0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "spawnfile", - "version": "0.1.26", + "version": "0.1.27", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "spawnfile", - "version": "0.1.26", + "version": "0.1.27", "license": "MIT", "dependencies": { "@huggingface/jinja": "0.5.10", diff --git a/package.json b/package.json index c263ace0..e475b6aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spawnfile", - "version": "0.1.26", + "version": "0.1.27", "description": "Canonical source compiler for autonomous agents and teams.", "license": "MIT", "type": "module", @@ -56,7 +56,7 @@ "test:verdict": "node -e \"const fs=require('fs'); const file='coverage/vitest-results.json'; if (!fs.existsSync(file)) { console.error('VERDICT: results file missing'); process.exit(1); } let result; try { result=JSON.parse(fs.readFileSync(file, 'utf8')); } catch { console.error('VERDICT: results file unparseable'); process.exit(1); } const total=result.numTotalTests; const totalSuites=result.numTotalTestSuites; const passedTests=result.numPassedTests; const failedTests=result.numFailedTests; const pendingTests=result.numPendingTests; const todoTests=result.numTodoTests; const failedSuites=result.numFailedTestSuites; if (typeof result.success !== 'boolean' || !Number.isInteger(passedTests) || passedTests < 0 || !Number.isInteger(failedTests) || failedTests < 0 || !Number.isInteger(pendingTests) || pendingTests < 0 || !Number.isInteger(todoTests) || todoTests < 0 || !Number.isInteger(failedSuites) || failedSuites < 0 || !Number.isInteger(totalSuites) || totalSuites < 0) { console.error('VERDICT: results schema unrecognised'); process.exit(1); } if (!Number.isInteger(total) || total <= 0) { console.error('VERDICT: no tests found'); process.exit(1); } if (passedTests + failedTests + pendingTests + todoTests !== total) { console.error('VERDICT: results counters do not reconcile (tests='+total+', passed='+passedTests+', failed='+failedTests+', skipped='+pendingTests+', todo='+todoTests+')'); process.exit(1); } const failed=result.success === false || failedTests > 0 || failedSuites > 0; if (failed) { console.error('VERDICT: test failures found (tests='+failedTests+', suites='+failedSuites+', success='+result.success+')'); process.exit(2); } if (passedTests === 0) { console.error('VERDICT: no tests actually ran (skipped='+pendingTests+', todo='+todoTests+')'); process.exit(1); } console.log('VERDICT: tests passed (tests='+total+', suites='+totalSuites+', passed='+passedTests+', skipped='+pendingTests+', todo='+todoTests+')');\"", "test:coverage-verdict": "node --import tsx -e \"import fs from 'node:fs'; const file='coverage/coverage-summary.json'; const fail=(message)=>{ console.error('COVERAGE VERDICT: '+message); process.exit(1); }; let summary; try { summary=JSON.parse(fs.readFileSync(file, 'utf8')); } catch (error) { fail(error && error.code === 'ENOENT' ? 'summary file missing' : 'summary file unparseable'); } import('./vitest.config.ts').then(({default:config})=>{ const thresholds=config.test && config.test.coverage && config.test.coverage.thresholds; if (!thresholds || typeof thresholds !== 'object' || !summary || typeof summary !== 'object' || !summary.total || typeof summary.total !== 'object') fail('summary or threshold schema unrecognised'); const metricNames=['branches','functions','lines','statements']; const metrics=metricNames.filter((metric)=>Object.prototype.hasOwnProperty.call(thresholds, metric)); if (metrics.length === 0) fail('no threshold metrics configured'); let shortfall=false; for (const metric of metrics) { const threshold=thresholds[metric]; const pct=summary.total[metric] && summary.total[metric].pct; if (typeof threshold !== 'number' || !Number.isFinite(threshold) || typeof pct !== 'number' || !Number.isFinite(pct)) fail('missing or non-numeric value for '+metric); console.log('COVERAGE: '+metric+' '+pct+'% (threshold '+threshold+'%)'); if (pct < threshold) shortfall=true; } if (shortfall) { console.error('COVERAGE VERDICT: thresholds not met'); process.exit(2); } console.log('COVERAGE VERDICT: thresholds met'); }).catch((error)=>fail('could not load vitest config: '+error.message));\"", "test:node": "mkdir -p coverage || exit 1; rm -f coverage/node-test.tap || exit 1; node --import tsx --test --test-reporter=tap src/runtime/pi/appControlDeliveryMetadata.test.ts > coverage/node-test.tap 2>&1; node_status=$?; cat coverage/node-test.tap; cat_status=$?; npm run test:node-verdict; verdict_status=$?; if [ \"$verdict_status\" -eq 2 ]; then exit 2; fi; if [ \"$verdict_status\" -ne 0 ]; then exit 1; fi; if [ \"$cat_status\" -ne 0 ]; then echo 'NODE VERDICT: could not read TAP output' >&2; exit 1; fi; if [ \"$node_status\" -ne 0 ]; then echo \"NODE VERDICT: node:test runner exited $node_status despite a passing TAP summary\" >&2; exit 1; fi; exit 0", - "test:node-verdict": "node -e \"const fs=require('fs'); const text=fs.readFileSync('coverage/node-test.tap', 'utf8'); const tests=text.match(/^# tests ([0-9]+)\\r?$/m); const suites=text.match(/^# suites ([0-9]+)\\r?$/m); const passed=text.match(/^# pass ([0-9]+)\\r?$/m); const failed=text.match(/^# fail ([0-9]+)\\r?$/m); const skipped=text.match(/^# skipped ([0-9]+)\\r?$/m); const todo=text.match(/^# todo ([0-9]+)\\r?$/m); const cancelled=text.match(/^# cancelled ([0-9]+)\\r?$/m); if (!tests || !suites || !passed || !failed || !skipped || !todo || !cancelled) { console.error('NODE VERDICT: TAP summary unrecognised'); process.exit(1); } const testCount=Number(tests[1]); const suiteCount=Number(suites[1]); const passCount=Number(passed[1]); const failCount=Number(failed[1]); const skippedCount=Number(skipped[1]); const todoCount=Number(todo[1]); const cancelledCount=Number(cancelled[1]); if (failCount > 0) { console.error('NODE VERDICT: test failures found (tests='+testCount+', failed='+failCount+')'); process.exit(2); } if (cancelledCount > 0) { console.error('NODE VERDICT: incomplete run (cancelled='+cancelledCount+')'); process.exit(1); } if (passCount + skippedCount + todoCount + cancelledCount !== testCount) { console.error('NODE VERDICT: TAP counts do not reconcile (tests='+testCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', cancelled='+cancelledCount+')'); process.exit(1); } if (testCount < 10) { console.error('NODE VERDICT: expected at least 10 node:test cases, found '+testCount+' \u2014 cases were removed or the file was gutted'); process.exit(1); } if (passCount < 10) { console.error('NODE VERDICT: expected at least 10 passing node:test cases, found '+passCount+' (skipped='+skippedCount+', todo='+todoCount+')'); process.exit(1); } if (suiteCount < 1) { console.error('NODE VERDICT: expected at least 1 node:test suite, found '+suiteCount); process.exit(1); } console.log('NODE VERDICT: tests passed (tests='+testCount+', suites='+suiteCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', failed=0)');\"", + "test:node-verdict": "node -e \"const fs=require('fs'); const text=fs.readFileSync('coverage/node-test.tap', 'utf8'); const tests=text.match(/^# tests ([0-9]+)\\r?$/m); const suites=text.match(/^# suites ([0-9]+)\\r?$/m); const passed=text.match(/^# pass ([0-9]+)\\r?$/m); const failed=text.match(/^# fail ([0-9]+)\\r?$/m); const skipped=text.match(/^# skipped ([0-9]+)\\r?$/m); const todo=text.match(/^# todo ([0-9]+)\\r?$/m); const cancelled=text.match(/^# cancelled ([0-9]+)\\r?$/m); if (!tests || !suites || !passed || !failed || !skipped || !todo || !cancelled) { console.error('NODE VERDICT: TAP summary unrecognised'); process.exit(1); } const testCount=Number(tests[1]); const suiteCount=Number(suites[1]); const passCount=Number(passed[1]); const failCount=Number(failed[1]); const skippedCount=Number(skipped[1]); const todoCount=Number(todo[1]); const cancelledCount=Number(cancelled[1]); if (failCount > 0) { console.error('NODE VERDICT: test failures found (tests='+testCount+', failed='+failCount+')'); process.exit(2); } if (cancelledCount > 0) { console.error('NODE VERDICT: incomplete run (cancelled='+cancelledCount+')'); process.exit(1); } if (passCount + skippedCount + todoCount + cancelledCount !== testCount) { console.error('NODE VERDICT: TAP counts do not reconcile (tests='+testCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', cancelled='+cancelledCount+')'); process.exit(1); } if (testCount < 10) { console.error('NODE VERDICT: expected at least 10 node:test cases, found '+testCount+' — cases were removed or the file was gutted'); process.exit(1); } if (passCount < 10) { console.error('NODE VERDICT: expected at least 10 passing node:test cases, found '+passCount+' (skipped='+skippedCount+', todo='+todoCount+')'); process.exit(1); } if (suiteCount < 1) { console.error('NODE VERDICT: expected at least 1 node:test suite, found '+suiteCount); process.exit(1); } console.log('NODE VERDICT: tests passed (tests='+testCount+', suites='+suiteCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', failed=0)');\"", "verify:training-image-modes": "tsx scripts/verify-training-image-modes.ts", "test:product-state-volume": "node --experimental-strip-types scripts/product-state-volume-integration.test.ts", "test:boundaries": "vitest run --coverage.enabled=false src/ownership/rootOwnershipBoundary.test.ts src/deployment/providerRuntimeBoundary.test.ts src/ownership/mnemePublicImportBoundary.test.ts src/target/containerBundleArchive.test.ts", From 0562dd728a66e43d800c52ace886c47407f16674 Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 23 Oct 2026 12:55:25 +0200 Subject: [PATCH 3/4] chore: preserve package script escaping --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e475b6aa..30910059 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "test:verdict": "node -e \"const fs=require('fs'); const file='coverage/vitest-results.json'; if (!fs.existsSync(file)) { console.error('VERDICT: results file missing'); process.exit(1); } let result; try { result=JSON.parse(fs.readFileSync(file, 'utf8')); } catch { console.error('VERDICT: results file unparseable'); process.exit(1); } const total=result.numTotalTests; const totalSuites=result.numTotalTestSuites; const passedTests=result.numPassedTests; const failedTests=result.numFailedTests; const pendingTests=result.numPendingTests; const todoTests=result.numTodoTests; const failedSuites=result.numFailedTestSuites; if (typeof result.success !== 'boolean' || !Number.isInteger(passedTests) || passedTests < 0 || !Number.isInteger(failedTests) || failedTests < 0 || !Number.isInteger(pendingTests) || pendingTests < 0 || !Number.isInteger(todoTests) || todoTests < 0 || !Number.isInteger(failedSuites) || failedSuites < 0 || !Number.isInteger(totalSuites) || totalSuites < 0) { console.error('VERDICT: results schema unrecognised'); process.exit(1); } if (!Number.isInteger(total) || total <= 0) { console.error('VERDICT: no tests found'); process.exit(1); } if (passedTests + failedTests + pendingTests + todoTests !== total) { console.error('VERDICT: results counters do not reconcile (tests='+total+', passed='+passedTests+', failed='+failedTests+', skipped='+pendingTests+', todo='+todoTests+')'); process.exit(1); } const failed=result.success === false || failedTests > 0 || failedSuites > 0; if (failed) { console.error('VERDICT: test failures found (tests='+failedTests+', suites='+failedSuites+', success='+result.success+')'); process.exit(2); } if (passedTests === 0) { console.error('VERDICT: no tests actually ran (skipped='+pendingTests+', todo='+todoTests+')'); process.exit(1); } console.log('VERDICT: tests passed (tests='+total+', suites='+totalSuites+', passed='+passedTests+', skipped='+pendingTests+', todo='+todoTests+')');\"", "test:coverage-verdict": "node --import tsx -e \"import fs from 'node:fs'; const file='coverage/coverage-summary.json'; const fail=(message)=>{ console.error('COVERAGE VERDICT: '+message); process.exit(1); }; let summary; try { summary=JSON.parse(fs.readFileSync(file, 'utf8')); } catch (error) { fail(error && error.code === 'ENOENT' ? 'summary file missing' : 'summary file unparseable'); } import('./vitest.config.ts').then(({default:config})=>{ const thresholds=config.test && config.test.coverage && config.test.coverage.thresholds; if (!thresholds || typeof thresholds !== 'object' || !summary || typeof summary !== 'object' || !summary.total || typeof summary.total !== 'object') fail('summary or threshold schema unrecognised'); const metricNames=['branches','functions','lines','statements']; const metrics=metricNames.filter((metric)=>Object.prototype.hasOwnProperty.call(thresholds, metric)); if (metrics.length === 0) fail('no threshold metrics configured'); let shortfall=false; for (const metric of metrics) { const threshold=thresholds[metric]; const pct=summary.total[metric] && summary.total[metric].pct; if (typeof threshold !== 'number' || !Number.isFinite(threshold) || typeof pct !== 'number' || !Number.isFinite(pct)) fail('missing or non-numeric value for '+metric); console.log('COVERAGE: '+metric+' '+pct+'% (threshold '+threshold+'%)'); if (pct < threshold) shortfall=true; } if (shortfall) { console.error('COVERAGE VERDICT: thresholds not met'); process.exit(2); } console.log('COVERAGE VERDICT: thresholds met'); }).catch((error)=>fail('could not load vitest config: '+error.message));\"", "test:node": "mkdir -p coverage || exit 1; rm -f coverage/node-test.tap || exit 1; node --import tsx --test --test-reporter=tap src/runtime/pi/appControlDeliveryMetadata.test.ts > coverage/node-test.tap 2>&1; node_status=$?; cat coverage/node-test.tap; cat_status=$?; npm run test:node-verdict; verdict_status=$?; if [ \"$verdict_status\" -eq 2 ]; then exit 2; fi; if [ \"$verdict_status\" -ne 0 ]; then exit 1; fi; if [ \"$cat_status\" -ne 0 ]; then echo 'NODE VERDICT: could not read TAP output' >&2; exit 1; fi; if [ \"$node_status\" -ne 0 ]; then echo \"NODE VERDICT: node:test runner exited $node_status despite a passing TAP summary\" >&2; exit 1; fi; exit 0", - "test:node-verdict": "node -e \"const fs=require('fs'); const text=fs.readFileSync('coverage/node-test.tap', 'utf8'); const tests=text.match(/^# tests ([0-9]+)\\r?$/m); const suites=text.match(/^# suites ([0-9]+)\\r?$/m); const passed=text.match(/^# pass ([0-9]+)\\r?$/m); const failed=text.match(/^# fail ([0-9]+)\\r?$/m); const skipped=text.match(/^# skipped ([0-9]+)\\r?$/m); const todo=text.match(/^# todo ([0-9]+)\\r?$/m); const cancelled=text.match(/^# cancelled ([0-9]+)\\r?$/m); if (!tests || !suites || !passed || !failed || !skipped || !todo || !cancelled) { console.error('NODE VERDICT: TAP summary unrecognised'); process.exit(1); } const testCount=Number(tests[1]); const suiteCount=Number(suites[1]); const passCount=Number(passed[1]); const failCount=Number(failed[1]); const skippedCount=Number(skipped[1]); const todoCount=Number(todo[1]); const cancelledCount=Number(cancelled[1]); if (failCount > 0) { console.error('NODE VERDICT: test failures found (tests='+testCount+', failed='+failCount+')'); process.exit(2); } if (cancelledCount > 0) { console.error('NODE VERDICT: incomplete run (cancelled='+cancelledCount+')'); process.exit(1); } if (passCount + skippedCount + todoCount + cancelledCount !== testCount) { console.error('NODE VERDICT: TAP counts do not reconcile (tests='+testCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', cancelled='+cancelledCount+')'); process.exit(1); } if (testCount < 10) { console.error('NODE VERDICT: expected at least 10 node:test cases, found '+testCount+' — cases were removed or the file was gutted'); process.exit(1); } if (passCount < 10) { console.error('NODE VERDICT: expected at least 10 passing node:test cases, found '+passCount+' (skipped='+skippedCount+', todo='+todoCount+')'); process.exit(1); } if (suiteCount < 1) { console.error('NODE VERDICT: expected at least 1 node:test suite, found '+suiteCount); process.exit(1); } console.log('NODE VERDICT: tests passed (tests='+testCount+', suites='+suiteCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', failed=0)');\"", + "test:node-verdict": "node -e \"const fs=require('fs'); const text=fs.readFileSync('coverage/node-test.tap', 'utf8'); const tests=text.match(/^# tests ([0-9]+)\\r?$/m); const suites=text.match(/^# suites ([0-9]+)\\r?$/m); const passed=text.match(/^# pass ([0-9]+)\\r?$/m); const failed=text.match(/^# fail ([0-9]+)\\r?$/m); const skipped=text.match(/^# skipped ([0-9]+)\\r?$/m); const todo=text.match(/^# todo ([0-9]+)\\r?$/m); const cancelled=text.match(/^# cancelled ([0-9]+)\\r?$/m); if (!tests || !suites || !passed || !failed || !skipped || !todo || !cancelled) { console.error('NODE VERDICT: TAP summary unrecognised'); process.exit(1); } const testCount=Number(tests[1]); const suiteCount=Number(suites[1]); const passCount=Number(passed[1]); const failCount=Number(failed[1]); const skippedCount=Number(skipped[1]); const todoCount=Number(todo[1]); const cancelledCount=Number(cancelled[1]); if (failCount > 0) { console.error('NODE VERDICT: test failures found (tests='+testCount+', failed='+failCount+')'); process.exit(2); } if (cancelledCount > 0) { console.error('NODE VERDICT: incomplete run (cancelled='+cancelledCount+')'); process.exit(1); } if (passCount + skippedCount + todoCount + cancelledCount !== testCount) { console.error('NODE VERDICT: TAP counts do not reconcile (tests='+testCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', cancelled='+cancelledCount+')'); process.exit(1); } if (testCount < 10) { console.error('NODE VERDICT: expected at least 10 node:test cases, found '+testCount+' \u2014 cases were removed or the file was gutted'); process.exit(1); } if (passCount < 10) { console.error('NODE VERDICT: expected at least 10 passing node:test cases, found '+passCount+' (skipped='+skippedCount+', todo='+todoCount+')'); process.exit(1); } if (suiteCount < 1) { console.error('NODE VERDICT: expected at least 1 node:test suite, found '+suiteCount); process.exit(1); } console.log('NODE VERDICT: tests passed (tests='+testCount+', suites='+suiteCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', failed=0)');\"", "verify:training-image-modes": "tsx scripts/verify-training-image-modes.ts", "test:product-state-volume": "node --experimental-strip-types scripts/product-state-volume-integration.test.ts", "test:boundaries": "vitest run --coverage.enabled=false src/ownership/rootOwnershipBoundary.test.ts src/deployment/providerRuntimeBoundary.test.ts src/ownership/mnemePublicImportBoundary.test.ts src/target/containerBundleArchive.test.ts", From 588aee359038a210418636e30e32ebc82dd4c2ee Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 23 Oct 2026 12:56:15 +0200 Subject: [PATCH 4/4] test: require fixture pin resolution before checkout --- scripts/native-helper-workflows.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/native-helper-workflows.test.ts b/scripts/native-helper-workflows.test.ts index d9af8057..f944751c 100644 --- a/scripts/native-helper-workflows.test.ts +++ b/scripts/native-helper-workflows.test.ts @@ -53,6 +53,11 @@ test("PR/main runs the same offline source provenance gate and inputs as release } assert.equal(job.steps.find((step) => step.name === "Check out Daimon provenance fixture")?.with?.ref, "${{ steps.daimon-provenance.outputs.ref }}"); + for (const candidate of [job, release.jobs.publish]) { + const resolve = candidate.steps.findIndex((step) => step.name === "Resolve exact Daimon provenance fixture revision"); + const checkout = candidate.steps.findIndex((step) => step.name === "Check out Daimon provenance fixture"); + assert.ok(resolve >= 0 && checkout > resolve, "resolve the pin before checkout, or an empty ref follows upstream HEAD"); + } const gate = job.steps.findIndex((step) => step.run?.includes("npm run test:source-provenance-docker")); const build = job.steps.findIndex((step) => step.run?.includes("npm run build")); assert.ok(build >= 0 && gate > build, "the gate requires compiled materializer output");