From c8cfd56c9b94971ad6d6675f151be37d3d9b0d0e Mon Sep 17 00:00:00 2001 From: Stefan Ayala Date: Sun, 16 Aug 2026 06:18:30 -0700 Subject: [PATCH] feat: add fixed-argv reviewed delivery --- .agents/skills/sdlc/SKILL.md | 1 + .codex-plugin/plugin.json | 2 +- .codex/hooks/dual-review.cjs | 409 ++++++++++++++++++++- PROVE-IT.md | 22 ++ README.md | 38 +- ROADMAP.md | 6 +- SDLC-LOOP.md | 1 + package.json | 2 +- skill-sources/sdlc/SKILL.template.md | 3 + templates/AGENTS.baseline.md | 1 + templates/AGENTS.md.tmpl | 1 + tests/test-adapter.sh | 10 + tests/test-packaging.sh | 4 +- tests/test-review-delivery.cjs | 522 +++++++++++++++++++++++++++ tests/test-skill.sh | 1 + 15 files changed, 994 insertions(+), 29 deletions(-) create mode 100644 tests/test-review-delivery.cjs diff --git a/.agents/skills/sdlc/SKILL.md b/.agents/skills/sdlc/SKILL.md index 9560cb8..eb4bb34 100644 --- a/.agents/skills/sdlc/SKILL.md +++ b/.agents/skills/sdlc/SKILL.md @@ -36,6 +36,7 @@ Use this skill for implementation, bug-fix, refactor, testing, release, publish, Incremental checkpoint: use affected proof, exact-diff author review, and at most one risk-based reviewer before committing a coherent green slice. During the ten-delivery pilot, the completion boundary sends the whole base-to-candidate diff through the bounded Sol High plus Fable High joint gate; outside the pilot, use Fable only when cross-model policy requires it. A finding produces one bounded corrective delta with targeted proof. A third same-plan correction means stop; human approval may authorize a replan with newly scoped work, not silently extend the exhausted plan. Record ten-delivery pilot outcomes in `benchmarks/review-cadence.csv` before making this cadence permanent. Severity ladder: P0 stops the line; P1 blocks completion; P2 is a bounded fix now or a follow-up issue; P3 never blocks and is recorded only when worthwhile. When two reviewers are required, run `node .codex/hooks/dual-review.cjs --base --consent-subscription-quota`. Sol High and Fable High assess the same frozen candidate independently. Clean agreement stops immediately; a verdict split receives one verbatim cross-feed round of findings and then produces one conservative joint receipt. Do not add another reconciliation exchange. Allow at most two corrective rounds; if P0/P1 remains, decompose, abandon, or escalate rather than waiving it or continuing an unbounded loop. + After that joint receipt is certified, integrate it through `node .codex/hooks/dual-review.cjs deliver github --message --branch --base --title --body `. Do not reconstruct the reviewed delivery with separate raw commit, push, PR, or merge commands. The fixed-argv delivery path honors configured Git hooks, commits the certified tree, pushes its immutable SHA, verifies the authoritative PR head/base, and requires at least one completed GitHub check before atomically advancing the unchanged base to that exact commit. Use `--allow-no-checks` only when the repository intentionally has no GitHub checks. A changed base, failing hook, failing check, or protected branch fails closed before integration. Use `deliver direct` only for an explicit non-GitHub integration path; it verifies the exact remote ref but does not claim GitHub CI semantics. For every corrective finding, check its provenance against the base. If the blocker is candidate-born and outside the allowlist, remove that accretion instead of repairing it. For a commit or push from a linked worktree, use a standalone `git -C commit ...` or `git -C push ...`; never rely only on the execution tool's `workdir`, because some Codex surfaces omit it from PreToolUse payloads. If the work is in a product repo, keep that session focused on the product repo. File a direct GitHub issue for proven reusable wizard findings and only switch to live wizard work if the product repo is actually blocked. diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 4bffffb..9d613d1 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "codex-sdlc-wizard", - "version": "0.7.37", + "version": "0.7.38", "description": "Install and maintain Codex SDLC enforcement in local repositories.", "author": { "name": "BaseInfinity", diff --git a/.codex/hooks/dual-review.cjs b/.codex/hooks/dual-review.cjs index 0703b8b..5df5072 100644 --- a/.codex/hooks/dual-review.cjs +++ b/.codex/hooks/dual-review.cjs @@ -14,6 +14,31 @@ const SENSITIVE_AUTH_ENV = [ "CLAUDE_CODE_USE_VERTEX", ]; +const DELIVERY_RETARGET_ENV = [ + "GH_HOST", + "GH_REPO", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", + "GIT_CEILING_DIRECTORIES", + "GIT_COMMON_DIR", + "GIT_CONFIG_COUNT", + "GIT_CONFIG_GLOBAL", + "GIT_CONFIG_NOSYSTEM", + "GIT_CONFIG_PARAMETERS", + "GIT_CONFIG_SYSTEM", + "GIT_DIR", + "GIT_INDEX_FILE", + "GIT_ASKPASS", + "GIT_EXEC_PATH", + "GIT_NAMESPACE", + "GIT_OBJECT_DIRECTORY", + "GIT_PREFIX", + "GIT_PROXY_COMMAND", + "GIT_SSH", + "GIT_SSH_COMMAND", + "GIT_WORK_TREE", + "GH_CONFIG_DIR", +]; + const REVIEW_SCHEMA = { type: "object", description: "Code-review verdict only. Do not edit files, implement changes, re-plan work, or rerun tests.", @@ -53,6 +78,43 @@ function help() { ].join("\n"); } +function deliveryHelp() { + return [ + "Usage:", + " node .codex/hooks/dual-review.cjs deliver github --message --branch --base --title --body [--allow-no-checks]", + " node .codex/hooks/dual-review.cjs deliver direct --message --branch [--remote ]", + "", + "Commits and publishes only the immutable candidate certified by the current dual-review receipt.", + ].join("\n"); +} + +function parseDeliveryArgs(args) { + const mode = String(args[0] || ""); + if (mode === "--help" || mode === "-h") return { help: true }; + if (mode !== "github" && mode !== "direct") return { error: "Delivery mode must be github or direct." }; + const values = { mode, remote: "origin", message: "", branch: "", base: "", title: "", body: "", allowNoChecks: false }; + const known = mode === "github" + ? new Set(["--remote", "--message", "--branch", "--base", "--title", "--body", "--allow-no-checks"]) + : new Set(["--remote", "--message", "--branch"]); + for (let index = 1; index < args.length; index += 1) { + const flag = args[index]; + if (!known.has(flag)) return { error: `Unknown delivery argument: ${flag}` }; + if (flag === "--allow-no-checks") { + values.allowNoChecks = true; + continue; + } + const value = String(args[index + 1] || ""); + if (value === "" || known.has(value)) return { error: `${flag} requires a value.` }; + values[flag.slice(2)] = value; + index += 1; + } + if (values.message === "" || values.branch === "") return { error: "Delivery requires --message and --branch." }; + if (mode === "github" && (values.base === "" || values.title === "" || values.body === "")) { + return { error: "GitHub delivery requires --base, --title, and --body." }; + } + return values; +} + function parseArgs(args) { let base = ""; let consent = false; @@ -279,16 +341,338 @@ function assertSubscriptionLane() { return auth; } -function sanitizedEnvironment() { - const environment = { ...process.env }; - for (const name of SENSITIVE_AUTH_ENV) delete environment[name]; +function environmentWithout(names) { + const blocked = new Set(names.map((name) => name.toUpperCase())); + const environment = {}; + for (const [name, value] of Object.entries(process.env)) { + if (!blocked.has(name.toUpperCase())) environment[name] = value; + } return environment; } +function sanitizedEnvironment() { + return environmentWithout(SENSITIVE_AUTH_ENV); +} + +function deliveryEnvironment() { + return { ...environmentWithout(DELIVERY_RETARGET_ENV), GIT_NO_REPLACE_OBJECTS: "1" }; +} + +function deliveryGitLaunch() { + const testPath = process.env.CODEX_SDLC_TEST_MODE === "1" + ? String(process.env.CODEX_SDLC_GIT_PATH || "") + : ""; + return testPath === "" ? { command: "git", prefix: [] } : { command: process.execPath, prefix: [path.resolve(testPath)] }; +} + +function deliveryGit(root, args, options = {}) { + const launch = deliveryGitLaunch(); + const result = run(launch.command, [...launch.prefix, "-C", root, ...args], { env: deliveryEnvironment(), ...options }); + if (result.status !== 0) throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed`); + return result.stdout.trim(); +} + +function deliveryCommit(root, message) { + return deliveryGit(root, ["commit", "-m", message]); +} + +function deliveryPush(root, args) { + return deliveryGit(root, ["push", "--no-follow-tags", "--recurse-submodules=no", ...args]); +} + +function deliveryRepositoryRoot() { + const result = run("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"], { env: deliveryEnvironment() }); + return result.status === 0 ? path.resolve(result.stdout.trim()) : ""; +} + +function ghLaunch() { + const testPath = process.env.CODEX_SDLC_TEST_MODE === "1" + ? String(process.env.CODEX_SDLC_GH_PATH || "") + : ""; + return testPath === "" ? { command: "gh", prefix: [] } : { command: process.execPath, prefix: [path.resolve(testPath)] }; +} + +function gh(root, args) { + const launch = ghLaunch(); + const result = run(launch.command, [...launch.prefix, ...args], { cwd: root, env: deliveryEnvironment() }); + if (result.status !== 0) throw new Error(result.stderr.trim() || `gh ${args.join(" ")} failed`); + return result.stdout.trim(); +} + +function reviewReceipt(root) { + const relative = deliveryGit(root, ["rev-parse", "--git-path", "codex-sdlc/dual-review.json"]); + const target = path.isAbsolute(relative) ? relative : path.join(root, relative); + let receipt; + try { + receipt = JSON.parse(fs.readFileSync(target, "utf8")); + } catch { + throw new Error("Certified dual-review receipt is missing or unreadable."); + } + if (receipt.status !== "certified" || receipt.joint_verdict !== "CERTIFIED") { + throw new Error("Dual-review receipt is not certified."); + } + for (const field of ["base_commit", "head_before_commit", "candidate_tree", "proof_workspace_fingerprint", "proof_created_at", "reviewer_policy"]) { + if (typeof receipt[field] !== "string" || receipt[field] === "") throw new Error(`Dual-review receipt lacks ${field}.`); + } + return { target, receipt }; +} + +function deliveryUntracked(root) { + return deliveryGit(root, ["ls-files", "--others", "--exclude-standard"]) + .split(/\r?\n/) + .filter(Boolean) + .filter((entry) => entry !== ".reviews" && !entry.startsWith(".reviews/")); +} + +function certifiedCommit(root, message, receipt) { + const head = deliveryGit(root, ["rev-parse", "HEAD"]); + const headTree = deliveryGit(root, ["rev-parse", "HEAD^{tree}"]); + const stagedTree = deliveryGit(root, ["write-tree"]); + const unstaged = run("git", ["-C", root, "diff", "--quiet", "--ignore-submodules", "--"], { env: deliveryEnvironment() }); + const untracked = deliveryUntracked(root); + if (unstaged.status !== 0 || untracked.length > 0) throw new Error("Candidate changed after review; tracked and untracked source state must be frozen."); + + if (head === receipt.head_before_commit) { + if (stagedTree !== receipt.candidate_tree) throw new Error("Staged candidate tree does not match the certified review receipt."); + if (headTree !== receipt.candidate_tree) deliveryCommit(root, message); + } else { + let parent = ""; + try { parent = deliveryGit(root, ["rev-parse", "HEAD^"]); } catch { /* root commit cannot resume here */ } + if (headTree !== receipt.candidate_tree || parent !== receipt.head_before_commit || stagedTree !== headTree) { + throw new Error("HEAD is not the immutable commit produced from the certified candidate."); + } + } + + const commit = deliveryGit(root, ["rev-parse", "HEAD"]); + if (deliveryGit(root, ["rev-parse", "HEAD^{tree}"]) !== receipt.candidate_tree) { + throw new Error("Created commit tree does not match the certified candidate tree."); + } + return commit; +} + +function validateBranch(root, branch) { + deliveryGit(root, ["check-ref-format", "--branch", branch]); +} + +function validateRemote(root, remote) { + if (remote.startsWith("-")) throw new Error("Delivery remote must be a configured remote name, not a Git option."); + const remotes = deliveryGit(root, ["remote"]).split(/\r?\n/).filter(Boolean); + if (!remotes.includes(remote)) throw new Error(`Delivery remote ${remote} is not configured in this repository.`); +} + +function parseGitHubRepository(url, label) { + const match = url.match(/^(?:(?:https?|ssh):\/\/(?:git@)?|git@)github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/i); + if (!match) throw new Error(`${label} is not a recognizable GitHub repository URL.`); + return `${match[1]}/${match[2]}`; +} + +function githubRepository(root, remote) { + const fetchUrls = deliveryGit(root, ["config", "--get-all", `remote.${remote}.url`]).split(/\r?\n/).filter(Boolean); + const pushResult = run("git", ["-C", root, "config", "--get-all", `remote.${remote}.pushurl`], { env: deliveryEnvironment() }); + const pushUrls = pushResult.status === 0 ? pushResult.stdout.trim().split(/\r?\n/).filter(Boolean) : []; + if (fetchUrls.length !== 1 || pushUrls.length > 1) throw new Error(`Remote ${remote} must have one unambiguous fetch/push target.`); + const url = fetchUrls[0]; + const pushUrl = pushUrls[0] || url; + if (pushUrl !== url) throw new Error(`Remote ${remote} has different fetch and push targets.`); + const repository = parseGitHubRepository(url, `Remote ${remote}`); + const effectiveFetch = deliveryGit(root, ["remote", "get-url", "--all", remote]).split(/\r?\n/).filter(Boolean); + const effectivePush = deliveryGit(root, ["remote", "get-url", "--push", "--all", remote]).split(/\r?\n/).filter(Boolean); + if (effectiveFetch.length !== 1 || effectivePush.length !== 1) { + throw new Error(`Remote ${remote} must resolve to one effective fetch/push target.`); + } + if (parseGitHubRepository(effectiveFetch[0], `Effective fetch target for ${remote}`) !== repository + || parseGitHubRepository(effectivePush[0], `Effective push target for ${remote}`) !== repository) { + throw new Error(`Remote ${remote} is rewritten to a different repository.`); + } + return repository; +} + +function parseJson(value, label) { + try { return JSON.parse(value); } catch { throw new Error(`${label} did not return valid JSON.`); } +} + +function checkState(check) { + const conclusion = String(check?.conclusion || "").toUpperCase(); + const state = String(check?.state || "").toUpperCase(); + const status = String(check?.status || "").toUpperCase(); + if (["FAILURE", "ERROR", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "STALE"].includes(conclusion) + || ["FAILURE", "ERROR", "CANCELLED"].includes(state)) return "failed"; + if (["SUCCESS", "NEUTRAL", "SKIPPED"].includes(conclusion) || state === "SUCCESS") return "passed"; + if (status === "COMPLETED" && conclusion === "") return "failed"; + return "pending"; +} + +function waitForPullRequest(root, repository, pullNumber, expectedHead, expectedBase, expectedHeadName, expectedBaseName, allowNoChecks) { + const timeout = process.env.CODEX_SDLC_TEST_MODE === "1" ? 500 : 15 * 60 * 1000; + const interval = process.env.CODEX_SDLC_TEST_MODE === "1" ? 10 : 10 * 1000; + const started = Date.now(); + while (true) { + const pull = parseJson(gh(root, ["pr", "view", String(pullNumber), "--repo", repository, + "--json", "number,state,isDraft,headRefName,baseRefName,headRefOid,baseRefOid,mergeable,mergeStateStatus,statusCheckRollup,mergeCommit"]), "gh pr view"); + if (pull.number !== pullNumber) throw new Error("Authoritative PR number changed during delivery."); + if (pull.headRefName !== expectedHeadName || pull.baseRefName !== expectedBaseName) { + throw new Error("Authoritative PR refs do not match the reviewed delivery refs."); + } + if (pull.headRefOid !== expectedHead) throw new Error("Authoritative PR head does not match the certified commit."); + if (pull.baseRefOid !== expectedBase) throw new Error("Authoritative PR base advanced after review; rebase and review the new candidate."); + if (pull.state !== "OPEN") throw new Error("Authoritative PR is not open for integration."); + if (pull.isDraft === true) throw new Error("Authoritative PR is still a draft."); + if (pull.mergeable === "CONFLICTING" || pull.mergeStateStatus === "DIRTY") { + throw new Error("Authoritative PR has merge conflicts."); + } + const states = Array.isArray(pull.statusCheckRollup) ? pull.statusCheckRollup.map(checkState) : []; + if (states.includes("failed")) throw new Error("A GitHub check failed for the certified PR head."); + if (states.includes("pending")) { + if (Date.now() - started >= timeout) throw new Error("Timed out waiting for GitHub checks on the certified PR head."); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, interval); + continue; + } + if (pull.mergeStateStatus === "BLOCKED") throw new Error("Authoritative PR is blocked by repository policy or approvals."); + if (pull.mergeable !== "MERGEABLE") { + if (Date.now() - started >= timeout) throw new Error("Timed out waiting for the authoritative PR to become mergeable."); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, interval); + continue; + } + if (pull.mergeStateStatus !== "CLEAN") { + if (Date.now() - started >= timeout) throw new Error("Timed out waiting for the authoritative PR to reach a clean merge state."); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, interval); + continue; + } + if (states.length > 0) return pull; + if (states.length === 0 && allowNoChecks) return pull; + if (Date.now() - started >= timeout) throw new Error("Timed out waiting for GitHub checks on the certified PR head."); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, interval); + } +} + +function writeDelivery(target, receipt, delivery) { + writeJsonAtomically(target, { ...receipt, delivery }); +} + +function remoteBranchCommit(root, remote, branch) { + const ref = `refs/heads/${branch}`; + const lines = deliveryGit(root, ["ls-remote", remote, ref]).split(/\r?\n/).filter(Boolean); + if (lines.length === 0) return ""; + if (lines.length !== 1) throw new Error(`Remote branch ${branch} did not resolve unambiguously.`); + const [commit, resolvedRef] = lines[0].split(/\s+/); + if (resolvedRef !== ref || !/^[0-9a-f]{40,64}$/i.test(commit || "")) { + throw new Error(`Remote branch ${branch} returned an invalid object ID.`); + } + return commit; +} + +function directDelivery(root, parsed, receiptState, commit) { + deliveryPush(root, [parsed.remote, `${commit}:refs/heads/${parsed.branch}`]); + const remote = deliveryGit(root, ["ls-remote", parsed.remote, `refs/heads/${parsed.branch}`]); + if (!remote.startsWith(`${commit}\t`)) throw new Error("Remote branch does not resolve to the certified commit."); + writeDelivery(receiptState.target, receiptState.receipt, { + status: "pushed", mode: "direct", commit, remote: parsed.remote, branch: parsed.branch, + }); +} + +function githubDelivery(root, parsed, receiptState, commit, repository) { + const ancestry = run("git", ["-C", root, "merge-base", "--is-ancestor", receiptState.receipt.base_commit, commit], { + env: deliveryEnvironment(), + }); + if (ancestry.status !== 0) throw new Error("Certified commit is not a descendant of the reviewed base commit."); + if (remoteBranchCommit(root, parsed.remote, parsed.base) === commit) { + const checkpoint = receiptState.receipt.delivery || {}; + if (checkpoint.status !== "validated" || checkpoint.mode !== "github" || checkpoint.commit !== commit + || checkpoint.repository !== repository || checkpoint.branch !== parsed.branch || checkpoint.base !== parsed.base + || checkpoint.checks_verified !== true || !Number.isInteger(checkpoint.pull_request)) { + throw new Error("Base already contains the certified commit without a matching PR/check validation checkpoint."); + } + writeDelivery(receiptState.target, receiptState.receipt, { + status: "integrated", mode: "github", commit, repository, branch: parsed.branch, + base: parsed.base, pull_request: checkpoint.pull_request, pull_state: "base_advanced", recovered: true, + }); + return; + } + deliveryPush(root, [parsed.remote, `${commit}:refs/heads/${parsed.branch}`]); + const existing = parseJson(gh(root, ["pr", "list", "--repo", repository, "--head", parsed.branch, + "--base", parsed.base, "--state", "open", "--json", "number,headRefOid,baseRefOid"]), "gh pr list"); + if (!Array.isArray(existing)) throw new Error("gh pr list returned an invalid result."); + let pullNumber; + if (existing.length === 0) { + const created = gh(root, ["pr", "create", "--repo", repository, "--head", parsed.branch, "--base", parsed.base, + "--title", parsed.title, "--body", parsed.body]); + const match = created.match(/\/pull\/(\d+)(?:\s|$)/); + if (!match) throw new Error("gh pr create did not return a pull-request URL."); + pullNumber = Number(match[1]); + } else if (existing.length !== 1) { + throw new Error("More than one open PR matches the reviewed delivery branch."); + } else { + if (existing[0].headRefOid !== commit || existing[0].baseRefOid !== receiptState.receipt.base_commit + || !Number.isInteger(existing[0].number)) { + throw new Error("Filtered PR identity does not match the certified delivery candidate."); + } + pullNumber = existing[0].number; + } + const pull = waitForPullRequest(root, repository, pullNumber, commit, receiptState.receipt.base_commit, + parsed.branch, parsed.base, parsed.allowNoChecks); + writeDelivery(receiptState.target, receiptState.receipt, { + status: "validated", mode: "github", commit, repository, branch: parsed.branch, + base: parsed.base, pull_request: pull.number, checks_verified: true, + }); + deliveryPush(root, [`--force-with-lease=refs/heads/${parsed.base}:${receiptState.receipt.base_commit}`, + parsed.remote, `${commit}:refs/heads/${parsed.base}`]); + if (remoteBranchCommit(root, parsed.remote, parsed.base) !== commit) { + throw new Error("Base branch does not resolve to the certified commit."); + } + const merged = parseJson(gh(root, ["pr", "view", String(pull.number), "--repo", repository, + "--json", "number,state,headRefOid,baseRefOid,mergeCommit"]), "gh pr view"); + writeDelivery(receiptState.target, receiptState.receipt, { + status: "integrated", mode: "github", commit, repository, branch: parsed.branch, + base: parsed.base, pull_request: pull.number, pull_state: merged.state, + }); +} + +function deliveryMain(args) { + const parsed = parseDeliveryArgs(args); + if (parsed.help) { + process.stdout.write(`${deliveryHelp()}\n`); + return 0; + } + if (parsed.error) { + process.stderr.write(`${parsed.error}\n${deliveryHelp()}\n`); + return 2; + } + try { + const root = deliveryRepositoryRoot(); + if (root === "") throw new Error("Reviewed delivery must run from a Git worktree."); + validateBranch(root, parsed.branch); + validateRemote(root, parsed.remote); + let repository = ""; + if (parsed.mode === "github") { + validateBranch(root, parsed.base); + if (parsed.branch === parsed.base) throw new Error("GitHub delivery branch must differ from its base branch."); + repository = githubRepository(root, parsed.remote); + } + const proof = proofStatus(root); + const receiptState = reviewReceipt(root); + if (receiptState.receipt.proof_workspace_fingerprint !== proof.workspace_fingerprint + || receiptState.receipt.proof_created_at !== proof.created_at) { + throw new Error("Current SDLC proof is not the proof certified by the reviewers."); + } + const commit = certifiedCommit(root, parsed.message, receiptState.receipt); + const checkpoint = receiptState.receipt.delivery || {}; + if (checkpoint.status !== "validated" || checkpoint.mode !== "github" || checkpoint.commit !== commit) { + writeDelivery(receiptState.target, receiptState.receipt, { status: "committed", mode: parsed.mode, commit }); + } + if (parsed.mode === "github") githubDelivery(root, parsed, receiptState, commit, repository); + else directDelivery(root, parsed, receiptState, commit); + process.stdout.write(`Reviewed ${parsed.mode} delivery completed for ${commit}.\n`); + return 0; + } catch (error) { + process.stderr.write(`${error.message}\n`); + return 2; + } +} + function proofStatus(root) { const guard = path.join(root, ".codex", "hooks", "git-guard.cjs"); if (!fs.existsSync(guard)) throw new Error("Missing .codex/hooks/git-guard.cjs."); - const result = run(process.execPath, [guard, "verify-proof", "--json"], { cwd: root }); + const result = run(process.execPath, [guard, "verify-proof", "--json"], { cwd: root, env: deliveryEnvironment() }); let status = null; try { status = JSON.parse(result.stdout); @@ -298,12 +682,13 @@ function proofStatus(root) { if (result.status !== 0 || status?.ok !== true) { throw new Error(`SDLC proof is ${status?.reason || "missing or stale"}.`); } -} - -function proofReceipt(root) { - const relative = git(root, ["rev-parse", "--git-path", "codex-sdlc/proof.json"]); + const relative = deliveryGit(root, ["rev-parse", "--git-path", "codex-sdlc/proof.json"]); const target = path.isAbsolute(relative) ? relative : path.join(root, relative); - return JSON.parse(fs.readFileSync(target, "utf8")); + try { + return JSON.parse(fs.readFileSync(target, "utf8")); + } catch { + throw new Error("SDLC proof receipt is missing or unreadable."); + } } function reviewReceiptPath(root) { @@ -526,8 +911,7 @@ async function main() { requireFrozenIndex(root); const baseCommit = git(root, ["rev-parse", "--verify", `${parsed.base}^{commit}`]); const binding = currentBinding(root, baseCommit); - proofStatus(root); - const proof = proofReceipt(root); + const proof = proofStatus(root); const patchBuffer = gitBuffer(root, ["diff", "--cached", "--binary", baseCommit]); if (patchBuffer.length === 0) throw new Error("The staged candidate patch is empty."); @@ -606,5 +990,6 @@ async function main() { module.exports = { buildWindowsCommandLine }; if (require.main === module) { - main().then((status) => { process.exitCode = status; }); + if (process.argv[2] === "deliver") process.exitCode = deliveryMain(process.argv.slice(3)); + else main().then((status) => { process.exitCode = status; }); } diff --git a/PROVE-IT.md b/PROVE-IT.md index c850e56..92ee2ff 100644 --- a/PROVE-IT.md +++ b/PROVE-IT.md @@ -74,6 +74,28 @@ Clean agreement stops immediately. A verdict split receives exactly one verbatim structured cross-feed round; the gate then emits one joint receipt and stops. It never permits a third reviewer exchange. +After the joint receipt is certified, use the fixed-argv delivery boundary +instead of separate raw commit, push, PR, and merge commands: + +```bash +node .codex/hooks/dual-review.cjs deliver github \ + --message "feat: describe the certified change" \ + --branch feature-branch \ + --base main \ + --title "Describe the certified change" \ + --body "Closes #123" +``` + +It commits the certified staged tree while honoring configured Git hooks, +pushes that immutable commit, verifies the authoritative PR head/base and at +least one completed check, then atomically advances the unchanged base to the +certified commit. An empty check rollup fails closed unless the caller +explicitly chooses `--allow-no-checks` for a repository with no GitHub checks. +A changed base, failing hook, failing check, or protected branch fails closed +before integration. For an +explicit non-GitHub path, `deliver direct` pushes and verifies the exact remote +ref but does not claim GitHub CI semantics. + For this repository, run and stamp the complete maintainer suite once with: ```bash diff --git a/README.md b/README.md index 0989268..187c5da 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ After either path changes skills, hooks, hook config, or helper scripts, restart Useful follow-ups after install: ```bash -npx codex-sdlc-wizard@0.7.37 check -npx codex-sdlc-wizard@0.7.37 update +npx codex-sdlc-wizard@0.7.38 check +npx codex-sdlc-wizard@0.7.38 update ``` If you want pinned release examples instead of `@latest`, see [Releases](#releases). @@ -285,10 +285,10 @@ How to choose: ```bash # recommended interactive bootstrap path -npx codex-sdlc-wizard@0.7.37 --model-profile maximum +npx codex-sdlc-wizard@0.7.38 --model-profile maximum # experimental efficiency trial when you explicitly choose it -npx codex-sdlc-wizard@0.7.37 --model-profile mixed +npx codex-sdlc-wizard@0.7.38 --model-profile mixed # floating latest release with the same bootstrap recommendation npx codex-sdlc-wizard@latest --model-profile maximum @@ -389,6 +389,19 @@ node .codex/hooks/dual-review.cjs --base main --consent-subscription-quota Sol High and Fable High review the same frozen candidate independently. Clean agreement stops after those two reviews. A verdict split gets exactly one verbatim cross-feed round, then the wrapper writes one conservative candidate-bound joint receipt; it never starts an unbounded reviewer dialogue. +Once the joint receipt is certified, deliver that exact candidate through the fixed-argv boundary rather than rebuilding the sequence with separate shell commands: + +```bash +node .codex/hooks/dual-review.cjs deliver github \ + --message "feat: describe the certified change" \ + --branch feature-branch \ + --base main \ + --title "Describe the certified change" \ + --body "Closes #123" +``` + +The command commits the certified staged tree while honoring configured Git hooks, pushes its immutable SHA, creates or reuses the explicitly targeted PR, verifies the authoritative head/base and at least one completed check, then atomically advances the unchanged base to that exact commit. An empty check rollup waits and fails closed by default; use `--allow-no-checks` only when the repository intentionally has no GitHub checks. A changed base, failing hook, failing check, or protected branch blocks integration. `deliver direct` is available only for an explicitly intended non-GitHub path; it verifies the exact remote ref but does not claim GitHub CI semantics. This is immediate exact-SHA integration after verification, not GitHub auto-merge. + ### Incremental checkpoints and the completion boundary For each coherent green slice, run affected proof, author-review the exact incremental diff, and use at most one risk-based reviewer before committing. During the ten-delivery pilot, the completion boundary is deliberately broader: freeze the candidate, run the broad proof once, and send the whole base-to-candidate diff through the bounded Sol High plus Fable High joint gate above. Outside the pilot, use Fable only when cross-model policy requires it. Fix a blocker as one bounded corrective delta with targeted proof. A third same-plan correction means stop; human approval may authorize a replan with newly scoped work, not silently extend the exhausted plan. @@ -448,11 +461,14 @@ This keeps dogfooding useful without turning every implementation session into w ## Releases -`0.7.37` adds Desktop-safe linked-worktree delivery guidance: commit and push -commands expose their absolute worktree target through `git -C`, so PreToolUse -hooks can bind proof correctly even when a Codex surface omits the tool -`workdir`. It includes the bounded-review, proof-aware review, Fable transport, -bounded reconciliation, and Windows proof/npm improvements from `0.7.36`. +`0.7.38` adds one fixed-argv delivery boundary for a certified candidate. It +commits the exact reviewed tree while honoring configured Git hooks, pushes +immutable object IDs, verifies the authoritative GitHub PR identity and at +least one completed check by default, and +atomically integrates only when the reviewed base is unchanged. This removes +the fragile multi-command handoff that could read the wrong worktree or publish +something other than the reviewed candidate. It includes the Desktop-safe +linked-worktree guidance and bounded-review improvements from `0.7.37`. Versioned releases for this adapter live at: @@ -462,7 +478,7 @@ If you are consuming this repo in a real project, prefer a tagged release over ` ```bash # npm / npx pinned to the current release -npx codex-sdlc-wizard@0.7.37 +npx codex-sdlc-wizard@0.7.38 # npm / npx floating on the newest published release npx codex-sdlc-wizard@latest @@ -472,7 +488,7 @@ npx codex-sdlc-wizard@latest # so $codex-sdlc-wizard is available inside Codex # git-based install -git clone --branch v0.7.37 --depth 1 https://github.com/BaseInfinity/codex-sdlc-wizard.git /tmp/codex-sdlc-wizard +git clone --branch v0.7.38 --depth 1 https://github.com/BaseInfinity/codex-sdlc-wizard.git /tmp/codex-sdlc-wizard ``` ### Maintainer Release Flow diff --git a/ROADMAP.md b/ROADMAP.md index c68c9eb..d843c22 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,9 +11,9 @@ ## Current State -- Current release candidate: `v0.7.37`, adding explicit linked-worktree Git targeting to the already-merged bounded-review, proof-aware review, Fable transport, bounded reconciliation, and Windows proof/npm improvements. -- Current GitHub release after this candidate is published: [`v0.7.37`](https://github.com/BaseInfinity/codex-sdlc-wizard/releases/tag/v0.7.37). -- Current npm release after this candidate is published: [`codex-sdlc-wizard@0.7.37`](https://www.npmjs.com/package/codex-sdlc-wizard/v/0.7.37). +- Current release candidate: `v0.7.38`, binding certified review evidence to one fixed-argv commit, push, PR-check, and exact integration boundary. +- Current GitHub release after this candidate is published: [`v0.7.38`](https://github.com/BaseInfinity/codex-sdlc-wizard/releases/tag/v0.7.38). +- Current npm release after this candidate is published: [`codex-sdlc-wizard@0.7.38`](https://www.npmjs.com/package/codex-sdlc-wizard/v/0.7.38). - Next release milestone: [`1.0.0 — Bounded autonomous delivery`](https://github.com/BaseInfinity/codex-sdlc-wizard/milestone/2). - The ten-delivery cadence pilot is installed on `main`; its measurement issue remains open until the recorded evidence supports a permanent policy. - Real Windows Codex Desktop and CLI acceptance is the last hardware-dependent gate. Mac/Linux implementation and proof continue before that handoff. diff --git a/SDLC-LOOP.md b/SDLC-LOOP.md index fc0802c..a9b49ca 100644 --- a/SDLC-LOOP.md +++ b/SDLC-LOOP.md @@ -28,6 +28,7 @@ Codex does not have a native `/sdlc` command. This file is the honest replacemen Use a prompt-only review when supplying custom proof-aware instructions. A custom prompt must not be combined with `--uncommitted`, `--base`, or `--commit`; those predefined target flags are for reviews without a custom prompt. Include the exact base identity, frozen candidate tree identity, proof command, and result and say `Do not rerun tests`. Targeted verification is allowed only for a concrete suspected defect; never rerun the broad suite. Missing or stale proof is a blocker to report, not permission to launch another broad suite. Reviewer role: inspect the frozen diff and return prioritized code-review findings only; do not edit, implement, run tests, re-plan, or perform follow-up work. The builder owns every correction through the normal SDLC loop. When two reviewers are required, run `node .codex/hooks/dual-review.cjs --base --consent-subscription-quota`. Sol High and Fable High assess the same frozen candidate independently. Clean agreement stops immediately; a verdict split gets one verbatim cross-feed of findings before one conservative joint receipt. Consent acknowledges Claude subscription-quota use. Do not add another reconciliation exchange. Allow at most two corrective rounds. If P0/P1 remains, decompose, abandon, or escalate; never waive it or continue an unbounded review loop. + After certification, integrate with `node .codex/hooks/dual-review.cjs deliver github --message --branch --base --title --body `. This fixed-argv path honors configured Git hooks, commits and publishes only the certified candidate, requires at least one completed GitHub check by default, then atomically advances the unchanged base to that exact commit. Use `--allow-no-checks` only when the repository intentionally has no GitHub checks. Do not replace it with separate raw commit/push/merge commands. Use `deliver direct` only when a non-GitHub path is explicitly intended. Check every corrective finding against the base. If the blocker is candidate-born and outside the allowlist, remove that accretion instead of repairing it. For a commit or push from a linked worktree, use a standalone `git -C commit ...` or `git -C push ...`; never rely only on the execution tool's `workdir`, because some Codex surfaces omit it from PreToolUse payloads. 9. Escalate honestly diff --git a/package.json b/package.json index 8eaebe0..a5dfc4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codex-sdlc-wizard", - "version": "0.7.37", + "version": "0.7.38", "description": "Codex SDLC plugin, adaptive setup wizard, and maintenance CLI", "license": "MIT", "funding": { diff --git a/skill-sources/sdlc/SKILL.template.md b/skill-sources/sdlc/SKILL.template.md index 81a9769..815513e 100644 --- a/skill-sources/sdlc/SKILL.template.md +++ b/skill-sources/sdlc/SKILL.template.md @@ -101,6 +101,8 @@ Use native Codex review for a second pass when the slice warrants it: When repo policy requires both reviewers, run `node .codex/hooks/dual-review.cjs --base --consent-subscription-quota`. Sol High and Fable High independently review the same frozen candidate. Clean agreement stops immediately; a verdict split receives one verbatim cross-feed round of findings and then produces one conservative joint receipt. Consent is explicit because this uses Claude subscription quota. Do not add another reconciliation exchange. +After that joint receipt is certified, integrate it through `node .codex/hooks/dual-review.cjs deliver github --message --branch --base --title --body `. Do not reconstruct the reviewed delivery with separate raw commit, push, PR, or merge commands. The fixed-argv delivery path honors configured Git hooks, commits the certified tree, pushes its immutable SHA, verifies the authoritative PR head/base, and requires at least one completed GitHub check before atomically advancing the unchanged base to that exact commit. Use `--allow-no-checks` only when the repository intentionally has no GitHub checks. A changed base, failing hook, failing check, or protected branch fails closed before integration. Use `deliver direct` only for an explicit non-GitHub integration path; it verifies the exact remote ref but does not claim GitHub CI semantics. + Run one broad proof run total on the frozen candidate through `node .codex/hooks/git-guard.cjs prove --reviewed`; do not run the suite directly and then rerun it through the guard. Use a prompt-only review when supplying custom proof-aware instructions. A custom prompt must not be combined with `--uncommitted`, `--base`, or `--commit`; those predefined target flags are for reviews without a custom prompt. Include the exact base identity, frozen candidate tree identity, proof command, and result and say `Do not rerun tests`. Targeted verification is allowed only for a concrete suspected defect; never rerun the broad suite. Missing or stale proof is a blocker to report, not permission to launch another broad suite. Reviewer role: inspect the frozen diff and return prioritized code-review findings only; do not edit, implement, run tests, re-plan, or perform follow-up work. The builder owns every correction through the normal SDLC loop. @@ -126,6 +128,7 @@ Never use auto-merge in this repo. `NEVER AUTO-MERGE` Read CI logs, handle valid review feedback, and merge explicitly only after the proof matches the diff. +The reviewed-delivery command performs that exact atomic integration immediately after its candidate and checks are verified; it does not enable GitHub auto-merge. ### 6. Final summary diff --git a/templates/AGENTS.baseline.md b/templates/AGENTS.baseline.md index 1b6bdba..7a7a4b9 100644 --- a/templates/AGENTS.baseline.md +++ b/templates/AGENTS.baseline.md @@ -18,6 +18,7 @@ Read `TESTING.md` and `ARCHITECTURE.md` when present and relevant. If `GOALS.md` Use a prompt-only review when supplying custom proof-aware instructions. A custom prompt must not be combined with `--uncommitted`, `--base`, or `--commit`; those predefined target flags are for reviews without a custom prompt. Include the exact base identity, frozen candidate tree identity, proof command, and result and say `Do not rerun tests`. Targeted verification is allowed only for a concrete suspected defect; never rerun the broad suite. Stale proof is a blocker to report, not permission to launch another broad suite. Reviewer role: inspect the frozen diff and return prioritized code-review findings only; do not edit, implement, run tests, re-plan, or perform follow-up work. The builder owns every correction through the normal SDLC loop. When both reviewers are required, run `node .codex/hooks/dual-review.cjs --base --consent-subscription-quota`. Sol High and Fable High review the same frozen candidate independently; clean agreement stops immediately, while a split gets one verbatim cross-feed round before one conservative joint receipt. Consent acknowledges Claude subscription-quota use. + After certification, integrate with `node .codex/hooks/dual-review.cjs deliver github --message --branch --base --title --body `. It honors configured Git hooks, commits and publishes only the certified candidate, and requires at least one completed PR check before atomically advancing an unchanged base to that exact commit. Use `--allow-no-checks` only when the repository intentionally has no GitHub checks. Use `deliver direct` only for an explicit non-GitHub integration path. If a blocker is candidate-born and outside the allowlist, remove that accretion instead of repairing it. ## Model Policy diff --git a/templates/AGENTS.md.tmpl b/templates/AGENTS.md.tmpl index cd67eb6..44eb47b 100644 --- a/templates/AGENTS.md.tmpl +++ b/templates/AGENTS.md.tmpl @@ -44,6 +44,7 @@ Use skills for the visible workflow contract, let hooks enforce silently, and ke - Use a prompt-only review when supplying custom proof-aware instructions. A custom prompt must not be combined with `--uncommitted`, `--base`, or `--commit`; those predefined target flags are for reviews without a custom prompt. Include the exact base identity, frozen candidate tree identity, proof command, and result and say `Do not rerun tests`. Targeted verification is allowed only for a concrete suspected defect; never rerun the broad suite. Stale proof is a blocker to report, not permission to launch another broad suite. - Reviewer role: inspect the frozen diff and return prioritized code-review findings only; do not edit, implement, run tests, re-plan, or perform follow-up work. The builder owns every correction through the normal SDLC loop. - When both reviewers are required, run `node .codex/hooks/dual-review.cjs --base --consent-subscription-quota`. Sol High and Fable High review the same frozen candidate independently; clean agreement stops immediately, while a split gets one verbatim cross-feed round before one conservative joint receipt. Consent acknowledges Claude subscription-quota use. + - After certification, integrate with `node .codex/hooks/dual-review.cjs deliver github --message --branch --base --title --body `. It honors configured Git hooks, commits and publishes only the certified candidate, and requires at least one completed PR check before atomically advancing an unchanged base to that exact commit. Use `--allow-no-checks` only when the repository intentionally has no GitHub checks. Use `deliver direct` only for an explicit non-GitHub integration path. - If a blocker is candidate-born and outside the allowlist, remove that accretion instead of repairing it. ## Commands diff --git a/tests/test-adapter.sh b/tests/test-adapter.sh index 5e1d73d..a7ec248 100755 --- a/tests/test-adapter.sh +++ b/tests/test-adapter.sh @@ -4529,6 +4529,7 @@ test_sdlc_skill_has_docsync_learning_and_merge_guard() { && grep -q 'capture learnings' "$skill" \ && grep -q 'NEVER AUTO-MERGE' "$skill" \ && grep -q 'dual-review.cjs' "$skill" \ + && grep -q 'dual-review.cjs deliver github' "$skill" \ && grep -q 'one verbatim cross-feed round' "$skill"; then pass "sdlc carries doc-sync, learning capture, and merge-guard rules" else @@ -5498,6 +5499,14 @@ NODE fi } +test_review_delivery_is_fixed_argv_and_candidate_bound() { + if node "$REPO_DIR/tests/test-review-delivery.cjs"; then + pass "Reviewed delivery uses fixed argv and integrates only the certified candidate" + else + fail "Reviewed delivery did not preserve exact candidate identity" + fi +} + test_pretool_blocks_commit test_pretool_blocks_push test_pretool_blocks_git_after_shell_prefixes @@ -5610,6 +5619,7 @@ test_fable_review_is_tool_free_high_and_candidate_bound test_fable_review_rejects_stale_proof test_fable_review_uses_windows_cmd_shim_and_freezes_before_proof_check test_dual_review_is_independent_bounded_and_candidate_bound +test_review_delivery_is_fixed_argv_and_candidate_bound echo "" echo "=== Results: $PASSED passed, $FAILED failed ===" diff --git a/tests/test-packaging.sh b/tests/test-packaging.sh index e8b63f4..f1f555f 100644 --- a/tests/test-packaging.sh +++ b/tests/test-packaging.sh @@ -989,6 +989,7 @@ test_readme_documents_native_codex_review() { grep -Eqi 'targeted verification.*concrete suspected defect|concrete suspected defect.*targeted verification' "$README" || has_targeted_verification_boundary=false grep -Eqi 'Base: ]+>.*Candidate: ]+>' "$README" || binds_base_and_candidate=false grep -Fq 'node .codex/hooks/dual-review.cjs --base main --consent-subscription-quota' "$README" || documents_bounded_dual_review=false + grep -Fq 'node .codex/hooks/dual-review.cjs deliver github' "$README" || documents_bounded_dual_review=false if [ "$has_review_command" = "true" ] && [ "$has_uncommitted" = "true" ] && @@ -1011,7 +1012,8 @@ test_readme_documents_native_codex_review() { } test_prove_it_documents_bounded_dual_review() { - if grep -Fq 'node .codex/hooks/dual-review.cjs --base --consent-subscription-quota' "$PROVE_IT"; then + if grep -Fq 'node .codex/hooks/dual-review.cjs --base --consent-subscription-quota' "$PROVE_IT" \ + && grep -Fq 'node .codex/hooks/dual-review.cjs deliver github' "$PROVE_IT"; then pass "PROVE-IT documents the bounded dual-review gate" else fail "PROVE-IT omits the bounded dual-review gate" diff --git a/tests/test-review-delivery.cjs b/tests/test-review-delivery.cjs new file mode 100644 index 0000000..c5fd36a --- /dev/null +++ b/tests/test-review-delivery.cjs @@ -0,0 +1,522 @@ +#!/usr/bin/env node +const childProcess = require("node:child_process"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); + +const repo = path.resolve(__dirname, ".."); +const deliveryScript = path.join(repo, ".codex", "hooks", "dual-review.cjs"); + +function run(command, args, options = {}) { + return childProcess.spawnSync(command, args, { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + ...options, + }); +} + +function must(command, args, options = {}) { + const result = run(command, args, options); + if (result.status !== 0) { + throw new Error(`${command} ${args.join(" ")} failed:\n${result.stdout}${result.stderr}`); + } + return result.stdout.trim(); +} + +function git(root, ...args) { + return must("git", ["-C", root, ...args]); +} + +function receiptPath(root) { + const target = git(root, "rev-parse", "--git-path", "codex-sdlc/dual-review.json"); + return path.isAbsolute(target) ? target : path.join(root, target); +} + +function proofPath(root) { + const target = git(root, "rev-parse", "--git-path", "codex-sdlc/proof.json"); + return path.isAbsolute(target) ? target : path.join(root, target); +} + +function makeFixture(prefix) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + const remote = `${root}.git`; + must("git", ["init", "--bare", "-q", remote]); + must("git", ["init", "-q", "-b", "main", root]); + git(root, "config", "user.email", "test@example.com"); + git(root, "config", "user.name", "SDLC Test"); + git(root, "remote", "add", "origin", remote); + const hooks = path.join(root, ".codex", "hooks"); + fs.mkdirSync(hooks, { recursive: true }); + fs.copyFileSync(path.join(repo, ".codex", "hooks", "git-guard.cjs"), path.join(hooks, "git-guard.cjs")); + fs.writeFileSync(path.join(root, "file.txt"), "baseline\n"); + git(root, "add", "file.txt", ".codex/hooks/git-guard.cjs"); + git(root, "commit", "-qm", "baseline"); + git(root, "push", "-q", "origin", "HEAD:refs/heads/main"); + git(root, "switch", "-qc", "feature"); + fs.writeFileSync(path.join(root, "file.txt"), "candidate\n"); + git(root, "add", "file.txt"); + const base = git(root, "rev-parse", "HEAD"); + const tree = git(root, "write-tree"); + const target = receiptPath(root); + fs.mkdirSync(path.dirname(target), { recursive: true }); + must(process.execPath, [path.join(hooks, "git-guard.cjs"), "prove", "--reviewed", "--check", "true"], { cwd: root }); + const proof = JSON.parse(fs.readFileSync(proofPath(root), "utf8")); + fs.writeFileSync(target, `${JSON.stringify({ + schema_version: 1, + status: "certified", + base_commit: base, + head_before_commit: base, + candidate_tree: tree, + patch_sha256: "sha256:test", + proof_workspace_fingerprint: proof.workspace_fingerprint, + proof_created_at: proof.created_at, + reviewer_policy: "sol-high+fable-high/independent-cross-feed-on-split/v1", + joint_verdict: "CERTIFIED", + })}\n`); + return { root, remote, base, tree, receipt: target }; +} + +function certifyCommittedCandidate(fixture) { + git(fixture.root, "commit", "-qm", "candidate checkpoint"); + const commit = git(fixture.root, "rev-parse", "HEAD"); + must(process.execPath, [path.join(fixture.root, ".codex", "hooks", "git-guard.cjs"), + "prove", "--reviewed", "--check", "true"], { cwd: fixture.root }); + const proof = JSON.parse(fs.readFileSync(proofPath(fixture.root), "utf8")); + const receipt = JSON.parse(fs.readFileSync(fixture.receipt, "utf8")); + receipt.head_before_commit = commit; + receipt.proof_workspace_fingerprint = proof.workspace_fingerprint; + receipt.proof_created_at = proof.created_at; + fs.writeFileSync(fixture.receipt, `${JSON.stringify(receipt)}\n`); + return commit; +} + +function fakeGh(directory) { + const target = path.join(directory, "fake-gh.cjs"); + fs.writeFileSync(target, `#!/usr/bin/env node +const fs = require("node:fs"); +const cp = require("node:child_process"); +const args = process.argv.slice(2); +const head = cp.spawnSync("git", ["-C", process.cwd(), "rev-parse", "HEAD"], { encoding: "utf8" }).stdout.trim(); +fs.appendFileSync(process.env.GH_LOG, JSON.stringify({ + args, + ghRepo: process.env.GH_REPO || "", + ghHost: process.env.GH_HOST || "", + lowerGitDir: process.env.git_dir || "", + lowerGhRepo: process.env.gh_repo || "", +}) + "\\n"); +const headMismatch = process.env.GH_MODE === "head-mismatch"; +const baseMismatch = process.env.GH_MODE === "base-mismatch"; +const remoteBase = cp.spawnSync("git", ["--git-dir", process.env.REMOTE_PATH, "rev-parse", "refs/heads/main"], { encoding: "utf8" }).stdout.trim(); +const merged = remoteBase === head; +if (args[0] === "pr" && args[1] === "list") { + process.stdout.write(process.env.GH_MODE === "ambiguous-pr" + ? JSON.stringify([{ number: 7, headRefOid: head, baseRefOid: process.env.BASE_SHA }]) + : "[]"); +} +else if (args[0] === "pr" && args[1] === "create") process.stdout.write("https://github.com/acme/project/pull/7\\n"); +else if (args[0] === "pr" && args[1] === "view") { + const wrongSelection = process.env.GH_MODE === "ambiguous-pr" && args[2] !== "7"; + const viewCount = fs.readFileSync(process.env.GH_LOG, "utf8").trim().split("\\n") + .map((line) => JSON.parse(line)).filter((call) => call.args[0] === "pr" && call.args[1] === "view").length; + process.stdout.write(JSON.stringify({ + number: wrongSelection ? 8 : 7, + state: merged ? "MERGED" : (process.env.GH_MODE === "closed" ? "CLOSED" : "OPEN"), + isDraft: process.env.GH_MODE === "draft", + headRefOid: headMismatch ? "0000000000000000000000000000000000000000" : head, + baseRefOid: baseMismatch ? "1111111111111111111111111111111111111111" : process.env.BASE_SHA, + headRefName: "feature", + baseRefName: wrongSelection ? "other-base" : "main", + mergeable: process.env.GH_MODE === "conflicting" ? "CONFLICTING" : "MERGEABLE", + mergeStateStatus: process.env.GH_MODE === "blocked" + ? "BLOCKED" + : (process.env.GH_MODE === "blocked-pending" && viewCount === 1 + ? "BLOCKED" + : (["unknown", "unstable"].includes(process.env.GH_MODE) + ? process.env.GH_MODE.toUpperCase() + : (process.env.GH_MODE === "conflicting" ? "DIRTY" : "CLEAN"))), + statusCheckRollup: process.env.GH_MODE === "checks-empty" + ? [] + : (process.env.GH_MODE === "blocked-pending" && viewCount === 1 + ? [{ status: "IN_PROGRESS", conclusion: "" }] + : [{ status: "COMPLETED", conclusion: "SUCCESS" }]), + mergeCommit: merged ? { oid: head } : null, + })); + if (process.env.GH_MODE === "base-race" && !merged) { + const tree = cp.spawnSync("git", ["--git-dir", process.env.REMOTE_PATH, "rev-parse", "refs/heads/main^{tree}"], { encoding: "utf8" }).stdout.trim(); + const raced = cp.spawnSync("git", ["--git-dir", process.env.REMOTE_PATH, "commit-tree", tree, "-p", process.env.BASE_SHA, "-m", "base race"], { + encoding: "utf8", + env: { ...process.env, GIT_AUTHOR_NAME: "Race", GIT_AUTHOR_EMAIL: "race@example.com", GIT_COMMITTER_NAME: "Race", GIT_COMMITTER_EMAIL: "race@example.com" }, + }).stdout.trim(); + cp.spawnSync("git", ["--git-dir", process.env.REMOTE_PATH, "update-ref", "refs/heads/main", raced]); + } +} +else if (args[0] === "api") process.stdout.write(JSON.stringify({ tree: { sha: process.env.CANDIDATE_TREE } })); +else { process.stderr.write("unexpected gh argv: " + JSON.stringify(args) + "\\n"); process.exit(2); } +`); + fs.chmodSync(target, 0o755); + return target; +} + +function fakeGit(directory) { +const target = path.join(directory, "git"); + fs.writeFileSync(target, `#!/usr/bin/env node +const cp = require("node:child_process"); +const fs = require("node:fs"); +const args = process.argv.slice(2); +fs.appendFileSync(process.env.GIT_LOG, JSON.stringify({ + args, + noReplace: process.env.GIT_NO_REPLACE_OBJECTS || "", +}) + "\\n"); +if (args.includes("push") || args.includes("ls-remote")) { + for (let index = 0; index < args.length; index += 1) { + if (args[index] === "origin") args[index] = process.env.REMOTE_PATH; + } +} +if (process.env.GIT_MODE === "post-push-observation-failure" + && args.includes("ls-remote") + && args.includes("refs/heads/main") + && !fs.existsSync(process.env.GIT_FAILURE_MARKER)) { + const rootIndex = args.indexOf("-C"); + const root = rootIndex >= 0 ? args[rootIndex + 1] : process.cwd(); + const head = cp.spawnSync(process.env.REAL_GIT, ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).stdout.trim(); + const remoteHead = cp.spawnSync(process.env.REAL_GIT, + ["--git-dir", process.env.REMOTE_PATH, "rev-parse", "refs/heads/main"], { encoding: "utf8" }).stdout.trim(); + if (head === remoteHead) { + fs.writeFileSync(process.env.GIT_FAILURE_MARKER, "failed after base push\\n"); + process.stderr.write("simulated post-push observation failure\\n"); + process.exit(92); + } +} +const result = cp.spawnSync(process.env.REAL_GIT, args, { stdio: "inherit", env: process.env }); +process.exit(result.status === null ? 2 : result.status); +`); + fs.chmodSync(target, 0o755); + return target; +} + +function invoke(fixture, mode, operation = "github", replacements = [], extraEnvironment = {}) { + const support = fs.mkdtempSync(path.join(os.tmpdir(), "review-delivery-gh-")); + const log = path.join(support, "gh.jsonl"); + const gitLog = path.join(support, "git.jsonl"); + const marker = path.join(support, "merged"); + const gh = fakeGh(support); + fakeGit(support); + if (operation === "github") { + git(fixture.root, "remote", "set-url", "origin", "git@github.com:acme/project.git"); + } + const args = [deliveryScript, "deliver", operation, + "--message", "feat: exact candidate", + "--branch", "feature"]; + if (operation === "github") args.push( + "--base", "main", "--title", "Exact candidate", "--body", "Closes #111", + ); + for (let index = 0; index < replacements.length; index += 2) { + const flag = replacements[index]; + const value = replacements[index + 1]; + if (value === null) { + args.push(flag); + continue; + } + const position = args.indexOf(flag); + if (position >= 0) args[position + 1] = value; + else args.push(flag, value); + } + const result = run(process.execPath, args, { + cwd: fixture.root, + env: { + ...process.env, + CODEX_SDLC_TEST_MODE: "1", + CODEX_SDLC_GH_PATH: gh, + CODEX_SDLC_GIT_PATH: path.join(support, "git"), + GH_LOG: log, + GIT_LOG: gitLog, + GH_MERGED_MARKER: marker, + GH_MODE: mode, + GIT_MODE: mode, + GIT_FAILURE_MARKER: `${fixture.remote}.post-push-failure`, + GH_REPO: "attacker/wrong-repo", + GH_HOST: "evil.invalid", + git_dir: "/attacker/wrong-git-dir", + gh_repo: "attacker/lowercase-repo", + BASE_SHA: fixture.base, + CANDIDATE_TREE: fixture.tree, + REMOTE_PATH: fixture.remote, + REAL_GIT: must("which", ["git"]), + ...extraEnvironment, + }, + }); + const calls = fs.existsSync(log) + ? fs.readFileSync(log, "utf8").trim().split("\n").filter(Boolean).map(JSON.parse) + : []; + const gitCalls = fs.existsSync(gitLog) + ? fs.readFileSync(gitLog, "utf8").trim().split("\n").filter(Boolean).map(JSON.parse) + : []; + return { result, calls, gitCalls, marker }; +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const cleanup = []; +try { + const success = makeFixture("review-delivery-success-"); + cleanup.push(success.root, success.remote); + const hostHooks = fs.mkdtempSync(path.join(os.tmpdir(), "review-delivery-host-hooks-")); + const hookMarker = path.join(hostHooks, "executed"); + cleanup.push(hostHooks); + for (const hook of ["post-commit", "pre-push"]) { + const hookPath = path.join(hostHooks, hook); + fs.writeFileSync(hookPath, `#!/bin/sh\nprintf '%s\\n' '${hook}' >> '${hookMarker}'\n`); + fs.chmodSync(hookPath, 0o755); + } + git(success.root, "config", "core.hooksPath", hostHooks); + const delivered = invoke(success, "clean"); + assert(delivered.result.status === 0, `GitHub delivery failed:\n${delivered.result.stdout}${delivered.result.stderr}`); + const hookCalls = fs.readFileSync(hookMarker, "utf8").trim().split("\n"); + assert(hookCalls.includes("post-commit") && hookCalls.filter((entry) => entry === "pre-push").length === 2, + "Configured commit/push hooks did not execute throughout delivery"); + const commit = git(success.root, "rev-parse", "HEAD"); + assert(git(success.root, "rev-parse", "HEAD^{tree}") === success.tree, "Committed tree differs from certified tree"); + assert(must("git", ["--git-dir", success.remote, "rev-parse", "refs/heads/feature"]) === commit, "Explicit feature ref was not pushed"); + assert(must("git", ["--git-dir", success.remote, "rev-parse", "refs/heads/main"]) === commit, "Base did not advance to the exact certified commit"); + assert(delivered.calls.every((call) => call.ghRepo === "" && call.ghHost === ""), "Ambient GH_REPO/GH_HOST leaked into gh"); + assert(delivered.calls.every((call) => call.lowerGitDir === "" && call.lowerGhRepo === ""), + "Case-variant retargeting variables leaked into delivery subprocesses"); + const mergeCalls = delivered.calls.filter((call) => call.args[0] === "pr" && call.args[1] === "merge"); + assert(mergeCalls.length === 0, "GitHub merge cannot guarantee the exact integrated tree"); + const finalReceipt = JSON.parse(fs.readFileSync(success.receipt, "utf8")); + assert(finalReceipt.delivery?.status === "integrated" && finalReceipt.delivery?.commit === commit, "Receipt lacks exact delivery result"); + + const alreadyCommitted = makeFixture("review-delivery-already-committed-"); + cleanup.push(alreadyCommitted.root, alreadyCommitted.remote); + const certifiedCommit = certifyCommittedCandidate(alreadyCommitted); + const alreadyCommittedResult = invoke(alreadyCommitted, "clean", "direct"); + assert(alreadyCommittedResult.result.status === 0, + `Already-committed certified candidate failed delivery:\n${alreadyCommittedResult.result.stdout}${alreadyCommittedResult.result.stderr}`); + assert(git(alreadyCommitted.root, "rev-parse", "HEAD") === certifiedCommit, + "Delivery created an extra commit for an already-committed certified candidate"); + assert(git(alreadyCommitted.root, "ls-remote", "origin", "refs/heads/feature").startsWith(certifiedCommit), + "Already-committed certified candidate was not published"); + + const replacementObjects = makeFixture("review-delivery-replacement-objects-"); + cleanup.push(replacementObjects.root, replacementObjects.remote); + const replacementObjectsResult = invoke(replacementObjects, "clean", "direct"); + assert(replacementObjectsResult.result.status === 0, + `Replacement-object-safe delivery failed:\n${replacementObjectsResult.result.stdout}${replacementObjectsResult.result.stderr}`); + assert(replacementObjectsResult.gitCalls.length > 0 + && replacementObjectsResult.gitCalls.every((call) => call.noReplace === "1"), + "Delivery Git commands did not disable replacement-object resolution"); + + const blockedByHook = makeFixture("review-delivery-hook-blocked-"); + cleanup.push(blockedByHook.root, blockedByHook.remote); + const blockingHooks = fs.mkdtempSync(path.join(os.tmpdir(), "review-delivery-blocking-hooks-")); + cleanup.push(blockingHooks); + const prePush = path.join(blockingHooks, "pre-push"); + fs.writeFileSync(prePush, "#!/bin/sh\nexit 97\n"); + fs.chmodSync(prePush, 0o755); + git(blockedByHook.root, "config", "core.hooksPath", blockingHooks); + const hookBlocked = invoke(blockedByHook, "clean"); + assert(hookBlocked.result.status !== 0, "Delivery ignored a failing configured pre-push hook"); + assert(must("git", ["--git-dir", blockedByHook.remote, "rev-parse", "refs/heads/main"]) === blockedByHook.base, + "Delivery advanced the base after a configured hook rejected the push"); + + const emptyChecks = makeFixture("review-delivery-empty-checks-"); + cleanup.push(emptyChecks.root, emptyChecks.remote); + const noChecks = invoke(emptyChecks, "checks-empty"); + assert(noChecks.result.status !== 0, "Delivery treated an empty GitHub check rollup as green"); + assert(must("git", ["--git-dir", emptyChecks.remote, "rev-parse", "refs/heads/main"]) === emptyChecks.base, + "Delivery advanced the base before GitHub checks became observable"); + + const explicitlyCheckless = makeFixture("review-delivery-checkless-opt-out-"); + cleanup.push(explicitlyCheckless.root, explicitlyCheckless.remote); + const checkless = invoke(explicitlyCheckless, "checks-empty", "github", ["--allow-no-checks", null]); + assert(checkless.result.status === 0, `Explicit check-less delivery failed:\n${checkless.result.stdout}${checkless.result.stderr}`); + assert(must("git", ["--git-dir", explicitlyCheckless.remote, "rev-parse", "refs/heads/main"]) + === git(explicitlyCheckless.root, "rev-parse", "HEAD"), "Explicit check-less delivery did not integrate the certified commit"); + + const stale = makeFixture("review-delivery-stale-"); + cleanup.push(stale.root, stale.remote); + fs.writeFileSync(path.join(stale.root, "file.txt"), "changed after review\n"); + git(stale.root, "add", "file.txt"); + const rejected = invoke(stale, "clean"); + assert(rejected.result.status !== 0, "Stale candidate was delivered"); + assert(rejected.calls.length === 0, "Stale candidate reached GitHub"); + assert(git(stale.root, "rev-list", "--count", "HEAD") === "1", "Stale candidate was committed"); + + const staleProof = makeFixture("review-delivery-stale-proof-"); + const otherProof = makeFixture("review-delivery-other-proof-"); + cleanup.push(staleProof.root, staleProof.remote, otherProof.root, otherProof.remote); + const proofTarget = git(staleProof.root, "rev-parse", "--git-path", "codex-sdlc/proof.json"); + const staleProofPath = path.isAbsolute(proofTarget) ? proofTarget : path.join(staleProof.root, proofTarget); + const expiredProof = JSON.parse(fs.readFileSync(staleProofPath, "utf8")); + expiredProof.expires_at = "2000-01-01T00:00:00.000Z"; + fs.writeFileSync(staleProofPath, `${JSON.stringify(expiredProof)}\n`); + const otherGitDirectory = git(otherProof.root, "rev-parse", "--absolute-git-dir"); + const staleProofResult = invoke(staleProof, "clean", "github", [], { GIT_DIR: otherGitDirectory }); + assert(staleProofResult.result.status !== 0, "Delivery accepted an expired proof receipt"); + assert(staleProofResult.calls.length === 0, "Expired proof reached GitHub"); + assert(git(staleProof.root, "rev-list", "--count", "HEAD") === "1", "Expired proof created a commit"); + + const replacedProof = makeFixture("review-delivery-replaced-proof-"); + cleanup.push(replacedProof.root, replacedProof.remote); + const reviewedProof = JSON.parse(fs.readFileSync(proofPath(replacedProof.root), "utf8")); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5); + must(process.execPath, [path.join(replacedProof.root, ".codex", "hooks", "git-guard.cjs"), + "prove", "--reviewed", "--check", "true"], { cwd: replacedProof.root }); + const replacementProof = JSON.parse(fs.readFileSync(proofPath(replacedProof.root), "utf8")); + assert(replacementProof.created_at !== reviewedProof.created_at, "Replacement proof did not receive a distinct identity"); + const replacedProofResult = invoke(replacedProof, "clean"); + assert(replacedProofResult.result.status !== 0, "Delivery accepted a proof different from the one reviewers certified"); + assert(replacedProofResult.calls.length === 0, "Replacement proof reached GitHub"); + assert(git(replacedProof.root, "rev-list", "--count", "HEAD") === "1", "Replacement proof created a commit"); + + const mismatch = makeFixture("review-delivery-mismatch-"); + cleanup.push(mismatch.root, mismatch.remote); + const mismatched = invoke(mismatch, "head-mismatch"); + assert(mismatched.result.status !== 0, "Authoritative PR head mismatch was accepted"); + assert(!mismatched.calls.some((call) => call.args[0] === "pr" && call.args[1] === "merge"), "Mismatched PR reached merge"); + + const ambiguous = makeFixture("review-delivery-ambiguous-pr-"); + cleanup.push(ambiguous.root, ambiguous.remote); + const exactPull = invoke(ambiguous, "ambiguous-pr"); + assert(exactPull.result.status === 0, `Exact PR-number delivery failed:\n${exactPull.result.stdout}${exactPull.result.stderr}`); + const viewedPulls = exactPull.calls.filter((call) => call.args[0] === "pr" && call.args[1] === "view"); + assert(viewedPulls.length > 0 && viewedPulls.every((call) => call.args[2] === "7"), + "Delivery did not bind PR verification to the exact filtered pull-request number"); + + const baseMismatch = makeFixture("review-delivery-base-mismatch-"); + cleanup.push(baseMismatch.root, baseMismatch.remote); + const baseMismatched = invoke(baseMismatch, "base-mismatch"); + assert(baseMismatched.result.status !== 0, "Authoritative PR base mismatch was accepted"); + assert(!baseMismatched.calls.some((call) => call.args[0] === "pr" && call.args[1] === "merge"), "PR with a changed base reached merge"); + + for (const mode of ["closed", "draft", "conflicting", "blocked", "unknown", "unstable"]) { + const unready = makeFixture(`review-delivery-${mode}-`); + cleanup.push(unready.root, unready.remote); + const result = invoke(unready, mode); + assert(result.result.status !== 0, `Delivery accepted a ${mode} PR`); + assert(must("git", ["--git-dir", unready.remote, "rev-parse", "refs/heads/main"]) === unready.base, + `Delivery advanced the base for a ${mode} PR`); + } + + const blockedPending = makeFixture("review-delivery-blocked-pending-"); + cleanup.push(blockedPending.root, blockedPending.remote); + const blockedPendingResult = invoke(blockedPending, "blocked-pending"); + assert(blockedPendingResult.result.status === 0, + `Delivery did not wait through GitHub's BLOCKED-while-checks-pending state:\n${blockedPendingResult.result.stdout}${blockedPendingResult.result.stderr}`); + const blockedPendingViews = blockedPendingResult.calls.filter((call) => call.args[0] === "pr" && call.args[1] === "view"); + assert(blockedPendingViews.length >= 2, "Delivery did not poll the blocked PR until required checks completed"); + + const interrupted = makeFixture("review-delivery-post-push-failure-"); + const interruptionMarker = `${interrupted.remote}.post-push-failure`; + cleanup.push(interrupted.root, interrupted.remote, interruptionMarker); + const interruptedResult = invoke(interrupted, "post-push-observation-failure"); + assert(interruptedResult.result.status !== 0, "Simulated post-push observation failure did not interrupt delivery"); + const interruptedCommit = git(interrupted.root, "rev-parse", "HEAD"); + assert(must("git", ["--git-dir", interrupted.remote, "rev-parse", "refs/heads/main"]) === interruptedCommit, + "Simulated failure occurred before the exact certified commit reached the base"); + const recoveredResult = invoke(interrupted, "post-push-observation-failure"); + assert(recoveredResult.result.status === 0, + `Delivery did not recover an already-integrated certified commit:\n${recoveredResult.result.stdout}${recoveredResult.result.stderr}`); + assert(git(interrupted.root, "rev-list", "--count", "HEAD") === "2", "Recovery created an extra commit"); + const recoveredReceipt = JSON.parse(fs.readFileSync(interrupted.receipt, "utf8")); + assert(recoveredReceipt.delivery?.status === "integrated" && recoveredReceipt.delivery?.commit === interruptedCommit, + "Recovery did not finalize the integrated delivery receipt"); + + const unvalidated = makeFixture("review-delivery-unvalidated-base-"); + cleanup.push(unvalidated.root, unvalidated.remote); + const directToBase = invoke(unvalidated, "clean", "direct", ["--branch", "main"]); + assert(directToBase.result.status === 0, `Direct delivery to base failed:\n${directToBase.result.stdout}${directToBase.result.stderr}`); + const launderingAttempt = invoke(unvalidated, "clean"); + assert(launderingAttempt.result.status !== 0, "GitHub delivery accepted an unvalidated commit already on the base"); + const unvalidatedReceipt = JSON.parse(fs.readFileSync(unvalidated.receipt, "utf8")); + assert(unvalidatedReceipt.delivery?.status !== "integrated" || unvalidatedReceipt.delivery?.mode !== "github", + "Unvalidated base update was laundered into an integrated GitHub receipt"); + + const rewritten = makeFixture("review-delivery-rewritten-url-"); + cleanup.push(rewritten.root, rewritten.remote); + git(rewritten.root, "config", `url.${rewritten.remote}.insteadOf`, "git@github.com:acme/project.git"); + const rewrittenResult = invoke(rewritten, "clean"); + assert(rewrittenResult.result.status !== 0, "Delivery accepted a Git URL rewrite to another repository"); + assert(git(rewritten.root, "rev-list", "--count", "HEAD") === "1", "URL rewrite created a commit before rejection"); + + const race = makeFixture("review-delivery-race-"); + cleanup.push(race.root, race.remote); + const raced = invoke(race, "base-race"); + assert(raced.result.status !== 0, "Advanced base was overwritten"); + assert(must("git", ["--git-dir", race.remote, "rev-parse", "refs/heads/main"]) !== git(race.root, "rev-parse", "HEAD"), "Base race landed an uncertified integration"); + + const sameBranch = makeFixture("review-delivery-same-branch-"); + cleanup.push(sameBranch.root, sameBranch.remote); + const same = invoke(sameBranch, "clean", "github", ["--branch", "main"]); + assert(same.result.status !== 0, "GitHub delivery accepted identical head and base branches"); + assert(git(sameBranch.root, "rev-list", "--count", "HEAD") === "1", "Invalid branch equality created a commit"); + + const remoteOption = makeFixture("review-delivery-remote-option-"); + cleanup.push(remoteOption.root, remoteOption.remote); + const option = invoke(remoteOption, "clean", "direct", ["--remote", "--repo=attacker.invalid/repo.git"]); + assert(option.result.status !== 0, "Direct delivery accepted a Git option as the remote"); + assert(git(remoteOption.root, "rev-list", "--count", "HEAD") === "1", "Invalid remote created a commit"); + + const divergent = makeFixture("review-delivery-divergent-remote-"); + const divergentPush = `${divergent.root}.push.git`; + cleanup.push(divergent.root, divergent.remote, divergentPush); + must("git", ["init", "--bare", "-q", divergentPush]); + git(divergent.root, "remote", "set-url", "--push", "origin", divergentPush); + const diverged = invoke(divergent, "clean"); + assert(diverged.result.status !== 0, "GitHub delivery accepted divergent fetch and push targets"); + assert(git(divergent.root, "rev-list", "--count", "HEAD") === "1", "Divergent remote created a commit"); + + const direct = makeFixture("review-delivery-direct-"); + cleanup.push(direct.root, direct.remote); + const directResult = invoke(direct, "clean", "direct"); + assert(directResult.result.status === 0, `Direct delivery failed:\n${directResult.result.stdout}${directResult.result.stderr}`); + const directCommit = git(direct.root, "rev-parse", "HEAD"); + assert(git(direct.root, "ls-remote", "origin", "refs/heads/feature").startsWith(directCommit), "Direct mode did not publish the exact certified commit"); + + const extraRefs = makeFixture("review-delivery-extra-refs-"); + cleanup.push(extraRefs.root, extraRefs.remote); + git(extraRefs.root, "tag", "-am", "unreviewed release tag", "unreviewed-release", "HEAD"); + git(extraRefs.root, "config", "push.followTags", "true"); + git(extraRefs.root, "config", "push.recurseSubmodules", "on-demand"); + const exactRefResult = invoke(extraRefs, "clean", "direct"); + assert(exactRefResult.result.status === 0, + `Exact-ref delivery failed:\n${exactRefResult.result.stdout}${exactRefResult.result.stderr}`); + assert(git(extraRefs.root, "ls-remote", "--tags", "origin") === "", + "Delivery published an ambient annotated tag outside the certified ref"); + + const linkedHost = makeFixture("review-delivery-linked-host-"); + const linked = `${linkedHost.root}-worktree`; + cleanup.push(linkedHost.root, linkedHost.remote, linked); + git(linkedHost.root, "reset", "--hard", "-q", "HEAD"); + git(linkedHost.root, "worktree", "add", "-qb", "linked", linked, "main"); + fs.writeFileSync(path.join(linked, "file.txt"), "linked candidate\n"); + git(linked, "add", "file.txt"); + const linkedBase = git(linked, "rev-parse", "HEAD"); + const linkedTree = git(linked, "write-tree"); + const linkedReceipt = receiptPath(linked); + fs.mkdirSync(path.dirname(linkedReceipt), { recursive: true }); + must(process.execPath, [path.join(linked, ".codex", "hooks", "git-guard.cjs"), "prove", "--reviewed", "--check", "true"], { cwd: linked }); + const linkedProof = JSON.parse(fs.readFileSync(proofPath(linked), "utf8")); + fs.writeFileSync(linkedReceipt, `${JSON.stringify({ + schema_version: 1, + status: "certified", + base_commit: linkedBase, + head_before_commit: linkedBase, + candidate_tree: linkedTree, + patch_sha256: "sha256:test", + proof_workspace_fingerprint: linkedProof.workspace_fingerprint, + proof_created_at: linkedProof.created_at, + reviewer_policy: "sol-high+fable-high/independent-cross-feed-on-split/v1", + joint_verdict: "CERTIFIED", + })}\n`); + const linkedResult = invoke({ root: linked, remote: linkedHost.remote, base: linkedBase, tree: linkedTree }, "clean", "direct"); + assert(linkedResult.result.status === 0, `Linked-worktree delivery failed:\n${linkedResult.result.stdout}${linkedResult.result.stderr}`); + const linkedCommit = git(linked, "rev-parse", "HEAD"); + assert(git(linked, "ls-remote", "origin", "refs/heads/feature").startsWith(linkedCommit), "Linked worktree did not publish the exact certified commit"); + + process.stdout.write("review delivery tests passed\n"); +} finally { + for (const target of cleanup) fs.rmSync(target, { recursive: true, force: true }); +} diff --git a/tests/test-skill.sh b/tests/test-skill.sh index dd5d95e..477bb86 100644 --- a/tests/test-skill.sh +++ b/tests/test-skill.sh @@ -510,6 +510,7 @@ test_sdlc_documents_bounded_dual_review() { for file in "$REPO_SDLC_SKILL" "$SHIPPED_SDLC_SKILL" "$SDLC_LOOP" "$AGENTS_BASELINE" "$AGENTS_TEMPLATE"; do grep -Fq 'dual-review.cjs --base --consent-subscription-quota' "$file" || valid=false + grep -Fq 'dual-review.cjs deliver github' "$file" || valid=false grep -Fqi 'Sol High' "$file" || valid=false grep -Fqi 'Fable High' "$file" || valid=false grep -Eqi 'subscription[- ]quota' "$file" || valid=false