Files
Nick 4f10766299 fix: classify zero-diff contributor PRs (#1172)
# Pull Request Description

Complete the zero-diff maintainer workflow by treating an authoritative empty merge-base-to-head raw diff as approval-safe inside `merge:batch`. The general workflow classifier remains fail-closed when change records are missing in any other context.

The regression test proves that an empty PR skips path and blob classification, requires no skill review, and still traverses the trusted evidence and workflow identity gates.
2026-08-17 03:47:20 +02:00

896 lines
29 KiB
JavaScript

const assert = require("assert");
const path = require("path");
const mergeBatch = require(path.join(__dirname, "..", "merge_batch.cjs"));
const BASE_SHA = "1".repeat(40);
const HEAD_SHA = "2".repeat(40);
const BLOB_SHA = "3".repeat(40);
const ZERO_SHA = "0".repeat(40);
assert.strictEqual(
mergeBatch.EVIDENCE_TIMEOUT_MS,
300_000,
"repository-wide trusted evidence must have a five-minute execution budget",
);
function makeCheckRun(name, status, conclusion, startedAt, id, suiteId = 1) {
return {
name,
status,
conclusion,
started_at: startedAt,
completed_at: startedAt,
created_at: startedAt,
id,
app: { id: 15368 },
check_suite: { id: suiteId },
};
}
function evidenceSnapshot(overrides = {}) {
return {
audit: { counts: { error: 0, warning: 0, info: 0 } },
...overrides,
};
}
{
const parsed = mergeBatch.parsePrList("450, 449 446");
assert.deepStrictEqual(parsed, [450, 449, 446]);
}
{
const parsed = mergeBatch.parseArgs([
"--prs",
"450",
"--reviewed-head",
HEAD_SHA,
"--reviewed-head",
BASE_SHA,
"--dry-run",
]);
assert.deepStrictEqual(parsed.reviewedHeads, [HEAD_SHA, BASE_SHA]);
assert.strictEqual(parsed.dryRun, true);
assert.throws(
() => mergeBatch.parseArgs(["--reviewed-head", HEAD_SHA.slice(0, 12)]),
/exact 40-character lowercase commit SHA/,
);
}
{
const summary = mergeBatch.extractSummaryBlock(`Summary line 1\nSummary line 2\n\n## Change Classification\n- [ ] Skill PR`);
assert.strictEqual(summary, "Summary line 1\nSummary line 2");
}
{
const aliases = mergeBatch.getRequiredCheckAliases({ hasSkillChanges: true });
assert.ok(aliases.some((entry) => !Array.isArray(entry) && entry.aliases.includes("review")));
assert.ok(aliases.some((entry) => entry.aliases.includes("pr-policy")));
assert.ok(aliases.some((entry) => entry.aliases.includes("pr-evidence")));
}
{
const runs = [
makeCheckRun("pr-policy", "completed", "failure", "2026-04-01T10:00:00Z", 1),
makeCheckRun("pr-policy", "completed", "success", "2026-04-01T10:10:00Z", 2),
makeCheckRun("source-validation", "in_progress", null, "2026-04-01T10:11:00Z", 3),
makeCheckRun("review", "completed", "success", "2026-04-01T10:12:00Z", 4),
];
const summaries = mergeBatch.summarizeRequiredCheckRuns(runs, [
["pr-policy"],
["source-validation"],
["review", "Skill Review & Optimize"],
]);
assert.deepStrictEqual(
summaries.map((entry) => entry.state),
["success", "pending", "success"],
);
const latest = mergeBatch.selectLatestCheckRuns(runs);
assert.strictEqual(latest.get("pr-policy").conclusion, "success");
const olderLateFailure = {
...makeCheckRun("artifact-preview", "completed", "failure", "2026-04-01T10:00:00Z", 7),
created_at: "2026-04-01T10:00:00Z",
completed_at: "2026-04-01T10:20:00Z",
};
const newerPending = {
...makeCheckRun("artifact-preview", "in_progress", null, "2026-04-01T10:10:00Z", 8),
created_at: "2026-04-01T10:10:00Z",
completed_at: null,
};
assert.strictEqual(
mergeBatch.selectLatestCheckRuns([olderLateFailure, newerPending]).get("artifact-preview").id,
8,
"run creation order must win over a stale run's later completion time",
);
const spoofed = { ...makeCheckRun("pr-policy", "completed", "success", "2026-04-01T10:20:00Z", 6), app: { id: 999 } };
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([spoofed], [["pr-policy"]]).map((entry) => entry.state),
["missing"],
"required checks must come from the GitHub Actions app",
);
const skippedGate = makeCheckRun("pr-evidence", "completed", "skipped", "2026-04-01T10:13:00Z", 5);
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([skippedGate], [["pr-evidence"]]).map((entry) => entry.state),
["failed"],
"a skipped deterministic gate must never pass",
);
}
{
const skippedReview = makeCheckRun("Skill Review / review", "completed", "skipped", "2026-04-01T10:00:00Z", 10);
const manualReview = makeCheckRun("Skill Review / manual-review-required", "completed", "success", "2026-04-01T10:01:00Z", 11);
const withoutAttestation = mergeBatch.getRequiredCheckAliases(
{ hasSkillChanges: true },
{ allowManualReview: false },
).at(-1);
const withAttestation = mergeBatch.getRequiredCheckAliases(
{ hasSkillChanges: true },
{ allowManualReview: true },
).at(-1);
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([skippedReview], [withoutAttestation]).map((entry) => entry.state),
["failed"],
"a skipped semantic review must never pass",
);
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([skippedReview, manualReview], [withoutAttestation]).map((entry) => entry.state),
["failed"],
"manual review must not count without an exact-head attestation",
);
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([skippedReview, manualReview], [withAttestation]).map((entry) => entry.state),
["success"],
"manual review may satisfy the check only after exact-head attestation",
);
const failedReview = makeCheckRun("Skill Review / review", "completed", "failure", "2026-04-01T10:02:00Z", 12);
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([failedReview, manualReview], [withAttestation]).map((entry) => entry.state),
["failed"],
"manual attestation must not override a real failed semantic review",
);
const skippedManual = makeCheckRun("Skill Review / manual-review-required", "completed", "skipped", "2026-04-01T10:03:00Z", 13);
assert.deepStrictEqual(
mergeBatch.summarizeRequiredCheckRuns([skippedReview, skippedManual], [withAttestation]).map((entry) => entry.state),
["missing"],
"a skipped manual-review job must not pass",
);
}
{
const literalArg = "safe&echo injected";
const stdout = mergeBatch.runCommand(
process.execPath,
["-e", "process.stdout.write(process.argv[1])", literalArg],
path.join(__dirname, "..", "..", ".."),
{ capture: true },
);
assert.strictEqual(stdout, literalArg);
}
{
const raw = Buffer.from(
`:000000 100644 ${ZERO_SHA} ${BLOB_SHA} A\0skills/example/SKILL.md\0` +
`:100644 100644 ${BLOB_SHA} ${HEAD_SHA} R100\0skills/example/references/old.md\0skills/example/references/new.md\0`,
"utf8",
);
const records = mergeBatch.parseRawDiff(raw);
assert.strictEqual(records.length, 2);
assert.deepStrictEqual(records[0], {
status: "A",
old_path: null,
new_path: "skills/example/SKILL.md",
old_mode: "000000",
new_mode: "100644",
old_oid: ZERO_SHA,
new_oid: BLOB_SHA,
similarity: null,
});
assert.strictEqual(records[1].status, "R");
assert.strictEqual(records[1].old_path, "skills/example/references/old.md");
assert.strictEqual(records[1].new_path, "skills/example/references/new.md");
assert.strictEqual(records[1].similarity, 100);
assert.throws(() => mergeBatch.parseRawDiff(raw.subarray(0, raw.length - 1)), /final NUL/);
assert.throws(() => mergeBatch.parseRawDiff(Buffer.alloc(0)), /empty/);
assert.deepStrictEqual(mergeBatch.parseRawDiff(Buffer.alloc(0), { allowEmpty: true }), []);
const mixedWidth = Buffer.from(
`:100644 100644 ${BLOB_SHA} ${"4".repeat(64)} M\0skills/example/SKILL.md\0`,
"utf8",
);
assert.throws(() => mergeBatch.parseRawDiff(mixedWidth), /Malformed raw Git diff header/);
const invalidUtf8 = Buffer.concat([
Buffer.from(`:000000 100644 ${ZERO_SHA} ${BLOB_SHA} A\0skills/example/references/`, "ascii"),
Buffer.from([0xff, 0]),
]);
assert.throws(() => mergeBatch.parseRawDiff(invalidUtf8), /canonical UTF-8/);
}
{
const records = mergeBatch.readRawChangeRecords("/tmp/repo", BASE_SHA, HEAD_SHA, {
runCommandBuffer() {
return Buffer.alloc(0);
},
});
assert.deepStrictEqual(
records,
[],
"a zero-diff contributor PR should remain mergeable through the protected batch workflow",
);
}
function workflowFixture(overrides = {}) {
return {
id: 100,
path: ".github/workflows/ci.yml",
state: "active",
...overrides,
};
}
function runFixture(overrides = {}) {
return {
id: 200,
workflow_id: 100,
path: ".github/workflows/ci.yml",
event: "pull_request",
head_sha: HEAD_SHA,
head_branch: "feature/example",
head_repository: { full_name: "contributor/repo" },
repository: { full_name: "owner/repo" },
pull_requests: [{ number: 450 }],
check_suite_id: 900,
...overrides,
};
}
{
const valid = mergeBatch.validateActionRequiredRuns(
[runFixture()],
[workflowFixture()],
450,
HEAD_SHA,
);
assert.strictEqual(valid.length, 1);
const emptyMetadataIdentity = {
headRefName: "feature/example",
headRepository: "contributor/repo",
baseRepository: "owner/repo",
};
const emptyMetadata = mergeBatch.validateActionRequiredRuns(
[runFixture({ pull_requests: [] })],
[workflowFixture()],
450,
HEAD_SHA,
undefined,
emptyMetadataIdentity,
);
assert.strictEqual(emptyMetadata.length, 1);
for (const [label, identity] of [
["wrong branch", { ...emptyMetadataIdentity, headRefName: "feature/other" }],
["wrong fork", { ...emptyMetadataIdentity, headRepository: "attacker/repo" }],
["wrong base repository", { ...emptyMetadataIdentity, baseRepository: "attacker/base" }],
]) {
assert.throws(
() => mergeBatch.validateActionRequiredRuns(
[runFixture({ pull_requests: [] })],
[workflowFixture()],
450,
HEAD_SHA,
undefined,
identity,
),
/exact fork identity does not match/,
label,
);
}
for (const [label, run, identity = emptyMetadataIdentity] of [
["missing pull request metadata", runFixture({ pull_requests: undefined })],
["non-array pull request metadata", runFixture({ pull_requests: {} })],
["nonempty malformed pull request metadata", runFixture({ pull_requests: [{}] })],
["missing run branch", runFixture({ pull_requests: [], head_branch: undefined })],
["missing run fork", runFixture({ pull_requests: [], head_repository: undefined })],
["missing run base repository", runFixture({ pull_requests: [], repository: undefined })],
["missing captured branch", runFixture({ pull_requests: [] }), { ...emptyMetadataIdentity, headRefName: undefined }],
["missing captured fork", runFixture({ pull_requests: [] }), { ...emptyMetadataIdentity, headRepository: undefined }],
["missing captured base repository", runFixture({ pull_requests: [] }), { ...emptyMetadataIdentity, baseRepository: undefined }],
]) {
assert.throws(
() => mergeBatch.validateActionRequiredRuns(
[run],
[workflowFixture()],
450,
HEAD_SHA,
undefined,
identity,
),
/exact fork identity does not match/,
label,
);
}
for (const [label, run, workflows, pattern] of [
["unrelated PR", runFixture({ pull_requests: [{ number: 451 }] }), [workflowFixture()], /does not contain #450/],
["wrong SHA", runFixture({ head_sha: BASE_SHA }), [workflowFixture()], /head SHA/],
["wrong event", runFixture({ event: "push" }), [workflowFixture()], /not pull_request/],
["unknown path", runFixture({ path: ".github/workflows/evil.yml" }), [workflowFixture()], /not allowlisted/],
["ID mismatch", runFixture({ workflow_id: 101 }), [workflowFixture()], /workflow ID/],
["path mismatch", runFixture(), [workflowFixture({ path: ".github/workflows/codeql.yml" })], /mapping/],
]) {
assert.throws(
() => mergeBatch.validateActionRequiredRuns([run], workflows, 450, HEAD_SHA),
pattern,
label,
);
}
}
function approvalDependencies(overrides = {}) {
const record = {
status: "A",
old_path: null,
new_path: "skills/example/SKILL.md",
old_mode: "000000",
new_mode: "100644",
old_oid: ZERO_SHA,
new_oid: BLOB_SHA,
};
return {
fetchPullRequestObjects() {},
resolveMergeBase() { return BASE_SHA; },
readRawChangeRecords() { return [record]; },
resolveBlobSizes() { return new Map([[BLOB_SHA, 100]]); },
getEvaluatorOid() { return "4".repeat(40); },
recomputeChangedSkillEvidence() { return { blocking: false, reasons: [] }; },
loadPullRequestDetails() {
return { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
},
listWorkflowDefinitions() { return [workflowFixture()]; },
listActionRequiredRuns() { return [runFixture()]; },
approveWorkflowRun() {},
...overrides,
};
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let blobReads = 0;
let classifications = 0;
const dependencies = approvalDependencies({
readRawChangeRecords() { return []; },
resolveBlobSizes() { blobReads += 1; return new Map(); },
classifyChangeRecords() { classifications += 1; throw new Error("empty diffs need no path classification"); },
listActionRequiredRuns() { return []; },
});
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
dryRun: true,
});
assert.strictEqual(result.policy.approvalSafe, true);
assert.deepStrictEqual(result.records, []);
assert.strictEqual(result.policy.requiresHumanReview, false);
assert.strictEqual(blobReads, 0);
assert.strictEqual(classifications, 0);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
const supportRecord = {
status: "M",
old_path: "skills/example/references/guide.md",
new_path: "skills/example/references/guide.md",
old_mode: "100644",
new_mode: "100644",
old_oid: BASE_SHA,
new_oid: BLOB_SHA,
};
const dependencies = approvalDependencies({
readRawChangeRecords() { return [supportRecord]; },
resolveBlobSizes() { return new Map([[BASE_SHA, 100], [BLOB_SHA, 100]]); },
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, { dependencies }),
/--reviewed-head/,
"skill support content must require an exact-head maintainer attestation",
);
const approved = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
dryRun: true,
});
assert.strictEqual(approved.policy.requiresHumanReview, true);
assert.deepStrictEqual(approved.policy.canonicalSkillChanges, []);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let approvals = 0;
let tupleReads = 0;
const dependencies = approvalDependencies({
loadPullRequestDetails() {
tupleReads += 1;
return { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
},
approveWorkflowRun() { approvals += 1; },
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, { dependencies }),
/--reviewed-head/,
);
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [BASE_SHA],
}),
/--reviewed-head/,
"a stale but full reviewed head must fail closed",
);
assert.strictEqual(approvals, 0);
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
});
assert.strictEqual(approvals, 1);
assert.strictEqual(tupleReads, 2);
assert.deepStrictEqual(result.approvedRuns.map((run) => run.id), [200]);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let approvals = 0;
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies: approvalDependencies({ approveWorkflowRun() { approvals += 1; } }),
reviewedHeads: [HEAD_SHA],
dryRun: true,
});
assert.strictEqual(approvals, 0);
assert.deepStrictEqual(result.approvedRuns, []);
assert.strictEqual(result.runs.length, 1);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let approvals = 0;
const dependencies = approvalDependencies({
listActionRequiredRuns() {
return [runFixture({ pull_requests: [{ number: 999 }] })];
},
approveWorkflowRun() { approvals += 1; },
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
}),
/does not contain #450/,
);
assert.strictEqual(approvals, 0);
}
{
const calls = [];
mergeBatch.fetchPullRequestObjects("/repo", BASE_SHA, HEAD_SHA, {
runCommand(command, args) { calls.push([command, args]); },
});
assert.deepStrictEqual(calls[0], [
"git",
["fetch", "--no-tags", "--no-write-fetch-head", "origin", BASE_SHA, HEAD_SHA],
]);
assert.ok(calls.every(([, args]) => !args.includes("checkout")));
assert.deepStrictEqual(calls.slice(1).map(([, args]) => args), [
["cat-file", "-e", `${BASE_SHA}^{commit}`],
["cat-file", "-e", `${HEAD_SHA}^{commit}`],
]);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let approvals = 0;
const dependencies = approvalDependencies({
loadPullRequestDetails() {
return { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: BASE_SHA };
},
approveWorkflowRun() { approvals += 1; },
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
}),
/head changed before approvals/,
);
assert.strictEqual(approvals, 0);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let approvals = 0;
let blobReads = 0;
const dependencies = approvalDependencies({
resolveBlobSizes() { blobReads += 1; return new Map([[BLOB_SHA, 100]]); },
classifyChangeRecords() {
return { approvalSafe: false, reasons: ["record_0:new_executable_mode"] };
},
approveWorkflowRun() { approvals += 1; },
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
}),
/not fork-approval-safe/,
);
assert.strictEqual(approvals, 0);
assert.strictEqual(blobReads, 0, "structurally unsafe diffs must fail before blob expansion");
}
{
const prDetails = {
number: 450,
baseRefName: "main",
baseRefOid: BASE_SHA,
headRefOid: HEAD_SHA,
headRefName: "maintenance/internal",
headRepository: { nameWithOwner: "OWNER/REPO" },
author: { login: "owner" },
};
let classifications = 0;
const dependencies = approvalDependencies({
classifyChangeRecords() {
classifications += 1;
return {
approvalSafe: false,
reasons: ["record_0:new_unapproved_path"],
requiresHumanReview: false,
canonicalSkillChanges: [],
};
},
listActionRequiredRuns() { return []; },
loadPullRequestDetails() { return prDetails; },
});
const result = mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
});
assert.strictEqual(result.sameRepository, true);
assert.strictEqual(classifications, 2);
assert.deepStrictEqual(result.runs, []);
}
{
const prDetails = {
number: 451,
baseRefName: "main",
baseRefOid: BASE_SHA,
headRefOid: HEAD_SHA,
headRefName: "maintenance/internal",
headRepository: { nameWithOwner: "owner/repo" },
author: { login: "collaborator" },
};
const dependencies = approvalDependencies({
classifyChangeRecords() {
return { approvalSafe: false, reasons: ["record_0:new_unapproved_path"] };
},
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
}),
/not fork-approval-safe/,
);
}
{
const record = {
status: "M",
old_path: "skills/example/SKILL.md",
new_path: "skills/example/SKILL.md",
old_mode: "100644",
new_mode: "100644",
old_oid: BASE_SHA,
new_oid: BLOB_SHA,
similarity: null,
};
const report = {
schema_version: 1,
base_ref: BASE_SHA,
head_ref: HEAD_SHA,
base_oid: BASE_SHA,
head_oid: HEAD_SHA,
blocking: false,
reasons: [],
changes: [{
change_type: "modified",
before: evidenceSnapshot(),
after: evidenceSnapshot(),
records: [record],
}],
};
assert.strictEqual(
mergeBatch.validateChangedSkillEvidence(report, {
mergeBaseOid: BASE_SHA,
headOid: HEAD_SHA,
rawRecords: [record],
}),
report,
);
const nestedBundleRecord = {
...record,
old_path: "skills/example/examples/app/package-lock.json",
new_path: "skills/example/examples/app/package-lock.json",
};
const nestedBundleReport = {
...report,
changes: [{ ...report.changes[0], records: [nestedBundleRecord] }],
};
assert.strictEqual(
mergeBatch.validateChangedSkillEvidence(nestedBundleReport, {
mergeBaseOid: BASE_SHA,
headOid: HEAD_SHA,
rawRecords: [nestedBundleRecord],
}),
nestedBundleReport,
);
assert.throws(
() => mergeBatch.validateChangedSkillEvidence(
{ ...report, head_oid: BLOB_SHA },
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [record] },
),
/head_oid does not match/,
);
assert.throws(
() => mergeBatch.validateChangedSkillEvidence(
{ ...report, changes: [{ ...report.changes[0], records: [record, record] }] },
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [record] },
),
/duplicate Git records/,
);
const orphan = { ...record, old_path: "skills/orphan/assets/note.md", new_path: "skills/orphan/assets/note.md" };
assert.throws(
() => mergeBatch.validateChangedSkillEvidence(
{ ...report, changes: [] },
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [orphan] },
),
/exact skill-content Git diff/,
);
assert.throws(
() => mergeBatch.validateChangedSkillEvidence(
{ ...report, changes: [{ ...report.changes[0], after: evidenceSnapshot({ audit: { counts: { error: -1, warning: 0, info: 0 } } }) }] },
{ mergeBaseOid: BASE_SHA, headOid: HEAD_SHA, rawRecords: [record] },
),
/missing or invalid/,
);
}
{
const protection = {
required_status_checks: {
strict: true,
checks: ["pr-policy", "pr-evidence", "source-validation", "artifact-preview"].map((context) => ({
context,
app_id: 15368,
})),
},
enforce_admins: { enabled: true },
required_pull_request_reviews: { required_approving_review_count: 0 },
allow_force_pushes: { enabled: false },
allow_deletions: { enabled: false },
};
assert.strictEqual(mergeBatch.validateEffectiveMainProtection(protection, []), true);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(
{ ...protection, required_status_checks: { strict: false, checks: protection.required_status_checks.checks } },
[],
),
/exact app-bound strict checks/,
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(
{ ...protection, required_status_checks: { strict: true, checks: [{ context: "pr-policy", app_id: 15368 }] } },
[],
),
/exact app-bound strict checks/,
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(
{ ...protection, required_pull_request_reviews: null },
[],
),
/through pull requests/,
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(
{ ...protection, allow_force_pushes: null },
[],
),
/disable force pushes/,
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(
{
...protection,
required_pull_request_reviews: {
required_approving_review_count: 0,
bypass_pull_request_allowances: { apps: [{ id: 1 }] },
},
},
[],
),
/bypass allowances/,
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(
protection,
[{
id: 9,
enforcement: "active",
target: "branch",
conditions: { ref_name: { include: ["refs/heads/main"], exclude: [] } },
bypass_actors: [{ actor_id: 1 }],
}],
),
/bypass actors/,
);
assert.strictEqual(
mergeBatch.validateEffectiveMainProtection(protection, [{
id: 10,
enforcement: "active",
target: "tag",
bypass_actors: [{ actor_id: 1 }],
}]),
true,
"tag rulesets must not block main",
);
assert.strictEqual(
mergeBatch.validateEffectiveMainProtection(protection, [{
id: 11,
enforcement: "evaluate",
target: "branch",
conditions: { ref_name: { include: ["refs/heads/main"], exclude: [] } },
bypass_actors: [{ actor_id: 1 }],
}]),
true,
"non-enforcing evaluation rulesets must not block main",
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(protection, [{
id: 12,
enforcement: "active",
target: "branch",
conditions: { ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] } },
bypass_actors: [],
rules: [{ type: "merge_queue" }],
}]),
/does not support deferred merge queues/,
);
assert.throws(
() => mergeBatch.validateEffectiveMainProtection(protection, [{
id: 13,
enforcement: "active",
target: "branch",
conditions: { ref_name: { include: ["refs/heads/ma[i]n"], exclude: [] } },
bypass_actors: [{ actor_id: 1 }],
}]),
/bypass actors/,
"unsupported fnmatch syntax must fail closed as potentially applicable to main",
);
}
{
const calls = [];
const state = mergeBatch.loadEffectiveMainProtection("/repo", "owner/repo", {
runGhApiJson(_root, args, options = {}) {
calls.push([args[0], options]);
if (args[0].endsWith("/branches/main/protection")) {
return {
required_status_checks: {
strict: true,
checks: ["pr-policy", "pr-evidence", "source-validation", "artifact-preview"].map((context) => ({ context, app_id: 15368 })),
},
enforce_admins: { enabled: true },
required_pull_request_reviews: { required_approving_review_count: 0 },
allow_force_pushes: { enabled: false },
allow_deletions: { enabled: false },
};
}
if (args[0].includes("/rulesets?")) {
return [[{ id: 101 }], [{ id: 202 }]];
}
return { id: Number(args[0].split("/").at(-1)), enforcement: "active", target: "branch" };
},
});
assert.deepStrictEqual(state.rulesets.map((item) => item.id), [101, 202]);
const paginated = calls.find(([endpoint]) => endpoint.includes("/rulesets?"));
assert.strictEqual(paginated[1].paginate, true);
assert.strictEqual(paginated[1].slurp, true);
}
{
const prDetails = {
number: 450,
title: "feat: safe change",
body: "Summary",
headRefOid: HEAD_SHA,
};
let captured;
const merged = mergeBatch.mergePullRequestImmediately("/repo", "owner/repo", prDetails, {
runCommand(command, args, _cwd, options) {
captured = { command, args, payload: JSON.parse(options.input) };
return JSON.stringify({ merged: true, sha: BLOB_SHA, message: "Pull Request successfully merged" });
},
});
assert.strictEqual(merged.merged, true);
assert.strictEqual(captured.command, "gh");
assert.ok(captured.args.includes("PUT"));
assert.strictEqual(captured.payload.sha, HEAD_SHA);
assert.strictEqual(captured.payload.merge_method, "squash");
assert.throws(
() => mergeBatch.mergePullRequestImmediately("/repo", "owner/repo", prDetails, {
runCommand() { return JSON.stringify({ merged: false, message: "queued" }); },
}),
/was not merged immediately: queued/,
);
}
{
const prDetails = { number: 450, baseRefName: "main", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA };
let approvals = 0;
const dependencies = approvalDependencies({
recomputeChangedSkillEvidence() {
return { blocking: true, reasons: ["example:score_decreased:90->80"] };
},
approveWorkflowRun() { approvals += 1; },
});
assert.throws(
() => mergeBatch.approveActionRequiredRuns("/repo", "owner/repo", prDetails, {
dependencies,
reviewedHeads: [HEAD_SHA],
}),
/trusted changed-skill evidence is blocking/,
);
assert.strictEqual(approvals, 0);
}
{
const expected = { baseOid: BASE_SHA, headOid: HEAD_SHA };
assert.throws(
() => mergeBatch.assertUnchangedTuple(
{ number: 450, baseRefName: "main", baseRefOid: BLOB_SHA, headRefOid: HEAD_SHA },
expected,
"before merge",
450,
),
/base\/head changed before merge/,
);
assert.throws(
() => mergeBatch.pullRequestTuple(
{ number: 450, baseRefName: "develop", baseRefOid: BASE_SHA, headRefOid: HEAD_SHA },
),
/must target main/,
);
assert.throws(
() => mergeBatch.pullRequestTuple({
number: 450,
baseRefName: "main",
baseRefOid: BASE_SHA,
headRefOid: HEAD_SHA,
autoMergeRequest: { enabledAt: "2026-07-13T00:00:00Z" },
}),
/deferred auto-merge enabled/,
);
}
console.log("ok");