diff --git a/tools/web/run-wbv2-continuous.mjs b/tools/web/run-wbv2-continuous.mjs index 09277f427..aac21369e 100644 --- a/tools/web/run-wbv2-continuous.mjs +++ b/tools/web/run-wbv2-continuous.mjs @@ -24,10 +24,11 @@ function waitMs(milliseconds) { } function command(command, logPath) { + const started = Date.now(); const result = spawnSync("bash", ["-lc", command], { cwd: root, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }); const output = `${result.stdout ?? ""}${result.stderr ?? ""}`; if (logPath) fs.writeFileSync(abs(logPath), output); - return { code: result.status ?? 1, output }; + return { code: result.status ?? 1, durationMs: Date.now() - started, output }; } function currentTask() { @@ -62,6 +63,20 @@ function assertContext() { if (result.code !== 0) throw new Error(`context gate failed (exit ${result.code})`); } +function typecheckProven(task) { + try { + const manifest = JSON.parse(read(`tests/golden/${task}/manifest.json`)); + const receiptPath = manifest.artifacts?.moduleReceipt?.path; + if (!receiptPath) return false; + const receipt = JSON.parse(read(receiptPath)); + return receipt.status === "PASS" && receipt.source?.unchanged === true + && receipt.commands?.some((entry) => entry.exitCode === 0 && entry.command.trim() === "npm --prefix web run typecheck"); + } + catch { + return false; + } +} + let iterations = 0; const summary = []; while (maxIterations === 0 || iterations < maxIterations) { @@ -82,7 +97,10 @@ while (maxIterations === 0 || iterations < maxIterations) { const after = currentTask(); const terminal = after.task === before.task && terminalTask(before.task); if (after.task === before.task && !terminal) throw new Error(`${before.task} focused command exited 0 without advancing queue; log=${logPath}`); - const gates = command("node tools/web/check-context-governance.mjs && node tools/web/check-execution-control-plane.mjs --allow-unrunnable && node tools/web/check-v2-task-index.mjs && node tools/web/check-v2-handoff.mjs && npm --prefix web run typecheck && npm --prefix web run lint && git diff --check", `tests/golden/${before.task}/continuous-gates.log`); + const typecheckCommand = typecheckProven(before.task) + ? "printf 'typecheck reused from hash-bound module receipt\\n'" + : "npm --prefix web run typecheck"; + const gates = command(`node tools/web/check-context-governance.mjs && node tools/web/check-execution-control-plane.mjs --allow-unrunnable && node tools/web/check-v2-task-index.mjs && node tools/web/check-v2-handoff.mjs && ${typecheckCommand} && npm --prefix web run lint && git diff --check`, `tests/golden/${before.task}/continuous-gates.log`); if (gates.code !== 0) throw new Error(`${before.task} post-handoff gate failed (exit ${gates.code}); log=tests/golden/${before.task}/continuous-gates.log`); iterations += 1; summary.push({ task: before.task, command: focused, nextTask: terminal ? null : after.task, terminal, log: logPath }); diff --git a/tools/web/run-wbv2-module-task.mjs b/tools/web/run-wbv2-module-task.mjs index 0e39ce87f..fbf7637ba 100644 --- a/tools/web/run-wbv2-module-task.mjs +++ b/tools/web/run-wbv2-module-task.mjs @@ -108,13 +108,14 @@ const reusableProductionEvidence = () => { 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` }); + results.push({ command, exitCode: 0, reused: true, durationMs: 0, output: `reused existing Chromium production evidence: ${reusedEvidence.evidencePath}\n` }); } } else { for (const command of record.module.commands ?? []) { + const started = Date.now(); 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 }); + results.push({ command, exitCode: result.status ?? 1, durationMs: Date.now() - started, output }); if ((result.status ?? 1) !== 0) { failed = true; break; } } } @@ -131,9 +132,9 @@ const report = { source: { paths: record.module.sourcePaths, before: sourceBefore, after: sourceAfter, unchanged: sourceUnchanged }, reusedReceipts, cases: { positive: record.module.positive, negative: record.module.negative, recovery: record.module.recovery }, - commands: results.map(({ command, exitCode }) => ({ command, exitCode })), + commands: results.map(({ command, exitCode, durationMs, reused }) => ({ command, exitCode, durationMs, ...(reused ? { reused: true } : {}) })), 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" }, + efficiency: { existingEvidenceReused: Boolean(reusedEvidence), skippedCommands: reusedEvidence ? results.length : 0, totalDurationMs: results.reduce((total, result) => total + (result.durationMs ?? 0), 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, }; diff --git a/tools/web/search-wbv2-batch-source.mjs b/tools/web/search-wbv2-batch-source.mjs index ee38a5947..0eb5b8955 100644 --- a/tools/web/search-wbv2-batch-source.mjs +++ b/tools/web/search-wbv2-batch-source.mjs @@ -30,6 +30,7 @@ for (const source of taskRecord?.module?.sourcePaths ?? []) { } const runtimeAllowlist = new Set([ "tools/web/run-wbv2-module-task.mjs", + "tools/web/run-wbv2-continuous.mjs", "tools/web/bind-wbv2-module-coverage.mjs", "tools/web/finalize-wbv2-module-batch.mjs", "tools/web/check-source-dynamic-inventory.mjs",