mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
Preserve applicable artifact processing phases and entered/unvisited status after interruption. Centralize JavaScript semantic applicability for full and incremental builders; retain native byte identity, actual JavaScript unknowns and structured-file provenance. Fixes #3948. Part of #4027. Validation:78 distinct focused cases across10files, full static/docs, and real Joplin timeout/default public CLI/MCP controls passed. Source pins, every attempt/outlier and compact resource/token projections are retained. No target/native provider execution or speedup claim. Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
483 lines
18 KiB
JavaScript
483 lines
18 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import assert from "node:assert/strict";
|
|
import { createHash, randomUUID } from "node:crypto";
|
|
import { createReadStream } from "node:fs";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { isAbsolute, join } from "node:path";
|
|
import { performance } from "node:perf_hooks";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parseArgs } from "node:util";
|
|
import { Client } from "@modelcontextprotocol/client";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
import { createEvidence } from "../../../dist/domain/evidence.js";
|
|
import { PrivateRuntimeRoot } from "../../../dist/process/PrivateRuntimeRoot.js";
|
|
import {
|
|
OwnedCommandFailure,
|
|
runOwnedCommand,
|
|
} from "../../../dist/process/OwnedCommand.js";
|
|
import { closeVerifierResources } from "../../lib/verifier-cleanup.mjs";
|
|
import {
|
|
matchesStructuredScopeExpectation,
|
|
structuredScopeExpectationsSchema,
|
|
} from "../../lib/javascript-file-scope-oracle.mjs";
|
|
|
|
const { values } = parseArgs({
|
|
options: {
|
|
artifact: { type: "string" },
|
|
"addon-path": { type: "string" },
|
|
"native-sha256": { type: "string" },
|
|
"source-path": { type: "string", multiple: true },
|
|
"structured-path": { type: "string", multiple: true },
|
|
"structured-expectations": { type: "string" },
|
|
"validate-structured-from-export": { type: "boolean", default: false },
|
|
"analysis-timeout-ms": { type: "string" },
|
|
"expect-timeout": { type: "boolean", default: false },
|
|
rounds: { type: "string", default: "3" },
|
|
"cli-parity": { type: "boolean", default: false },
|
|
"tokenizer-python": { type: "string" },
|
|
"require-safe": { type: "boolean", default: false },
|
|
},
|
|
strict: true,
|
|
});
|
|
assert.ok(
|
|
values.artifact && isAbsolute(values.artifact),
|
|
"--artifact must be absolute",
|
|
);
|
|
assert.ok(
|
|
values["addon-path"] &&
|
|
!isAbsolute(values["addon-path"]) &&
|
|
values["addon-path"]
|
|
.split("/")
|
|
.every((part) => part !== "" && part !== "." && part !== ".."),
|
|
"--addon-path must be a normalized archive-relative path",
|
|
);
|
|
assert.match(
|
|
values["native-sha256"] ?? "",
|
|
/^[a-f0-9]{64}$/,
|
|
"--native-sha256 pins prior native bytes; it does not prove availability",
|
|
);
|
|
const rounds = Number(values.rounds);
|
|
assert.ok(Number.isSafeInteger(rounds) && rounds > 0);
|
|
const analysisTimeoutMs =
|
|
values["analysis-timeout-ms"] === undefined
|
|
? undefined
|
|
: Number(values["analysis-timeout-ms"]);
|
|
assert.ok(
|
|
analysisTimeoutMs === undefined ||
|
|
(Number.isSafeInteger(analysisTimeoutMs) && analysisTimeoutMs > 0),
|
|
);
|
|
assert.ok(!values["expect-timeout"] || analysisTimeoutMs !== undefined);
|
|
assert.ok(!values["validate-structured-from-export"] || values["cli-parity"]);
|
|
const structuredExpectations = structuredScopeExpectationsSchema.parse(
|
|
JSON.parse(values["structured-expectations"] ?? "{}"),
|
|
);
|
|
for (const path of Object.keys(structuredExpectations))
|
|
assert.ok(
|
|
values["structured-path"]?.includes(path),
|
|
`Expected structured path was not selected for verification: ${path}`,
|
|
);
|
|
if (values["expect-timeout"])
|
|
for (const path of values["structured-path"] ?? [])
|
|
assert.ok(
|
|
Object.hasOwn(structuredExpectations, path),
|
|
`--structured-expectations must name the expected operation/status for ${path}`,
|
|
);
|
|
const environment = {
|
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
REA_LOG_LEVEL: "silent",
|
|
REA_ANALYSIS_PROVIDER: "auto",
|
|
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-deep-provider/hopper",
|
|
};
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [fileURLToPath(new URL("../../rea.mjs", import.meta.url)), "mcp"],
|
|
env: environment,
|
|
stderr: "pipe",
|
|
});
|
|
const client = new Client({
|
|
name: "javascript-native-handoff-verifier",
|
|
version: "1",
|
|
});
|
|
const serialize = (value) => JSON.stringify(value);
|
|
const digest = (value) =>
|
|
createHash("sha256").update(serialize(value)).digest("hex");
|
|
const fileDigest = async (path) => {
|
|
const hash = createHash("sha256");
|
|
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
return hash.digest("hex");
|
|
};
|
|
const simulatedNativeEvidence = (path, sha256) =>
|
|
createEvidence(
|
|
{ path, sha256, format: "raw-binary" },
|
|
{ id: "ghidra", name: "Ghidra", version: "simulated" },
|
|
{
|
|
operation: "binary_overview",
|
|
parameters: {},
|
|
result: {
|
|
summary:
|
|
"Simulated subject-linkage input; no native provider executed.",
|
|
},
|
|
limitations: ["No native provider behavior is established."],
|
|
},
|
|
);
|
|
let temporary;
|
|
let primaryError;
|
|
let retainTemporary = false;
|
|
let report;
|
|
const ownedCommand = async (command, arguments_, timeoutMs = 60_000) => {
|
|
try {
|
|
return await runOwnedCommand(
|
|
{
|
|
command,
|
|
arguments: arguments_,
|
|
cwd: temporary.path,
|
|
env: environment,
|
|
runId: `rea-native-handoff-${randomUUID()}`,
|
|
expectedCommand: command === process.execPath ? process.execPath : null,
|
|
sweepTokenOwnedProcesses: true,
|
|
},
|
|
{ timeoutMs, diagnosticBytes: 16 * 1024 * 1024 },
|
|
);
|
|
} catch (cause) {
|
|
retainTemporary ||=
|
|
cause instanceof OwnedCommandFailure && cause.cleanupOwner !== undefined;
|
|
throw cause;
|
|
}
|
|
};
|
|
try {
|
|
const archiveSha256 = await fileDigest(values.artifact);
|
|
const companion = join(`${values.artifact}.unpacked`, values["addon-path"]);
|
|
let companionSha256 = null;
|
|
try {
|
|
companionSha256 = await fileDigest(companion);
|
|
} catch (cause) {
|
|
if (cause?.code !== "ENOENT") throw cause;
|
|
}
|
|
if (companionSha256 !== null)
|
|
assert.equal(companionSha256, values["native-sha256"]);
|
|
const native = simulatedNativeEvidence(companion, values["native-sha256"]);
|
|
const container = simulatedNativeEvidence(values.artifact, archiveSha256);
|
|
await client.connect(transport);
|
|
transport.stderr?.on("data", () => undefined);
|
|
const tools = (await client.listTools()).tools;
|
|
const catalog = tools.map(({ name, description, inputSchema }) => ({
|
|
name,
|
|
description,
|
|
inputSchema,
|
|
}));
|
|
const selected = catalog.filter(({ name }) =>
|
|
["analyze_javascript_application", "trace_application_feature"].includes(
|
|
name,
|
|
),
|
|
);
|
|
assert.equal(selected.length, 2);
|
|
const call = async (name, arguments_, allowTimeout = false) => {
|
|
const response = await client.callTool(
|
|
{ name, arguments: arguments_ },
|
|
{ timeout: 360_000 },
|
|
);
|
|
let projection = response.structuredContent;
|
|
if (response.isError === true) {
|
|
assert.ok(
|
|
allowTimeout,
|
|
serialize({ name, arguments: arguments_, response }),
|
|
);
|
|
const text = response.content.find(({ type }) => type === "text");
|
|
assert.ok(text?.type === "text");
|
|
projection = JSON.parse(text.text);
|
|
assert.equal(projection.error.code, "provider_timeout");
|
|
assert.equal(
|
|
projection.error.details.partial_observation.kind,
|
|
"retained-evidence",
|
|
);
|
|
} else assert.ok(projection?.evidence_id);
|
|
return {
|
|
request: { name, arguments: arguments_ },
|
|
response: projection,
|
|
wire_response: response,
|
|
};
|
|
};
|
|
const prepStart = performance.now();
|
|
const preparation = await call(
|
|
"analyze_javascript_application",
|
|
{
|
|
input_path: values.artifact,
|
|
detail: "summary",
|
|
...(analysisTimeoutMs === undefined
|
|
? {}
|
|
: { analysis_timeout_ms: analysisTimeoutMs }),
|
|
...(values["source-path"] === undefined
|
|
? {}
|
|
: { source_paths: values["source-path"] }),
|
|
},
|
|
values["expect-timeout"],
|
|
);
|
|
const preparationMs = performance.now() - prepStart;
|
|
const timedOut = preparation.wire_response.isError === true;
|
|
assert.equal(timedOut, values["expect-timeout"]);
|
|
if (!timedOut)
|
|
assert.equal(preparation.response.subject.digest.sha256, archiveSha256);
|
|
const sourceId = timedOut
|
|
? preparation.response.error.details.partial_observation.evidence_id
|
|
: preparation.response.normalized_result.parent_evidence_id;
|
|
assert.match(sourceId, /^ev_[a-f0-9]{64}$/);
|
|
const query = {
|
|
application: { kind: "retained-evidence", evidence_id: sourceId },
|
|
native_observations: [container, native],
|
|
seed: {
|
|
kind: "string",
|
|
value: values["addon-path"],
|
|
match: "exact",
|
|
case_sensitive: true,
|
|
},
|
|
direction: "outgoing",
|
|
};
|
|
const samples = [];
|
|
let first;
|
|
for (let round = 0; round < rounds; round++) {
|
|
const start = performance.now();
|
|
const result = await call("trace_application_feature", query);
|
|
const elapsedMs = performance.now() - start;
|
|
if (first === undefined) first = result;
|
|
else assert.equal(result.response.evidence_id, first.response.evidence_id);
|
|
samples.push({
|
|
round,
|
|
elapsed_ms: elapsedMs,
|
|
structured_reply_bytes: Buffer.byteLength(serialize(result.response)),
|
|
full_tool_reply_bytes: Buffer.byteLength(serialize(result.wire_response)),
|
|
});
|
|
}
|
|
const trace = first.response.normalized_result;
|
|
const addons = trace.graph.nodes.filter(
|
|
({ kind }) => kind === "native-addon",
|
|
);
|
|
assert.equal(
|
|
addons.length,
|
|
1,
|
|
"The selected native add-on must remain visible",
|
|
);
|
|
const addon = addons[0];
|
|
const unavailable = addon.observations.some(
|
|
({ properties }) => properties.availability === "unavailable",
|
|
);
|
|
const expectedLinks = [
|
|
sourceId,
|
|
...(companionSha256 === null ? [] : [native.evidence_id]),
|
|
].sort();
|
|
const safeIdentity =
|
|
serialize(trace.evidence_links) === serialize(expectedLinks) &&
|
|
(companionSha256 === null
|
|
? unavailable &&
|
|
addon.identity.strategy !== "content-digest" &&
|
|
trace.coverage.status === "partial" &&
|
|
trace.native_handoffs.length === 0
|
|
: addon.identity.strategy === "content-digest" &&
|
|
addon.identity.sha256 === companionSha256 &&
|
|
trace.native_handoffs.length === 1 &&
|
|
trace.native_handoffs[0].artifact_sha256 === companionSha256 &&
|
|
trace.native_handoffs[0].status === "evidence-linked" &&
|
|
serialize(trace.native_handoffs[0].evidence_ids) ===
|
|
serialize([native.evidence_id]));
|
|
const analysisScopes = (result, path) =>
|
|
result.graph.nodes.flatMap((node) =>
|
|
node.kind !== "unknown"
|
|
? []
|
|
: node.observations.flatMap((observation) =>
|
|
observation.properties.path === path &&
|
|
observation.properties.analysis_failure !== undefined
|
|
? [{ node_id: node.node_id, ...observation }]
|
|
: [],
|
|
),
|
|
);
|
|
const nativeScopes = analysisScopes(trace, values["addon-path"]);
|
|
const structuredQueries = [];
|
|
const structuredScopes = [];
|
|
for (const path of values["validate-structured-from-export"]
|
|
? []
|
|
: (values["structured-path"] ?? [])) {
|
|
const result = await call("trace_application_feature", {
|
|
...query,
|
|
native_observations: [],
|
|
seed: { ...query.seed, value: path },
|
|
});
|
|
structuredQueries.push(result);
|
|
structuredScopes.push({
|
|
source: "retained-feature-trace",
|
|
path,
|
|
scopes: analysisScopes(result.response.normalized_result, path),
|
|
evidence_id: result.response.evidence_id,
|
|
source_coverage: result.response.normalized_result.coverage,
|
|
graph_coverage: result.response.normalized_result.graph.coverage,
|
|
});
|
|
}
|
|
let cliParity = null;
|
|
if (values["cli-parity"]) {
|
|
temporary = await PrivateRuntimeRoot.create({
|
|
prefix: "rea-native-handoff-verifier-",
|
|
});
|
|
const bundlePath = join(temporary.path, "bundle.json");
|
|
const exported = await client.callTool({
|
|
name: "export_evidence_bundle",
|
|
arguments: { path: bundlePath },
|
|
});
|
|
assert.notEqual(exported.isError, true, serialize(exported));
|
|
const bundle = JSON.parse(await readFile(bundlePath, "utf8"));
|
|
const parent = bundle.records.find(
|
|
({ evidence_id }) => evidence_id === sourceId,
|
|
);
|
|
assert.ok(parent, "Export omitted the retained parent");
|
|
assert.equal(parent.normalized_result.root_artifact_sha256, archiveSha256);
|
|
if (values["validate-structured-from-export"])
|
|
for (const path of values["structured-path"] ?? []) {
|
|
const retained = parent.normalized_result;
|
|
const matchingNodes = retained.graph.nodes.filter((node) =>
|
|
node.observations.some(({ properties }) => properties.path === path),
|
|
);
|
|
assert.ok(matchingNodes.length > 0, `Missing retained path: ${path}`);
|
|
// Diagnostic validation of the complete backend record is distinct
|
|
// from the inline native analyst task used by this control's tokens.
|
|
structuredScopes.push({
|
|
source: "exported-parent-validation",
|
|
path,
|
|
scopes: analysisScopes(retained, path),
|
|
source_evidence_id: parent.evidence_id,
|
|
retained_matching_nodes: matchingNodes.length,
|
|
graph_coverage: retained.graph.coverage,
|
|
});
|
|
}
|
|
const request = join(temporary.path, "trace.json");
|
|
// The CLI's portable file contract takes complete Evidence; MCP resolves a
|
|
// same-session reference into the same shared application workflow.
|
|
await writeFile(request, serialize({ ...query, application: parent }));
|
|
const result = await ownedCommand(
|
|
process.execPath,
|
|
[
|
|
fileURLToPath(new URL("../../rea.mjs", import.meta.url)),
|
|
"trace-application-feature",
|
|
request,
|
|
"--json",
|
|
],
|
|
120_000,
|
|
);
|
|
const evidence = JSON.parse(result.stdout.text);
|
|
assert.deepEqual(evidence.normalized_result, trace);
|
|
assert.equal(evidence.evidence_id, first.response.evidence_id);
|
|
cliParity = {
|
|
exact_cli_mcp_trace_parity: true,
|
|
evidence_id: evidence.evidence_id,
|
|
};
|
|
}
|
|
const safeApplicability =
|
|
nativeScopes.length === 0 &&
|
|
structuredScopes.every(({ path, scopes }) =>
|
|
timedOut
|
|
? matchesStructuredScopeExpectation(
|
|
scopes,
|
|
structuredExpectations[path],
|
|
)
|
|
: scopes.length === 0,
|
|
);
|
|
let tokenCosts = null;
|
|
if (values["tokenizer-python"]) {
|
|
temporary ??= await PrivateRuntimeRoot.create({
|
|
prefix: "rea-native-handoff-verifier-",
|
|
});
|
|
const input = join(temporary.path, "token-input.json");
|
|
await writeFile(
|
|
input,
|
|
serialize({
|
|
catalog,
|
|
selected,
|
|
preparation,
|
|
queries: [first, ...structuredQueries],
|
|
}),
|
|
);
|
|
const result = await ownedCommand(values["tokenizer-python"], [
|
|
"-c",
|
|
`import json,sys,tiktoken
|
|
with open(sys.argv[1],encoding='utf-8') as f:d=json.load(f)
|
|
enc=tiktoken.get_encoding('cl100k_base')
|
|
def n(v):return len(enc.encode(json.dumps(v,ensure_ascii=False,separators=(',',':')),disallowed_special=()))
|
|
requests=n(d['preparation']['request'])+sum(n(q['request']) for q in d['queries'])
|
|
structured=n(d['preparation']['response'])+sum(n(q['response']) for q in d['queries'])
|
|
full_reply=n(d['preparation']['wire_response'])+sum(n(q['wire_response']) for q in d['queries'])
|
|
s=n(d['selected']);a=n(d['catalog'])
|
|
print(json.dumps(dict(tokenizer='cl100k_base',tiktoken_version=tiktoken.__version__,selected_catalog=s,full_catalog=a,requests=requests,structured_replies=structured,full_tool_replies=full_reply,selected_catalog_structured_task=s+requests+structured,full_catalog_structured_task=a+requests+structured,selected_catalog_full_reply_task=s+requests+full_reply,full_catalog_full_reply_task=a+requests+full_reply)))`,
|
|
input,
|
|
]);
|
|
tokenCosts = JSON.parse(result.stdout.text);
|
|
}
|
|
report = {
|
|
scope:
|
|
"Real shipped-ASAR native identity through public CLI/stdio MCP; simulated native Evidence only, no provider/application executed",
|
|
artifact: { path: values.artifact, sha256: archiveSha256 },
|
|
addon_path: values["addon-path"],
|
|
companion_sha256_observed: companionSha256,
|
|
prior_native_sha256_supplied: values["native-sha256"],
|
|
source_paths: values["source-path"] ?? null,
|
|
preparation_outcome: timedOut ? "provider_timeout" : "completed",
|
|
analysis_timeout_ms: analysisTimeoutMs ?? null,
|
|
parent_evidence_id: sourceId,
|
|
trace_evidence_id: first.response.evidence_id,
|
|
trace_subject: first.response.subject,
|
|
preparation_ms: preparationMs,
|
|
samples,
|
|
safe_identity: safeIdentity,
|
|
safe_applicability: safeApplicability,
|
|
native_analysis_scopes: nativeScopes,
|
|
structured_scopes: structuredScopes,
|
|
structured_expectations: structuredExpectations,
|
|
modeled_structured_trace_count: structuredQueries.length,
|
|
node: {
|
|
node_id: addon.node_id,
|
|
identity: addon.identity,
|
|
unavailable_observed: unavailable,
|
|
basis: addon.observations.map(({ evidence }) => ({
|
|
authority: evidence.authority,
|
|
state: evidence.state,
|
|
confidence: evidence.confidence,
|
|
artifact: evidence.artifact,
|
|
location: evidence.location,
|
|
})),
|
|
},
|
|
native_handoffs: trace.native_handoffs,
|
|
evidence_links: trace.evidence_links,
|
|
source_coverage: trace.coverage,
|
|
graph_coverage: trace.graph.coverage,
|
|
facts_sha256: digest({
|
|
node: addon,
|
|
native_handoffs: trace.native_handoffs,
|
|
evidence_links: trace.evidence_links,
|
|
coverage: trace.coverage,
|
|
}),
|
|
cli_parity: cliParity,
|
|
token_costs: tokenCosts,
|
|
conditions:
|
|
"Fresh MCP session, selected analysis followed by retained traces. Task token counts include one analysis summary or parsed timeout error, one native trace and each requested structured-path trace, with name/description/inputSchema catalog projections. Structured Evidence/parsed error and complete-tool-reply projections are alternatives, not billing. Repeated native rounds, export, CLI parity and tokenizer are validation overhead outside task tokens but inside total resource receipts. Filesystem/build caches uncontrolled. Incorrect identity/applicability is not a successful equivalent task or efficiency credit.",
|
|
};
|
|
if (values["require-safe"]) {
|
|
assert.ok(safeIdentity, "Native identity/linkage oracle failed");
|
|
assert.ok(safeApplicability, "File analysis applicability oracle failed");
|
|
}
|
|
} catch (cause) {
|
|
primaryError = cause;
|
|
throw cause;
|
|
} finally {
|
|
await closeVerifierResources(
|
|
[
|
|
() => client.close(),
|
|
() => transport.close(),
|
|
async () => {
|
|
if (retainTemporary)
|
|
throw new Error(
|
|
`Command cleanup incomplete; retaining owned inputs at ${temporary?.path}.`,
|
|
);
|
|
await temporary?.close();
|
|
},
|
|
],
|
|
primaryError,
|
|
);
|
|
}
|
|
console.log(serialize(report));
|