fix(playable): close release validation lifecycle gaps

This commit is contained in:
shen
2026-09-11 21:23:44 +08:00
parent f39a55fdd5
commit cc5573ddc1
13 changed files with 230 additions and 7 deletions
+19 -2
View File
@@ -56,10 +56,16 @@ function argsOf(argv) {
}
function readJson(file) {
try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
try {
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
if (!isObject(value)) return { __read_error: `${path.basename(file)}: root must be an object` };
return value;
}
catch (error) { return { __read_error: `${path.basename(file)}: ${error.code || 'invalid JSON'}` }; }
}
function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
function secretLiterals() {
return ['MT_ACCOUNT', 'MT_PASSWORD'].map((name) => process.env[name] || '').filter((v) => v.length >= SECRET_MIN_SCAN);
}
@@ -160,7 +166,8 @@ function validateReport(report, options, errors) {
for (const line of lines) {
let event;
try { event = JSON.parse(line); } catch { badEvents += 1; continue; }
if (!EVENT_KEYS.every((key) => Object.hasOwn(event, key)) || event.run_id !== options.run_id
if (!isObject(event) || !EVENT_KEYS.every((key) => Object.hasOwn(event, key)) || event.run_id !== options.run_id
|| !Number.isSafeInteger(event.monotonic_us) || event.monotonic_us < 0
|| typeof event.payload !== 'object' || event.payload === null || Array.isArray(event.payload)
|| !Number.isInteger(event.connection_epoch) || event.monotonic_us < lastUs) badEvents += 1;
else lastUs = event.monotonic_us;
@@ -273,6 +280,16 @@ function sealRelease(dir) {
if (report.run_id !== run.run_id) errors.fail.push(`${label}: report run_id mismatch`);
if (report.suite !== run.suite) errors.fail.push(`${label}: report suite mismatch`);
if (report.exit_gate?.checked !== true || report.exit_gate?.process_code !== 0 || report.exit_gate?.timed_out !== false) errors.fail.push(`${label}: report not sealed by a clean exit gate`);
// The client exit gate runs before the external RSS seal. An interrupted
// or crashed memory sealer must not leave a releasable intermediate PASS.
if (run.suite === 'soak' && report.status === 'PASS') {
const memoryCases = Array.isArray(report.runner_cases)
? report.runner_cases.filter((item) => item?.id === 'STB-MEMORY-01') : [];
if (memoryCases.length !== 1 || memoryCases[0].status !== 'PASS'
|| report.memory?.verdict?.status !== 'PASS') {
errors.fail.push(`${label}: STB-MEMORY-01 external memory seal missing or not PASS`);
}
}
checkBuild(report.build, manifest.candidate, `${label}: `, errors);
if (report.status === 'PASS') passedBySuite[run.suite] = (passedBySuite[run.suite] || 0) + 1;
else if (report.status === 'BLOCKED') {