import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { chmodSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { __testing } from "../extensions/gentle-ai.ts"; import { EXTERNAL_RELEASE_EVIDENCE, GATE_RESULT, GATE_TARGET_KIND, PUSH_UPDATE_KIND, RELEASE_FAST_PATH_PROTECTED_REF, REVIEW_MODE, REVIEW_TRANSITION, TERMINAL_STATE, ReviewTransactionStore, type AuthoritativeReceiptV1, canonicalHash, createReceiptForState, createReviewState, evaluateGateTarget, evaluateReleaseFastPathV1, recheckReleaseFastPathRemoteHeadV1, resolveConfiguredPushDestinationV1, validateReviewGate, type GateTargetV1, type GhCommandRunnerV1, type ReceiptBodyV1, type ReceiptEnvelopeV1, type ReleaseFastPathEvidenceV1, type ReviewBudgetV1, type ReviewStateV1, } from "../lib/review-transaction.ts"; import { REVIEW_LENS, REVIEW_ROUTE } from "../lib/review-triggers.ts"; import { inheritedUnsafeGitEnvironmentKeys } from "../lib/review-repository.ts"; import { qualifiedReviewLockPlatform, testSnapshot } from "./review-test-fixtures.ts"; interface GateRepository { repository: string; remote: string; remotePath: string; baseTree: string; finalTree: string; changedTree: string; baseCommit: string; finalCommit: string; tagObject: string; } test("unsafe publication Git environment matching preserves actual mixed-case keys", () => { assert.deepEqual( inheritedUnsafeGitEnvironmentKeys({ git_config_count: "1", Git_Config_Key_0: "remote.origin.pushurl", git_config_value_0: "attacker", GIT_SSH_COMMAND: "unsafe", GIT_ASKPASS: "safe", }), ["GIT_SSH_COMMAND", "Git_Config_Key_0", "git_config_count", "git_config_value_0"], ); }); function createGateRepository(t: test.TestContext): GateRepository { const parent = mkdtempSync(join(tmpdir(), "gentle-pi-gate-repo-")); const repository = join(parent, "repo"); mkdirSync(repository); t.after(() => rmSync(parent, { recursive: true, force: true })); const git = (...args: string[]): string => execFileSync("git", args, { cwd: repository, encoding: "utf8" }).trim(); git("init", "-b", "main"); writeFileSync(join(repository, "app.ts"), "export const value = 1;\n"); git("add", "."); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "commit", "-m", "base"); const baseCommit = git("rev-parse", "HEAD"); const baseTree = git("rev-parse", "HEAD^{tree}"); git("branch", "base", baseCommit); writeFileSync(join(repository, "app.ts"), "export const value = 2;\n"); git("add", "."); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "commit", "-m", "final"); const finalCommit = git("rev-parse", "HEAD"); const finalTree = git("rev-parse", "HEAD^{tree}"); git("branch", "final", finalCommit); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "tag", "-a", "v1.2.3", "-m", "release", finalCommit); const tagObject = git("rev-parse", "refs/tags/v1.2.3^{object}"); writeFileSync(join(repository, "app.ts"), "export const value = 3;\n"); git("add", "."); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "commit", "-m", "changed"); const changedTree = git("rev-parse", "HEAD^{tree}"); const remote = join(parent, "remote.git"); execFileSync("git", ["clone", "--bare", repository, remote], { cwd: parent, stdio: ["ignore", "pipe", "pipe"], }); execFileSync("git", ["--git-dir", remote, "update-ref", "refs/heads/main", baseCommit], { cwd: parent, }); git("remote", "add", "origin", remote); git("update-ref", "refs/heads/main", finalCommit); return { repository, remote: "origin", remotePath: remote, baseTree, finalTree, changedTree, baseCommit, finalCommit, tagObject, }; } function budget(overrides: Partial = {}): ReviewBudgetV1 { return { review_batches: 1, review_actors: 0, refuter_batches: 1, fix_batches: 1, validator_runs: 1, final_verifications: 1, judgment_rounds: 0, judge_runs: 0, ...overrides, }; } function initialState(repository: GateRepository, lineageId = "approved-lineage"): ReviewStateV1 { return createReviewState({ lineageId, mode: REVIEW_MODE.ORDINARY, snapshot: testSnapshot({ baseTree: repository.baseTree, completeTree: repository.finalTree, route: REVIEW_ROUTE.STANDARD, lenses: [REVIEW_LENS.READABILITY], }), evidenceHash: "b".repeat(64), budget: budget({ review_actors: 1 }), }); } function receiptFor(state: ReviewStateV1): ReceiptEnvelopeV1 { return createReceiptForState(state); } function temporaryAuthority(t: test.TestContext): GateRepository & { store: ReviewTransactionStore; receipt: ReceiptEnvelopeV1; authoritativeReceipt: AuthoritativeReceiptV1; } { const repository = createGateRepository(t); const store = ReviewTransactionStore.forRepository(repository.repository, { mutationLockPlatform: qualifiedReviewLockPlatform() }); store.create(initialState(repository), "start-approved-lineage"); store.runReducerOperation({ lineageId: "approved-lineage", transition: REVIEW_TRANSITION.ORDINARY_DISCOVERY, idempotencyKey: "discover", input: { rows: [] }, }); store.runReducerOperation({ lineageId: "approved-lineage", transition: REVIEW_TRANSITION.ORDINARY_EVIDENCE, idempotencyKey: "evidence", input: { deterministicResults: [] }, }); store.runReducerOperation({ lineageId: "approved-lineage", transition: REVIEW_TRANSITION.ORDINARY_FINAL_VERIFICATION, idempotencyKey: "verify", input: { passed: true }, }); const state = store.read("approved-lineage"); return { ...repository, store, receipt: receiptFor(state), authoritativeReceipt: store.createAuthoritativeReceipt("approved-lineage") }; } test("lifecycle command classification identifies gates but never runs review routing", () => { assert.equal(__testing.classifyReviewEvent("git commit -m fix"), "pre-commit"); assert.equal(__testing.classifyReviewEvent("git -C /repo push origin main"), "pre-push"); assert.equal(__testing.classifyReviewEvent("gh pr create --draft"), "pre-pr"); assert.equal(__testing.classifyReviewEvent("gh release create v1.2.3"), "pre-release"); assert.equal(__testing.classifyReviewEvent("git status"), null); }); test("unbranded receipts are rejected before lifecycle gate evaluation", (t) => { const { repository, finalTree, store, receipt } = temporaryAuthority(t); execFileSync("git", ["read-tree", finalTree], { cwd: repository }); assert.throws(() => validateReviewGate({ store, receipt, target: { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: finalTree }, repositoryCwd: repository, idempotencyKey: "unbranded-gate", scopeBudget: budget(), }), /branded authoritative receipt/i); }); test("authoritative receipts cannot be validated through another repository store", (t) => { const authority = temporaryAuthority(t); const other = temporaryAuthority(t); assert.throws(() => validateReviewGate({ store: other.store, receipt: authority.authoritativeReceipt, target: { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: authority.finalTree }, repositoryCwd: authority.repository, idempotencyKey: "cross-store-gate", scopeBudget: budget(), }), /authority/i); }); test("exact intended commit target allows with zero actors and journal replay is stable", (t) => { const { repository, finalTree, store, receipt, authoritativeReceipt } = temporaryAuthority(t); execFileSync("git", ["read-tree", finalTree], { cwd: repository }); const target = { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: finalTree, } as const; const first = validateReviewGate({ store, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "gate-commit-1", scopeBudget: budget(), }); assert.equal(first.status, GATE_RESULT.ALLOW); assert.equal(first.actor_count, 0); assert.equal(first.target_hash, canonicalHash(target)); const replay = validateReviewGate({ store: ReviewTransactionStore.forRepository(repository, { mutationLockPlatform: qualifiedReviewLockPlatform() }), receipt: store.createAuthoritativeReceipt(receipt.body.lineage_id), target, repositoryCwd: repository, idempotencyKey: "gate-commit-1", scopeBudget: budget({ fix_batches: 99 }), }); assert.deepEqual(replay, first); assert.equal(store.read(receipt.body.lineage_id).revision, 4); }); test("exact gate retries replay before stale receipt binding while new requests still deny", (t) => { const { repository, finalTree, store, authoritativeReceipt } = temporaryAuthority(t); execFileSync("git", ["read-tree", finalTree], { cwd: repository }); const target = { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: finalTree } as const; const first = validateReviewGate({ store, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "stale-retry", scopeBudget: budget() }); const replay = validateReviewGate({ store, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "stale-retry", scopeBudget: budget() }); assert.deepEqual(replay, first); assert.throws(() => validateReviewGate({ store, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "stale-new-request", scopeBudget: budget() }), /stale|unbound/i); }); test("intended commit target denies when the actual staged tree drifted after approval", (t) => { const { repository, finalTree, changedTree, receipt } = temporaryAuthority(t); assert.equal( execFileSync("git", ["write-tree"], { cwd: repository, encoding: "utf8" }).trim(), changedTree, ); const result = evaluateGateTarget( receipt, { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: finalTree, }, repository, ); assert.equal(result.status, GATE_RESULT.DENY); assert.match(result.reason, /staged tree.*intended commit tree/i); }); test("changed exact target returns one deterministic child with a non-refreshing budget", (t) => { const { repository, changedTree, store, receipt, authoritativeReceipt } = temporaryAuthority(t); const target = { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: changedTree, } as const; const first = validateReviewGate({ store, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "scope-1", scopeBudget: budget({ review_actors: 4 }), }); assert.equal(first.status, GATE_RESULT.SCOPE_CHANGED); assert.equal(first.actor_count, 0); assert.equal(first.child_claim?.target_tree, changedTree); assert.equal(first.child_claim?.budget.review_actors, 4); const replayUnderAnotherGateKey = validateReviewGate({ store, receipt: store.createAuthoritativeReceipt(receipt.body.lineage_id), target, repositoryCwd: repository, idempotencyKey: "scope-2", scopeBudget: budget({ review_actors: 99, fix_batches: 99 }), }); assert.equal( replayUnderAnotherGateKey.child_claim?.child_lineage_id, first.child_claim?.child_lineage_id, ); assert.equal(replayUnderAnotherGateKey.child_claim?.budget.review_actors, 4); assert.equal(replayUnderAnotherGateKey.child_claim?.budget.fix_batches, 1); }); test("push gate allows normal same-name updates while preserving exact-old and create rules", (t) => { const authority = temporaryAuthority(t); const { repository, remote, baseTree, finalTree, baseCommit, finalCommit, receipt } = authority; const destination = resolveConfiguredPushDestinationV1(repository, remote); const target = { kind: GATE_TARGET_KIND.PUSH, remote, destination_id: destination.destination_id, updates: [ { kind: PUSH_UPDATE_KIND.CREATE, source_ref: "refs/heads/final", destination_ref: "refs/heads/feature", old_object: null, old_peeled_commit: null, old_tree: null, new_object: finalCommit, new_peeled_commit: finalCommit, new_tree: finalTree, }, { kind: PUSH_UPDATE_KIND.UPDATE, source_ref: "refs/heads/main", destination_ref: "refs/heads/main", old_object: baseCommit, old_peeled_commit: baseCommit, old_tree: baseTree, new_object: finalCommit, new_peeled_commit: finalCommit, new_tree: finalTree, }, ], } as const; const allowed = evaluateGateTarget(receipt, target, repository); assert.equal(allowed.status, GATE_RESULT.ALLOW, allowed.reason); assert.equal(allowed.actor_count, 0); const driftedUpdate = { kind: GATE_TARGET_KIND.PUSH, remote, updates: [{ ...target.updates[1], old_object: finalCommit, old_peeled_commit: finalCommit, old_tree: finalTree, }], } as const; assert.equal( evaluateGateTarget(receipt, driftedUpdate, repository).status, GATE_RESULT.DENY, ); const createOverExisting = { kind: GATE_TARGET_KIND.PUSH, remote, updates: [{ ...target.updates[0], destination_ref: "refs/heads/final" }], } as const; assert.equal( evaluateGateTarget(receipt, createOverExisting, repository).status, GATE_RESULT.DENY, ); const reversed = { ...target, updates: [...target.updates].reverse() }; assert.equal(evaluateGateTarget(receipt, reversed, repository).status, GATE_RESULT.DENY); const deletion = { kind: GATE_TARGET_KIND.PUSH, updates: [{ kind: "delete", destination_ref: "refs/heads/main" }], } as unknown as GateTargetV1; assert.equal(evaluateGateTarget(receipt, deletion, repository).status, GATE_RESULT.DENY); assert.equal( evaluateGateTarget( receipt, { ...target, updates: [{ ...target.updates[0], new_peeled_commit: baseCommit }, target.updates[1]], }, repository, ).status, GATE_RESULT.DENY, ); assert.equal( evaluateGateTarget(receipt, { ...target, remote: "missing-remote" }, repository).status, GATE_RESULT.DENY, ); const changedDestination = join(authority.repository, "changed-destination.git"); execFileSync("git", ["init", "--bare", changedDestination], { stdio: ["ignore", "pipe", "pipe"] }); execFileSync("git", ["remote", "set-url", "--add", "--push", remote, changedDestination], { cwd: repository }); assert.equal( evaluateGateTarget(receipt, target, repository).status, GATE_RESULT.DENY, "a target bound to the former publication destination must fail closed", ); }); test("push gate treats only successful empty output as absent and fails closed on probe failures", async (t) => { const authority = temporaryAuthority(t); const { repository, remote, finalTree, finalCommit, receipt } = authority; const destination = resolveConfiguredPushDestinationV1(repository, remote); const target = { kind: GATE_TARGET_KIND.PUSH, remote, destination_id: destination.destination_id, updates: [{ kind: PUSH_UPDATE_KIND.CREATE, source_ref: "refs/heads/final", destination_ref: "refs/heads/feature", old_object: null, old_peeled_commit: null, old_tree: null, new_object: finalCommit, new_peeled_commit: finalCommit, new_tree: finalTree, }], } as const; assert.equal(evaluateGateTarget(receipt, target, repository).status, GATE_RESULT.ALLOW); const realGit = execFileSync("which", ["git"], { encoding: "utf8" }).trim(); const originalPath = process.env.PATH; for (const [name, body] of [ ["nonzero", "exit 1"], ["malformed", "printf 'not-a-valid-row\\n'; exit 0"], ["ambiguous", `printf '${finalCommit}\\trefs/heads/feature\\n${finalCommit}\\trefs/heads/feature\\n'; exit 0`], ] as const) { await t.test(name, () => { const bin = join(authority.repository, `fake-git-${name}`); mkdirSync(bin); const fakeGit = join(bin, "git"); writeFileSync(fakeGit, `#!/bin/sh\nif [ "$1" = "ls-remote" ]; then ${body}; fi\nexec "${realGit}" "$@"\n`); chmodSync(fakeGit, 0o755); process.env.PATH = `${bin}:${originalPath ?? ""}`; try { const result = evaluateGateTarget(receipt, target, repository); assert.equal(result.status, GATE_RESULT.DENY, result.reason); } finally { process.env.PATH = originalPath; } }); } }); test("push CREATE rejects unreviewed ancestor history", (t) => { const authority = temporaryAuthority(t); const { repository, remote, baseTree, finalTree, baseCommit, receipt } = authority; const git = (...args: string[]): string => execFileSync("git", args, { cwd: repository, encoding: "utf8" }).trim(); git("checkout", "--force", "--detach", baseCommit); writeFileSync(join(repository, "ancestor.ts"), "export const unreviewed = true;\n"); git("add", "."); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "commit", "-m", "unreviewed ancestor"); const unreviewedParent = git("rev-parse", "HEAD"); git("read-tree", finalTree); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "commit", "-m", "reviewed tree"); const newCommit = git("rev-parse", "HEAD"); git("branch", "unsafe-first-push", newCommit); execFileSync("git", ["--git-dir", authority.remotePath, "fetch", repository, `${unreviewedParent}:refs/heads/unreviewed`], { stdio: ["ignore", "pipe", "pipe"], }); const destination = resolveConfiguredPushDestinationV1(repository, remote); const target = { kind: GATE_TARGET_KIND.PUSH, remote, destination_id: destination.destination_id, updates: [{ kind: PUSH_UPDATE_KIND.CREATE, source_ref: "refs/heads/unsafe-first-push", destination_ref: "refs/heads/unsafe-first-push", old_object: null, old_peeled_commit: null, old_tree: null, new_object: newCommit, new_peeled_commit: newCommit, new_tree: finalTree, }], } as const; const result = evaluateGateTarget(receipt, target, repository); assert.equal(result.status, GATE_RESULT.DENY, result.reason); assert.match(result.reason, /parent tree|base tree/i); assert.notEqual(git("rev-parse", `${unreviewedParent}^{tree}`), baseTree); }); test("PR and release gates resolve exact refs, commits, tag objects, peels, and trees", (t) => { const authority = temporaryAuthority(t); const { repository, baseTree, finalTree, changedTree, baseCommit, finalCommit, receipt } = authority; const pullRequest = { kind: GATE_TARGET_KIND.PULL_REQUEST, base_ref: "refs/heads/base", base_commit: baseCommit, base_tree: baseTree, head_ref: "refs/heads/final", head_commit: finalCommit, head_tree: finalTree, } as const; const pullRequestResult = evaluateGateTarget(receipt, pullRequest, repository); assert.equal(pullRequestResult.status, GATE_RESULT.ALLOW, pullRequestResult.reason); assert.equal( evaluateGateTarget(receipt, { ...pullRequest, base_commit: finalCommit }, repository).status, GATE_RESULT.DENY, ); const release = releaseTarget(authority); const releaseResult = evaluateGateTarget(receipt, release, repository); assert.equal(releaseResult.status, GATE_RESULT.ALLOW, releaseResult.reason); assert.equal( evaluateGateTarget(receipt, { ...release, tree: changedTree }, repository).status, GATE_RESULT.DENY, ); }); test("unsupported, ambiguous, nonexistent, and non-approved targets fail closed", (t) => { const { repository, finalTree, receipt: approved } = temporaryAuthority(t); assert.equal( evaluateGateTarget(approved, { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: "HEAD", }, repository).status, GATE_RESULT.DENY, ); assert.equal( evaluateGateTarget(approved, { kind: "branch", ref: "main" } as unknown as GateTargetV1, repository) .status, GATE_RESULT.DENY, ); const escalated = structuredClone(approved); escalated.body.terminal_state = TERMINAL_STATE.ESCALATED; escalated.receipt_hash = canonicalHash(escalated.body); assert.equal( evaluateGateTarget(escalated, { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: finalTree, }, repository).status, GATE_RESULT.DENY, ); assert.equal( evaluateGateTarget(approved, { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: "f".repeat(40), }, repository).status, GATE_RESULT.DENY, ); }); test("scope child claim and parent gate journal publish atomically across faults", (t) => { const { repository, changedTree, store, receipt, authoritativeReceipt } = temporaryAuthority(t); const before = store.read(receipt.body.lineage_id); let injected = false; const faulty = ReviewTransactionStore.forRepository(repository, { mutationLockPlatform: qualifiedReviewLockPlatform(), faultInjector(point) { if (!injected && point === "before-head-rename") { injected = true; throw new Error("scope publication fault"); } }, }); const target = { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: changedTree, } as const; assert.throws( () => validateReviewGate({ store: faulty, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "scope-fault", scopeBudget: budget({ review_actors: 4 }), }), /scope publication fault/, ); const afterFault = store.read(receipt.body.lineage_id); assert.equal(afterFault.revision, before.revision); assert.deepEqual(afterFault.child_claims ?? [], []); assert.equal(afterFault.request_journal.length, before.request_journal.length); const published = validateReviewGate({ store, receipt: authoritativeReceipt, target, repositoryCwd: repository, idempotencyKey: "scope-fault", scopeBudget: budget({ review_actors: 4 }), }); assert.equal(published.child_claim?.child_lineage_id, canonicalHash({ parent_lineage_id: receipt.body.lineage_id, target_tree: changedTree, })); assert.equal(store.read(receipt.body.lineage_id).child_claims?.length, 1); }); test("receipt gate cannot bypass independent dangerous-command safety", async () => { let safetyCalls = 0; let gateCalls = 0; const safetyBlock = { block: true, reason: "dangerous command denied" }; const result = await __testing.enforceReviewGateAndCommandSafety( "git push origin main", () => { gateCalls += 1; return undefined; }, async () => { safetyCalls += 1; return safetyBlock; }, ); assert.deepEqual(result, safetyBlock); assert.equal(safetyCalls, 1); assert.equal(gateCalls, 0); const gateBlock = { block: true, reason: "exact receipt required" }; const blocked = await __testing.enforceReviewGateAndCommandSafety( "git push origin main", () => { gateCalls += 1; return gateBlock; }, async () => { safetyCalls += 1; return undefined; }, ); assert.deepEqual(blocked, gateBlock); assert.equal(safetyCalls, 2); assert.equal(gateCalls, 1); }); function releaseTarget(repository: GateRepository): GateTargetV1 { return { kind: GATE_TARGET_KIND.RELEASE, tag_ref: "refs/tags/v1.2.3", tag_object: repository.tagObject, peeled_commit: repository.finalCommit, tree: repository.finalTree, }; } function fastPathEvidence( repository: GateRepository, overrides: Partial = {}, ): ReleaseFastPathEvidenceV1 { return { protected_ref: RELEASE_FAST_PATH_PROTECTED_REF, remote: repository.remote, ci: { revision: repository.finalCommit, status: "success" }, external_evidence: EXTERNAL_RELEASE_EVIDENCE.NONE, post_incident: false, ...overrides, }; } function setRemoteMain(repository: GateRepository, commit: string): void { execFileSync("git", ["--git-dir", repository.remotePath, "update-ref", "refs/heads/main", commit]); } // A stub `gh` command runner that only reports success when the exact SHA // under test is present in the invoked arguments — mirroring `gh api // repos/{owner}/{repo}/commits//status` being bound to one exact SHA. function successfulGhCommandRunner(expectedSha: string): GhCommandRunnerV1 { return (args) => ({ status: args.some((arg) => arg.includes(expectedSha)) ? 0 : 1, stdout: "success", }); } test("release fast path proves the immutable origin/main SHA and ignores local branch position and worktree dirtiness", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); // Local publication inputs must be irrelevant: detached HEAD at an older commit plus a dirty worktree. execFileSync("git", ["checkout", "--force", "--detach", repository.baseCommit], { cwd: repository.repository, stdio: ["ignore", "pipe", "pipe"], }); writeFileSync(join(repository.repository, "app.ts"), "export const value = 999; // dirty\n"); const evaluation = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(evaluation.eligible, true, evaluation.reason); assert.equal(evaluation.remote_head, repository.finalCommit); assert.match(evaluation.reason, /immutable origin\/main SHA/i); }); test("release fast path fails closed when the tag does not target the current immutable origin/main SHA", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.baseCommit); const evaluation = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { ci: { revision: repository.baseCommit, status: "success" } }), repositoryCwd: repository.repository, }); assert.equal(evaluation.eligible, false); assert.match(evaluation.reason, /not the current immutable origin\/main SHA/i); }); test("release fast path requires successful required CI bound to the exact origin/main SHA", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); for (const ci of [ { revision: repository.baseCommit, status: "success" }, { revision: repository.finalCommit, status: "failure" }, { revision: repository.finalCommit, status: "pending" }, ]) { const evaluation = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { ci }), repositoryCwd: repository.repository, }); assert.equal(evaluation.eligible, false, JSON.stringify(ci)); assert.match(evaluation.reason, /required CI/i); } }); test("release fast path fails closed on escalating or invalidating fresh risk evidence", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); for (const disposition of [ EXTERNAL_RELEASE_EVIDENCE.ESCALATING, EXTERNAL_RELEASE_EVIDENCE.INVALIDATING, ]) { const evaluation = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { external_evidence: disposition }), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(evaluation.eligible, false, disposition); assert.match(evaluation.reason, /vulnerability, policy, provenance, signing, generated-artifact, or release evidence/i); } }); test("major, post-incident, and unprovable-version releases always require explicit extraordinary review", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); const git = (...args: string[]): string => execFileSync("git", args, { cwd: repository.repository, encoding: "utf8" }).trim(); const postIncident = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { post_incident: true }), repositoryCwd: repository.repository, }); assert.equal(postIncident.eligible, false); assert.match(postIncident.reason, /extraordinary review/i); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "tag", "-a", "v2.0.0", "-m", "major", repository.finalCommit); const majorTagObject = git("rev-parse", "refs/tags/v2.0.0^{object}"); const major = evaluateReleaseFastPathV1({ target: { ...releaseTarget(repository), tag_ref: "refs/tags/v2.0.0", tag_object: majorTagObject }, evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, }); assert.equal(major.eligible, false); assert.match(major.reason, /major releases require explicit extraordinary review/i); git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "tag", "-a", "nightly", "-m", "opaque", repository.finalCommit); const opaqueTagObject = git("rev-parse", "refs/tags/nightly^{object}"); const opaque = evaluateReleaseFastPathV1({ target: { ...releaseTarget(repository), tag_ref: "refs/tags/nightly", tag_object: opaqueTagObject }, evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, }); assert.equal(opaque.eligible, false); assert.match(opaque.reason, /major release cannot be ruled out/i); }); test("release fast path treats vX.0.0 and pre-1.0 v0.Y.0 minor bumps as major-equivalent, while v0.Y.Z patch releases remain eligible", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); const git = (...args: string[]): string => execFileSync("git", args, { cwd: repository.repository, encoding: "utf8" }).trim(); const tagRelease = (name: string): GateTargetV1 => { git("-c", "user.name=Gate Test", "-c", "user.email=gate@example.invalid", "tag", "-a", name, "-m", name, repository.finalCommit); const tagObject = git("rev-parse", `refs/tags/${name}^{object}`); return { ...releaseTarget(repository), tag_ref: `refs/tags/${name}`, tag_object: tagObject }; }; const v100 = evaluateReleaseFastPathV1({ target: tagRelease("v1.0.0"), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(v100.eligible, false, v100.reason); assert.match(v100.reason, /major releases require explicit extraordinary review/i); const v0160 = evaluateReleaseFastPathV1({ target: tagRelease("v0.16.0"), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(v0160.eligible, false, v0160.reason); assert.match(v0160.reason, /major releases require explicit extraordinary review/i); const v0151 = evaluateReleaseFastPathV1({ target: tagRelease("v0.15.1"), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(v0151.eligible, true, v0151.reason); }); test("release fast path applies only to protected main with a provable remote head and exact tag identity", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); const wrongRef = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { protected_ref: "refs/heads/develop" }), repositoryCwd: repository.repository, }); assert.equal(wrongRef.eligible, false); assert.match(wrongRef.reason, /protected refs\/heads\/main/i); execFileSync("git", ["--git-dir", repository.remotePath, "update-ref", "-d", "refs/heads/main"]); const missingRemoteHead = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, }); assert.equal(missingRemoteHead.eligible, false); assert.match(missingRemoteHead.reason, /cannot be proven/i); setRemoteMain(repository, repository.finalCommit); const forgedTag = evaluateReleaseFastPathV1({ target: { ...releaseTarget(repository), tag_object: repository.finalCommit === repository.tagObject ? repository.baseCommit : repository.finalCommit }, evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, }); assert.equal(forgedTag.eligible, false); }); test("release fast path denies a remote endpoint that is not the repository's actually configured remote name", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); const fileUrl = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { remote: `file://${repository.remotePath}` }), repositoryCwd: repository.repository, }); assert.equal(fileUrl.eligible, false, "a file:// URL supplied as remote must not be eligible"); const attackerPath = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { remote: repository.remotePath }), repositoryCwd: repository.repository, }); assert.equal(attackerPath.eligible, false, "a bare filesystem path supplied as remote must not be eligible"); const nonConfigured = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { remote: "not-a-configured-remote" }), repositoryCwd: repository.repository, }); assert.equal(nonConfigured.eligible, false, "a remote name absent from the repository's configured remotes must not be eligible"); // The actually configured remote name must still resolve. const configured = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository, { remote: "origin" }), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(configured.eligible, true, configured.reason); }); test("release fast path requires independently derived CI success via the gh CLI bound to the exact remote SHA — caller-supplied CI evidence alone is never sufficient", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); // Caller-supplied CI evidence self-reports success for the exact SHA, but // no gh command runner is supplied and the repository's remote is not a // real GitHub remote, so independent derivation is unprovable — the fast // path must fail closed rather than trust the self-report alone. const undeprovable = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, }); assert.equal(undeprovable.eligible, false, undeprovable.reason); assert.match(undeprovable.reason, /independently derived|gh CLI/i); // gh unavailable/erroring must fail closed. const ghUnavailable = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: () => ({ status: 1, stdout: "", error: new Error("gh: command not found") }), }); assert.equal(ghUnavailable.eligible, false); assert.match(ghUnavailable.reason, /independently derived|gh CLI/i); // Derived success bound to the wrong SHA must still deny: the runner only // reports success for the base commit, never the exact remote head that // the fast path is required to query. const wrongShaBinding = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.baseCommit), }); assert.equal(wrongShaBinding.eligible, false); // Derived success bound to the exact remote SHA is eligible. const derived = evaluateReleaseFastPathV1({ target: releaseTarget(repository), evidence: fastPathEvidence(repository), repositoryCwd: repository.repository, ghCommandRunner: successfulGhCommandRunner(repository.finalCommit), }); assert.equal(derived.eligible, true, derived.reason); }); test("release fast path remote head recheck detects an advanced or unresolvable protected main before tag push", (t) => { const repository = createGateRepository(t); setRemoteMain(repository, repository.finalCommit); const unchanged = recheckReleaseFastPathRemoteHeadV1({ repositoryCwd: repository.repository, remote: repository.remote, expectedRemoteHead: repository.finalCommit, }); assert.deepEqual(unchanged, { advanced: false, remote_head: repository.finalCommit }); setRemoteMain(repository, repository.baseCommit); const advanced = recheckReleaseFastPathRemoteHeadV1({ repositoryCwd: repository.repository, remote: repository.remote, expectedRemoteHead: repository.finalCommit, }); assert.equal(advanced.advanced, true); execFileSync("git", ["--git-dir", repository.remotePath, "update-ref", "-d", "refs/heads/main"]); const missing = recheckReleaseFastPathRemoteHeadV1({ repositoryCwd: repository.repository, remote: repository.remote, expectedRemoteHead: repository.finalCommit, }); assert.deepEqual(missing, { advanced: true, remote_head: null }); });