mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
* test: cover dependency pin guard * build: add dependency vulnerability gate * build: add dependency risk report * build: add dependency drift reports * build: include dependency ownership surface evidence * build: rename dependency report commands * build: respect release age exclusions in risk report * build: clarify transitive risk accounting * build: remove transitive risk exception registry * build: clarify transitive risk signal wording * ci: attach dependency evidence to release preflight * ci: extract dependency release evidence generator * build: rename ownership surface dependency report * ci: clarify release evidence naming * build: clarify recently published risk report * build: reorder transitive risk report sections * build: fix ownership surface pluralization * ci: surface dependency changes on PRs * ci: harden dependency change awareness * ci: use dependency changed PR label * build: fix dependency report lint * docs: add dependency safety changelog
173 lines
5.9 KiB
TypeScript
173 lines
5.9 KiB
TypeScript
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
DEPENDENCY_EVIDENCE_REPORTS,
|
|
collectDependencyEvidenceSummaryCounts,
|
|
createDependencyEvidenceManifest,
|
|
renderDependencyEvidenceStepSummary,
|
|
renderDependencyEvidenceSummary,
|
|
resolvePreviousReleaseTag,
|
|
resolveReleaseTag,
|
|
} from "../../scripts/generate-dependency-release-evidence.mjs";
|
|
|
|
async function writeJson(dir: string, fileName: string, value: unknown) {
|
|
await writeFile(path.join(dir, fileName), `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
describe("generate-dependency-release-evidence", () => {
|
|
it("defines the release evidence command list and policy classifications", () => {
|
|
expect(DEPENDENCY_EVIDENCE_REPORTS.map(({ command, policy }) => ({ command, policy }))).toEqual(
|
|
[
|
|
{ command: "pnpm deps:vuln:gate", policy: "hard-blocking" },
|
|
{ command: "pnpm deps:transitive-risk:report", policy: "report-only" },
|
|
{ command: "pnpm deps:ownership-surface:report", policy: "report-only" },
|
|
{ command: "pnpm deps:changes:report", policy: "report-only" },
|
|
],
|
|
);
|
|
});
|
|
|
|
it("creates the dependency evidence manifest shape", () => {
|
|
const manifest = createDependencyEvidenceManifest({
|
|
generatedAt: "2026-05-13T00:00:00.000Z",
|
|
releaseTag: "v2026.5.13-beta.1",
|
|
releaseRef: "v2026.5.13-beta.1",
|
|
releaseSha: "abc123",
|
|
npmDistTag: "beta",
|
|
packageVersion: "2026.5.13-beta.1",
|
|
workflowRunId: "123",
|
|
workflowRunAttempt: "2",
|
|
dependencyChangeBaseRef: "v2026.5.1",
|
|
});
|
|
|
|
expect(manifest).toEqual({
|
|
schemaVersion: 1,
|
|
generatedAt: "2026-05-13T00:00:00.000Z",
|
|
releaseTag: "v2026.5.13-beta.1",
|
|
releaseRef: "v2026.5.13-beta.1",
|
|
releaseSha: "abc123",
|
|
npmDistTag: "beta",
|
|
packageName: "openclaw",
|
|
packageVersion: "2026.5.13-beta.1",
|
|
workflowRunId: "123",
|
|
workflowRunAttempt: "2",
|
|
dependencyChangeBaseRef: "v2026.5.1",
|
|
reports: DEPENDENCY_EVIDENCE_REPORTS,
|
|
});
|
|
});
|
|
|
|
it("uses a synthetic release tag for validation-only SHA preflight input", () => {
|
|
expect(
|
|
resolveReleaseTag({
|
|
releaseRef: "0123456789abcdef0123456789abcdef01234567",
|
|
packageVersion: "2026.5.13",
|
|
}),
|
|
).toBe("v2026.5.13");
|
|
expect(
|
|
resolveReleaseTag({
|
|
releaseRef: "v2026.5.13-beta.1",
|
|
packageVersion: "2026.5.13-beta.1",
|
|
}),
|
|
).toBe("v2026.5.13-beta.1");
|
|
});
|
|
|
|
it("falls back to fetching tags when local previous-release resolution misses", () => {
|
|
const calls: Array<{ command: string; args: string[] }> = [];
|
|
let describeCalls = 0;
|
|
const execFileSyncImpl = (command: string, args: string[] = []) => {
|
|
calls.push({ command, args });
|
|
if (command !== "git") {
|
|
throw new Error(`unexpected command: ${command}`);
|
|
}
|
|
if (args[0] === "describe") {
|
|
describeCalls += 1;
|
|
if (describeCalls === 1) {
|
|
throw new Error("tag not found");
|
|
}
|
|
return "v2026.5.1\n";
|
|
}
|
|
if (args[0] === "fetch") {
|
|
return "";
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(" ")}`);
|
|
};
|
|
|
|
expect(
|
|
resolvePreviousReleaseTag({
|
|
rootDir: "/repo",
|
|
execFileSyncImpl,
|
|
}),
|
|
).toBe("v2026.5.1");
|
|
expect(calls.map(({ args }) => args[0])).toEqual(["describe", "fetch", "describe"]);
|
|
expect(calls[1].args).toEqual(["fetch", "--tags", "--force", "origin"]);
|
|
});
|
|
|
|
it("collects report counts and renders human summaries", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-release-dependency-evidence-test-"));
|
|
await writeJson(dir, "dependency-vulnerability-gate.json", {
|
|
blockers: [{ id: "GHSA-blocker" }],
|
|
findings: [{ id: "GHSA-blocker" }, { id: "GHSA-report" }],
|
|
});
|
|
await writeJson(dir, "transitive-manifest-risk-report.json", {
|
|
findingCount: 17,
|
|
workspaceExcludedFindingCount: 3,
|
|
metadataFailures: [{ packageName: "missing" }],
|
|
});
|
|
await writeJson(dir, "dependency-ownership-surface-report.json", {
|
|
summary: {
|
|
lockfilePackageCount: 101,
|
|
buildRiskPackageCount: 8,
|
|
},
|
|
});
|
|
await writeJson(dir, "dependency-changes-report.json", {
|
|
summary: {
|
|
dependencyFileChanges: 4,
|
|
addedPackages: 5,
|
|
removedPackages: 6,
|
|
changedPackages: 7,
|
|
},
|
|
});
|
|
|
|
const counts = await collectDependencyEvidenceSummaryCounts(dir);
|
|
expect(counts).toEqual({
|
|
vulnerabilityBlockers: 1,
|
|
vulnerabilityFindings: 2,
|
|
transitiveRiskSignals: 17,
|
|
workspaceExcludedTransitiveSignals: 3,
|
|
transitiveMetadataFailures: 1,
|
|
ownershipLockfilePackages: 101,
|
|
ownershipBuildRiskPackages: 8,
|
|
dependencyFileChanges: 4,
|
|
dependencyAddedPackages: 5,
|
|
dependencyRemovedPackages: 6,
|
|
dependencyChangedPackages: 7,
|
|
});
|
|
|
|
const summary = renderDependencyEvidenceSummary({
|
|
releaseTag: "v2026.5.13",
|
|
releaseSha: "abc123",
|
|
baseRef: "v2026.5.1",
|
|
counts,
|
|
});
|
|
expect(summary).toContain("- npm advisory vulnerability hard blockers: 1");
|
|
expect(summary).toContain("- Transitive manifest reported risk signals: 17");
|
|
expect(summary).toContain("- Dependency change baseline: `v2026.5.1`");
|
|
expect(summary).toContain("- Resolved package changes: +5 -6 changed 7");
|
|
|
|
const stepSummary = renderDependencyEvidenceStepSummary({
|
|
evidenceArtifactName: "openclaw-release-dependency-evidence-v2026.5.13",
|
|
baseRef: "v2026.5.1",
|
|
counts,
|
|
});
|
|
expect(stepSummary).toContain(
|
|
"- Evidence artifact: `openclaw-release-dependency-evidence-v2026.5.13`",
|
|
);
|
|
expect(stepSummary).toContain("- npm advisory vulnerability hard blockers: `1`");
|
|
|
|
await expect(
|
|
readFile(path.join(dir, "dependency-vulnerability-gate.json"), "utf8"),
|
|
).resolves.toContain("GHSA-blocker");
|
|
});
|
|
});
|