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

这个提交包含在:
2026-07-16 05:17:38 +08:00
父节点 abb6a22f5c
当前提交 28e8e746b7
共修改 29 个文件,包含 1849 行新增和 233 行删除
+49 -3
查看文件
@@ -3,6 +3,22 @@ import { existsSync, readdirSync, statSync } from "node:fs";
import { extname } from "node:path";
const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").trim().replace(/\/$/, "");
const expectedBuildShaInput = String(process.env.LIVE_SMOKE_EXPECTED_BUILD_SHA ?? "").trim();
function normalizeBuildSha(value) {
const normalized = String(value ?? "").trim().toLowerCase();
if (normalized === "unknown") {
return "unknown";
}
return /^[0-9a-f]{7,64}$/.test(normalized) ? normalized.slice(0, 12) : null;
}
function buildShaMatches(expected, observed) {
if (!expected || !observed || observed === "unknown") {
return null;
}
return expected.startsWith(observed) || observed.startsWith(expected);
}
function present(key) {
return Boolean(process.env[key]?.trim());
@@ -108,7 +124,7 @@ function addMissing(checks) {
checks.missing.push("ocrOrEmployeeCredentialReady");
}
if (!checks.liveOcr.invoiceSampleReady) {
checks.missing.push("PLAYWRIGHT_OCR_LIVE_FILE 或 PLAYWRIGHT_OCR_LIVE_DIR 中的 PDF 样票");
checks.missing.push("invoiceSampleReady");
}
}
@@ -121,12 +137,40 @@ async function main() {
const ready = await fetchJson(`${baseUrl}/ready`);
const wecomConfig = await fetchJson(`${baseUrl}/api/auth/wecom/config`);
const authCapabilities = await fetchJson(`${baseUrl}/api/auth/capabilities`);
const findings = checks.missing.map((code) => ({ code, severity: "manual_or_config" }));
if (!ready.ok) findings.push({ code: "publicReady", severity: "blocker" });
const expectedBuildSha = normalizeBuildSha(expectedBuildShaInput);
const observedBuildSha = normalizeBuildSha(
ready.json?.data?.buildSha ?? ready.json?.error?.details?.buildSha,
);
const buildMatches = buildShaMatches(expectedBuildSha, observedBuildSha);
if (ready.ok && (!observedBuildSha || observedBuildSha === "unknown")) {
findings.push({ code: "publicBuildShaUnavailable", severity: "manual_or_config" });
} else if (ready.ok && expectedBuildSha && buildMatches === false) {
findings.push({ code: "publicBuildShaMismatch", severity: "manual_or_config" });
}
if (expectedBuildShaInput && !expectedBuildSha) {
findings.push({ code: "expectedBuildShaInvalid", severity: "manual_or_config" });
}
if (!wecomConfig.ok) findings.push({ code: "wecomConfigReady", severity: "blocker" });
if (!authCapabilities.ok) findings.push({ code: "authCapabilitiesReady", severity: "blocker" });
const status = findings.some((finding) => finding.severity === "blocker")
? "FAIL"
: findings.length
? "PASS_WITH_FINDINGS"
: "PASS";
const result = {
status,
findingCount: findings.length,
findings,
checkedAt: new Date().toISOString(),
...checks,
publicReady: {
ok: ready.ok,
status: ready.status,
expectedBuildSha,
observedBuildSha,
buildShaMatches: buildMatches,
},
wecomConfig: {
ok: wecomConfig.ok,
@@ -135,7 +179,7 @@ async function main() {
entryEnabled: wecomConfig.json?.data?.entryEnabled ?? null,
oauthEnabled: wecomConfig.json?.data?.oauthEnabled ?? null,
defaultReturnTo: wecomConfig.json?.data?.defaultReturnTo ?? null,
loginUrl: wecomConfig.json?.data?.loginUrl ?? null,
loginUrlReady: Boolean(wecomConfig.json?.data?.loginUrl),
},
authCapabilities: {
ok: authCapabilities.ok,
@@ -150,8 +194,10 @@ async function main() {
if (jsonOnly) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(`[live-smoke-env] status=${result.status} findings=${result.findingCount}`);
console.log(`[live-smoke-env] base=${result.baseUrl}`);
console.log(`[live-smoke-env] publicReady=${result.publicReady.ok} status=${result.publicReady.status}`);
console.log(`[live-smoke-env] buildShaObserved=${result.publicReady.observedBuildSha ?? "unavailable"} match=${result.publicReady.buildShaMatches ?? "not_checked"}`);
console.log(`[live-smoke-env] wecom=${result.wecomConfig.statusText ?? "unknown"} entry=${result.wecomConfig.entryEnabled} oauth=${result.wecomConfig.oauthEnabled}`);
console.log(`[live-smoke-env] authCapabilities=${result.authCapabilities.ok} emailDelivery=${result.authCapabilities.emailDeliveryReady}`);
console.log(`[live-smoke-env] liveSmokeReady=${result.liveSmokeReady}`);
@@ -161,7 +207,7 @@ async function main() {
}
}
if (strict && (!result.liveSmokeReady || !result.liveOcrReady || !result.publicReady.ok || !result.wecomConfig.ok || !result.authCapabilities.ok)) {
if (strict && result.status !== "PASS") {
process.exit(1);
}
}