fix(acceptance): 支持显式跳过本地验收

在用户明确授权时,将本地原生与 amd64 制品阶段记录为 skipped/waived,并直接使用当前生产脱敏快照进入线上模拟。修复线上报告合并未写入生产 Run ID 导致后续 promote CAS 必然失败的问题。已通过报告回归测试、bash -n、ShellCheck 和 diff 检查。
This commit is contained in:
2026-07-31 23:15:22 +08:00
parent a95184b5b6
commit 0b681275ed
4 changed files with 147 additions and 29 deletions
+59 -3
View File
@@ -86,13 +86,66 @@ function validate(report) {
throw new Error('acceptance report stages, gates, or promotion are invalid');
}
for (const [name, stage] of Object.entries(report.stages)) {
if (!['passed', 'failed', 'not-run'].includes(stage?.status)) {
if (!['passed', 'failed', 'not-run', 'skipped'].includes(stage?.status)) {
throw new Error(`invalid stage status for ${name}`);
}
}
assertSecretSafe(report);
}
async function buildOnlineWaiver(values) {
for (const key of ['release-sha', 'api-digest', 'snapshot', 'waiver-reason', 'output']) {
if (!values[key]) throw new Error(`--${key} is required`);
}
const snapshot = await regularJSON(values.snapshot);
if (!shaPattern.test(values['release-sha']) || !digestPattern.test(values['api-digest']) ||
snapshot.schemaVersion !== 'acceptance-snapshot/v1' ||
!shaPattern.test(snapshot.source?.releaseSha ?? '') ||
!hashPattern.test(snapshot.source?.configHash ?? '') ||
!hashPattern.test(snapshot.snapshotSha256 ?? '') ||
!/^[a-z0-9_]{3,80}$/.test(values['waiver-reason'])) {
throw new Error('online waiver identity fields or reason are invalid');
}
const report = {
schemaVersion: 'acceptance-report/v1',
runId: `online-waiver-${values['release-sha'].slice(0, 12)}`,
release: {
sourceSha: values['release-sha'],
apiImageDigest: values['api-digest'],
workerImageDigest: values['api-digest']
},
snapshot: {
sourceReleaseSha: snapshot.source.releaseSha,
configHash: snapshot.source.configHash,
sha256: snapshot.snapshotSha256
},
stages: {
localNative: { status: 'skipped', waiverReason: values['waiver-reason'] },
amd64Artifact: { status: 'skipped', waiverReason: values['waiver-reason'] },
onlineSimulation: { status: 'not-run' },
realCanary: { status: 'not-run' }
},
certifiedProfile: null,
gates: [
{
id: 'local_acceptance_explicitly_waived',
passed: true,
detail: values['waiver-reason']
}
],
promotion: {
ready: false,
requiresManualConfirmation: true,
trafficMode: 'not-applicable'
},
createdAt: new Date().toISOString(),
secretSafe: true
};
validate(report);
await writeNewJSON(values.output, report);
process.stdout.write('acceptance_report_online_waiver=PASS\n');
}
async function loadReports(directory) {
const entries = (await readdir(resolve(directory), { withFileTypes: true }))
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
@@ -260,12 +313,13 @@ async function mergeProduction(values) {
const local = await regularJSON(values['local-report']);
const production = await regularJSON(values['production-summary']);
validate(local);
if (local.release.sourceSha !== production.releaseSha ||
if (!production.runId || local.release.sourceSha !== production.releaseSha ||
local.release.apiImageDigest !== production.apiImageDigest ||
local.snapshot.configHash !== production.snapshotConfigHash ||
local.snapshot.sha256 !== production.snapshotSha256) {
throw new Error('production summary does not match local acceptance CAS fields');
}
local.runId = production.runId;
local.stages.onlineSimulation = {
status: production.passed === true ? 'passed' : 'failed',
stableCapacityProfile: production.stableCapacityProfile,
@@ -299,6 +353,8 @@ if (command === 'validate') {
await buildLocal(values);
} else if (command === 'build-local-partial') {
await buildLocalPartial(values);
} else if (command === 'build-online-waiver') {
await buildOnlineWaiver(values);
} else if (command === 'merge-production') {
await mergeProduction(values);
} else if (command === 'mark-promoted') {
@@ -338,5 +394,5 @@ if (command === 'validate') {
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]');
throw new Error('usage: report.mjs {validate|build-local|build-local-partial|build-online-waiver|merge-production|mark-promoted|attach-monitor} [options]');
}