refactor: give Inspector its own capability directory (#869)

This commit is contained in:
ysopwnable
2026-10-07 13:53:25 +08:00
committed by GitHub
parent e0fd8207a1
commit 021ba690b0
29 changed files with 73 additions and 41 deletions
+2 -2
View File
@@ -20,10 +20,10 @@ REA is a local-only tool. Preserve caller-selected inputs, captured output, URLs
REA is a layered ESM TypeScript application. Dependencies flow inward from pure domain logic through contracts, providers, application workflows, and CLI/MCP adapters. See [docs/architecture.mermaid](docs/architecture.mermaid) for the component map.
- `src/domain/` owns pure provider-neutral semantics; `src/contracts/` owns caller-visible schemas and the canonical tool inventory.
- `src/hopper/`, `src/ghidra/`, `src/ida/`, `src/browser/`, `src/native/`, `src/artifacts/`, and `src/dotnet/` own provider-specific boundaries. Keep provider protocols out of domain and application code.
- `src/hopper/`, `src/ghidra/`, `src/ida/`, `src/browser/`, `src/inspector/`, `src/native/`, `src/artifacts/`, and `src/dotnet/` own provider-specific boundaries. Keep provider protocols out of domain and application code.
- `src/application/` composes shared CLI/MCP workflows; `src/server/` translates MCP requests; `src/cli.ts` and `src/main.ts` are the CLI and MCP entry points.
- `src/process/` owns shared process lifecycle primitives, not provider wire protocols. `bridge/` contains provider-side adapters.
- `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` contains real-toolchain checks.
- `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` and capability directories under `scripts/verify/` contain real-toolchain checks.
- `docs/product-catalog.json` is generated. Update its source contracts and regenerate it; do not edit it directly.
## Build, Test, and Development Commands
+4
View File
@@ -28,6 +28,7 @@ graph TD
IDA_MCP["External ida-pro-mcp<br/>GUI proxy or database supervisor<br/>upstream installation and engine lifecycle"]
AUX["src/artifacts/ + src/native/<br/>disjoint artifact and native capabilities"]
BROWSER["src/browser/<br/>CDP / Playwright capture<br/>captured script adapters and publication"]
INSPECTOR["src/inspector/<br/>passive V8 Inspector discovery and capture<br/>script and target locations"]
DOTNET["src/dotnet/<br/>Managed static metadata provider<br/>PE/CLI metadata inventory, members, IL"]
FIRMWARE["src/firmware/<br/>Binwalk / Unblob adapters<br/>owned processes, report normalization, private extraction"]
ANDROID["src/android/<br/>Static APK adapter<br/>owned SDK stdio, pinned headless JADX, normalization"]
@@ -94,6 +95,9 @@ graph TD
SERVER --> CONTRACTS
SERVER --> APP
APP --> BROWSER
APP -->|runtime observation port| INSPECTOR
INSPECTOR -->|shared CDP and file-location helpers| BROWSER
INSPECTOR --> DOMAIN
APP --> DOTNET
DOTNET --> DOMAIN
HOPPER --> HOPPER_BRIDGE
+18
View File
@@ -167,6 +167,24 @@ behavior. Snapshots still require an active target and concrete analysis profile
extracting investigation ownership does not make them a target-free record store.
#721 remains responsible for any future lifetime-policy change.
## Inspector adapter ownership
`src/inspector/` owns V8 Inspector discovery, passive capture, script/target
location interpretation and its lightweight provider identity. The composition
factory and optional loader use this owner. Browser CDP connection, endpoint/value
and Electron file-location helpers remain deliberate shared adapter utilities at
their existing paths; no provider wire protocol moves into domain or process.
Loopback fixtures and producer boundaries now live under
`tests/fixtures/inspector/` and `tests/boundary/inspector/`. The socket-backed
unresolved-target reconciliation test moves from composition into the forked
boundary lane with its original assertions. Adapter tests retain fork isolation.
`npm run verify:inspector` keeps its public development entrypoint and delegates
to `scripts/verify/inspector/runtime-observation.mjs`, using the real Node fixture
under `tests/conformance/inspector/`. Catalog imports, source guards and test
discovery follow the new paths. Observation authority, lifecycle, producer
interpretation and caller-visible contracts stay unchanged.
## Auxiliary source declaration pilot
Artifact, macOS native and managed static metadata now belongs to each adapter's
+9
View File
@@ -171,6 +171,15 @@ the CLI and stdio MCP, including special filenames, unresolved discovery
locations, and independently resolved loaded scripts. Double-quote filenames
are tested on POSIX only because Windows does not support them.
Inspector adapters live in `src/inspector/`, with loopback fixtures under
`tests/fixtures/inspector/` and forked producer boundaries under
`tests/boundary/inspector/`. The real lane is owned by
`scripts/verify/inspector/runtime-observation.mjs` and
`tests/conformance/inspector/`. Browser and Inspector deliberately share the
existing browser CDP transport/value and file-location helpers. Node fixture
success proves this Inspector workflow on the tested host; it does not prove
Electron GUI behavior or another engine.
## Android APK analysis
`npm run verify:android` requires an existing Java 17+ and an explicit
+1 -1
View File
@@ -135,7 +135,7 @@
"verify:browser:dom": "node scripts/verify-browser-dom-destinations.mjs",
"verify:browser:network": "npm run build:cached && node scripts/verify-browser-network.mjs",
"verify:electron": "npm run build:cached && node scripts/verify-real-electron.mjs",
"verify:inspector": "npm run build:cached && node scripts/verify-real-javascript-runtime-observation.mjs",
"verify:inspector": "npm run build:cached && node scripts/verify/inspector/runtime-observation.mjs",
"verify:javascript-runtime": "npm run build:cached && node scripts/verify-local-javascript-runtime.mjs",
"verify:javascript:digests": "npm run build:cached && node scripts/verify-large-javascript-digests.mjs",
"verify:application-workflows": "npm run build:cached && node scripts/verify-local-application-workflows.mjs",
+1 -1
View File
@@ -68,7 +68,7 @@ const SOURCE_PATHS = {
browserScenarioProvider: "dist/browser/PlaywrightBrowserScenarioProvider.js",
electronProvider: "dist/browser/CdpElectronProvider.js",
electronActiveProvider: "dist/browser/PlaywrightElectronActiveProvider.js",
v8InspectorProvider: "dist/browser/V8InspectorProvider.js",
v8InspectorProvider: "dist/inspector/V8InspectorProvider.js",
evidence: "dist/domain/evidence.js",
evidenceBundle: "dist/domain/evidenceBundle.js",
evidenceCompletion: "dist/domain/evidenceCompletionLedger.js",
+1
View File
@@ -10,6 +10,7 @@ const MIGRATED_PROVIDER_ROOTS = new Set([
"ghidra",
"hopper",
"ida",
"inspector",
"javascript",
]);
@@ -11,7 +11,7 @@ import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
const execute = promisify(execFile);
const repository = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const repository = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
const root = await realpath(await mkdtemp(join(tmpdir(), "rea-inspector-")));
const environment = {
...process.env,
@@ -43,9 +43,7 @@ try {
for (const name of names) {
const targetPath =
name === "node-target.mjs"
? await realpath(
join(repository, "tests/conformance/runtime-inspector", name),
)
? await realpath(join(repository, "tests/conformance/inspector", name))
: join(root, name);
if (name !== "node-target.mjs")
await writeFile(targetPath, "setInterval(() => {}, 1000);\n");
-7
View File
@@ -29,10 +29,3 @@ export const PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY: ProviderIdentity =
name: "REA Playwright active Electron observation provider",
version: "1",
});
/** Identity available without loading the V8InspectorProvider implementation. */
export const V8_INSPECTOR_PROVIDER_IDENTITY: ProviderIdentity = Object.freeze({
id: "rea-v8-inspector",
name: "REA passive Node/Electron V8 Inspector provider",
version: "1",
});
@@ -1,5 +1,5 @@
import type { JavaScriptRuntimeObservationPort } from "../application/JavaScriptRuntimeObservationPort.js";
import { V8InspectorProvider } from "../browser/V8InspectorProvider.js";
import { V8InspectorProvider } from "../inspector/V8InspectorProvider.js";
/** Construct a fresh provider without opening a target or acquiring an engine. */
export const createJavaScriptRuntimeObservationProvider =
@@ -8,8 +8,8 @@ import {
PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY,
CDP_ELECTRON_PROVIDER_IDENTITY,
PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY,
V8_INSPECTOR_PROVIDER_IDENTITY,
} from "../browser/providerIdentities.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "../inspector/providerIdentity.js";
import { err, ok, type Result } from "../domain/result.js";
/** Precisely typed startup factories for the five existing observation ports. */
@@ -9,7 +9,7 @@ import type {
JavaScriptRuntimeLocation,
JavaScriptRuntimeTargetLocation,
} from "../domain/javascriptRuntimeObservation.js";
import { authorizedElectronFile } from "./ElectronFileScope.js";
import { authorizedElectronFile } from "../browser/ElectronFileScope.js";
export type RuntimeLocationDecision =
| { readonly allowed: true; readonly location: JavaScriptRuntimeLocation }
@@ -6,7 +6,7 @@ import type {
} from "../domain/javascriptRuntimeObservation.js";
import { BrowserObservationError } from "../domain/browserObservationError.js";
import { type BrowserObservationOperation } from "../domain/browserObservationErrors.js";
import { parseCdpEndpointValue, readCdpJson } from "./CdpEndpoint.js";
import { parseCdpEndpointValue, readCdpJson } from "../browser/CdpEndpoint.js";
const versionSchema = z
.object({
@@ -21,8 +21,8 @@ import {
recordValue,
cdpStringValue,
delayWithCancellation,
} from "./CdpCaptureValues.js";
import { CdpConnection, type CdpEvent } from "./CdpConnection.js";
} from "../browser/CdpCaptureValues.js";
import { CdpConnection, type CdpEvent } from "../browser/CdpConnection.js";
import {
authorizeRuntimeTargetLocation,
inspectorExclusionKey,
@@ -39,8 +39,8 @@ import {
} from "./V8InspectorEndpoint.js";
/** Public identity committed by passive V8 Inspector observations. */
export { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentities.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentities.js";
export { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentity.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentity.js";
export interface ScriptDraft {
readonly rawUrl: string;
+8
View File
@@ -0,0 +1,8 @@
import type { ProviderIdentity } from "../application/AnalysisProvider.js";
/** Identity available without loading the V8InspectorProvider implementation. */
export const V8_INSPECTOR_PROVIDER_IDENTITY: ProviderIdentity = Object.freeze({
id: "rea-v8-inspector",
name: "REA passive Node/Electron V8 Inspector provider",
version: "1",
});
@@ -7,9 +7,9 @@ import { promisify } from "node:util";
import { afterEach, describe, expect, test } from "vitest";
import { observeJavaScriptRuntime } from "../../../src/application/JavaScriptRuntimeObservationService.js";
import { V8InspectorProvider } from "../../../src/browser/V8InspectorProvider.js";
import { V8InspectorProvider } from "../../../src/inspector/V8InspectorProvider.js";
import { observeJavaScriptRuntimeInputSchema } from "../../../src/domain/javascriptRuntimeObservation.js";
import { startFakeV8Inspector } from "../../fixtures/fakeV8Inspector.js";
import { startFakeV8Inspector } from "../../fixtures/inspector/fakeV8Inspector.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const execute = promisify(execFile);
+1 -1
View File
@@ -22,7 +22,7 @@ import { CDP_BROWSER_PROVIDER_IDENTITY } from "../../../src/browser/CdpBrowserPr
import { CDP_ELECTRON_PROVIDER_IDENTITY } from "../../../src/browser/CdpElectronProvider.js";
import { PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY } from "../../../src/browser/PlaywrightElectronActiveProvider.js";
import { PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY } from "../../../src/browser/PlaywrightBrowserScenarioProvider.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "../../../src/browser/V8InspectorProvider.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "../../../src/inspector/V8InspectorProvider.js";
import { CLI_COMMAND_NAMES } from "../../../src/cliCommandNames.js";
import { createCli } from "../../../src/cli.js";
import {
@@ -10,8 +10,8 @@ import {
observeJavaScriptRuntime,
} from "../../../src/application/JavaScriptRuntimeObservationService.js";
import { reconcileJavaScriptRuntimeEvidence } from "../../../src/application/JavaScriptRuntimeReconciliationService.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "../../../src/browser/V8InspectorProvider.js";
import { V8InspectorProvider } from "../../../src/browser/V8InspectorProvider.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "../../../src/inspector/V8InspectorProvider.js";
import { V8InspectorProvider } from "../../../src/inspector/V8InspectorProvider.js";
import { JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE } from "../../../src/contracts/javascriptRuntimeReconciliationExample.js";
import type {
JavaScriptRuntimeObservation,
@@ -19,7 +19,7 @@ import type {
} from "../../../src/domain/javascriptRuntimeObservation.js";
import { observeJavaScriptRuntimeInputSchema } from "../../../src/domain/javascriptRuntimeObservation.js";
import { javascriptRuntimeReconciliationResultSchema } from "../../../src/domain/javascriptRuntimeReconciliationSchemas.js";
import { startFakeV8Inspector } from "../../fixtures/fakeV8Inspector.js";
import { startFakeV8Inspector } from "../../fixtures/inspector/fakeV8Inspector.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("passive V8 Inspector provider", () => {
@@ -4,8 +4,8 @@ import { pathToFileURL } from "node:url";
import { afterEach, expect, test } from "vitest";
import { V8InspectorProvider } from "../../../src/browser/V8InspectorProvider.js";
import { startFakeV8Inspector } from "../../fixtures/fakeV8Inspector.js";
import { V8InspectorProvider } from "../../../src/inspector/V8InspectorProvider.js";
import { startFakeV8Inspector } from "../../fixtures/inspector/fakeV8Inspector.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const roots: string[] = [];
@@ -6,9 +6,9 @@ import { expect, test } from "vitest";
import { analyzeJavaScriptApplication } from "../../../src/application/JavaScriptApplicationService.js";
import { createJavaScriptRuntimeObservationEvidence } from "../../../src/application/JavaScriptRuntimeObservationEvidence.js";
import { V8InspectorProvider } from "../../../src/browser/V8InspectorProvider.js";
import { V8InspectorProvider } from "../../../src/inspector/V8InspectorProvider.js";
import { reconcileJavaScriptRuntime } from "../../../src/domain/javascriptRuntimeReconciliation.js";
import { startFakeV8Inspector } from "../../fixtures/fakeV8Inspector.js";
import { startFakeV8Inspector } from "../../fixtures/inspector/fakeV8Inspector.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
test("keeps unresolved target identity unknown while matching verified loaded scripts", async () => {
@@ -7,7 +7,7 @@ import { afterEach, describe, expect, test } from "vitest";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { observeJavaScriptRuntime } from "../../../src/application/JavaScriptRuntimeObservationService.js";
import { V8InspectorProvider } from "../../../src/browser/V8InspectorProvider.js";
import { V8InspectorProvider } from "../../../src/inspector/V8InspectorProvider.js";
import {
javascriptRuntimeObservationSchema,
javascriptRuntimeTargetListSchema,
@@ -15,7 +15,7 @@ import {
} from "../../../src/domain/javascriptRuntimeObservation.js";
import { createServer } from "../../../src/server/createServer.js";
import { observed } from "../../fixtures/analysisExecution.js";
import { startFakeV8Inspector } from "../../fixtures/fakeV8Inspector.js";
import { startFakeV8Inspector } from "../../fixtures/inspector/fakeV8Inspector.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("JavaScript runtime observation MCP tools", () => {
+4 -1
View File
@@ -23,7 +23,9 @@ describe("incremental module import boundaries", () => {
["src/application/probe.ts", "../composition/android.js", false],
["src/server/probe.ts", "../firmware/FirmwareProvider.js", false],
["src/server/probe.ts", "../browser/CdpBrowserProvider.js", false],
["src/application/probe.ts", "../browser/V8InspectorProvider.js", false],
["src/application/probe.ts", "../inspector/V8InspectorProvider.js", false],
["src/server/probe.ts", "../inspector/V8InspectorEndpoint.js", false],
["src/composition/probe.ts", "../inspector/V8InspectorProvider.js", true],
["src/composition/probe.ts", "../browser/CdpBrowserProvider.js", true],
[
"src/application/probe.ts",
@@ -40,6 +42,7 @@ describe("incremental module import boundaries", () => {
["src/composition/probe.ts", "../firmware/FirmwareProvider.js", true],
["src/domain/android/probe.ts", "./types.js", true],
["src/domain/javascript/probe.ts", "./types.js", true],
["src/domain/inspector/probe.ts", "./types.js", true],
["src/domain/firmware/probe.ts", "../android/types.js", true],
["src/domain/android/probe.ts", "../../android/JadxProvider.js", false],
["src/application/android/probe.ts", "../../domain/android/types.js", true],
+2 -4
View File
@@ -1,9 +1,7 @@
import type { BrowserObservationPort } from "../../src/application/BrowserObservationPort.js";
import type { JavaScriptRuntimeObservationPort } from "../../src/application/JavaScriptRuntimeObservationPort.js";
import {
CDP_BROWSER_PROVIDER_IDENTITY,
V8_INSPECTOR_PROVIDER_IDENTITY,
} from "../../src/browser/providerIdentities.js";
import { CDP_BROWSER_PROVIDER_IDENTITY } from "../../src/browser/providerIdentities.js";
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "../../src/inspector/providerIdentity.js";
import { createBrowserScenarioProvider } from "../../src/composition/browserScenario.js";
import { createElectronObservationProvider } from "../../src/composition/electronObservation.js";
import { createElectronScenarioProvider } from "../../src/composition/electronScenario.js";
+1 -1
View File
@@ -37,7 +37,7 @@ const TEST_PROJECTS = [
name: "adapters",
include: [
"src/*.test.ts",
"src/{artifacts,browser,dotnet,firmware,ghidra,hopper,ida,javascript,native,process,reference,server,windows}/**/*.test.ts",
"src/{artifacts,browser,dotnet,firmware,ghidra,hopper,ida,inspector,javascript,native,process,reference,server,windows}/**/*.test.ts",
],
pool: "forks" as const,
maxWorkers: MAX_TEST_WORKERS,