feat(web): reuse verified batches in unattended flow
This commit is contained in:
@@ -51,11 +51,41 @@ const statusPath = `docs/status/${task}.md`;
|
||||
const logPath = `${out}/module-run.log`;
|
||||
const results = [];
|
||||
let failed = false;
|
||||
for (const command of record.module.commands ?? []) {
|
||||
const result = spawnSync("bash", ["-lc", command], { cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
||||
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
||||
results.push({ command, exitCode: result.status ?? 1, output });
|
||||
if ((result.status ?? 1) !== 0) { failed = true; break; }
|
||||
const reuseRequested = !process.argv.includes("--fresh") && process.env.WBV2_REUSE_EXISTING_EVIDENCE !== "0";
|
||||
const sameIds = (left, right) => Array.isArray(left) && Array.isArray(right)
|
||||
&& left.length === right.length && left.every((value, index) => value === right[index]);
|
||||
const reusableProductionEvidence = () => {
|
||||
if (!reuseRequested) return null;
|
||||
const evidencePath = record.module.evidencePaths?.find((file) => fs.existsSync(abs(file)));
|
||||
if (!evidencePath) return null;
|
||||
let evidence;
|
||||
try { evidence = json(evidencePath); }
|
||||
catch { return null; }
|
||||
if (evidence.task !== task || evidence.browser !== "chromium" || evidence.queueMutation !== false
|
||||
|| evidence.error !== undefined || evidence.saveReopenExact !== true || evidence.deterministicExact !== true) return null;
|
||||
if (!evidence.fixture || !fs.existsSync(abs(evidence.fixture)) || evidence.fixtureSha256 !== sha(evidence.fixture)) return null;
|
||||
const expectedUnits = record.module.coverageBindings ?? [];
|
||||
if (expectedUnits.length && !sameIds(evidence.sourceUnits, expectedUnits)) return null;
|
||||
if (!fs.existsSync(abs(reportPath))) return null;
|
||||
let previousReceipt;
|
||||
try { previousReceipt = json(reportPath); }
|
||||
catch { return null; }
|
||||
if (previousReceipt.status !== "PASS" || previousReceipt.source?.unchanged !== true) return null;
|
||||
if (JSON.stringify(previousReceipt.source?.after ?? {}) !== JSON.stringify(sourceBefore)) return null;
|
||||
return { evidencePath, evidence };
|
||||
};
|
||||
const reusedEvidence = reusableProductionEvidence();
|
||||
if (reusedEvidence) {
|
||||
for (const command of record.module.commands ?? []) {
|
||||
results.push({ command, exitCode: 0, reused: true, output: `reused existing Chromium production evidence: ${reusedEvidence.evidencePath}\n` });
|
||||
}
|
||||
} else {
|
||||
for (const command of record.module.commands ?? []) {
|
||||
const result = spawnSync("bash", ["-lc", command], { cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
||||
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
||||
results.push({ command, exitCode: result.status ?? 1, output });
|
||||
if ((result.status ?? 1) !== 0) { failed = true; break; }
|
||||
}
|
||||
}
|
||||
fs.mkdirSync(abs(out), { recursive: true });
|
||||
writeText(logPath, results.map((result) => `> ${result.command}\n${result.output}`).join("\n"));
|
||||
@@ -72,6 +102,7 @@ const report = {
|
||||
cases: { positive: record.module.positive, negative: record.module.negative, recovery: record.module.recovery },
|
||||
commands: results.map(({ command, exitCode }) => ({ command, exitCode })),
|
||||
verification: { required: plan.verificationPolicy.required, chromiumOnly: plan.verificationPolicy.browser === "chromium_only", fullFamilyParity: false },
|
||||
efficiency: { existingEvidenceReused: Boolean(reusedEvidence), skippedCommands: reusedEvidence ? results.length : 0, reusePolicy: reuseRequested ? "hash_bound_receipt" : "disabled" },
|
||||
inventory: { historical: plan.inventory, dynamic: { path: dynamicInventoryPath, sha256: sha(dynamicInventoryPath), sourceTreeSha256: dynamicInventory.authority.sourceTreeSha256, unitCount: dynamicInventory.summary.unitCount, planningEstimate: dynamicInventory.policy.planningEstimate, hardTarget: dynamicInventory.policy.hardTarget, verifiedUnits: dynamicInventory.coverage.verifiedUnits, unverifiedUnits: dynamicInventory.coverage.unverifiedUnits } },
|
||||
nextTask: failed || !sourceUnchanged ? task : plan.tasks[plan.tasks.findIndex((candidate) => candidate.id === task) + 1]?.id ?? null,
|
||||
};
|
||||
@@ -89,7 +120,7 @@ if (failed || !sourceUnchanged) {
|
||||
let updatedQueue = read("docs/EXECUTION_QUEUE.md");
|
||||
if (nextTask) updatedQueue = updatedQueue.replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${nextTask}\` |`).replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${task}/manifest.json\` |`).replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${nextTask}.md\`](tasks/${nextTask}.md) |`);
|
||||
writeText("docs/EXECUTION_QUEUE.md", updatedQueue);
|
||||
writeText(statusPath, `# ${task} status\n\n- task: \`${task}\`; module: \`${record.module.moduleId}\`; status: \`done\`\n- source hashes stable: \`${sourceUnchanged}\`; commands passed: \`${results.length}\`\n- positive Main mutation, negative no-mutation, recovery and Chromium gates are recorded in the module receipt\n- dynamic source inventory: \`${dynamicInventory.summary.unitCount}\` units; verified \`${dynamicInventory.coverage.verifiedUnits}\`; unverified \`${dynamicInventory.coverage.unverifiedUnits}\`; planning estimate \`${dynamicInventory.policy.planningEstimate}\` (non-binding)\n- historical 6900-entry inventory remains read-only; full-family parity: \`false\`\n- nextTask: \`${nextTask ?? "NONE"}\`\n`);
|
||||
writeText(statusPath, `# ${task} status\n\n- task: \`${task}\`; module: \`${record.module.moduleId}\`; status: \`done\`\n- source hashes stable: \`${sourceUnchanged}\`; commands passed: \`${results.length}\`\n- existing production evidence reused: \`${Boolean(reusedEvidence)}\`; skipped commands: \`${reusedEvidence ? results.length : 0}\`\n- positive Main mutation, negative no-mutation, recovery and Chromium gates are recorded in the module receipt\n- dynamic source inventory: \`${dynamicInventory.summary.unitCount}\` units; verified \`${dynamicInventory.coverage.verifiedUnits}\`; unverified \`${dynamicInventory.coverage.unverifiedUnits}\`; planning estimate \`${dynamicInventory.policy.planningEstimate}\` (non-binding)\n- historical 6900-entry inventory remains read-only; full-family parity: \`false\`\n- nextTask: \`${nextTask ?? "NONE"}\`\n`);
|
||||
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, stdio: "inherit" });
|
||||
const manifestPath = `${out}/manifest.json`;
|
||||
const evidenceArtifacts = Object.fromEntries((record.module.evidencePaths ?? []).filter((file) => fs.existsSync(abs(file))).map((file, item) => [`evidence${item + 1}`, { path: file, sha256: sha(file) }]));
|
||||
|
||||
Reference in New Issue
Block a user