完善 readiness、发布追溯与敏感治理(07-15-production-trust-closeout-cleanup)

这个提交包含在:
2026-07-16 05:17:38 +08:00
父节点 abb6a22f5c
当前提交 28e8e746b7
共修改 29 个文件,包含 1849 行新增和 233 行删除
@@ -0,0 +1,85 @@
const path = require("node:path");
const { pathToFileURL } = require("node:url");
let pendingOcr;
describe("pending OCR readiness contract", () => {
beforeAll(async () => {
const scriptPath = path.resolve(__dirname, "../../../../scripts/qa/check-pending-ocr.mjs");
pendingOcr = await import(pathToFileURL(scriptPath).href);
});
it("normalizes unique positive thresholds", () => {
expect(pendingOcr.parseThresholds(["--thresholds=10,2,5,2,0,invalid"])).toEqual([2, 5, 10]);
});
it("reports a non-clean SKIP when the database target is unavailable", async () => {
const summary = await pendingOcr.buildPendingOcrSummary({
databaseReady: false,
thresholds: [2, 5, 10],
countPending: vi.fn(),
now: new Date("2026-07-15T00:00:00.000Z"),
});
expect(summary).toMatchObject({
status: "SKIP",
findingCount: 1,
findings: [{ code: "database_not_configured", severity: "manual_or_config" }],
thresholds: [],
});
});
it("reports PASS when no pending OCR rows exceed any threshold", async () => {
const countPending = vi.fn(async () => 0);
const summary = await pendingOcr.buildPendingOcrSummary({
databaseReady: true,
thresholds: [2, 5],
countPending,
now: new Date("2026-07-15T00:00:00.000Z"),
});
expect(summary).toMatchObject({ status: "PASS", findingCount: 0, findings: [] });
expect(summary.thresholds).toEqual([
{ thresholdMinutes: 2, count: 0 },
{ thresholdMinutes: 5, count: 0 },
]);
expect(countPending).toHaveBeenNthCalledWith(1, {
status: "pending_ocr",
created_at: { lte: new Date("2026-07-14T23:58:00.000Z") },
});
});
it("reports PASS_WITH_FINDINGS using the unique pending row upper bound", async () => {
const counts = [2, 4, 7];
const summary = await pendingOcr.buildPendingOcrSummary({
databaseReady: true,
thresholds: [2, 5, 10],
countPending: vi.fn(async () => counts.shift()),
now: new Date("2026-07-15T00:00:00.000Z"),
});
expect(summary).toMatchObject({
status: "PASS_WITH_FINDINGS",
findingCount: 7,
findings: [{ code: "pending_ocr_detected", severity: "finding", count: 7 }],
});
});
it("constructs and closes the shared Prisma 7 adapter without opening a database connection", async () => {
const previousDatabaseUrl = process.env.DATABASE_URL;
process.env.DATABASE_URL = "postgresql://local-user:[email protected]:1/hyb_oa_test";
try {
await expect(pendingOcr.executePendingOcrCheck({
databaseReady: true,
thresholds: [],
})).resolves.toMatchObject({ status: "PASS", findingCount: 0 });
} finally {
if (previousDatabaseUrl === undefined) {
delete process.env.DATABASE_URL;
} else {
process.env.DATABASE_URL = previousDatabaseUrl;
}
}
});
});
@@ -0,0 +1,77 @@
const path = require("node:path");
const { pathToFileURL } = require("node:url");
const ROOT_DIR = path.resolve(__dirname, "../../../..");
let scanner;
describe("current fact-source sensitive content gate", () => {
beforeAll(async () => {
const scriptPath = path.join(ROOT_DIR, "scripts/qa/check-sensitive-content.mjs");
scanner = await import(pathToFileURL(scriptPath).href);
});
it("reports only redacted metadata for secret, account, path and infrastructure matches", () => {
const source = [
"SERVICE_PASSWORD=do-not-disclose-value",
"[email protected]",
"phone=13800000000",
"workspace=/home/developer/private/project",
"server=203.0.113.10",
].join("\n");
const findings = scanner.scanSensitiveText(source, "fixture.md");
expect(findings.map((item) => item.category)).toEqual(expect.arrayContaining([
"assigned_secret",
"email_account",
"phone_account",
"personal_workspace_path",
"public_ipv4",
]));
expect(JSON.stringify(findings)).not.toMatch(/do-not-disclose|person@|13800000000|203\.0\.113\.10/);
});
it("allows explicit placeholders, environment interpolation and loopback addresses", () => {
const source = [
"SERVICE_PASSWORD=<managed-secret>",
"DATABASE_URL=${LOCAL_DATABASE_URL}",
"admin=<admin-email>",
"workspace=<repo-root>/server",
"server=127.0.0.1",
].join("\n");
expect(scanner.scanSensitiveText(source, "fixture.md")).toEqual([]);
});
it("derives current docs from the status matrix without pulling historical issue rows", () => {
const matrix = [
"| ID | Contract | Type | Purpose | Status |",
"| --- | --- | --- | --- | --- |",
"| `CURRENT-DOC` | `docs/current.md` | fact | current | `CURRENT` |",
"| `HISTORY-DOC` | `docs/history.md` | evidence | old | `EVIDENCE` |",
"| `CURRENT-ISSUE` | `issues/current.csv` | `CURRENT` | issue only |",
].join("\n");
expect(scanner.extractCurrentDocumentFiles(matrix)).toEqual(["docs/current.md"]);
});
it("includes every CURRENT docs contract in the default scan set", () => {
const files = scanner.resolveDefaultScanFiles(ROOT_DIR);
expect(files).toEqual(expect.arrayContaining([
"docs/测试规约与回归门禁.md",
"docs/需求-资产管理规约.md",
"docs/管理员手册.md",
"docs/员工快速上手.md",
]));
});
it("passes the registered current fact sources and deployment entrypoints", () => {
expect(scanner.scanSensitiveContent({ rootDir: ROOT_DIR })).toMatchObject({
status: "PASS",
findingCount: 0,
filesScanned: expect.any(Number),
findings: [],
});
});
});
@@ -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",
});
});
});
@@ -0,0 +1,27 @@
const path = require("node:path");
const { pathToFileURL } = require("node:url");
let governance;
describe("Trellis context manifest governance", () => {
beforeAll(async () => {
const scriptPath = path.resolve(__dirname, "../../../../scripts/qa/check-trellis-governance.mjs");
governance = await import(pathToFileURL(scriptPath).href);
});
it("does not require sub-agent manifests in Codex inline mode", () => {
expect(governance.contextManifestPolicy("inline")).toEqual({
required: false,
requireRealEntries: false,
rejectSeedRows: false,
});
});
it("keeps strict manifest validation for sub-agent dispatch", () => {
expect(governance.contextManifestPolicy("sub-agent")).toEqual({
required: true,
requireRealEntries: true,
rejectSeedRows: true,
});
});
});
@@ -0,0 +1,178 @@
const { execFile } = require("node:child_process");
const { mkdtemp, readFile, rm } = require("node:fs/promises");
const http = require("node:http");
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 readinessReport;
function passSteps() {
return Object.fromEntries(
readinessReport.READINESS_STEP_NAMES.map((name) => [name, {
status: "PASS",
kind: "required",
findingCount: 0,
logRef: `artifacts/qa-flow/test/${name}.log`,
}]),
);
}
function listen(server) {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
server.off("error", reject);
resolve();
});
});
}
function close(server) {
return new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
const EXPECTED_BUILD_SHA = "abcdef123456";
describe("live smoke readiness aggregation", () => {
beforeAll(async () => {
const scriptPath = path.join(ROOT_DIR, "scripts/qa/live-smoke-readiness-report.mjs");
readinessReport = await import(pathToFileURL(scriptPath).href);
});
it.each([
["missing database", "pendingOcr"],
["missing login credentials", "liveSmoke"],
["missing OCR sample", "liveOcr"],
])("propagates %s as PASS_WITH_FINDINGS", (_label, stepName) => {
const steps = passSteps();
steps[stepName] = {
...steps[stepName],
status: "SKIP",
findingCount: 1,
};
const result = readinessReport.evaluateReadinessSteps(steps);
expect(result.status).toBe("PASS_WITH_FINDINGS");
expect(result.findingCount).toBeGreaterThan(0);
expect(result.findings).toEqual(expect.arrayContaining([
expect.objectContaining({ source: stepName, severity: "manual_or_config" }),
]));
});
it("reports clean PASS only when every prerequisite and step is ready", () => {
expect(readinessReport.evaluateReadinessSteps(passSteps())).toMatchObject({
status: "PASS",
findingCount: 0,
findings: [],
});
});
it("treats a required command failure as a blocker", () => {
const steps = passSteps();
steps.publicReady = { ...steps.publicReady, status: "FAIL", findingCount: 1 };
expect(readinessReport.evaluateReadinessSteps(steps)).toMatchObject({
status: "FAIL",
findingCount: 1,
});
});
it("runs the shell parent against a local public stub and writes a redacted non-clean summary", async () => {
const reportDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-readiness-test-"));
const server = http.createServer((request, response) => {
response.setHeader("content-type", "application/json; charset=utf-8");
if (request.url === "/ready") {
response.end(JSON.stringify({ data: { ok: true, buildSha: EXPECTED_BUILD_SHA } }));
return;
}
if (request.url === "/api/auth/wecom/config") {
response.end(JSON.stringify({
data: {
status: "ready",
entryEnabled: true,
oauthEnabled: true,
defaultReturnTo: "/portal",
loginUrl: "/api/auth/wecom/login",
},
}));
return;
}
if (request.url === "/api/auth/capabilities") {
response.end(JSON.stringify({
data: {
emailDelivery: { ready: true, statusReasons: [] },
emailLoginCode: { enabled: true },
passwordReset: { enabled: true },
},
}));
return;
}
if (request.url === "/") {
response.setHeader("content-type", "text/html; charset=utf-8");
response.end('<!doctype html><html><body>OA<script src="/assets/index-test.js"></script></body></html>');
return;
}
if (request.url === "/assets/index-test.js") {
response.setHeader("content-type", "application/javascript; charset=utf-8");
response.end(`document.documentElement.dataset.buildSha="${EXPECTED_BUILD_SHA}";`);
return;
}
response.statusCode = 404;
response.end(JSON.stringify({ error: "not_found" }));
});
try {
await listen(server);
const address = server.address();
const env = { ...process.env };
for (const key of [
"DATABASE_URL",
"LIVE_SMOKE_ADMIN_IDENTIFIER",
"LIVE_SMOKE_ADMIN_PASSWORD",
"LIVE_SMOKE_EMPLOYEE_IDENTIFIER",
"LIVE_SMOKE_EMPLOYEE_PASSWORD",
"PLAYWRIGHT_OCR_LIVE_IDENTIFIER",
"PLAYWRIGHT_OCR_LIVE_PASSWORD",
"PLAYWRIGHT_OCR_LIVE_FILE",
"PLAYWRIGHT_OCR_LIVE_DIR",
"LIVE_OCR_INVOICE_FILE",
]) {
delete env[key];
}
env.LIVE_SMOKE_BASE_URL = `http://127.0.0.1:${address.port}`;
env.LIVE_SMOKE_READINESS_DIR = reportDir;
env.LIVE_SMOKE_EXPECTED_BUILD_SHA = EXPECTED_BUILD_SHA;
env.QA_ISSUE_ID = "READINESS-TEST";
const result = await execFileAsync("bash", [path.join(ROOT_DIR, "scripts/qa/run-live-smoke-readiness.sh")], {
cwd: ROOT_DIR,
env,
maxBuffer: 2 * 1024 * 1024,
});
const summaryText = await readFile(path.join(reportDir, "summary.json"), "utf8");
const summary = JSON.parse(summaryText);
expect(result.stdout).toContain("[live-readiness][PASS_WITH_FINDINGS]");
expect(summary.status).toBe("PASS_WITH_FINDINGS");
expect(summary.findingCount).toBeGreaterThan(0);
expect(summary.steps.pendingOcr.status).toBe("SKIP");
expect(summary.steps.liveSmoke.status).toBe("SKIP");
expect(summary.steps.liveOcr.status).toBe("SKIP");
expect(summary.steps.privacy.status).toBe("PASS");
expect(summary.expectedBuildSha).toBe(EXPECTED_BUILD_SHA);
expect(summary.observedBuildSha).toBe(EXPECTED_BUILD_SHA);
expect(summary.buildShaMatches).toBe(true);
expect(summary.childReports.pendingOcr.findingCount).toBeGreaterThan(0);
expect(summaryText).not.toContain(ROOT_DIR);
expect(summaryText).not.toMatch(/DATABASE_URL|LIVE_SMOKE_ADMIN_PASSWORD|PLAYWRIGHT_OCR_LIVE_FILE/);
} finally {
await close(server);
await rm(reportDir, { recursive: true, force: true });
}
}, 30_000);
});