fix: 补齐候选诊断环境与证据保护(09-06-release-gate-integrity)
这个提交包含在:
@@ -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));
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户