Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions scripts/lib/workflow-bootstrap.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ function normalizeHead(headSha) {
return value;
}

function normalizeBase(baseSha) {
if (baseSha === null || baseSha === undefined || baseSha === "") return null;
const value = String(baseSha).trim().toLowerCase();
if (!HEAD_RE.test(value)) throw new Error("workflow_bootstrap_base_invalid");
return value;
}

function stateRoot(override) {
return resolve(
override || process.env.GITHUB_DELIVERY_STATE_DIR || join(homedir(), ".github-delivery"),
Expand Down Expand Up @@ -67,6 +74,7 @@ function assertCheckpointIdentity(snapshot, { repo, headSha }) {
export function bootstrapLocalPrWorkflow({ repo, headSha, baseSha = null, stateDir } = {}) {
const normalizedRepo = normalizeRepo(repo);
const normalizedHead = normalizeHead(headSha);
const normalizedBase = normalizeBase(baseSha);
const checkpointPath = localPrWorkflowCheckpointPath({
repo: normalizedRepo,
headSha: normalizedHead,
Expand All @@ -76,7 +84,7 @@ export function bootstrapLocalPrWorkflow({ repo, headSha, baseSha = null, stateD
const initialSnapshot = createDeliveryWorkflowController({
workflow: profile.workflow,
repo: normalizedRepo,
baseSha: baseSha ? String(baseSha) : null,
baseSha: normalizedBase,
headSha: normalizedHead,
graph: profile.graph,
startPhase: profile.startPhase,
Expand All @@ -101,9 +109,9 @@ export function bootstrapLocalPrWorkflow({ repo, headSha, baseSha = null, stateD
headSha: normalizedHead,
});
if (
baseSha &&
normalizedBase &&
storedSnapshot.baseSha &&
String(storedSnapshot.baseSha).toLowerCase() !== String(baseSha).toLowerCase()
String(storedSnapshot.baseSha).toLowerCase() !== normalizedBase
) {
throw new Error("workflow_bootstrap_checkpoint_base_mismatch");
}
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/workflow-state-grounding.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ test("show re-derives controller state instead of trusting raw checkpoint guidan
}
});

test("local workflow bootstrap rejects abbreviated base SHA before checkpoint comparison", () => {
const stateDir = mkdtempSync(join(tmpdir(), "github-delivery-bootstrap-base-"));
try {
assert.throws(
() => bootstrapLocalPrWorkflow({
repo: "acme/widgets",
headSha: HEAD,
baseSha: "a".repeat(9),
stateDir,
}),
/workflow_bootstrap_base_invalid/,
);
} finally {
rmSync(stateDir, { recursive: true, force: true });
}
});

test("local workflow bootstrap upgrades reused raw checkpoint guidance", () => {
const stateDir = mkdtempSync(join(tmpdir(), "github-delivery-bootstrap-grounding-"));
try {
Expand Down
Loading