fix(acceptance): 阻断本地控制面漂移污染验收

本地 K3s 节点此前在 Kubelet 中登记为宿主机资源,负载可挤压 etcd 并在 server 重启后继续污染同一 Run。现在为三节点设置真实可调度预算、独立 etcd/数据卷和锁定依赖镜像,并持续核对 server 与 API/etcd 健康。\n\n每次验收生成独立 Run ID,报告使用独占或原子写入,负载错误记录具体阶段;数据库鉴权不可用返回带 Retry-After 的 503,避免基础设施故障被误报为 401。\n\n验证:Go 全量测试、go vet、gofmt、OpenAPI、bash -n、ShellCheck、报告测试和 k3d 配置解析均通过。
This commit is contained in:
2026-07-31 23:07:19 +08:00
parent 015ff8ea6c
commit a95184b5b6
20 changed files with 970 additions and 85 deletions
+36 -10
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { createHash } from 'node:crypto';
import { lstat, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
import { lstat, mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
const shaPattern = /^[0-9a-f]{40}$/;
@@ -26,6 +26,29 @@ async function regularJSON(path) {
return JSON.parse(await readFile(absolute, 'utf8'));
}
async function writeNewJSON(path, value) {
const absolute = resolve(path);
await mkdir(dirname(absolute), { recursive: true, mode: 0o700 });
await writeFile(absolute, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
}
async function replaceJSONAtomically(input, output, value) {
const inputPath = resolve(input);
const outputPath = resolve(output);
if (inputPath !== outputPath) {
await writeNewJSON(outputPath, value);
return;
}
const temporary = `${outputPath}.tmp-${process.pid}-${Date.now()}`;
try {
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
await rename(temporary, outputPath);
} catch (error) {
await unlink(temporary).catch(() => {});
throw error;
}
}
function assertSecretSafe(value, path = '$') {
if (Array.isArray(value)) {
value.forEach((item, index) => assertSecretSafe(item, `${path}[${index}]`));
@@ -85,6 +108,8 @@ async function loadReports(directory) {
profile: report.profile,
passed: report.passed,
phases: report.phases,
failure: report.failure,
failureOperation: report.failureOperation,
startedAt: report.startedAt,
finishedAt: report.finishedAt
});
@@ -150,8 +175,7 @@ async function buildLocal(values) {
secretSafe: true
};
validate(report);
await mkdir(dirname(resolve(values.output)), { recursive: true, mode: 0o700 });
await writeFile(resolve(values.output), `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
await writeNewJSON(values.output, report);
process.stdout.write(`acceptance_report_local=PASS sha256=${createHash('sha256').update(JSON.stringify(report)).digest('hex')}\n`);
}
@@ -172,6 +196,10 @@ async function buildLocalPartial(values) {
throw new Error('partial local report inputs have incompatible CAS identity');
}
const completedLoadsPassed = loads.every((item) => item.passed === true);
const failureGate = values['failure-gate'] ?? 'local_execution_incomplete';
if (!/^[a-z0-9_]+$/.test(failureGate)) {
throw new Error('--failure-gate must be a stable lowercase gate ID');
}
const report = {
schemaVersion: 'acceptance-report/v1',
runId: runtime.runId,
@@ -202,7 +230,7 @@ async function buildLocalPartial(values) {
} : null,
gates: [
{
id: 'local_execution_complete',
id: failureGate,
passed: false,
detail: `stopped during ${values['failure-phase']}`
},
@@ -221,8 +249,7 @@ async function buildLocalPartial(values) {
secretSafe: true
};
validate(report);
await mkdir(dirname(resolve(values.output)), { recursive: true, mode: 0o700 });
await writeFile(resolve(values.output), `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
await writeNewJSON(values.output, report);
process.stdout.write('acceptance_report_local_partial=PASS\n');
}
@@ -259,8 +286,7 @@ async function mergeProduction(values) {
};
local.createdAt = new Date().toISOString();
validate(local);
await mkdir(dirname(resolve(values.output)), { recursive: true, mode: 0o700 });
await writeFile(resolve(values.output), `${JSON.stringify(local, null, 2)}\n`, { mode: 0o600 });
await writeNewJSON(values.output, local);
process.stdout.write(`acceptance_report_production=PASS promotion_ready=${local.promotion.ready}\n`);
}
@@ -285,7 +311,7 @@ if (command === 'validate') {
}
report.promotion.trafficMode = 'live';
report.promotion.promotedAt = new Date().toISOString();
await writeFile(resolve(output), `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
await replaceJSONAtomically(input, output, report);
process.stdout.write('acceptance_report_promoted=PASS\n');
} else if (command === 'attach-monitor') {
const input = values.input ?? '';
@@ -309,7 +335,7 @@ if (command === 'validate') {
report.promotion.ready = false;
report.promotion.trafficMode = 'validation';
}
await writeFile(resolve(output), `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
await replaceJSONAtomically(input, output, report);
process.stdout.write(`acceptance_report_monitor=PASS monitor_passed=${monitor.passed === true}\n`);
} else {
throw new Error('usage: report.mjs {validate|build-local|build-local-partial|merge-production|mark-promoted|attach-monitor} [options]');