fix: 补齐候选诊断环境与证据保护(09-06-release-gate-integrity)

这个提交包含在:
2026-09-08 07:21:19 +08:00
父节点 66f25b1fa8
当前提交 99167127b5
共修改 33 个文件,包含 2915 行新增和 67 行删除
+39
查看文件
@@ -1,11 +1,47 @@
#!/usr/bin/env node
import fs from "node:fs";
import { createHash } from "node:crypto";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = process.cwd();
const registeredEvidence = [{
document: ".trellis/tasks/archive/2026-07/07-18-small-team-internal-readiness/evidence.md",
directory: "output/playwright/07-18-small-team-internal-readiness",
count: 14,
}];
export function validateRegisteredEvidence(root = repoRoot, registrations = registeredEvidence) {
const result = { checked: 0, errors: [] };
for (const registration of registrations) {
let lines;
try { lines = fs.readFileSync(path.join(root, registration.document), "utf8").split(/\r?\n/); }
catch { result.errors.push(`evidence_document_missing:${registration.document}`); continue; }
const header = lines.findIndex(line => /^\|\s*截图\s*\|\s*SHA-256\s*\|\s*$/i.test(line));
if (header < 0 || !/^\|\s*-+\s*\|\s*-+\s*\|\s*$/.test(lines[header + 1] ?? "")) { result.errors.push(`evidence_table_missing:${registration.document}`); continue; }
const rows = [];
for (let index = header + 2; index < lines.length && lines[index].startsWith("|"); index += 1) rows.push(lines[index]);
if (rows.length !== registration.count) result.errors.push(`evidence_count_mismatch:${registration.document}`);
const names = new Set();
for (const row of rows) {
const match = row.match(/^\|\s*`([^`]+)`\s*\|\s*`([a-f0-9]{64})`\s*\|\s*$/i);
if (!match || !/^[\w.-]+\.png$/.test(match[1]) || match[1].startsWith(".") || names.has(match[1])) { result.errors.push(`evidence_row_invalid:${registration.document}`); continue; }
names.add(match[1]);
const relative = path.join(registration.directory, match[1]);
const target = path.join(root, relative);
try {
if (!fs.lstatSync(target).isFile() || fs.realpathSync(target) !== path.join(fs.realpathSync(root), relative)) { result.errors.push(`evidence_not_regular_file:${relative}`); continue; }
const observed = createHash("sha256").update(fs.readFileSync(target)).digest("hex");
if (observed !== match[2].toLowerCase()) result.errors.push(`evidence_digest_mismatch:${relative}`);
else result.checked += 1;
} catch { result.errors.push(`evidence_file_missing_or_unreadable:${relative}`); }
}
}
return result;
}
const requiredSpecs = [
".trellis/spec/hyb-oa/index.md",
".trellis/spec/hyb-oa/project-governance.md",
@@ -342,6 +378,9 @@ export function runGovernanceChecks() {
checkDocsRegistration(result);
checkRemovedProjectSkillReferences(result);
checkSensitiveData(result);
const evidence = validateRegisteredEvidence();
for (const error of evidence.errors) fail(result, "evidence.sha256", error);
if (!evidence.errors.length) pass(result, "evidence.sha256", `${evidence.checked} registered local evidence files verified`);
} catch (error) {
fail(result, "exception", error instanceof Error ? error.message : String(error));
}