fix(release): prevent stale beta validation evidence (#103798)

* fix(release): ignore nested squash revert markers

* fix(release): preserve squash revert targets

* docs(release): state installer doctor contract

* fix(release): recognize conventional squash reverts
This commit is contained in:
Peter Steinberger
2026-07-10 17:26:56 +01:00
committed by GitHub
parent fc2afc83da
commit e43f225c81
3 changed files with 70 additions and 5 deletions

View File

@@ -317,13 +317,27 @@ function closingReferencesIn(text) {
return references;
}
function standardRevertedHash(message) {
return message
export function standardRevertedHash(message) {
const paragraphs = message
.trim()
.split(/\n\s*\n/)
.map((paragraph) => paragraph.trim())
.map((paragraph) => paragraph.match(/^This reverts commit ([0-9a-f]{7,40})\.$/i)?.[1])
.find(Boolean);
.map((paragraph) => paragraph.trim());
const messageIsRevert = /^(?:[a-z][a-z0-9-]*(?:\([^)]+\))?!?:\s*)?revert\b/i.test(
paragraphs[0] ?? "",
);
for (const [index, paragraph] of paragraphs.entries()) {
const revertedHash = paragraph.match(/^This reverts commit ([0-9a-f]{7,40})\.$/i)?.[1];
if (!revertedHash) {
continue;
}
// GitHub squash messages can embed a reverted intermediate commit. Its
// marker follows the corresponding bullet and does not revert the squash.
if (!messageIsRevert && /^\*\s+Revert\b/i.test(paragraphs[index - 1] ?? "")) {
continue;
}
return revertedHash;
}
return undefined;
}
function handlesIn(text) {

View File

@@ -482,6 +482,8 @@ if (typeof updateStep.command !== "string" || !updateStep.command.includes(expec
throw new Error(`global update step missing expected tgz URL: ${JSON.stringify(updateStep)}`);
}
const doctorStep = steps.find((step) => step?.name === "openclaw doctor");
// Every baseline that passes verify_installed_cli implements this contract;
// the sole earlier npm artifact has no CLI and cannot reach this parser.
if (!doctorStep) {
throw new Error("missing openclaw doctor step in update JSON");
}

View File

@@ -9,6 +9,7 @@ import {
cumulativeShippedPullRequests,
highlightCountError,
releaseNoteReferences,
standardRevertedHash,
subtractShippedPullRequests,
withoutExcludedContributionRecords,
} from "../../.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs";
@@ -32,6 +33,54 @@ function git(cwd: string, args: string[]): string {
}
describe("release-note verification", () => {
it("ignores nested revert markers in squash-merge bodies", () => {
const nestedRevert = [
"feat(android): render display math (#101435)",
"",
"* feat(android): render display math",
"",
' * Revert "docs(changelog): note display math"',
"",
`This reverts commit ${"a".repeat(40)}.`,
].join("\n");
const topLevelRevert = [
'Revert "fix(qa): keep smoke profile on one channel (#101173)" (#101184)',
"",
`This reverts commit ${"b".repeat(40)}.`,
].join("\n");
const squashRevert = [
"Revert chat session picker inline search (#85527)",
"",
'* Revert "fix(ui): keep chat session search inline (#85490)"',
"",
`This reverts commit ${"c".repeat(40)}.`,
"",
"* fix(ui): clear applied chat picker search on empty input",
].join("\n");
const conventionalSquashRevert = [
"chore: revert dependency guard backfill machinery (#87867)",
"",
'* Revert "ci: isolate dependency guard backfill label (#87882)"',
"",
`This reverts commit ${"d".repeat(40)}.`,
"",
"* ci: preserve clawsweeper bot label filter",
].join("\n");
const explainedTopLevelRevert = [
"revert: restore a provider default",
"",
"The replacement broke non-native endpoints.",
"",
`This reverts commit ${"e".repeat(40)}.`,
].join("\n");
expect(standardRevertedHash(nestedRevert)).toBeUndefined();
expect(standardRevertedHash(topLevelRevert)).toBe("b".repeat(40));
expect(standardRevertedHash(squashRevert)).toBe("c".repeat(40));
expect(standardRevertedHash(conventionalSquashRevert)).toBe("d".repeat(40));
expect(standardRevertedHash(explainedTopLevelRevert)).toBe("e".repeat(40));
});
it("counts only top-level Highlights bullets and enforces the 5-8 policy input", () => {
const highlights = [
"### Highlights",