Files
openclaw/test/scripts/release-notes-ledger.test.ts
T
Vincent Koc d73eb23b29 fix(release): report seeded changelog provenance truthfully (#119897)
* fix(release): report seeded changelog provenance truthfully

* fix(release): reject legacy provenance formatting

* fix(release): enforce complete provenance records
2026-08-06 19:22:16 +08:00

433 lines
11 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
contributionRecordFor,
ledgerChecks,
ledgerFor,
renderContributionRecordEntry,
} from "../../.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs";
const targetSha = "a".repeat(40);
function contributionLedger({
nodes,
seededPullRequests = [],
sourcePullRequests = [],
sourceReferences = [],
}: {
nodes: Map<number, Record<string, unknown>>;
seededPullRequests?: number[];
sourcePullRequests?: number[];
sourceReferences?: number[];
}) {
return ledgerFor(
"v2026.7.2-beta.7",
targetSha,
[...nodes.keys()],
nodes,
new Map(),
new Map(),
{ issuesByPullRequest: new Map() },
{
legacyIssues: new Map(),
pullRequests: new Map(
seededPullRequests.map((number) => [
number,
{ externalReferences: [], references: [], thanks: [] },
]),
),
},
new Set(sourcePullRequests),
sourceReferences,
[],
[],
new Set(),
[],
Date.parse("2026-08-05T00:00:00Z"),
) as ReturnType<typeof ledgerFor> & {
provenance: {
inRangePullRequests: number;
retainedSeedOnlyPullRequests: number;
uniquePullRequests: number;
};
};
}
describe("renderContributionRecordEntry", () => {
it("keeps external and linked issue references without repeating PR title references", () => {
expect(
renderContributionRecordEntry({
number: 123,
title: "Fix local openclaw/openclaw#45 and openclaw/imsg#141",
linkedIssues: [{ number: 45 }, { number: 67 }],
thanks: ["alice", "bob"],
}),
).toBe("- **PR #123** Related #45, openclaw/imsg#141, #67. Thanks @alice and @bob.");
});
it("deduplicates resolved issues and retains seeded cross-repository references", () => {
expect(
renderContributionRecordEntry({
number: 124,
title: "Fix #45, #45, and OpenClaw/imsg#141",
externalReferences: ["openclaw/imsg#141"],
priorReferences: [67],
linkedIssues: [{ number: 45 }, { number: 67 }],
thanks: [],
}),
).toBe("- **PR #124** Related #45, OpenClaw/imsg#141, #67.");
});
it("renders every source PR even without issue references or credits", () => {
expect(
renderContributionRecordEntry({
number: 456,
title: "Internal cleanup",
linkedIssues: [],
thanks: [],
}),
).toBe("- **PR #456**");
});
it("retains references and credits when a compact record is seeded again", () => {
const line = "- **PR #125** Related #45, openclaw/imsg#141. Thanks @alice and @bob.";
const record = contributionRecordFor({
source: [
"## 2026.7.1",
"",
"### Complete contribution record",
"",
"#### Pull requests",
"",
line,
].join("\n"),
});
const seeded = record.pullRequests.get(125);
expect(seeded).toEqual({
externalReferences: ["openclaw/imsg#141"],
references: [45],
thanks: ["alice", "bob"],
});
expect(
renderContributionRecordEntry({
number: 125,
title: "Title changed after release",
priorReferences: seeded?.references,
externalReferences: seeded?.externalReferences,
linkedIssues: [{ number: 45 }],
thanks: seeded?.thanks ?? [],
}),
).toBe(line);
});
it("retains seeded credits when the production ledger is rebuilt", () => {
const priorRecord = contributionRecordFor({
source: [
"## 2026.7.1",
"",
"### Complete contribution record",
"",
"#### Pull requests",
"",
"- **PR #125** Thanks @alice and @bob.",
].join("\n"),
});
const nodes = new Map([
[
125,
{
__typename: "PullRequest",
author: { __typename: "User", login: "carol" },
closingIssuesReferences: { nodes: [] },
mergedAt: "2026-07-08T00:00:00Z",
title: "fix: keep release credits",
},
],
]);
const result = ledgerFor(
"v2026.6.11",
targetSha,
[125],
nodes,
new Map(),
new Map(),
{ issuesByPullRequest: new Map() },
priorRecord,
new Set([125]),
new Set(),
new Set(),
new Set(),
new Set(),
[],
Date.parse("2026-07-09T00:00:00Z"),
);
expect(result.ledger).toContain("- **PR #125** Thanks @carol and @alice and @bob.");
});
it("counts associated and PR-typed source refs before retained seed-only rows", () => {
const nodes = new Map(
[1, 2, 3].map((number) => [
number,
{
__typename: "PullRequest",
closingIssuesReferences: { nodes: [] },
mergedAt: "2026-08-04T00:00:00Z",
title: `fix: contribution ${number}`,
},
]),
);
const result = contributionLedger({
nodes,
seededPullRequests: [1, 3],
sourcePullRequests: [1],
sourceReferences: [2],
});
expect(result.provenance).toEqual({
inRangePullRequests: 2,
retainedSeedOnlyPullRequests: 1,
uniquePullRequests: 3,
});
expect(result.ledger).toContain("2 in-range PRs + 1 retained seed-only PR = 3 unique PRs.");
});
it("reports zero retained seed-only PRs when every row is in range", () => {
const nodes = new Map([
[
1,
{
__typename: "PullRequest",
closingIssuesReferences: { nodes: [] },
mergedAt: "2026-08-04T00:00:00Z",
title: "fix: in-range contribution",
},
],
]);
const result = contributionLedger({ nodes, sourcePullRequests: [1] });
expect(result.provenance).toMatchObject({
inRangePullRequests: 1,
retainedSeedOnlyPullRequests: 0,
uniquePullRequests: 1,
});
});
it("reports all rows as retained seed-only when the release range has no PRs", () => {
const nodes = new Map(
[1, 2].map((number) => [
number,
{
__typename: "PullRequest",
closingIssuesReferences: { nodes: [] },
mergedAt: "2026-08-04T00:00:00Z",
title: `fix: seeded contribution ${number}`,
},
]),
);
const result = contributionLedger({ nodes, seededPullRequests: [1, 2] });
expect(result.provenance).toMatchObject({
inRangePullRequests: 0,
retainedSeedOnlyPullRequests: 2,
uniquePullRequests: 2,
});
});
it("rejects a forged canonical range and seed partition", () => {
const source = [
"## 2026.7.1",
"",
"### Highlights",
"",
"- Highlight one.",
"- Highlight two.",
"- Highlight three.",
"- Highlight four.",
"- Highlight five.",
"",
"### Changes",
"",
"### Fixes",
"",
"### Complete contribution record",
"",
`This audited record covers the complete base..${targetSha} history: 0 in-range PRs + 1 retained seed-only PR = 1 unique PR.`,
"",
"#### Pull requests",
"",
"- **PR #456**",
].join("\n");
const entry = {
number: 456,
title: "fix: example",
editorialEligible: true,
priorReferences: [],
externalReferences: [],
linkedIssues: [],
thanks: [],
};
expect(
ledgerChecks(
{
source,
expectedProvenance: {
inRangePullRequests: 1,
retainedSeedOnlyPullRequests: 0,
uniquePullRequests: 1,
},
},
[entry],
new Map([[456, { __typename: "PullRequest" }]]),
[],
),
).toContain("contribution record provenance partition does not match generated inventory");
});
it("retains references from a verbose record when the source title changes", () => {
const record = contributionRecordFor({
source: [
"## 2026.7.1",
"",
"### Complete contribution record",
"",
"#### Pull requests",
"",
"- **PR #126** Fix #46 and openclaw/imsg#142. Related #68. Thanks @alice.",
].join("\n"),
});
const seeded = record.pullRequests.get(126);
expect(seeded).toEqual({
externalReferences: ["openclaw/imsg#142"],
references: [46, 68],
thanks: ["alice"],
});
});
it("requires complete resolved issue tokens rather than matching substrings", () => {
const source = [
"## 2026.7.1",
"",
"### Highlights",
"",
"- Highlight one.",
"- Highlight two.",
"- Highlight three.",
"- Highlight four.",
"- Highlight five.",
"",
"### Changes",
"",
"### Fixes",
"",
"### Complete contribution record",
"",
`This audited record covers the complete base..${targetSha} history: 1 merged PR.`,
"",
"#### Pull requests",
"",
"- **PR #456** Related openclaw/imsg#141.",
].join("\n");
const entry = {
number: 456,
title: "Internal cleanup",
editorialEligible: false,
priorReferences: [],
externalReferences: [],
linkedIssues: [{ number: 45 }, { number: 141 }],
thanks: [],
};
expect(
ledgerChecks({ source }, [entry], new Map([[456, { __typename: "PullRequest" }]]), []),
).toEqual([
"missing #45 on contribution record for PR #456",
"missing #141 on contribution record for PR #456",
]);
});
it("does not require an out-of-range PR referenced by a PR title", () => {
const line = "- **PR #456**";
const source = [
"## 2026.7.1",
"",
"### Highlights",
"",
"- Highlight one.",
"- Highlight two.",
"- Highlight three.",
"- Highlight four.",
"- Highlight five.",
"",
"### Changes",
"",
"### Fixes",
"",
"### Complete contribution record",
"",
`This audited record covers the complete base..${targetSha} history: 1 merged PR.`,
"",
"#### Pull requests",
"",
line,
].join("\n");
const entry = {
number: 456,
title: "docs: align backup rules (#123)",
editorialEligible: false,
priorReferences: [123],
externalReferences: [],
linkedIssues: [],
thanks: [],
};
expect(renderContributionRecordEntry(entry)).toBe(line);
expect(
ledgerChecks({ source }, [entry], new Map([[456, { __typename: "PullRequest" }]]), []),
).toEqual([]);
});
it("accepts case-only differences in cross-repository references", () => {
const line = "- **PR #127** Related OpenClaw/imsg#143.";
const source = [
"## 2026.7.1",
"",
"### Highlights",
"",
"- Highlight one.",
"- Highlight two.",
"- Highlight three.",
"- Highlight four.",
"- Highlight five.",
"",
"### Changes",
"",
"### Fixes",
"",
"### Complete contribution record",
"",
`This audited record covers the complete base..${targetSha} history: 1 merged PR.`,
"",
"#### Pull requests",
"",
line,
].join("\n");
const entry = {
number: 127,
title: "Internal cleanup",
editorialEligible: false,
priorReferences: [],
externalReferences: ["openclaw/imsg#143"],
linkedIssues: [],
thanks: [],
};
expect(
ledgerChecks({ source }, [entry], new Map([[127, { __typename: "PullRequest" }]]), []),
).toEqual([]);
});
});