perf(web): centralize batch handoff rotation
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-09-03 15:50:39 +08:00
parent d602a13e2b
commit 07e63efa25
6 changed files with 78 additions and 30 deletions

View File

@@ -451,7 +451,6 @@ def main() -> int:
# The root is a completed queue-enable record. Preparing its context again
# wastes budget and can fail solely because the parent manifest grew; only
# the active implementation batch needs a packet.
run_node("tools/web/check-task-context.mjs", "--task", module_task, "--write")
print(f"wbv2-successor-authorized root={root_task} next={module_task} module={batch['moduleId']} batchSize={len(batch['units'])} reused={len(batch.get('candidateUnits', batch['units'])) - len(batch['units'])} inventoryDelta=0")
return 0

View File

@@ -22,7 +22,8 @@ const inputPaths = [
parentManifest,
`tests/golden/${task}/task-context.json`,
];
const inputHash = digest(inputPaths.map((file) => `${file}\0${digest(read(file))}\n`).join(""));
const inputHashFor = () => digest(inputPaths.map((file) => `${file}\0${fs.existsSync(abs(file)) ? digest(read(file)) : "MISSING"}\n`).join(""));
const inputHash = inputHashFor();
let cached = null;
try { cached = JSON.parse(fs.readFileSync(cachePath, "utf8")); }
catch { /* First use or an interrupted write. */ }
@@ -31,14 +32,15 @@ if (!process.argv.includes("--force") && cached?.status === "PASS" && cached.tas
process.exit(0);
}
const result = spawnSync(process.execPath, [abs("tools/web/print-task-context.mjs"), "--task", task], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
const check = spawnSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
const check = spawnSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
if ((result.status ?? 1) !== 0 || (check.status ?? 1) !== 0) {
process.stderr.write(`${result.stdout ?? ""}${result.stderr ?? ""}${check.stdout ?? ""}${check.stderr ?? ""}`);
process.exit(Math.max(result.status ?? 1, check.status ?? 1));
}
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
const payload = { schemaVersion: 1, status: "PASS", task, inputHash, refreshedAt: new Date().toISOString() };
const refreshedInputHash = inputHashFor();
const payload = { schemaVersion: 1, status: "PASS", task, inputHash: refreshedInputHash, refreshedAt: new Date().toISOString() };
const temporary = `${cachePath}.tmp-${process.pid}`;
fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`);
fs.renameSync(temporary, cachePath);
process.stdout.write(`wbv2-context-cache-refresh task=${task} inputHash=${inputHash}\n`);
process.stdout.write(`wbv2-context-cache-refresh task=${task} inputHash=${refreshedInputHash}\n`);

View File

@@ -58,11 +58,6 @@ function taskCommand(task) {
return match[1];
}
function assertContext() {
const result = command("node tools/web/check-wbv2-context-cache.mjs", "tests/golden/WBV2/continuous-context.log");
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`));
@@ -81,7 +76,6 @@ let iterations = 0;
const summary = [];
while (maxIterations === 0 || iterations < maxIterations) {
const before = currentTask();
assertContext();
// A completed V2 plan intentionally leaves the final task as an auditable
// pointer. Do not replay its implementation command on later unattended
// passes; close the runner cleanly at the terminal boundary.
@@ -112,7 +106,6 @@ while (maxIterations === 0 || iterations < maxIterations) {
: [];
const handoffPaths = [
"docs/EXECUTION_QUEUE.md",
"nextTask.md",
`docs/tasks/${before.task}.md`,
...(terminal ? [] : [`docs/tasks/${after.task}.md`]),
`docs/status/${before.task}.md`,

View File

@@ -153,7 +153,6 @@ if (failed || !sourceUnchanged) {
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- 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) }]));
const artifacts = { parentManifest: { path: parentPath, sha256: sha(parentPath) }, ...Object.fromEntries(record.module.sourcePaths.map((source, item) => [`source${item + 1}`, { path: source, sha256: sha(source) }])), ...evidenceArtifacts, moduleReceipt: { path: reportPath, sha256: sha(reportPath) }, status: { path: statusPath, sha256: sha(statusPath) }, log: { path: logPath, sha256: sha(logPath) }, plan: { path: index.source.path, sha256: sha(index.source.path) }, taskContext: { path: `${out}/task-context.json`, sha256: sha(`${out}/task-context.json`) } };
@@ -162,14 +161,5 @@ if (failed || !sourceUnchanged) {
execFileSync(process.execPath, [abs("tools/web/bind-wbv2-module-coverage.mjs"), "--task", task], { cwd: root, stdio: "inherit" });
execFileSync(process.execPath, [abs("tools/web/finalize-wbv2-module-batch.mjs"), "--task", task], { cwd: root, stdio: "inherit" });
}
if (nextTask) execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", nextTask, "--write"], { cwd: root, stdio: "inherit" });
let next = read("nextTask.md");
const tick = String.fromCharCode(96);
const snapshotTask = nextTask ?? task;
const snapshotState = nextTask ? "in_progress" : "done";
const snapshotNext = nextTask ? plan.tasks[plan.tasks.findIndex((candidate) => candidate.id === nextTask) + 1]?.id ?? "NONE" : "NONE";
const snapshotCommand = nextTask ? `node tools/web/run-wbv2-module-task.mjs --task ${nextTask} --write` : "node tools/web/check-wbv2-module-execution.mjs";
next = next.replace(/\| Current task when this file was written \| `[^`]+` \|/u, `| Current task when this file was written | ${tick}${snapshotTask}${tick} |`).replace(/\| Current task state \| `[^`]+` \|/u, `| Current task state | ${tick}${snapshotState}${tick} |`).replace(/\| Parent manifest \| `[^`]+` \|/u, `| Parent manifest | ${tick}tests/golden/${task}/manifest.json${tick} |`).replace(/\| Current task card \| `[^`]+` \|/u, `| Current task card | ${tick}docs/tasks/${snapshotTask}.md${tick} |`).replace(/\| Current task-context nextTask \| `[^`]+` \|/u, `| Current task-context nextTask | ${tick}${snapshotNext}${tick} |`).replace(/\| Focused command \| `[^`]+` \|/u, `| Focused command | ${tick}${snapshotCommand}${tick} |`);
writeText("nextTask.md", next);
console.log(`module-task-complete task=${task} module=${record.module.moduleId} next=${nextTask} commands=${results.length}`);
}

View File

@@ -1,4 +1,5 @@
import fs from "node:fs";
import crypto from "node:crypto";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
@@ -79,6 +80,57 @@ function read(file) {
return fs.readFileSync(abs(file), "utf8");
}
function refreshTaskContext(task) {
const result = spawnSync(process.execPath, [abs("tools/web/check-wbv2-context-cache.mjs"), "--task", task], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
if ((result.status ?? 1) !== 0) throw new Error(`context rotation failed for ${task}: ${result.stderr ?? result.stdout ?? "unknown error"}`);
}
function refreshContinuation(task) {
const continuationPath = abs("nextTask.md");
const markerPath = abs("tests/golden/WBV2/continuation-snapshot.json");
if (!fs.existsSync(continuationPath)) return false;
const pointer = currentTask();
const index = JSON.parse(read("tests/golden/WBV2/task-index.json"));
const plan = JSON.parse(read(index.source.path));
const taskIndex = plan.tasks.findIndex((candidate) => candidate.id === task);
const terminal = terminalTask(task);
const nextTask = terminal ? null : plan.tasks[taskIndex + 1]?.id ?? null;
const inputFiles = ["docs/EXECUTION_QUEUE.md", `docs/tasks/${task}.md`, pointer.parentManifest, `tests/golden/${task}/task-context.json`];
const fingerprint = crypto.createHash("sha256").update(inputFiles.map((file) => `${file}\0${crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex")}\n`).join("")).digest("hex");
try {
const marker = JSON.parse(fs.readFileSync(markerPath, "utf8"));
if (marker.task === task && marker.fingerprint === fingerprint) return false;
}
catch { /* First rotation or an interrupted marker write. */ }
const tick = String.fromCharCode(96);
let next = fs.readFileSync(continuationPath, "utf8");
const snapshotTask = task;
const snapshotState = terminal ? "done" : "in_progress";
const snapshotNext = nextTask ?? "NONE";
const snapshotCommand = nextTask ? `node tools/web/run-wbv2-module-task.mjs --task ${nextTask} --write` : "node tools/web/check-wbv2-module-execution.mjs";
next = next.replace(/\| Current task when this file was written \| `[^`]+` \|/u, `| Current task when this file was written | ${tick}${snapshotTask}${tick} |`).replace(/\| Current task state \| `[^`]+` \|/u, `| Current task state | ${tick}${snapshotState}${tick} |`).replace(/\| Parent manifest \| `[^`]+` \|/u, `| Parent manifest | ${tick}tests/golden/${task}/manifest.json${tick} |`).replace(/\| Current task card \| `[^`]+` \|/u, `| Current task card | ${tick}docs/tasks/${snapshotTask}.md${tick} |`).replace(/\| Current task-context nextTask \| `[^`]+` \|/u, `| Current task-context nextTask | ${tick}${snapshotNext}${tick} |`).replace(/\| Focused command \| `[^`]+` \|/u, `| Focused command | ${tick}${snapshotCommand}${tick} |`);
fs.writeFileSync(continuationPath, next);
const marker = { schemaVersion: 1, task, fingerprint, refreshedAt: new Date().toISOString(), nextTask: snapshotNext };
const temporary = `${markerPath}.tmp-${process.pid}`;
fs.writeFileSync(temporary, `${JSON.stringify(marker, null, 2)}\n`);
fs.renameSync(temporary, markerPath);
return true;
}
function commitContinuation() {
const add = spawnSync("git", ["add", "-f", "nextTask.md", "tests/golden/WBV2/continuation-snapshot.json"], { cwd: root, encoding: "utf8" });
if ((add.status ?? 1) !== 0) throw new Error(`continuation staging failed: ${add.stderr ?? add.stdout ?? "unknown error"}`);
const pending = spawnSync("git", ["diff", "--cached", "--quiet"], { cwd: root, encoding: "utf8" });
if ((pending.status ?? 0) === 1) {
const commit = spawnSync("git", ["commit", "-m", "chore(web): rotate continuation snapshot"], { cwd: root, encoding: "utf8" });
if ((commit.status ?? 1) !== 0) throw new Error(`continuation commit failed: ${commit.stderr ?? commit.stdout ?? "unknown error"}`);
if (pushEnabled) {
const push = spawnSync("git", ["push", "origin", "main"], { cwd: root, encoding: "utf8" });
if ((push.status ?? 1) !== 0) throw new Error(`continuation push failed: ${push.stderr ?? push.stdout ?? "unknown error"}`);
}
}
}
function currentTask() {
const queue = read("docs/EXECUTION_QUEUE.md");
const task = queue.match(/\| 当前任务 \| `(WBV2-P\d+-\d+)` \|/u)?.[1];
@@ -250,6 +302,16 @@ try {
process.stdout.write(`scheduler-preflight-ready task=${before.task} class=${readiness.taskClass}\n`);
break;
}
try {
refreshTaskContext(before.task);
}
catch (error) {
recordFailure(root, { task: before.task, phase: "scheduler", reason: "context_rotation_failed", detail: error?.message ?? String(error) });
writeCheckpoint({ state: "blocked", iteration, before, reason: "context_rotation_failed", error: error?.message ?? String(error) });
process.stderr.write(`scheduler-blocked task=${before.task} reason=context_rotation_failed\n`);
exitCode = 2;
break;
}
writeCheckpoint({ state: "running", iteration: iteration + 1, before, readiness });
const child = runOne(before);
const after = currentTask();
@@ -268,6 +330,16 @@ try {
exitCode = 3;
break;
}
try {
if (refreshContinuation(after?.task ?? before.task) && commitEnabled) commitContinuation();
}
catch (error) {
recordFailure(root, { task: before.task, phase: "scheduler", reason: "continuation_rotation_failed", detail: error?.message ?? String(error) });
writeCheckpoint({ state: "blocked", iteration, before, after, reason: "continuation_rotation_failed", error: error?.message ?? String(error) });
process.stderr.write(`scheduler-blocked task=${before.task} reason=continuation_rotation_failed\n`);
exitCode = 5;
break;
}
if (after && after.task === before.task && terminalTask(before.task)) {
writeCheckpoint({ state: "complete", iteration: iteration + 1, before, after, childExit: child.code, reason: "terminal_module_complete", childLog: child.outputPath });
completed = true;

View File

@@ -148,14 +148,6 @@ try {
break;
}
const before = currentTask();
const context = run(process.execPath, ["tools/web/check-wbv2-context-cache.mjs", "--task", before.task]);
appendLog(`preflight task=${before.task} contextExit=${context.code}`);
if (context.code !== 0) {
recordFailure(root, { task: before.task, phase: "supervisor", reason: "preflight_failed", childExit: context.code });
writeCheckpoint({ state: "blocked", iteration, current: before, reason: "preflight_failed", contextExit: context.code });
exitCode = 2;
break;
}
writeCheckpoint({ state: "running", iteration: iteration + 1, before, child: childArgs.join(" ") });
const child = run(process.execPath, childArgs);
appendLog(`child task=${before.task} exit=${child.code}\n${child.output}`);