Compare commits

...
Author SHA1 Message Date
morluto 11940ac24d fix(verification): preserve source-location shutdown failures 2026-11-11 05:49:21 -05:00
5 changed files with 205 additions and 18 deletions

No files matched your search

@@ -147,10 +147,15 @@ the measured build and selected source-location owners. A later unrelated genera
desktop SQLite diagnostic change and rebuild are outside this snapshot; later
whole-tree hashes do not replace the measured one.
The [portable driver](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/source-location-delivery/task.mjs)
has SHA-256 `b21ec9d5315682337eb55a4ad208e2145a7397d8d567cdb6653facd3f0bec90e`.
The measured revision of the [portable driver](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/source-location-delivery/task.mjs)
had SHA-256 `b21ec9d5315682337eb55a4ad208e2145a7397d8d567cdb6653facd3f0bec90e`.
The [observer](https://github.com/morluto/rea/blob/main/scripts/verify/analysis/budget-calibration-run.py)
has SHA-256 `c18a06c44715c97ec9cbc76248dfbcd862d0e5ca3cd0c159d868bc7fa1fa9e9f`.
The current driver additionally checks the MCP child's final exit and output
closure, preserving both task and shutdown failures. Its shared process-owner
sources are included in new aggregation hashes; the historical measurements
above have not been rerun with this lifecycle correction.
Use the repository's built runtime and pinned Node. Acquisition refuses changed
publisher bytes; the existing acquisition helper creates `artifacts.json` and
analyst manifests under the selected artifact directory.
@@ -170,7 +170,7 @@ def main():
medians = {name: {'runs': len(group), **{field: statistics.median(record[field] for record in group) for field in fields}} for name, group in groups.items()}
assert all(len(group) == 3 for group in groups.values()) and len(groups) == 6
recorded_runtime = json.loads((args.measurement_dir / 'runtime.json').read_text())
paths = ['scripts/verify/analysis/source-location-delivery/task.mjs', 'scripts/verify/analysis/budget-calibration-run.py', 'scripts/rea.mjs', 'dist/cli.js', 'dist/mcp/main.js', 'src/application/WebSourceLocationService.ts', 'src/domain/analysisView/webSourceLocationView.ts', 'src/application/analysisView/AnalysisViewService.ts', 'src/contracts/webScriptToolContracts.ts', 'src/server/ToolResultDelivery.ts', 'src/server/toolResult.ts', 'src/domain/sourceMapLimits.ts', 'src/server/registerWebSourceLocationTool.ts', 'src/cli/webSourceLocationCommands.ts', 'scripts/verify/analysis/source-location-delivery/aggregate.py', 'package-lock.json', 'docs/verification/source-map-budgets-2026-11-09/artifacts.json']
paths = ['scripts/verify/analysis/source-location-delivery/task.mjs', 'scripts/verify/analysis/budget-calibration-run.py', 'scripts/rea.mjs', 'dist/cli.js', 'dist/mcp/main.js', 'src/application/WebSourceLocationService.ts', 'src/domain/analysisView/webSourceLocationView.ts', 'src/application/analysisView/AnalysisViewService.ts', 'src/contracts/webScriptToolContracts.ts', 'src/server/ToolResultDelivery.ts', 'src/server/toolResult.ts', 'src/domain/sourceMapLimits.ts', 'src/server/registerWebSourceLocationTool.ts', 'src/cli/webSourceLocationCommands.ts', 'scripts/verify/analysis/source-location-delivery/aggregate.py', 'package-lock.json', 'docs/verification/source-map-budgets-2026-11-09/artifacts.json', 'scripts/lib/verifier-cleanup.mjs', 'scripts/lib/verifier-source-loader.mjs', 'src/process/ProviderProcess.ts']
hashes = {path: sha256(repo / path) for path in paths}
assert hashes[paths[0]] == recorded_runtime['driver_sha256']
assert hashes[paths[1]] == recorded_runtime['observer_sha256']
@@ -3,6 +3,8 @@ import { readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import assert from "node:assert/strict";
import { isAbsolute, join, relative, resolve, sep } from "node:path";
import { withVerifierCleanup } from "../../../lib/verifier-cleanup.mjs";
import { importVerifierSource } from "../../../lib/verifier-source-loader.mjs";
// Acquire artifactDir with docs/verification/source-map-budgets-2026-11-09/acquire.py.
// This driver runs the public compiled stdio workflow; no target application is executed.
@@ -99,6 +101,10 @@ assert.equal(bytes.length, source.bytes);
assert.equal(createHash("sha256").update(bytes).digest("hex"), source.sha256);
const artifact = JSON.parse(bytes.toString("utf8"));
const manifestPath = localFile(source.generated_script.path + ".manifest.json");
const { ProviderProcessSupervisor } = await importVerifierSource(
new URL("../../../../src/process/ProviderProcess.ts", import.meta.url).href,
import.meta.url,
);
const child = spawn(process.execPath, [join(root, "scripts/rea.mjs"), "mcp"], {
cwd: root,
env: {
@@ -115,6 +121,14 @@ const child = spawn(process.execPath, [join(root, "scripts/rea.mjs"), "mcp"], {
},
stdio: ["pipe", "pipe", "pipe"],
});
const supervisor = new ProviderProcessSupervisor(
{ process: child, ownsProcessLifetime: true },
{
captureStdout: false,
// Protocol and bounded stderr receipts remain owned by this driver.
maxDiagnosticBytes: 0,
},
);
let buffer = "",
sequence = 0,
stderr = "";
@@ -199,7 +213,7 @@ const contentOf = (r) => {
assert(!r.isError, JSON.stringify(errorOf(r)));
return r.structuredContent;
};
try {
await withVerifierCleanup(async () => {
await rpc("initialize", {
protocolVersion: "2025-03-26",
capabilities: {},
@@ -410,17 +424,24 @@ try {
{ flag: "wx" },
);
console.log(JSON.stringify(result));
} finally {
child.stdin.end();
if (child.exitCode === null && child.signalCode === null)
await new Promise((resolve, reject) => {
const timer = setTimeout(() => {
child.kill("SIGTERM");
reject(Error("MCP close timeout"));
}, 5000);
child.once("exit", () => {
clearTimeout(timer);
resolve();
});
});
}
}, [
async () => {
child.stdin.end();
const exited = await supervisor.waitForExit(5000);
const stopped = await supervisor.stop();
if (stopped.status === "incomplete")
throw Object.assign(
new Error(`MCP child cleanup incomplete: ${stopped.reason}`, {
cause: stopped,
}),
{ cleanupOwner: supervisor },
);
assert(exited, "MCP close timeout");
assert(supervisor.observedOutputClose(), "MCP output did not close");
const exit = supervisor.observedExit();
assert(
exit?.code === 0 && exit.signal === null,
`MCP server exited (${String(exit?.code)}, ${String(exit?.signal)}): ${stderr}`,
);
},
]);
@@ -0,0 +1,92 @@
import { createHash } from "node:crypto";
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { processTest } from "../../support/process/processFixture.js";
const hash = (text: string) => createHash("sha256").update(text).digest("hex");
for (const mode of ["clean", "nonzero", "primary-clean", "primary-hang"]) {
processTest(
`source-location driver preserves task and shutdown outcomes: ${mode}`,
async ({ processes }) => {
const root = await createTestTempDirectory("rea-source-location-close-");
await mkdir(join(root, "scripts"));
await copyFile(
resolve("tests/fixtures/sourceLocationDeliveryChild.mjs"),
join(root, "scripts/rea.mjs"),
);
const text = "x".repeat(800);
const map = JSON.stringify({
version: 3,
sources: ["authored.js"],
sourcesContent: [text],
names: [],
mappings: "AAAA",
});
const point = { line: 1, column: 0 };
await writeFile(join(root, "fixture.js.map"), map);
await writeFile(
join(root, "artifacts.json"),
JSON.stringify([
{
path: "fixture.js.map",
url: "https://fixture.invalid/fixture.js.map",
bytes: Buffer.byteLength(map),
sha256: hash(map),
point,
generated_script: { path: "fixture.js" },
expected_matches: [
{
source_index: 0,
source: "authored.js",
original_line: 1,
original_column: 300,
content_sha256: hash(text),
},
],
},
]),
);
const result = await processes.run(
process.execPath,
[
resolve("scripts/verify/analysis/source-location-delivery/task.mjs"),
root,
JSON.stringify({
artifactDir: root,
select: "fixture.",
detail: "complete",
}),
],
{
env: { ...process.env, SOURCE_LOCATION_FIXTURE_MODE: mode },
timeoutMs: 20_000,
},
);
expect(result.signal).toBeNull();
expect(await readFile(join(root, "closed.txt"), "utf8")).toBe("EOF");
if (mode === "clean") {
expect(result.exitCode).toBe(0);
expect(JSON.parse(result.stdout)).toMatchObject({
evidence_id: "fixture-evidence",
source_map_sha256: hash(map),
matches: [{ content: { snippet: { length_utf16: 512 } } }],
});
return;
}
expect(result.exitCode).not.toBe(0);
if (mode === "nonzero") expect(result.stderr).toContain("17");
if (mode.startsWith("primary"))
expect(result.stderr).toContain("controlled primary mapping refusal");
if (mode.endsWith("hang")) {
expect(result.stderr).toContain("MCP close timeout");
expect(await readFile(join(root, "terminated.txt"), "utf8")).toBe(
"TERM",
);
}
},
);
}
+69
View File
@@ -0,0 +1,69 @@
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { createInterface } from "node:readline";
// A protocol fixture for driver shutdown; no REA or target code is loaded.
const root = process.cwd();
const mode = process.env.SOURCE_LOCATION_FIXTURE_MODE;
const source = JSON.parse(
readFileSync(join(root, "artifacts.json"), "utf8"),
)[0];
const map = readFileSync(join(root, source.path), "utf8");
const text = JSON.parse(map).sourcesContent[0];
const input = createInterface({ input: process.stdin });
const reply = (id, result) =>
console.log(JSON.stringify({ jsonrpc: "2.0", id, result }));
// Bound the fixture even if a broken driver never closes its child.
setTimeout(() => process.exit(91), 12_000).unref();
input.on("line", (line) => {
const request = JSON.parse(line);
if (request.id === undefined) return;
if (request.method === "initialize") return reply(request.id, {});
if (request.method === "tools/list")
return reply(request.id, {
tools: [{ name: "trace_web_source_location" }],
});
if (request.method !== "tools/call")
throw new Error(`Unexpected fixture request: ${request.method}`);
reply(request.id, {
content: [],
structuredContent: {
evidence_id: "fixture-evidence",
raw_result: { source_map_text: map },
normalized_result: {
source_map: {
sha256: mode.startsWith("primary")
? "controlled primary mapping refusal"
: source.sha256,
},
lookup: {
requested: source.point,
matched_generated_position: source.point,
},
matches: [
{
state: "mapped",
source_index: 0,
reported_source: "authored.js",
original_position: { line: 1, column: 300, offset: 300 },
content: {
state: "embedded",
text,
utf8_sha256: source.expected_matches[0].content_sha256,
},
},
],
},
},
});
});
input.on("close", () => {
writeFileSync(join(root, "closed.txt"), "EOF");
if (!mode.endsWith("hang")) process.exit(mode === "nonzero" ? 17 : 0);
setInterval(() => {}, 1000);
process.on("SIGTERM", () => {
writeFileSync(join(root, "terminated.txt"), "TERM");
process.exit(0);
});
});