From 05612d480781dc7962604f8e307f27ca577062e8 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Mon, 21 Sep 2026 00:27:57 +0200 Subject: [PATCH 1/2] test(pre-open): bind artifacts to generator identity --- tests/unit/pre-open-gate-compact.test.mjs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/unit/pre-open-gate-compact.test.mjs b/tests/unit/pre-open-gate-compact.test.mjs index 422914e9..2b00e5eb 100644 --- a/tests/unit/pre-open-gate-compact.test.mjs +++ b/tests/unit/pre-open-gate-compact.test.mjs @@ -1,7 +1,10 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { compactPreOpenGateReport } from "../../scripts/pre-open-gate.mjs"; +import { + compactPreOpenGateReport, + preOpenGeneratorIdentity, +} from "../../scripts/pre-open-gate.mjs"; const HEAD = "b".repeat(40); @@ -81,6 +84,17 @@ test("compact pre-open report exposes only authoritative remaining obligations", assert.equal(Object.hasOwn(compact.evidenceRequirements.lenses.silent_failures, "status"), false); }); +test("pre-open output carries generator version and implementation digests", () => { + const generator = preOpenGeneratorIdentity(); + assert.match(generator.githubDeliveryVersion, /^\d+\.\d+\.\d+$/); + assert.match(generator.preOpenGateDigest, /^sha256:[a-f0-9]{64}$/); + assert.match(generator.reviewScopeDigest, /^sha256:[a-f0-9]{64}$/); + + const report = blockedReport(); + report.generator = generator; + assert.deepEqual(compactPreOpenGateReport(report).generator, generator); +}); + test("ready compact report has one unambiguous publication disposition", () => { const report = blockedReport(); report.decision = "ready"; From 2d7d1c430d323eac80735d8e674c92fd7505a8ee Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Mon, 21 Sep 2026 00:28:00 +0200 Subject: [PATCH 2/2] fix(pre-open): stamp generator provenance --- scripts/pre-open-gate.mjs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/scripts/pre-open-gate.mjs b/scripts/pre-open-gate.mjs index ea9f347b..1f7244ec 100644 --- a/scripts/pre-open-gate.mjs +++ b/scripts/pre-open-gate.mjs @@ -1,5 +1,7 @@ #!/usr/bin/env node +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { isDirectInvocation } from "./lib/direct-invocation.mjs"; @@ -27,6 +29,26 @@ function usageError() { throw new Error("Usage: node scripts/pre-open-gate.mjs OWNER/REPO BASE_REF HEAD_REF [--compact] [--output FILE] [--evidence-file FILE] [--hygiene-file FILE] [--checkpoint FILE] [--remote REMOTE] | --self-test"); } +function sha256File(url) { + return `sha256:${createHash("sha256").update(readFileSync(url)).digest("hex")}`; +} + +export function preOpenGeneratorIdentity() { + let githubDeliveryVersion = null; + try { + githubDeliveryVersion = String( + JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))?.version || "", + ).trim() || null; + } catch { + githubDeliveryVersion = null; + } + return { + githubDeliveryVersion, + preOpenGateDigest: sha256File(new URL(import.meta.url)), + reviewScopeDigest: sha256File(new URL("./lib/review-scope.mjs", import.meta.url)), + }; +} + function probeCoverage(plan, evidence) { const requiredProbes = plan.requiredProbes || []; const errors = validateProbeEvidence(evidence?.probes ?? {}, { @@ -108,6 +130,7 @@ function report({ repo, baseRef, headRef, baseRefOid, headRefOid, diffIdentity, return { schemaVersion: 1, kind: "github-delivery/pre-open-gate", + generator: preOpenGeneratorIdentity(), repo, baseRef, headRef, @@ -212,6 +235,7 @@ export function compactPreOpenGateReport(result) { return { schemaVersion: 1, kind: "github-delivery/pre-open-gate-summary", + generator: result?.generator || preOpenGeneratorIdentity(), repo: result?.repo, baseRef: result?.baseRef, headRef: result?.headRef,