Skip to content

Commit e43f225

Browse files
authored
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
1 parent fc2afc8 commit e43f225

3 files changed

Lines changed: 70 additions & 5 deletions

File tree

.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,27 @@ function closingReferencesIn(text) {
317317
return references;
318318
}
319319

320-
function standardRevertedHash(message) {
321-
return message
320+
export function standardRevertedHash(message) {
321+
const paragraphs = message
322322
.trim()
323323
.split(/\n\s*\n/)
324-
.map((paragraph) => paragraph.trim())
325-
.map((paragraph) => paragraph.match(/^This reverts commit ([0-9a-f]{7,40})\.$/i)?.[1])
326-
.find(Boolean);
324+
.map((paragraph) => paragraph.trim());
325+
const messageIsRevert = /^(?:[a-z][a-z0-9-]*(?:\([^)]+\))?!?:\s*)?revert\b/i.test(
326+
paragraphs[0] ?? "",
327+
);
328+
for (const [index, paragraph] of paragraphs.entries()) {
329+
const revertedHash = paragraph.match(/^This reverts commit ([0-9a-f]{7,40})\.$/i)?.[1];
330+
if (!revertedHash) {
331+
continue;
332+
}
333+
// GitHub squash messages can embed a reverted intermediate commit. Its
334+
// marker follows the corresponding bullet and does not revert the squash.
335+
if (!messageIsRevert && /^\*\s+Revert\b/i.test(paragraphs[index - 1] ?? "")) {
336+
continue;
337+
}
338+
return revertedHash;
339+
}
340+
return undefined;
327341
}
328342

329343
function handlesIn(text) {

scripts/docker/install-sh-smoke/run.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ if (typeof updateStep.command !== "string" || !updateStep.command.includes(expec
482482
throw new Error(`global update step missing expected tgz URL: ${JSON.stringify(updateStep)}`);
483483
}
484484
const doctorStep = steps.find((step) => step?.name === "openclaw doctor");
485+
// Every baseline that passes verify_installed_cli implements this contract;
486+
// the sole earlier npm artifact has no CLI and cannot reach this parser.
485487
if (!doctorStep) {
486488
throw new Error("missing openclaw doctor step in update JSON");
487489
}

test/scripts/verify-release-notes.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
cumulativeShippedPullRequests,
1010
highlightCountError,
1111
releaseNoteReferences,
12+
standardRevertedHash,
1213
subtractShippedPullRequests,
1314
withoutExcludedContributionRecords,
1415
} from "../../.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs";
@@ -32,6 +33,54 @@ function git(cwd: string, args: string[]): string {
3233
}
3334

3435
describe("release-note verification", () => {
36+
it("ignores nested revert markers in squash-merge bodies", () => {
37+
const nestedRevert = [
38+
"feat(android): render display math (#101435)",
39+
"",
40+
"* feat(android): render display math",
41+
"",
42+
' * Revert "docs(changelog): note display math"',
43+
"",
44+
`This reverts commit ${"a".repeat(40)}.`,
45+
].join("\n");
46+
const topLevelRevert = [
47+
'Revert "fix(qa): keep smoke profile on one channel (#101173)" (#101184)',
48+
"",
49+
`This reverts commit ${"b".repeat(40)}.`,
50+
].join("\n");
51+
const squashRevert = [
52+
"Revert chat session picker inline search (#85527)",
53+
"",
54+
'* Revert "fix(ui): keep chat session search inline (#85490)"',
55+
"",
56+
`This reverts commit ${"c".repeat(40)}.`,
57+
"",
58+
"* fix(ui): clear applied chat picker search on empty input",
59+
].join("\n");
60+
const conventionalSquashRevert = [
61+
"chore: revert dependency guard backfill machinery (#87867)",
62+
"",
63+
'* Revert "ci: isolate dependency guard backfill label (#87882)"',
64+
"",
65+
`This reverts commit ${"d".repeat(40)}.`,
66+
"",
67+
"* ci: preserve clawsweeper bot label filter",
68+
].join("\n");
69+
const explainedTopLevelRevert = [
70+
"revert: restore a provider default",
71+
"",
72+
"The replacement broke non-native endpoints.",
73+
"",
74+
`This reverts commit ${"e".repeat(40)}.`,
75+
].join("\n");
76+
77+
expect(standardRevertedHash(nestedRevert)).toBeUndefined();
78+
expect(standardRevertedHash(topLevelRevert)).toBe("b".repeat(40));
79+
expect(standardRevertedHash(squashRevert)).toBe("c".repeat(40));
80+
expect(standardRevertedHash(conventionalSquashRevert)).toBe("d".repeat(40));
81+
expect(standardRevertedHash(explainedTopLevelRevert)).toBe("e".repeat(40));
82+
});
83+
3584
it("counts only top-level Highlights bullets and enforces the 5-8 policy input", () => {
3685
const highlights = [
3786
"### Highlights",

0 commit comments

Comments
 (0)