Files
rea/experiments/analysis-efficiency/android-components.mjs
T
gensokyo-dev 7e8f7f6101 feat(android): expose selected manifest component declarations (#7379)
Extend static manifest declarations and retain precise source locations and historical unknowns. Share bounded selected component views across CLI and MCP with complete parent Evidence.

Verified real ApiDemos, an AAPT2-built fixture, legacy Evidence, default-package metadata and relevant CLI/MCP contracts. Recorded whole-task token estimates, timing/resource limits and independent main-failure controls in the PR.

Co-authored-by: Harsh <johiyaharsh@gmail.com>
2026-10-27 12:37:10 +08:00

295 lines
9.2 KiB
JavaScript

/** Real APK task through public MCP, with complete retained Evidence and an independent XML oracle. */
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { parseArgs } from "node:util";
import { performance } from "node:perf_hooks";
import { createVerifierCommands } from "../../scripts/lib/verify-commands.mjs";
const { values } = parseArgs({
options: {
repository: { type: "string" },
target: { type: "string" },
output: { type: "string" },
mode: { type: "string" },
"java-home": { type: "string" },
jar: { type: "string" },
action: {
type: "string",
default: "android.provider.Telephony.SMS_RECEIVED",
},
},
});
for (const name of [
"repository",
"target",
"output",
"mode",
"java-home",
"jar",
])
assert.ok(values[name], `Missing --${name}`);
assert.ok(["before", "after"].includes(values.mode));
const repo = resolve(values.repository),
target = resolve(values.target),
output = resolve(values.output);
await mkdir(output, { mode: 0o700 });
const digest = createHash("sha256");
for await (const chunk of createReadStream(target)) digest.update(chunk);
const sha256 = digest.digest("hex");
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 commands = await createVerifierCommands();
const env = {
PATH: process.env.PATH,
JAVA_HOME: resolve(values["java-home"]),
REA_JADX_MCP_JAR: resolve(values.jar),
REA_JADX_HEAP_MIB: "1024",
REA_JADX_ACTIVE_PROCESSOR_COUNT: "2",
NODE_OPTIONS: "--max-old-space-size=3072",
UV_THREADPOOL_SIZE: "2",
REA_LOG_LEVEL: "silent",
REA_MCP_TOOL_FAMILIES: "android,application,session",
REA_MCP_INPUT_SCHEMA_PROFILE: "compact",
};
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(repo, "scripts/rea.mjs"), "mcp"],
cwd: repo,
env,
stderr: "pipe",
maxBufferSize: 16 * 1024 * 1024,
});
const diagnostics = [];
let diagnosticBytes = 0;
let omittedDiagnosticBytes = 0;
transport.stderr?.on("data", (chunk) => {
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
const retained = bytes.subarray(0, Math.max(0, 65536 - diagnosticBytes));
if (retained.length > 0) diagnostics.push(retained);
diagnosticBytes += retained.length;
omittedDiagnosticBytes += bytes.length - retained.length;
});
const client = new Client({
name: "rea-real-android-components",
version: "1",
});
let catalog;
const calls = [],
runs = [];
const invoke = async (name, arguments_) => {
const definitions = catalog.tools.filter((tool) => tool.name === name);
assert.equal(definitions.length, 1);
const start = performance.now();
const result = await client.callTool(
{ name, arguments: arguments_ },
{ toolDefinition: definitions[0], timeout: 120000 },
);
calls.push({
name,
arguments: arguments_,
result,
elapsed_ms: performance.now() - start,
});
assert.notEqual(
result.isError,
true,
`${name}: ${JSON.stringify(result.content)}`,
);
return result.structuredContent;
};
const selectedFields = [
"kind",
"declared_name",
"name",
"exported",
"exported_default",
"permission",
"permission_source",
"enabled",
"enabled_declaration",
"process",
"intent_filters",
"meta_data",
"location",
];
const stripLocations = (row) => {
const { location: _location, ...fields } = row;
return {
...fields,
intent_filters: row.intent_filters?.map(
({
location: _filterLocation,
source_index: _filterIndex,
coverage: _coverage,
...filter
}) => ({
...filter,
data: filter.data.map(({ location: _dataLocation, ...data }) => data),
}),
),
meta_data: row.meta_data?.map(
({ location: _metadataLocation, ...metadata }) => metadata,
),
};
};
const assertOracleFacts = (row, expected, source) => {
assert.equal(row.declaration_coverage, "complete");
const cleaned = stripLocations(row);
for (const [key, value] of Object.entries(expected))
assert.deepEqual(
cleaned[key],
value,
`Independent oracle: ${expected.source_index}.${key}`,
);
assert.equal(
source.slice(
row.location.offset,
row.location.offset + row.kind.length + 1,
),
"<" + row.kind,
);
};
try {
await client.connect(transport);
catalog = await client.listTools();
await writeFile(join(output, "catalog.json"), JSON.stringify(catalog));
for (const temperature of ["first-capture", "warm-session"]) {
const firstCall = calls.length;
const start = performance.now();
const result = await invoke(
"inspect_android_package",
values.mode === "before"
? { path: target }
: {
path: target,
detail: "summary",
view: {
kind: "android",
facet: "components",
actions: [values.action],
fields: selectedFields,
limit: 64,
},
},
);
const rpcMs = performance.now() - start;
const normalized = result.normalized_result;
const parentId =
values.mode === "before"
? result.evidence_id
: normalized.parent_evidence_id;
const path = join(output, temperature + "-evidence.json");
await invoke("export_evidence_bundle", { path });
const bundle = JSON.parse(await readFile(path, "utf8"));
const parent = bundle.records.find(
(record) => record.evidence_id === parentId,
);
assert.ok(parent);
assert.equal(parent.subject.digest.sha256, sha256);
const oraclePath = join(
dirname(fileURLToPath(import.meta.url)),
"fixtures/android-components/manifest-oracle.py",
);
const oracle = JSON.parse(
(
await commands.exec(
"python3",
[oraclePath, path, parentId, values.action],
{ timeout: 15000, maxBuffer: 4 * 1024 * 1024 },
)
).stdout,
);
assert.ok(
oracle.components.length > 0,
"The completed task must exercise a real positive action match",
);
const complete = parent.normalized_result;
assert.equal(complete.components.items.length, oracle.inventory.length);
assert.equal(oracle.inventory.length, oracle.source_components);
for (const [index, expected] of oracle.inventory.entries()) {
const row = complete.components.items[index];
if (values.mode === "after")
assertOracleFacts(
{ ...row, source_index: index },
expected,
complete.manifest.text,
);
else {
for (const key of ["kind", "declared_name", "name", "permission"])
assert.deepEqual(
row[key],
expected[key],
`Legacy inventory: ${index}.${key}`,
);
}
}
if (values.mode === "after") {
assert.equal(normalized.facet, "components");
assert.equal(normalized.source_status, "complete");
assert.equal(normalized.source_components, oracle.source_components);
assert.equal(normalized.coverage.exhausted, true);
assert.equal(normalized.coverage.total, oracle.components.length);
assert.equal(normalized.items.length, oracle.components.length);
for (let i = 0; i < oracle.components.length; i++) {
const row = normalized.items[i],
expected = oracle.components[i];
assertOracleFacts(row, expected, complete.manifest.text);
}
} else {
for (const expected of oracle.components) {
const row = normalized.components.items[expected.source_index];
assert.equal(row.name, expected.name);
assert.ok(row.actions.includes(values.action));
}
}
runs.push({
temperature,
rpc_ms: rpcMs,
calls: calls.length - firstCall,
matched_components: oracle.components.length,
source_components: oracle.source_components,
parent_evidence_id: parentId,
answer_verified: true,
inventory_verified: oracle.inventory.length,
inventory_scope:
values.mode === "after"
? "declared-structured-facets"
: "legacy-identity-permission",
});
}
} finally {
// exec drains already adopted owners; adopt the live MCP client only at its final cleanup boundary.
commands.adopt(client, "real Android component MCP client");
await commands.close();
await writeFile(join(output, "stderr.log"), Buffer.concat(diagnostics));
await writeFile(join(output, "calls.json"), JSON.stringify(calls), {
mode: 0o600,
});
await writeFile(
join(output, "report.json"),
JSON.stringify(
{
mode: values.mode,
target,
sha256,
runs,
tool_count: catalog?.tools.length,
omitted_diagnostic_bytes: omittedDiagnosticBytes,
},
null,
2,
),
);
}
console.log(JSON.stringify({ mode: values.mode, runs, verified: true }));