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
13 changes: 11 additions & 2 deletions .github/workflows/github-draft-release-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,14 @@ jobs:
echo "::error title=Release compare metadata failed::Could not validate ${base_sha}...${TARGET_SHA} against GitHub. Manual recovery: confirm both commits are reachable in the repository, then retry." >&2
exit 1
fi
# GitHub Compare metadata has no top-level head_commit field. Validate
# the remote target through the exact commit endpoint instead.
if ! gh api --method GET \
"repos/${GITHUB_REPOSITORY}/commits/${TARGET_SHA}" \
> release-assets/TARGET_COMMIT_METADATA.json; then
echo "::error title=Release target metadata failed::Could not validate target commit ${TARGET_SHA} against GitHub. Manual recovery: confirm the merged main commit is reachable in the repository, then retry." >&2
exit 1
fi

node scripts/build-release-compare.mjs \
--base "$base_sha" \
Expand All @@ -316,7 +324,7 @@ jobs:

api_base="$(jq -r '.base_commit.sha // empty' release-assets/COMPARE_METADATA.json)"
api_merge_base="$(jq -r '.merge_base_commit.sha // empty' release-assets/COMPARE_METADATA.json)"
api_head="$(jq -r '.head_commit.sha // empty' release-assets/COMPARE_METADATA.json)"
api_target="$(jq -r '.sha // empty' release-assets/TARGET_COMMIT_METADATA.json)"
api_status="$(jq -r '.status // empty' release-assets/COMPARE_METADATA.json)"
api_ahead="$(jq -r '.ahead_by // -1' release-assets/COMPARE_METADATA.json)"
api_behind="$(jq -r '.behind_by // -1' release-assets/COMPARE_METADATA.json)"
Expand All @@ -325,7 +333,7 @@ jobs:
local_files="$(jq -r '.files | length' release-assets/COMPARE.json)"
local_head="$(jq -r '.head_commit.sha // empty' release-assets/COMPARE.json)"

if [[ "$api_base" != "$base_sha" || "$api_merge_base" != "$base_sha" || "$api_head" != "$TARGET_SHA" || "$local_head" != "$TARGET_SHA" ]]; then
if [[ "$api_base" != "$base_sha" || "$api_merge_base" != "$base_sha" || "$api_target" != "$TARGET_SHA" || "$local_head" != "$TARGET_SHA" ]]; then
echo "::error title=Release comparison identity mismatch::GitHub and the trusted checkout do not agree on the base, merge base, or target. Manual recovery: refresh the target checkout and retry without changing the tag." >&2
exit 1
fi
Expand Down Expand Up @@ -853,6 +861,7 @@ jobs:
release-assets/RELEASE_NOTES_SOURCE.json
release-assets/QUALITY_REPORT.json
release-assets/COMPARE_METADATA.json
release-assets/TARGET_COMMIT_METADATA.json
release-assets/COMPARE.json
release-assets/PULL_REQUESTS.json
release-assets/RELEASE_EVIDENCE.json
Expand Down
29 changes: 29 additions & 0 deletions tests/release-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,34 @@ describe("GitHub Draft Release v2 workflow", () => {
expect(download).toContain("SHA256SUMS.txt");
});

it("does not expect a nonexistent head_commit in GitHub Compare metadata", () => {
const realCompareMetadataShape = {
base_commit: { sha: "base-sha" },
merge_base_commit: { sha: "base-sha" },
status: "ahead",
ahead_by: 2,
behind_by: 0,
total_commits: 2,
commits: [{ sha: "first-commit" }],
files: [],
};
expect(realCompareMetadataShape).not.toHaveProperty("head_commit");

const snapshot = draftScript("Build complete release change snapshot");
expect(snapshot).not.toContain("api_head=");
expect(snapshot).not.toContain(
"'.head_commit.sha // empty' release-assets/COMPARE_METADATA.json",
);
expect(snapshot).toContain('"repos/${GITHUB_REPOSITORY}/commits/${TARGET_SHA}"');
expect(snapshot).toContain("TARGET_COMMIT_METADATA.json");
expect(snapshot).toContain("Release target metadata failed");
expect(snapshot).toContain("api_target");
expect(snapshot).toContain('"$api_target" != "$TARGET_SHA"');
expect(snapshot).toContain(
"'.head_commit.sha // empty' release-assets/COMPARE.json",
);
});

it("records independently auditable commits, PRs, files, versions, and assets", () => {
for (const stepName of [
"Build complete release change snapshot",
Expand Down Expand Up @@ -432,6 +460,7 @@ describe("GitHub Draft Release v2 workflow", () => {
expect(JSON.stringify(uploadAudit)).toContain("RELEASE_NOTES_SOURCE.json");
expect(JSON.stringify(uploadAudit)).toContain("QUALITY_REPORT.json");
expect(JSON.stringify(uploadAudit)).toContain("COMPARE_METADATA.json");
expect(JSON.stringify(uploadAudit)).toContain("TARGET_COMMIT_METADATA.json");
expect(JSON.stringify(uploadAudit)).toContain("COMPARE.json");
expect(JSON.stringify(uploadAudit)).toContain("PULL_REQUESTS.json");
expect(draftScript("Create draft release and upload every asset")).toContain(
Expand Down