完善 readiness、发布追溯与敏感治理(07-15-production-trust-closeout-cleanup)
这个提交包含在:
@@ -0,0 +1,157 @@
|
||||
const { execFile } = require("node:child_process");
|
||||
const { mkdtemp, rm, writeFile } = require("node:fs/promises");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const { promisify } = require("node:util");
|
||||
const { pathToFileURL } = require("node:url");
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const ROOT_DIR = path.resolve(__dirname, "../../../..");
|
||||
let provenance;
|
||||
let repoDir;
|
||||
|
||||
async function git(args, cwd = repoDir) {
|
||||
return execFileAsync("git", args, { cwd });
|
||||
}
|
||||
|
||||
async function createCommit(message = "initial") {
|
||||
await git(["add", "tracked.txt"]);
|
||||
await git(["commit", "-m", message]);
|
||||
}
|
||||
|
||||
describe("deployment source provenance", () => {
|
||||
beforeAll(async () => {
|
||||
const scriptPath = path.join(ROOT_DIR, "deploy/trialrun/check-source-provenance.mjs");
|
||||
provenance = await import(pathToFileURL(scriptPath).href);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
repoDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-test-"));
|
||||
await git(["init", "--initial-branch=main"]);
|
||||
await git(["config", "user.name", "Test User"]);
|
||||
await git(["config", "user.email", "[email protected]"]);
|
||||
await writeFile(path.join(repoDir, "tracked.txt"), "initial\n", "utf8");
|
||||
await createCommit();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(repoDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("requires an upstream by default and permits an explicit unpublished override", () => {
|
||||
expect(() => provenance.collectSourceProvenance({ rootDir: repoDir })).toThrowError(
|
||||
expect.objectContaining({ code: "upstream_missing" }),
|
||||
);
|
||||
|
||||
expect(provenance.collectSourceProvenance({ rootDir: repoDir, allowUnpublished: true })).toMatchObject({
|
||||
status: "PASS",
|
||||
published: false,
|
||||
overrideUsed: true,
|
||||
branch: "main",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects tracked and untracked working-tree changes before any deployment", async () => {
|
||||
await writeFile(path.join(repoDir, "tracked.txt"), "changed\n", "utf8");
|
||||
await writeFile(path.join(repoDir, "untracked.txt"), "untracked\n", "utf8");
|
||||
|
||||
expect(() => provenance.collectSourceProvenance({ rootDir: repoDir, allowUnpublished: true })).toThrowError(
|
||||
expect.objectContaining({ code: "dirty_worktree" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts a clean commit that is contained in its upstream", async () => {
|
||||
const remoteDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-remote-"));
|
||||
try {
|
||||
await execFileAsync("git", ["init", "--bare", remoteDir]);
|
||||
await git(["remote", "add", "origin", remoteDir]);
|
||||
await git(["push", "--set-upstream", "origin", "main"]);
|
||||
|
||||
expect(provenance.collectSourceProvenance({ rootDir: repoDir })).toMatchObject({
|
||||
status: "PASS",
|
||||
published: true,
|
||||
overrideUsed: false,
|
||||
upstream: "origin/main",
|
||||
});
|
||||
} finally {
|
||||
await rm(remoteDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a clean local commit that has not reached upstream", async () => {
|
||||
const remoteDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-remote-"));
|
||||
try {
|
||||
await execFileAsync("git", ["init", "--bare", remoteDir]);
|
||||
await git(["remote", "add", "origin", remoteDir]);
|
||||
await git(["push", "--set-upstream", "origin", "main"]);
|
||||
await writeFile(path.join(repoDir, "tracked.txt"), "ahead\n", "utf8");
|
||||
await createCommit("ahead");
|
||||
|
||||
expect(() => provenance.collectSourceProvenance({ rootDir: repoDir })).toThrowError(
|
||||
expect.objectContaining({ code: "commit_not_in_upstream" }),
|
||||
);
|
||||
expect(provenance.collectSourceProvenance({ rootDir: repoDir, allowUnpublished: true })).toMatchObject({
|
||||
published: false,
|
||||
overrideUsed: true,
|
||||
});
|
||||
} finally {
|
||||
await rm(remoteDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("refreshes the remote-tracking ref before deciding whether a commit is still published", async () => {
|
||||
const remoteDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-remote-"));
|
||||
const replacementDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-replacement-"));
|
||||
try {
|
||||
await execFileAsync("git", ["init", "--bare", remoteDir]);
|
||||
await git(["remote", "add", "origin", remoteDir]);
|
||||
await git(["push", "--set-upstream", "origin", "main"]);
|
||||
expect(provenance.collectSourceProvenance({ rootDir: repoDir })).toMatchObject({ published: true });
|
||||
|
||||
await execFileAsync("git", ["init", "--initial-branch=main"], { cwd: replacementDir });
|
||||
await execFileAsync("git", ["config", "user.name", "Replacement"], { cwd: replacementDir });
|
||||
await execFileAsync("git", ["config", "user.email", "[email protected]"], { cwd: replacementDir });
|
||||
await writeFile(path.join(replacementDir, "replacement.txt"), "replacement\n", "utf8");
|
||||
await execFileAsync("git", ["add", "replacement.txt"], { cwd: replacementDir });
|
||||
await execFileAsync("git", ["commit", "-m", "replacement"], { cwd: replacementDir });
|
||||
await execFileAsync("git", ["remote", "add", "origin", remoteDir], { cwd: replacementDir });
|
||||
await execFileAsync("git", ["push", "--force", "origin", "main"], { cwd: replacementDir });
|
||||
|
||||
expect(provenance.collectSourceProvenance({ rootDir: repoDir })).toMatchObject({ published: true });
|
||||
expect(provenance.refreshUpstreamRef({ rootDir: repoDir })).toMatchObject({
|
||||
refreshed: true,
|
||||
upstream: "origin/main",
|
||||
remote: "origin",
|
||||
});
|
||||
expect(() => provenance.collectSourceProvenance({ rootDir: repoDir })).toThrowError(
|
||||
expect.objectContaining({ code: "commit_not_in_upstream" }),
|
||||
);
|
||||
} finally {
|
||||
await rm(remoteDir, { recursive: true, force: true });
|
||||
await rm(replacementDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("requires an allowlisted audit reason for the unpublished override", async () => {
|
||||
const script = path.join(ROOT_DIR, "deploy/trialrun/check-source-provenance.mjs");
|
||||
|
||||
await expect(execFileAsync("node", [script, `--root=${repoDir}`], {
|
||||
env: { ...process.env, DEPLOY_ALLOW_UNPUBLISHED_COMMIT: "1" },
|
||||
})).rejects.toMatchObject({ code: 1 });
|
||||
|
||||
const { stdout } = await execFileAsync("node", [script, `--root=${repoDir}`], {
|
||||
env: {
|
||||
...process.env,
|
||||
DEPLOY_ALLOW_UNPUBLISHED_COMMIT: "1",
|
||||
DEPLOY_UNPUBLISHED_REASON: "offline-disaster-recovery",
|
||||
},
|
||||
});
|
||||
expect(JSON.parse(stdout)).toMatchObject({
|
||||
published: false,
|
||||
remoteVerified: false,
|
||||
overrideUsed: true,
|
||||
overrideRequested: true,
|
||||
overrideReason: "offline-disaster-recovery",
|
||||
});
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户