mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
6d5532a204
Page retained web-bundle endpoint observations and inferences through shared CLI/MCP projections, preserving source identities, scoped unknowns, coverage and complete Evidence access. Based on HarsH’s original implementation in #7213 (6b1d5d42). Real Chromium/CLI/SDK verification and whole-task cost limits are recorded in docs/verification/web-bundle-paging-2026-10-26.md. Explicit user authorization permits leaving human-readiness declarations unchecked and bypassing reproduced main failures (ADB listing fixture, package catalog expectation, untouched APK verifier formatting). No human-review attestation is made. Co-authored-by: HarsH <harshasalkar7@gmail.com>
383 lines
12 KiB
JavaScript
383 lines
12 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createServer } from "node:http";
|
|
import { readFile, writeFile, mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
import { join, resolve } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { createRequire } from "node:module";
|
|
import { pathToFileURL } from "node:url";
|
|
import { parseArgs } from "node:util";
|
|
import { performance } from "node:perf_hooks";
|
|
import { createVerifierCommands } from "../../scripts/lib/verify-commands.mjs";
|
|
import { withVerifierCleanup } from "../../scripts/lib/verifier-cleanup.mjs";
|
|
// Public CLI capture and same-session retained MCP queries. All assets and output
|
|
// paths are explicitly supplied; this verifier installs no browser or application.
|
|
const { values } = parseArgs({
|
|
options: {
|
|
repository: { type: "string" },
|
|
mode: { type: "string" },
|
|
output: { type: "string" },
|
|
parent: { type: "string" },
|
|
application: { type: "string" },
|
|
browser: { type: "string" },
|
|
task: { type: "string", default: "all" },
|
|
"verify-cli": { type: "boolean", default: false },
|
|
},
|
|
});
|
|
assert.ok(values.repository && values.mode && values.output);
|
|
const repo = resolve(values.repository),
|
|
mode = values.mode,
|
|
out = resolve(values.output),
|
|
bundle = values.parent && resolve(values.parent);
|
|
assert.ok(["capture", "complete", "selected"].includes(mode));
|
|
assert.ok(["all", "first-candidate"].includes(values.task));
|
|
assert.ok(mode === "capture" ? values.application && values.browser : bundle);
|
|
await mkdir(out);
|
|
const require = createRequire(join(repo, "package.json"));
|
|
const { Client } = await import(
|
|
pathToFileURL(require.resolve("@modelcontextprotocol/client"))
|
|
);
|
|
const { StdioClientTransport } = await import(
|
|
pathToFileURL(require.resolve("@modelcontextprotocol/client/stdio"))
|
|
);
|
|
const env = {
|
|
PATH: process.env.PATH,
|
|
NODE_OPTIONS: "--max-old-space-size=3072",
|
|
UV_THREADPOOL_SIZE: "2",
|
|
REA_LOG_LEVEL: "silent",
|
|
REA_MCP_INPUT_SCHEMA_PROFILE: "compact",
|
|
REA_MCP_TOOL_FAMILIES: "application,session",
|
|
};
|
|
const commands =
|
|
mode === "capture" || values["verify-cli"]
|
|
? await createVerifierCommands()
|
|
: undefined;
|
|
if (mode === "capture") {
|
|
const { chromium } = require("playwright-core");
|
|
const assets = resolve(values.application);
|
|
const allowed = new Set(
|
|
await (await import("node:fs/promises")).readdir(assets),
|
|
);
|
|
const server = createServer(async (req, res) => {
|
|
try {
|
|
const name =
|
|
new URL(req.url, "http://127.0.0.1").pathname.slice(1) || "index.html";
|
|
assert.ok(allowed.has(name));
|
|
const data = await readFile(join(assets, name));
|
|
res.setHeader(
|
|
"Content-Type",
|
|
name.endsWith(".js")
|
|
? "text/javascript"
|
|
: name.endsWith(".css")
|
|
? "text/css"
|
|
: name.endsWith(".json")
|
|
? "application/json"
|
|
: "text/html",
|
|
);
|
|
res.setHeader(
|
|
"Content-Type",
|
|
res.getHeader("Content-Type") + "; charset=utf-8",
|
|
);
|
|
res.end(data);
|
|
} catch {
|
|
res.writeHead(404);
|
|
res.end();
|
|
}
|
|
});
|
|
await new Promise((r) => server.listen(0, "127.0.0.1", r));
|
|
const origin = `http://127.0.0.1:${server.address().port}`;
|
|
const userData = await mkdtemp(join(tmpdir(), "rea-6198-chrome-"));
|
|
let context;
|
|
await withVerifierCleanup(async () => {
|
|
context = await chromium.launchPersistentContext(userData, {
|
|
executablePath: resolve(values.browser),
|
|
headless: true,
|
|
args: ["--remote-debugging-port=0", "--no-sandbox"],
|
|
});
|
|
context
|
|
.pages()[0]
|
|
.on("pageerror", (e) => console.error("PAGE ERROR", e.message));
|
|
context
|
|
.pages()[0]
|
|
.on("response", (r) => console.error("RESPONSE", r.status(), r.url()));
|
|
await context.pages()[0].goto(origin, { waitUntil: "networkidle" });
|
|
const port = (
|
|
await readFile(join(userData, "DevToolsActivePort"), "utf8")
|
|
).split("\n")[0];
|
|
const endpoint = `http://127.0.0.1:${port}`;
|
|
const targets = await (await fetch(`${endpoint}/json/list`)).json();
|
|
const target = targets.find(
|
|
(t) => t.type === "page" && t.url === origin + "/",
|
|
);
|
|
assert.ok(target);
|
|
const started = performance.now();
|
|
const { stdout, stderr } = await commands.exec(
|
|
process.execPath,
|
|
[
|
|
join(repo, "scripts/rea.mjs"),
|
|
"analyze-web-bundle",
|
|
endpoint,
|
|
target.id,
|
|
"--observation-ms",
|
|
"1000",
|
|
"--max-heap-mb",
|
|
"1024",
|
|
"--analysis-timeout-ms",
|
|
"60000",
|
|
"--total-analysis-timeout-ms",
|
|
"90000",
|
|
"--detail",
|
|
"summary",
|
|
"--evidence_output",
|
|
join(out, "complete-evidence.json"),
|
|
"--view",
|
|
JSON.stringify({
|
|
kind: "web-bundle",
|
|
collection: "inferences",
|
|
limit: 1,
|
|
}),
|
|
"--format",
|
|
"json",
|
|
],
|
|
{ cwd: repo, env, maxBuffer: 64 * 1024 ** 2, timeout: 120000 },
|
|
);
|
|
await writeFile(join(out, "stderr.txt"), stderr);
|
|
const delivery = JSON.parse(stdout);
|
|
const view = delivery.summary.normalized_result;
|
|
const complete = JSON.parse(
|
|
await readFile(join(out, "complete-evidence.json"), "utf8"),
|
|
);
|
|
const evidence = complete.records.find(
|
|
(row) => row.evidence_id === view.parent_evidence_id,
|
|
);
|
|
assert.ok(evidence);
|
|
assert.equal(evidence.operation, "analyze_web_bundle");
|
|
assert.ok(evidence.normalized_result.observations);
|
|
assert.deepEqual(
|
|
view.items,
|
|
evidence.normalized_result.inferences
|
|
.slice(0, 1)
|
|
.map((row, source_index) => ({ source_index, ...row })),
|
|
);
|
|
assert.deepEqual(view.source_unknowns, evidence.normalized_result.unknowns);
|
|
await writeFile(join(out, "delivery.json"), stdout);
|
|
await writeFile(join(out, "parent.json"), JSON.stringify(evidence));
|
|
// Public export/import format comes from the CLI's complete Evidence publication.
|
|
console.log(
|
|
JSON.stringify({
|
|
capture_ms: performance.now() - started,
|
|
evidence_id: evidence.evidence_id,
|
|
counts: {
|
|
endpoints: evidence.normalized_result.observations.endpoints.length,
|
|
inferences: evidence.normalized_result.inferences.length,
|
|
},
|
|
completeness: evidence.normalized_result.completeness,
|
|
}),
|
|
);
|
|
}, [
|
|
() => commands.close(),
|
|
async () => {
|
|
await context?.close();
|
|
await rm(userData, { recursive: true, force: true });
|
|
},
|
|
() =>
|
|
new Promise((done, reject) =>
|
|
server.close((error) => (error ? reject(error) : done())),
|
|
),
|
|
]);
|
|
process.exit(0);
|
|
}
|
|
const parent = JSON.parse(await readFile(bundle, "utf8"));
|
|
const report = parent.normalized_result;
|
|
const { createEvidenceBundle } = await import(
|
|
pathToFileURL(join(repo, "dist/domain/evidenceBundle.js"))
|
|
);
|
|
const bundleFile = join(out, "input-bundle.json");
|
|
await writeFile(bundleFile, JSON.stringify(createEvidenceBundle([parent])));
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [join(repo, "scripts/rea.mjs"), "mcp"],
|
|
cwd: repo,
|
|
env,
|
|
stderr: "pipe",
|
|
});
|
|
transport.stderr?.resume();
|
|
const client = new Client({
|
|
name: "rea-web-bundle-paging-verifier",
|
|
version: "1",
|
|
});
|
|
const calls = [];
|
|
let catalog;
|
|
async function invoke(name, args) {
|
|
const toolDefinition = catalog.tools.find((t) => t.name === name);
|
|
assert.ok(toolDefinition);
|
|
const start = performance.now();
|
|
const result = await client.callTool(
|
|
{ name, arguments: args },
|
|
{ toolDefinition, timeout: 90000 },
|
|
);
|
|
calls.push({
|
|
name,
|
|
arguments: args,
|
|
result,
|
|
elapsed_ms: performance.now() - start,
|
|
});
|
|
assert.notEqual(result.isError, true, JSON.stringify(result));
|
|
return result.structuredContent;
|
|
}
|
|
await withVerifierCleanup(async () => {
|
|
await client.connect(transport);
|
|
catalog = await client.listTools();
|
|
await writeFile(join(out, "catalog.json"), JSON.stringify(catalog));
|
|
const runs = [];
|
|
for (const temperature of ["cold", "warm"]) {
|
|
const start = performance.now();
|
|
const callStart = calls.length;
|
|
if (temperature === "cold")
|
|
await invoke("import_evidence_bundle", { path: bundleFile });
|
|
if (mode === "complete") {
|
|
const full = await invoke("get_evidence_bundle", {});
|
|
const actual = full.result.records.find(
|
|
(r) => r.evidence_id === parent.evidence_id,
|
|
);
|
|
assert.deepEqual(actual, parent);
|
|
runs.push({
|
|
temperature,
|
|
elapsed_ms: performance.now() - start,
|
|
calls: calls.length - callStart,
|
|
});
|
|
continue;
|
|
}
|
|
const source = {
|
|
kind: "retained-evidence",
|
|
evidence_id: parent.evidence_id,
|
|
};
|
|
if (values.task === "all") {
|
|
const summary = await invoke("inspect_analysis_view", {
|
|
source,
|
|
view: { kind: "web-bundle", collection: "summary" },
|
|
});
|
|
assert.equal(
|
|
summary.normalized_result.summary.counts.endpoints,
|
|
report.observations.endpoints.length,
|
|
);
|
|
}
|
|
if (values.task === "first-candidate") {
|
|
const value = await invoke("inspect_analysis_view", {
|
|
source,
|
|
view: {
|
|
kind: "web-bundle",
|
|
collection: "inferences",
|
|
kinds: ["endpoint_candidate"],
|
|
limit: 1,
|
|
},
|
|
});
|
|
const index = report.inferences.findIndex(
|
|
(row) => row.kind === "endpoint_candidate",
|
|
);
|
|
assert.equal(
|
|
value.normalized_result.coverage.total,
|
|
report.inferences.filter((row) => row.kind === "endpoint_candidate")
|
|
.length,
|
|
);
|
|
assert.deepEqual(
|
|
value.normalized_result.items,
|
|
index < 0 ? [] : [{ source_index: index, ...report.inferences[index] }],
|
|
);
|
|
assert.deepEqual(
|
|
value.normalized_result.source_unknowns,
|
|
report.unknowns,
|
|
);
|
|
} else
|
|
for (const collection of ["endpoints", "inferences"]) {
|
|
const expected =
|
|
collection === "endpoints"
|
|
? report.observations.endpoints
|
|
: report.inferences;
|
|
const rows = [];
|
|
let offset = 0;
|
|
do {
|
|
const value = await invoke("inspect_analysis_view", {
|
|
source,
|
|
view: { kind: "web-bundle", collection, offset, limit: 32 },
|
|
});
|
|
const page = value.normalized_result;
|
|
assert.deepEqual(page.source_unknowns, report.unknowns);
|
|
assert.deepEqual(page.completeness, report.completeness);
|
|
assert.equal(page.coverage.total, expected.length);
|
|
for (const row of page.items) {
|
|
assert.deepEqual(row, {
|
|
source_index: rows.length,
|
|
...expected[rows.length],
|
|
});
|
|
rows.push(row);
|
|
}
|
|
if (page.coverage.exhausted) break;
|
|
assert.ok(page.coverage.next_offset > offset);
|
|
offset = page.coverage.next_offset;
|
|
} while (true);
|
|
assert.equal(rows.length, expected.length);
|
|
}
|
|
runs.push({
|
|
temperature,
|
|
elapsed_ms: performance.now() - start,
|
|
calls: calls.length - callStart,
|
|
});
|
|
}
|
|
if (mode === "selected" && values["verify-cli"]) {
|
|
for (const collection of ["endpoints", "inferences"]) {
|
|
const input = JSON.stringify({
|
|
source: { kind: "evidence-file", path: bundle },
|
|
view: {
|
|
kind: "web-bundle",
|
|
collection,
|
|
limit: 32,
|
|
},
|
|
});
|
|
const { stdout } = await commands.exec(
|
|
process.execPath,
|
|
[
|
|
join(repo, "scripts/rea.mjs"),
|
|
"inspect-analysis-view",
|
|
input,
|
|
"--format",
|
|
"json",
|
|
],
|
|
{ cwd: repo, env, maxBuffer: 64 * 1024 ** 2, timeout: 90000 },
|
|
);
|
|
const cli = JSON.parse(stdout);
|
|
assert.deepEqual(
|
|
cli.normalized_result.items,
|
|
(collection === "endpoints"
|
|
? report.observations.endpoints
|
|
: report.inferences
|
|
)
|
|
.slice(0, 32)
|
|
.map((r, source_index) => ({
|
|
source_index,
|
|
...r,
|
|
})),
|
|
);
|
|
assert.deepEqual(cli.normalized_result.source_unknowns, report.unknowns);
|
|
await writeFile(join(out, `cli-${collection}.json`), stdout);
|
|
}
|
|
}
|
|
await writeFile(join(out, "calls.json"), JSON.stringify(calls));
|
|
await writeFile(
|
|
join(out, "metrics.json"),
|
|
JSON.stringify(
|
|
{
|
|
mode,
|
|
task: values.task,
|
|
runs,
|
|
counts: {
|
|
endpoints: report.observations.endpoints.length,
|
|
inferences: report.inferences.length,
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
console.log(JSON.stringify({ mode, task: values.task, runs }));
|
|
}, [() => commands?.close(), () => client.close()]);
|