refactor: consolidate boundary semantics and prune redundant tests

Share parser modes and collision-free composite keys, enforce architecture guards, and consolidate redundant unit assertions into filesystem, CLI, MCP, and producer-backed workflows. Preserve concurrent upstream boundary fixes and exclude legacy Objective-C class-name bookkeeping labels.

Validation: source/composition suites (3358 passing), focused canonical digest and MCP checks, typecheck/lint, Knip, formatting, architecture guards, and real Foundation archive and Apple legacy simulator CLI/MCP verification. No real Hopper, Ghidra, or IDA verification claim.
This commit is contained in:
morluto 2026-10-10 05:56:01 +08:00
parent 077d6c407b
commit 392f1f3107
78 changed files with 1065 additions and 2020 deletions

View File

@ -330,6 +330,8 @@ jobs:
- run: npm run verify:keyed-archive
- run: npm run verify:asset-catalog
- run: npm run verify:macos-bundle
- name: Legacy simulator platform verification
run: npm run verify:legacy-simulator
- name: Native installer and compiled NIB hierarchy regressions
run: npm run test:focused -- tests/boundary/filesystem/installScript.test.ts tests/boundary/filesystem/nibNativeHierarchy.test.ts tests/boundary/filesystem/interfaceBuilderBundle.test.ts
- name: Real binary metadata and Interface Builder integration
@ -423,7 +425,6 @@ jobs:
tests/acceptance/webModuleCli.test.ts
tests/acceptance/webSourceLocationCli.test.ts
tests/boundary/cli/referenceSourceImportStatus.test.ts
src/cliReferenceSourceImportStatus.test.ts
tests/boundary/filesystem/asarInventoryFreshness.test.ts
tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts
tests/conformance/providers/ghidra/windowsCapabilities.test.ts

View File

@ -88,7 +88,7 @@
"prebuild": "npm run deps:check",
"build": "node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills --force",
"precheck": "npm run deps:check",
"check": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint format:check knip metadata:check",
"check": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint format:check knip metadata:check && npm run verify:architecture-guards",
"precheck:fast": "npm run deps:check",
"check:fast": "turbo run typecheck lint --output-logs=errors-only",
"precheck:changed": "npm run deps:check",
@ -144,6 +144,8 @@
"verify:native-ui": "npm run build:cached && node scripts/verify-native-ui.mjs",
"verify:keyed-archive": "npm run build:cached && node scripts/verify/apple/keyed-archive.mjs",
"verify:macos-bundle": "npm run build:cached && node scripts/verify/apple/macos-bundle.mjs",
"verify:legacy-simulator": "npm run build:cached && node scripts/verify/apple/legacy-simulator.mjs",
"verify:architecture-guards": "node scripts/verify/architecture-guards.mjs",
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
"verify:ghidra:swift": "npm run build:cached && node scripts/verify-real-ghidra-swift.mjs",
"verify:ida": "npm run build:cached && node scripts/verify-real-ida.mjs",

View File

@ -148,8 +148,27 @@ try {
target,
status,
})),
[{ source: null, path: ["UID"], target: 1, status: "resolved" }],
[
{ source: null, path: ["CF$UID"], target: 1, status: "resolved" },
{ source: null, path: ["UID"], target: 1, status: "resolved" },
],
);
const selected = await artifactCli("inspect-keyed-archive", path, [
"--root",
"UID",
]);
assert.deepEqual(
selected.references,
uidGraph.references.filter(({ path }) => path[0] === "UID"),
);
await withArtifactMcp(path, async (client) => {
assert.deepEqual(
await artifactMcpResult(client, "inspect_keyed_archive", {
root: "UID",
}),
selected,
);
});
}
const numericTests = spawnSync(
"npm",

View File

@ -0,0 +1,142 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import {
artifactCliEvidence,
artifactMcpResult,
withArtifactMcp,
} from "../../lib/artifact-e2e.mjs";
const exec = promisify(execFile);
if (process.platform !== "darwin")
throw new Error(
"Legacy simulator verification requires a macOS host with full Xcode.",
);
for (const tool of ["clang", "ld", "vtool", "otool"])
await exec("/usr/bin/xcrun", ["--find", tool], { timeout: 30_000 });
const { stdout: sdk } = await exec(
"/usr/bin/xcrun",
["--sdk", "iphonesimulator", "--show-sdk-path"],
{ timeout: 30_000 },
);
const sdkPath = sdk.trim();
assert.ok(sdkPath.length > 0, "iPhoneSimulator SDK path is empty");
const root = await mkdtemp(join(tmpdir(), "rea-legacy-simulator-"));
const xcrun = (...args) =>
exec("/usr/bin/xcrun", args, {
timeout: 120_000,
maxBuffer: 16 * 1024 * 1024,
});
try {
// Source-owned fixture, never executed: a legacy deployment target makes
// the Apple driver emit LC_VERSION_MIN_IPHONEOS (no simulator flag), while
// the modern dylib carries LC_BUILD_VERSION with an explicit simulator ID.
await writeFile(join(root, "library.c"), "int fixture(void) { return 7; }\n");
await writeFile(
join(root, "main.c"),
"extern int fixture(void);\nint main(void) { return fixture(); }\n",
);
for (const [source, deployment] of [
["library", "14.0"],
["main", "9.0"],
]) {
await xcrun(
"clang",
"-target",
`x86_64-apple-ios${deployment}-simulator`,
"-isysroot",
sdkPath,
"-c",
join(root, `${source}.c`),
"-o",
join(root, `${source}.o`),
);
}
const common = ["-arch", "x86_64", "-syslibroot", sdkPath, "-lSystem"];
await xcrun(
"ld",
...common,
"-dylib",
"-platform_version",
"ios-simulator",
"14.0",
"14.0",
"-install_name",
"@rpath/libFixture.dylib",
join(root, "library.o"),
"-o",
join(root, "libFixture.dylib"),
);
await xcrun(
"clang",
"-target",
"x86_64-apple-ios9.0-simulator",
"-isysroot",
sdkPath,
"-e",
"_main",
"-rpath",
"@loader_path",
join(root, "main.o"),
join(root, "libFixture.dylib"),
"-o",
join(root, "Main"),
);
// Apple oracle: the executable really uses the legacy command, the dylib
// really declares the modern simulator platform.
const { stdout: legacyBuild } = await xcrun(
"vtool",
"-show-build",
join(root, "Main"),
);
assert.match(legacyBuild, /LC_VERSION_MIN_IPHONEOS/u);
assert.doesNotMatch(legacyBuild, /LC_BUILD_VERSION/u);
const { stdout: modernBuild } = await xcrun(
"vtool",
"-show-build",
join(root, "libFixture.dylib"),
);
assert.match(modernBuild, /IOSSIMULATOR/u);
// Real CLI over the fixture: the target is the executable file (plain
// directories are not binary targets); @loader_path resolves against its
// directory. The legacy x86 image must classify as iOS simulator
// (platform 7) and resolve against the modern dylib.
// The fixture is inspected, never executed or loaded into this process.
const mainPath = join(root, "Main");
const trace = (await artifactCliEvidence("trace-dylib-resolution", mainPath))
.normalized_result;
const main = trace.images.find(({ path }) => path === "Main");
assert.ok(main, "trace omitted the Main image");
assert.equal(main.parse_status, "parsed");
assert.deepEqual(main.slices[0]?.platforms, [7]);
const edge = trace.edges.find(
({ loader, install_name: name }) =>
loader === "Main" && name === "@rpath/libFixture.dylib",
);
assert.ok(edge, "trace omitted the @rpath edge");
assert.deepEqual(edge.resolution, {
status: "resolved",
image: "libFixture.dylib",
});
await withArtifactMcp(mainPath, async (client) => {
assert.deepEqual(
await artifactMcpResult(client, "trace_dylib_resolution"),
trace,
);
});
console.log(
"Legacy simulator verification passed against Apple clang, ld, vtool, and otool.",
);
} finally {
await rm(root, { recursive: true, force: true });
}

View File

@ -0,0 +1,136 @@
import assert from "node:assert/strict";
import { readFile, readdir } from "node:fs/promises";
import { join } from "node:path";
// Architecture guards for the strict-identity / best-effort-inference split.
// - Evidence ordering must be locale-independent (ICU varies by host), so
// src/domain orders with compareUnicodeCodePoints, never localeCompare.
// - Composite dedup keys must not NUL-join variable-length or free-form
// MIDDLE fields: ["a\0b"] collapses with ["a", "b"] and drops a finding.
// compositeKey (JSON tuple) is the single owner for composite keys.
// - Transparent wrappers unwrap in exactly one place
// (javascriptAstValues.unwrapJavaScriptExpression); direct isTS* unwrapping
// elsewhere drifts (the `satisfies` class of bug).
// - AST walks go through traverseJavaScriptAst/childNodes: bespoke
// Object.values walkers diverge on loc/comment fields and overflow the
// stack on deep generated chains.
const root = new URL("../../", import.meta.url).pathname;
const listFiles = async (dir) => {
const output = [];
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) output.push(...(await listFiles(path)));
else if (
entry.name.endsWith(".ts") &&
!entry.name.endsWith(".test.ts") &&
!entry.name.endsWith(".fixture.ts")
)
output.push(path);
}
return output;
};
const stripComments = (content) =>
content
.split("\n")
.filter((line) => !line.trimStart().startsWith("//"))
.join("\n")
.replace(/\/\*[\s\S]*?\*\//gu, "");
const sources = await listFiles(join(root, "src"));
const files = await Promise.all(
sources.map(async (path) => ({
path: path.slice(root.length),
content: stripComments(await readFile(path, "utf8")),
})),
);
const inDir = (dir) => files.filter((file) => file.path.startsWith(dir));
const failures = [];
const check = (label, selected, pattern, allow = []) => {
const offenders = selected.filter(
(file) => pattern.test(file.content) && !allow.includes(file.path),
);
if (offenders.length > 0)
failures.push(
`${label}:\n${offenders.map((file) => ` ${file.path}`).join("\n")}`,
);
};
check(
"src/domain uses localeCompare (use compareUnicodeCodePoints)",
inDir("src/domain/"),
/\.localeCompare\(/,
);
check(
"NUL-joined list key (use compositeKey)",
files,
/\.join\(["']\\0["']\)/,
[
// Sorted pair of constrained trace IDs; documented in plan.
"src/domain/process/processTraceSpecification.ts",
],
);
check(
"direct transparent-wrapper unwrap outside javascriptAstValues (use unwrapJavaScriptExpression)",
files,
/isTSAsExpression|isTSSatisfiesExpression|isTSNonNullExpression|isTSTypeAssertion|isTSInstantiationExpression|isParenthesizedExpression/,
["src/domain/javascript/javascriptAstValues.ts"],
);
check(
"bespoke AST child walker (use traverseJavaScriptAst/childNodes)",
files,
/VISITOR_KEYS/,
["src/domain/javascript/javascriptSemanticTraversal.ts"],
);
// Fixed-arity backtick keys audited 2026-10-10: every prefix field is NUL-free
// (hex digests, file paths, enums, numbers, or compositeKey JSON which escapes
// NUL), with free-form source strings only in final position. New backtick-\0
// keys must either meet that shape (and extend this list with justification)
// or use compositeKey.
check(
"new backtick-NUL composite key (audit against the rule above)",
files,
/`[^`\n]*\\0/,
[
"src/application/javascript/ElectronBoundaryAnalysis.ts",
"src/application/javascript/ElectronBoundaryGraphContext.ts",
"src/application/javascript/JavaScriptArtifactGraphContext.ts",
"src/browser/CdpCaptureCompleteness.ts",
"src/browser/CdpCaptureEventHandlers.ts",
"src/browser/CdpPageCapture.ts",
"src/browser/WebSourceMapFetcher.ts",
"src/domain/javascript/electronStaticAnalysisBrowser.ts",
"src/domain/javascript/electronStaticAnalysisIpc.ts",
"src/domain/javascript/javascriptApplicationChangeGraph.ts",
"src/domain/javascript/javascriptApplicationVersionItems.ts",
"src/domain/javascript/javascriptApplicationVersionKeys.ts",
"src/domain/javascript/javascriptExportShapeSelection.ts",
"src/domain/javascript/javascriptFeatureSeed.ts",
"src/domain/javascript/javascriptFeatureTraversal.ts",
"src/domain/javascript/javascriptNativeHandoff.ts",
"src/domain/javascript/javascriptRuntimeReconciliationMatching.ts",
"src/domain/javascript/javascriptRuntimeReconciliationSchemas.ts",
"src/domain/javascript/javascriptSemanticQuery.ts",
"src/domain/javascript/javascriptStaticAnalysisBundler.ts",
"src/domain/javascript/javascriptStaticAnalysisCalls.ts",
"src/domain/javascript/javascriptStaticAnalysisFindings.ts",
"src/domain/localPath.ts",
"src/domain/process/processTraceEvaluation.ts",
"src/domain/process/processTraceSpecification.ts",
"src/domain/unicodeCodePointOrder.ts",
"src/domain/webBundleAnalyzerInspection.ts",
],
);
if (failures.length > 0) {
console.error(`Architecture guards failed:\n\n${failures.join("\n\n")}\n`);
process.exit(1);
}
console.log(`Architecture guards passed across ${files.length} source files.`);

View File

@ -4,7 +4,6 @@ import { describe, expect, it } from "vitest";
import {
installLinuxHopper,
linuxHopperLauncherDigestSupported,
linuxHopperInstallDisclosure,
linuxPackageManagerCommands,
linuxSharedLibrariesAvailable,
parseLinuxDistribution,
@ -114,44 +113,11 @@ describe("Linux Hopper installation", () => {
expect(linuxHopperLauncherDigestSupported("0".repeat(64))).toBe(false);
});
it("discloses the exact download, integrity evidence, and privileged command", () => {
expect(linuxHopperInstallDisclosure("deb", false)).toEqual({
downloadUrl:
"https://www.hopperapp.com:443/downloader/public/Hopper-6.4.2-Linux-demo.deb",
expectedBytes: 35_755_772,
expectedSha1: "e4f79dff602648a8ff4a88b773875b6bfac0dc65",
commands: [
"pkexec apt-get install -y <verified-hopper.deb> xvfb xauth python3 libx11-6 libxtst6",
],
});
});
it.each([
["deb", "apt-get"],
["rpm", "dnf"],
["arch", "pacman"],
] as const)("selects the %s native package manager", (family, executable) => {
expect(
linuxPackageManagerCommands(family, "/tmp/Hopper package", true),
).toContainEqual(
expect.objectContaining({
executable,
}),
);
expect(
linuxPackageManagerCommands(family, "/tmp/Hopper package", false),
).toContainEqual(
expect.objectContaining({
executable: "pkexec",
args: expect.arrayContaining([executable, "/tmp/Hopper package"]),
}),
);
});
it.each([
["deb", ["xvfb", "xauth", "python3", "libx11-6", "libxtst6"]],
["deb", "apt-get", ["xvfb", "xauth", "python3", "libx11-6", "libxtst6"]],
[
"rpm",
"dnf",
[
"xorg-x11-server-Xvfb",
"xorg-x11-xauth",
@ -160,17 +126,30 @@ describe("Linux Hopper installation", () => {
"libXtst",
],
],
["arch", ["xorg-server-xvfb", "xorg-xauth", "python", "libx11", "libxtst"]],
[
"arch",
"pacman",
["xorg-server-xvfb", "xorg-xauth", "python", "libx11", "libxtst"],
],
] as const)(
"installs the %s demo-session dependencies",
(family, packages) => {
"selects the %s native package manager and dependencies",
(family, executable, packages) => {
expect(
linuxPackageManagerCommands(family, "/tmp/Hopper package", true),
).toContainEqual(
expect.objectContaining({
executable,
args: expect.arrayContaining([...packages]),
}),
);
expect(
linuxPackageManagerCommands(family, "/tmp/Hopper package", false),
).toContainEqual(
expect.objectContaining({
executable: "pkexec",
args: expect.arrayContaining([executable, "/tmp/Hopper package"]),
}),
);
},
);

View File

@ -2,11 +2,7 @@ import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import {
installMacHopper,
macHopperInstallDisclosure,
type MacHopperInstallHost,
} from "./MacHopper.js";
import { installMacHopper, type MacHopperInstallHost } from "./MacHopper.js";
class FakeMacHopperHost implements MacHopperInstallHost {
readonly archive = new TextEncoder().encode("hopper-dmg");
@ -75,18 +71,6 @@ class FakeMacHopperHost implements MacHopperInstallHost {
}
describe("macOS Hopper installation", () => {
it("discloses network and command boundaries before installation", () => {
expect(macHopperInstallDisclosure).toMatchObject({
networkOrigins: [
"https://www.hopperapp.com/include/files-api.php?request=releases&public=true",
"https://www.hopperapp.com:443/downloader/public/",
],
});
expect(macHopperInstallDisclosure.commands).toContain(
"hdiutil attach -readonly -nobrowse -mountpoint <private-mount> <verified-hopper.dmg>",
);
});
it("installs, verifies, opens, and cleans the official package", async () => {
const host = new FakeMacHopperHost();
await expect(installMacHopper({}, host)).resolves.toEqual({

View File

@ -3,27 +3,6 @@ import { expect, it } from "vitest";
import { runSetup } from "./Setup.js";
import { FakeSetupHost, options } from "./Setup.fixture.js";
it("uses the shared initial host snapshot for setup planning", async () => {
const host = new FakeSetupHost();
host.initialSetupState = async (scope) => ({
hopperPath: "/snapshot/Hopper",
providerEnvironment: { HOPPER_LAUNCHER_PATH: "/snapshot/Hopper" },
doctor: await host.doctor(scope),
});
host.clients = [{ name: "codex", configPath: "/codex.json" }];
const result = await runSetup(
{ ...options(false), dryRun: true, clientIds: ["codex"] },
host,
);
expect(result.status).toBe("planned");
expect(result.plannedActions.map(({ id }) => id)).toContain(
"configure_client:codex",
);
expect(host.doctorCalls).toBe(1);
});
it("refreshes doctor after approved setup changes", async () => {
const host = new FakeSetupHost();
host.doctorHealthy = false;
@ -41,5 +20,4 @@ it("refreshes doctor after approved setup changes", async () => {
);
expect(result.doctor.healthy).toBe(true);
expect(host.doctorCalls).toBe(2);
});

View File

@ -7,6 +7,7 @@ import {
type JavaScriptArtifactGraphCoverage,
} from "./JavaScriptArtifactGraphContext.js";
import { astObservationEvidence } from "./JavaScriptArtifactGraphEvidence.js";
import { compositeKey } from "../../domain/unicodeCodePointOrder.js";
import {
addElectronInferenceEdge,
electronFindingSourceNode,
@ -50,7 +51,9 @@ const addNativeBinding = (input: NativeBindingInput): void => {
identity: electronObservationIdentity(
context,
"javascript-requested-native-export",
`${file.path}:${value.specifier}:${value.binding_kind}:${value.members.join("\0")}:${electronRangeKey(value.location)}`,
// Members are a variable-length source-derived list: compositeKey keeps
// ["a\0b"] distinct from ["a", "b"] inside the digested identity.
`${file.path}:${value.specifier}:${value.binding_kind}:${compositeKey(value.members)}:${electronRangeKey(value.location)}`,
),
observations: [
{

View File

@ -31,28 +31,49 @@ const analyzeSource = async (
);
};
it.each([
"fetch('');",
"new WebSocket('');",
"navigator.serviceWorker.register('');",
"new Worker('');",
"const x = require('');",
"import y from ''; y();",
"import('');",
"localStorage.getItem('');",
"localStorage.setItem('', 1);",
"indexedDB.open('');",
"win.loadFile('');",
"win.loadURL('');",
"app.get('', handler);",
"process.on('', () => 1);",
"const { EventEmitter } = require('events'); new EventEmitter().on('', () => 1);",
"const { spawn } = require('child_process'); spawn('/bin/x').on('', () => 1);",
"const { ipcMain } = require('electron'); ipcMain.handle('', () => 1);",
"const { ipcRenderer } = require('electron'); ipcRenderer.invoke('');",
"const { contextBridge } = require('electron'); contextBridge.exposeInMainWorld('', {});",
])("analyzes the legal empty string literal in %s", async (source) => {
await expect(analyzeSource(source)).resolves.toBeDefined();
it("retains empty literal evidence across the application boundary surfaces", async () => {
const sources = [
"fetch('');",
"new WebSocket('');",
"navigator.serviceWorker.register('');",
"new Worker('');",
"const x = require('');",
"import y from ''; y();",
"import('');",
"localStorage.getItem('');",
"localStorage.setItem('', 1);",
"indexedDB.open('');",
"win.loadFile('');",
"win.loadURL('');",
"app.get('', handler);",
"process.on('', () => 1);",
"const { EventEmitter } = require('events'); new EventEmitter().on('', () => 1);",
"const { spawn } = require('child_process'); spawn('/bin/x').on('', () => 1);",
"const { ipcMain } = require('electron'); ipcMain.handle('', () => 1);",
"const { ipcRenderer } = require('electron'); ipcRenderer.invoke('');",
"const { contextBridge } = require('electron'); contextBridge.exposeInMainWorld('', {});",
];
const output = await analyzeSource(sources.join("\n"));
expect(output.graph.nodes).toEqual(
expect.arrayContaining([
expect.objectContaining({ kind: "endpoint" }),
expect.objectContaining({ kind: "worker" }),
expect.objectContaining({ kind: "ipc-channel" }),
expect.objectContaining({ kind: "context-bridge-api" }),
]),
);
expect(output.semantic_graph?.nodes).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "request",
properties: expect.objectContaining({ endpoint: "" }),
}),
expect.objectContaining({
kind: "event",
properties: expect.objectContaining({ event_name: "" }),
}),
]),
);
});
it("analyzes a plain long-line loader without package metadata", async () => {

View File

@ -159,21 +159,26 @@ describe("module specifier punctuation", () => {
});
describe("ESM module URL decoding", () => {
it.each([
["./plain.mjs", "plain.mjs"],
["./space%20name.mjs", "space name.mjs"],
["./pr%C3%A9load.mjs", "préload.mjs"],
["./name%3F%23.mjs", "name?#.mjs"],
["./percent%2520.mjs?cache=1#v2", "percent%20.mjs"],
])(
"points %s imports to the file loaded by Node",
async (specifier, target) => {
const root = await createTestTempDirectory("rea-module-url-");
const main = join(root, "main.mjs");
await writeFile(
main,
`import value from ${JSON.stringify(specifier)}; console.log(value);`,
);
it("resolves URL-encoded imports to the files loaded by Node", async () => {
const cases = [
["./plain.mjs", "plain.mjs"],
["./space%20name.mjs", "space name.mjs"],
["./pr%C3%A9load.mjs", "préload.mjs"],
["./name%3F%23.mjs", "name?#.mjs"],
["./percent%2520.mjs?cache=1#v2", "percent%20.mjs"],
] as const;
const root = await createTestTempDirectory("rea-module-url-");
const main = join(root, "main.mjs");
await writeFile(
main,
cases
.map(
([specifier], index) =>
`import value${index} from ${JSON.stringify(specifier)}; console.log(value${index});`,
)
.join("\n"),
);
for (const [specifier, target] of cases) {
await writeFile(
join(root, target),
`export default ${JSON.stringify(target)};`,
@ -181,19 +186,23 @@ describe("ESM module URL decoding", () => {
const encoded = specifier.slice(2).split("?", 1)[0]?.split("#", 1)[0];
if (encoded !== undefined && encoded !== target)
await writeFile(join(root, encoded), 'export default "encoded decoy";');
const native = await promisify(execFile)(process.execPath, [main], {
timeout: 5_000,
});
expect(native.stdout.trim()).toBe(target);
}
const native = await promisify(execFile)(process.execPath, [main], {
timeout: 5_000,
});
expect(native.stdout.trim().split("\n")).toEqual(
cases.map(([, target]) => target),
);
const result = await analyzeJavaScriptApplication({
input_path: root,
format: "directory",
});
if (!result.ok) throw result.error;
const { graph } = javascriptApplicationAnalysisResultSchema.parse(
result.value.normalized_result,
);
const result = await analyzeJavaScriptApplication({
input_path: root,
format: "directory",
});
if (!result.ok) throw result.error;
const { graph } = javascriptApplicationAnalysisResultSchema.parse(
result.value.normalized_result,
);
for (const [specifier, target] of cases) {
const imports = graph.edges.filter(
(edge) =>
edge.relation === "imports" &&
@ -202,8 +211,8 @@ describe("ESM module URL decoding", () => {
expect(imports.length).toBeGreaterThan(0);
for (const edge of imports)
expect(edge.properties.resolved_path).toBe(target);
},
);
}
});
it("preserves CommonJS literal percent names", () => {
expect(
@ -249,26 +258,21 @@ const packageEntryUrlCases = [
["main", "./actual#file.cjs", "actual#file.cjs"],
] as const;
describe("package exports URL paths", () => {
it.each(
(["import", "require"] as const).flatMap((moduleKind) =>
packageEntryUrlCases.map(([field, declaredPath, target]) => ({
moduleKind,
field,
declaredPath,
target,
})),
),
)(
"matches Node $moduleKind for $field $declaredPath",
async ({ moduleKind, field, declaredPath, target }) => {
const root = await createTestTempDirectory("rea-package-export-url-");
const packageRoot = join(root, "node_modules", "fixture");
it.each(["import", "require"] as const)(
"matches Node %s across package exports and main path representations",
async (moduleKind) => {
const root = await createTestTempDirectory("rea-package-export-url-");
for (const [
index,
[field, declaredPath, target],
] of packageEntryUrlCases.entries()) {
const name = `fixture-${index}`;
const packageRoot = join(root, "node_modules", name);
await mkdir(packageRoot, { recursive: true });
await writeFile(
join(packageRoot, "package.json"),
JSON.stringify({
name: "fixture",
name,
[field]: declaredPath,
}),
);
@ -282,41 +286,54 @@ describe("package exports URL paths", () => {
join(packageRoot, literal),
'module.exports="literal decoy";',
);
const main = join(
root,
moduleKind === "import" ? "main.mjs" : "main.cjs",
);
await writeFile(
main,
moduleKind === "import"
? 'import value from "fixture"; console.log(value);'
: 'console.log(require("fixture"));',
);
const native = await promisify(execFile)(process.execPath, [main], {
timeout: 5_000,
});
expect(native.stdout.trim()).toBe(target);
const result = await analyzeJavaScriptApplication({
input_path: root,
format: "directory",
});
if (!result.ok) throw result.error;
const { graph } = javascriptApplicationAnalysisResultSchema.parse(
result.value.normalized_result,
);
}
const main = join(root, moduleKind === "import" ? "main.mjs" : "main.cjs");
await writeFile(
main,
packageEntryUrlCases
.map((_, index) =>
moduleKind === "import"
? `import value${index} from "fixture-${index}"; console.log(value${index});`
: `console.log(require("fixture-${index}"));`,
)
.join("\n"),
);
const native = await promisify(execFile)(process.execPath, [main], {
timeout: 5_000,
});
expect(native.stdout.trim().split("\n")).toEqual(
packageEntryUrlCases.map(([, , target]) => target),
);
const result = await analyzeJavaScriptApplication({
input_path: root,
format: "directory",
});
if (!result.ok) throw result.error;
const { graph } = javascriptApplicationAnalysisResultSchema.parse(
result.value.normalized_result,
);
for (const [
index,
[field, declaredPath, target],
] of packageEntryUrlCases.entries()) {
const imports = graph.edges.filter(
(edge) =>
edge.relation === "imports" &&
edge.properties.specifier === "fixture",
edge.properties.specifier === `fixture-${index}`,
);
expect(imports.length).toBeGreaterThan(0);
expect(
imports.length,
`${moduleKind}: ${field} ${declaredPath}`,
).toBeGreaterThan(0);
for (const edge of imports)
expect(edge.properties.resolved_path).toBe(
`node_modules/fixture/${target}`,
`node_modules/fixture-${index}/${target}`,
);
},
);
}
},
);
describe("package exports URL path rejections", () => {
it.each(
(["import", "require"] as const).flatMap((moduleKind) =>
[

View File

@ -1,5 +1,3 @@
import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import { MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE } from "../../contracts/managed/managedWorkflowExamples.js";
@ -24,50 +22,6 @@ const exampleMethod = () => {
};
describe("managed decompiler reconstruction import", () => {
it("imports decompiler output as inference locked to static IL evidence", () => {
const result = importManagedReconstruction(exampleInput());
expect(result).toMatchObject({
phase: "reconstruction-import",
executed: false,
static_observation: {
artifact_sha256: "2".repeat(64),
mvid: "11112222-3333-4444-9555-666677778888",
},
decompiler: {
name: "ilspycmd",
version: "9.1.0.7988",
family: "ilspy",
},
summary: {
imported_methods: 1,
decompiled_csharp_methods: 1,
},
methods: [
{
token: "0x06000001",
signature_sha256: "3".repeat(64),
normalized_il_sha256: "5".repeat(64),
validation: {
matched_static_member: true,
exact_build_required: true,
canonical_observation: false,
confidence_floor: "inference",
},
},
],
});
expect(result.reconstruction_id).toMatch(/^mre_[a-f0-9]{64}$/u);
expect(result.methods[0]?.reconstruction.text_sha256).toBe(
createHash("sha256")
.update("internal static void Main() { }")
.digest("hex"),
);
expect(result.limitations.join(" ")).toContain(
"metadata and IL observations remain canonical",
);
});
it("rejects reconstruction text hash drift", () => {
expect(() =>
importManagedReconstruction({
@ -147,29 +101,6 @@ describe("managed decompiler reconstruction import", () => {
}),
).toThrow(/does not match/u);
});
it("wraps imported reconstruction in Evidence", () => {
const evidence = importManagedReconstructionEvidence(exampleInput());
if (!evidence.ok) throw evidence.error;
expect(evidence.value).toMatchObject({
operation: "import_managed_reconstruction",
provider: { id: "rea-dotnet-workflows" },
confidence: "inferred",
authority: "analyst-inference",
normalized_result: {
phase: "reconstruction-import",
methods: [
{
reconstruction: {
kind: "decompiled-csharp",
language: "csharp",
},
},
],
},
});
});
});
describe("managed reconstruction input diagnostics", () => {

View File

@ -71,31 +71,6 @@ describe("native API switch uncertainty", () => {
});
describe("native API inspection", () => {
it("preserves structured type and jump-table boundary evidence", () => {
const dossier = functionDossierSchema.parse(ghidraFunctionDossier());
expect(projectNativeApiInspection(dossier)).toMatchObject({
procedure: { address: "0x401000", name: "fixture_main" },
boundary: {
available: true,
return_type: { data_type: "int", confidence: "medium" },
jump_tables: [
{
dispatch_address: "0x401010",
data_sources: [{ address: "0x403000" }],
mappings: [
{
target_address: "0x401020",
},
],
},
],
},
unsupported_branches: [],
residual_unknowns: [],
});
});
it("preserves unsupported provider branches as residual unknowns", () => {
const value = ghidraFunctionDossier();
if (typeof value !== "object" || value === null || Array.isArray(value))

View File

@ -51,41 +51,6 @@ describe("inert keyed archive decoding", () => {
expect.objectContaining({ source: 1, target: 2, status: "resolved" }),
);
});
it.each([
["binary", { UID: 1 }],
["XML", { CF$UID: 1 }],
] as const)(
"resolves a %s root encoded under a UID-named key",
(format, reference) => {
const named = {
...archive,
$objects: ["$null", "payload"],
$top: { UID: reference, CF$UID: reference },
};
const graph = decodeKeyedArchiveBytes(
Buffer.from(format === "binary" ? buildBinary(named) : build(named)),
{ offset: 0, limit: 2 },
);
expect(
graph.references.map(({ source, path, target, status }) => ({
source,
path,
target,
status,
})),
).toEqual([
{ source: null, path: ["CF$UID"], target: 1, status: "resolved" },
{ source: null, path: ["UID"], target: 1, status: "resolved" },
]);
const selected = decodeKeyedArchiveBytes(
Buffer.from(format === "binary" ? buildBinary(named) : build(named)),
{ root: "UID", offset: 0, limit: 2 },
);
expect(selected.references).toEqual([
expect.objectContaining({ path: ["UID"], target: 1 }),
]);
},
);
it("reports an XML archive dictionary keyed __proto__ as omitted", () => {
const xml = build({ ...archive, $objects: ["$null", "value"] }).replace(
"<key>$top</key>",

View File

@ -6,6 +6,7 @@ import type {
WebMcpDiscovery,
} from "../domain/webMcpDiscovery.js";
import { inferJsonShape } from "../domain/jsonShape.js";
import { compositeKey } from "../domain/unicodeCodePointOrder.js";
import type { CdpEndpointDiscovery, CdpEndpointTarget } from "./CdpEndpoint.js";
import type { CdpConnection, CdpEvent } from "./CdpConnection.js";
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
@ -502,8 +503,11 @@ const buildWebMcpResult = (options: WebMcpResultOptions): WebMcpDiscovery => {
const toolKey = (frameUrl: string, frameId: string, name: string): string =>
// Registrations without a known admitted frame are excluded above, so the
// frame ID here always distinguishes owners and keys cannot collapse.
`webmcp_${createHash("sha256").update(`${frameUrl}\0${frameId}\0${name}`).digest("hex")}`;
// frame ID here always distinguishes owners. Composite tuple keeps a NUL
// inside one field from collapsing two registrations before hashing.
`webmcp_${createHash("sha256")
.update(compositeKey([frameUrl, frameId, name]))
.digest("hex")}`;
const booleanOrNull = (value: unknown): boolean | null =>
typeof value === "boolean" ? value : null;

View File

@ -268,6 +268,20 @@ const fetchOne = async (
}
};
/**
* Fetch redirect statuses per WHATWG Fetch (redirect status list:
* 301, 302, 303, 307, 308). Single owner for redirect decisions.
* 300/304/305/306 carry `Location` without being redirects — following
* them would replace the observed failure with an unrelated map.
* See https://fetch.spec.whatwg.org/#redirect-status
*/
export const isFetchRedirectStatus = (status: number): boolean =>
status === 301 ||
status === 302 ||
status === 303 ||
status === 307 ||
status === 308;
const fetchFollowingApprovedRedirects = async (
initialUrl: string,
allowedOrigins: readonly string[],
@ -298,9 +312,10 @@ const fetchFollowingApprovedRedirects = async (
await response.body?.cancel(signal.reason).catch(() => undefined);
throw signal.reason;
}
// Location is a redirect target only for the Fetch redirect statuses.
// A 304 or another 3xx response must retain its own HTTP failure.
if (![301, 302, 303, 307, 308].includes(response.status))
// `Location` is a redirect target only for Fetch redirect statuses.
// Other 3xx responses retain their own HTTP failure (strict identity:
// never replace the observed failure with an unrelated map).
if (!isFetchRedirectStatus(response.status))
return { response, fetchedUrl: current };
const location = response.headers.get("location");
if (location === null) return { response, fetchedUrl: current };

View File

@ -1,95 +0,0 @@
import { describe, expect, it } from "vitest";
import { isCliOperationFailure } from "./cliLogging.js";
import {
analysisCliErrorEnvelopeSchema,
analysisErrorProjectionSchema,
} from "./contracts/errorSchemas.js";
import { AnalysisInputError } from "./domain/analysisErrorCore.js";
import { projectAnalysisError } from "./domain/analysisErrorProjection.js";
const projection = projectAnalysisError(new AnalysisInputError("test-input"));
const envelope = { error: "Application workflow failed", ...projection };
describe("CLI JSON input failure recognition", () => {
it.each([
{},
{ input_path: "input.json" },
{ input_reason: "invalid-json" },
{ input_path: "input.json", input_reason: "invalid-json" },
{ input_path: "missing.json", input_reason: "read-failed" },
{ input_path: "large.json", input_reason: "too-large" },
{ input_path: "", input_reason: "read-failed" },
])("recognizes a complete envelope with metadata %j", (metadata) => {
const value = { ...envelope, ...metadata };
expect(analysisCliErrorEnvelopeSchema.parse(value)).toEqual(value);
expect(isCliOperationFailure(value)).toBe(true);
});
it.each([
{ input_path: null },
{ input_path: 1 },
{ input_path: ["input.json"] },
{ input_reason: null },
{ input_reason: 1 },
{ input_reason: "" },
{ input_reason: "unknown" },
{ input_reason: ["invalid-json"] },
{ unknown_field: "extra" },
{ input_path: "input.json", input_reason: "invalid-json", extra: true },
{ error: "" },
{ error: 1 },
{ retryable: "false" },
{ remediation: { action: "retry", extra: true } },
])("rejects malformed or unknown envelope fields %j", (changes) => {
const value = { ...envelope, ...changes };
expect(analysisCliErrorEnvelopeSchema.safeParse(value).success).toBe(false);
expect(isCliOperationFailure(value)).toBe(false);
});
it.each(["code", "category", "message", "retryable", "remediation"])(
"requires the complete canonical projection, including %s",
(field) => {
const value = Object.fromEntries(
Object.entries({ ...envelope, input_reason: "invalid-json" }).filter(
([key]) => key !== field,
),
);
expect(analysisCliErrorEnvelopeSchema.safeParse(value).success).toBe(
false,
);
expect(isCliOperationFailure(value)).toBe(false);
},
);
it.each([
{ error: "ordinary data" },
{ code: "invalid_request", message: "ordinary data" },
{
error: "ordinary data",
code: "invalid_request",
input_path: "input.json",
},
{ error: { code: "invalid_request" } },
{ result: envelope },
{ input_path: "input.json", input_reason: "invalid-json" },
null,
[],
])("keeps ordinary or incomplete error-like data successful: %j", (value) => {
expect(isCliOperationFailure(value)).toBe(false);
});
it("keeps CLI metadata out of the strict canonical error schema", () => {
expect(analysisErrorProjectionSchema.parse(projection)).toEqual(projection);
for (const metadata of [
{ error: "Application workflow failed" },
{ input_path: "input.json" },
{ input_reason: "invalid-json" },
{ extra: true },
])
expect(
analysisErrorProjectionSchema.safeParse({ ...projection, ...metadata })
.success,
).toBe(false);
});
});

View File

@ -24,60 +24,19 @@ import {
type UninstallHost,
} from "./application/Uninstall.js";
import { CLI_COMMANDS } from "./cliCommandNames.js";
import { isProcessCliFailure } from "./application/process/ProcessCli.js";
describe("CLI operation status classification", () => {
it.each([
[
"projected error",
projectAnalysisError(new ConfigurationError("invalid setting")),
],
[
"MCP-shaped projected error",
{
error: projectAnalysisError(new ConfigurationError("invalid setting")),
},
],
["unhealthy diagnostic", { healthy: false, checks: [] }],
])("classifies %s as failure", (_label, value) => {
expect(isCliOperationFailure(value)).toBe(true);
});
it.each([
[
"ordinary data with code",
{ code: "configuration_invalid", message: "source data" },
],
[
"evidence about an error",
{
result: projectAnalysisError(new ConfigurationError("source setting")),
},
],
["requested dry run", { status: "planned" }],
["partial analysis", { status: "partial" }],
["unsupported facet with evidence", { status: "unsupported" }],
["cancelled setup", { status: "cancelled" }],
["ready setup", { status: "ready" }],
["complete uninstall", { status: "complete" }],
["current version", { status: "current" }],
["completed update", { status: "updated" }],
["healthy diagnostics", { healthy: true, checks: [] }],
["bounded evidence", { evidence: [{ truncated: true }] }],
])("keeps %s successful", (_label, value) => {
expect(isCliOperationFailure(value)).toBe(false);
});
it("uses the process command's typed error output", () => {
expect(
isProcessCliFailure({
error: "Process command failed",
category: "invalid_input",
message: "The capture input is invalid.",
}),
).toBe(true);
expect(isProcessCliFailure({ error: "data about an error" })).toBe(false);
});
it("does not treat ordinary error-looking data or nested Evidence as command failure", () => {
expect(
isCliOperationFailure({
code: "configuration_invalid",
message: "source data",
}),
).toBe(false);
expect(
isCliOperationFailure({
result: projectAnalysisError(new ConfigurationError("source setting")),
}),
).toBe(false);
});
describe("CLI command outcome classification", () => {

View File

@ -1,126 +0,0 @@
import pino from "pino";
import { describe, expect, it } from "vitest";
import {
projectReferenceSourceImportError,
type ReferenceSourceImportError,
} from "./application/ReferenceSourceImportTypes.js";
import { isReferenceSourceImportCliFailure } from "./cli/referenceSourceImportStatus.js";
import { isCliOperationFailure, logCliCommand } from "./cliLogging.js";
import { ConfigurationError } from "./domain/configurationErrors.js";
import { projectAnalysisError } from "./domain/analysisErrorProjection.js";
const importFailure = (code: ReferenceSourceImportError["code"]) => ({
error: "Import failed",
...projectReferenceSourceImportError({
tag: "reference-source-import",
code,
message: "Internal import diagnostic",
}),
});
const configurationFailure = {
error: "Import failed",
...projectAnalysisError(new ConfigurationError("Invalid import setting")),
};
const invalidRootFailure = importFailure("invalid-root");
const successfulValues: readonly (readonly [string, unknown])[] = [
["undefined", undefined],
["null", null],
["array", [invalidRootFailure]],
["string", "Import failed"],
["missing error label", { category: "invalid_input", message: "failure" }],
["missing category", { error: "Import failed", message: "failure" }],
["missing message", { error: "Import failed", category: "invalid_input" }],
["empty message", { ...invalidRootFailure, message: "" }],
["non-string message", { ...invalidRootFailure, message: 1 }],
["null message", { ...invalidRootFailure, message: null }],
["empty category", { ...invalidRootFailure, category: "" }],
["non-string category", { ...invalidRootFailure, category: 1 }],
["near category", { ...invalidRootFailure, category: "invalid-root" }],
["foreign category", { ...invalidRootFailure, category: "timeout" }],
["near error label", { ...invalidRootFailure, error: "Import failure" }],
["label suffix", { ...invalidRootFailure, error: "Import failed " }],
["empty error label", { ...invalidRootFailure, error: "" }],
["non-string error label", { ...invalidRootFailure, error: true }],
["extra field", { ...invalidRootFailure, source: "ordinary data" }],
["extra undefined field", { ...invalidRootFailure, source: undefined }],
["nested legacy error", { error: invalidRootFailure }],
["ordinary error-like data", { error: "source error", message: "data" }],
[
"ordinary code and message",
{ code: "configuration_invalid", message: "data" },
],
["nested evidence", { result: invalidRootFailure }],
["partial result", { status: "partial" }],
["unsupported result", { status: "unsupported" }],
["successful result", { status: "complete", entries: [] }],
];
describe("reference-source import CLI failure classification", () => {
it.each(["cancelled", "invalid-root", "unsupported", "io", "parse"] as const)(
"recognizes the projected %s failure only at this command boundary",
(code) => {
const value = importFailure(code);
expect(isCliOperationFailure(value)).toBe(false);
expect(isReferenceSourceImportCliFailure(value)).toBe(true);
},
);
it("retains canonical configuration failure classification", () => {
expect(isReferenceSourceImportCliFailure(configurationFailure)).toBe(true);
});
it.each(successfulValues)("keeps %s successful", (_label, value) => {
expect(isReferenceSourceImportCliFailure(value)).toBe(false);
});
});
describe("reference-source import CLI logging seam", () => {
it.each([
["cancelled", importFailure("cancelled"), true],
["invalid-root", invalidRootFailure, true],
["unsupported", importFailure("unsupported"), true],
["io", importFailure("io"), true],
["parse", importFailure("parse"), true],
["configuration", configurationFailure, true],
["success", { status: "complete", entries: [] }, false],
] as const)(
"preserves %s output and logs its status",
async (_label, value, failed) => {
const records: unknown[] = [];
const logger = pino(
{ level: "info" },
{
write: (message) => {
records.push(JSON.parse(message) as unknown);
},
},
);
const previousExitCode = process.exitCode;
try {
process.exitCode = undefined;
const output = await logCliCommand(
logger,
"import-reference-source",
async () => value,
isReferenceSourceImportCliFailure,
);
expect(output).toBe(value);
expect.soft(process.exitCode).toBe(failed ? 1 : undefined);
expect(records).toEqual([
expect.objectContaining({
command: "import-reference-source",
status: failed ? "error" : "ok",
level: failed ? 50 : 30,
msg: failed ? "CLI command failed" : "CLI command completed",
}),
]);
} finally {
process.exitCode = previousExitCode;
}
},
);
});

View File

@ -170,12 +170,6 @@ it("preserves whitespace in selected browser and Electron filesystem names", ()
});
});
it("keeps evidence bundle export overwrite disabled by default", () => {
expect(
exportEvidenceBundleInputSchema.safeParse({ path: "/tmp/bundle.json" }),
).toMatchObject({ success: true, data: { overwrite: false } });
});
describe.runIf(process.platform === "win32")(
"Windows absolute path forms",
() => {

View File

@ -1,7 +1,6 @@
import { describe, expect, it } from "vitest";
import { traceJavaScriptSemanticsRequestSchema } from "./javascript/applicationWorkflowInputContracts.js";
import { ARTIFACT_TOOL_CONTRACTS } from "./artifactToolContracts.js";
import { JAVASCRIPT_FEATURE_TRACE_EXAMPLE } from "./javascript/javascriptApplicationWorkflowExamples.js";
import {
MANAGED_APPLICATION_GRAPH_EXAMPLE,
@ -63,13 +62,4 @@ describe("workflow input contracts", () => {
managedApplicationGraphReferenceInputSchema.safeParse({}).success,
).toBe(false);
});
it("accepts an explicit artifact integrity continuation policy", () => {
const input = { integrity_policy: "record-and-continue" as const };
const inspect = ARTIFACT_TOOL_CONTRACTS.find(
({ name }) => name === "inspect_artifact",
);
if (inspect === undefined) throw new Error("Missing inspect_artifact");
expect(inspect.inputSchema.safeParse(input).success).toBe(true);
});
});

View File

@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest";
import {
analysisProfileSchema,
analysisProfilesEqual,
createAnalysisProfile,
} from "./analysisProfile.js";
import type { JsonValue } from "./jsonValue.js";
@ -14,20 +13,6 @@ const PROVIDER = {
} as const;
describe("analysis profile commitments", () => {
it("canonicalizes parameter key order and validates its digest", () => {
const first = createAnalysisProfile(PROVIDER, {
architecture: "arm64",
analyzers: { strings: true, functions: true },
});
const reordered = createAnalysisProfile(PROVIDER, {
analyzers: { functions: true, strings: true },
architecture: "arm64",
});
expect(reordered.digest).toBe(first.digest);
expect(analysisProfilesEqual(first, reordered)).toBe(true);
expect(analysisProfileSchema.parse(first)).toEqual(first);
});
it("separates provider builds and semantic parameters", () => {
const baseline = createAnalysisProfile(PROVIDER, { loader: "default" });
const changedBuild = createAnalysisProfile(

View File

@ -2,33 +2,7 @@ import { expect, it } from "vitest";
import { projectPlistValue } from "./plistValue.js";
it("projects plist data, dates, and non-finite reals as typed values", () => {
expect(
projectPlistValue({
blob: new Uint8Array([0, 1, 2]),
when: new Date("2020-01-01T00:00:00Z"),
nested: [{ ratio: 0.5, missing: Number.NaN, unbounded: -Infinity }],
plain: { "": "empty key", flag: true, count: 42 },
}),
).toEqual({
value: {
blob: { $plist_type: "data", base64: "AAEC" },
when: { $plist_type: "date", iso8601: "2020-01-01T00:00:00.000Z" },
nested: [
{
ratio: 0.5,
missing: { $plist_type: "real", value: null },
unbounded: { $plist_type: "real", value: null },
},
],
plain: { "": "empty key", flag: true, count: 42 },
},
unknownRealCount: 2,
});
});
it("rejects values a plist decoder cannot produce", () => {
expect(() => projectPlistValue({ value: Symbol("x") })).toThrow();
it("rejects excessive plist nesting without exhausting the stack", () => {
const deep: unknown[] = [];
let cursor = deep;
for (let depth = 0; depth < 200; depth += 1) {

View File

@ -1,191 +0,0 @@
import { expect, it } from "vitest";
import {
artifactContradictionId,
artifactEdgeId,
artifactGraphDigest,
artifactIdForContent,
artifactManifestId,
occurrenceIdForLocation,
} from "./artifactIdentity.js";
import { appleProjectionId } from "./apple/appleApplication.js";
it("keeps content identity separate from occurrence location", () => {
const root = artifactIdForContent("a".repeat(64));
expect(root).toBe(
"art_91d55c997599a1b301a52f2ad8559a9f2bfa28927d7cb2c391f3cedf6072ccdd",
);
expect(artifactIdForContent("a".repeat(64))).toBe(root);
expect(
occurrenceIdForLocation({
rootArtifactId: "art_root",
logicalPath: "one.bin",
entryKind: "file",
}),
).toBe(
"occ_e6b5ac5042e4f050a3cb25f5af8419660c01f19523189470161f44113b4c16ec",
);
expect(
occurrenceIdForLocation({
rootArtifactId: root,
logicalPath: "one.bin",
entryKind: "file",
}),
).not.toBe(
occurrenceIdForLocation({
rootArtifactId: root,
logicalPath: "two.bin",
entryKind: "file",
}),
);
expect(
occurrenceIdForLocation({
rootArtifactId: root,
logicalPath: "one.bin",
entryKind: "file",
}),
).not.toBe(
occurrenceIdForLocation({
rootArtifactId: artifactIdForContent("b".repeat(64)),
logicalPath: "one.bin",
entryKind: "file",
}),
);
});
it("commits to graph arrays in the order established by their owner", () => {
const nodes = [
{ artifact_id: "art-a", sha256: "a" },
{ artifact_id: "art-b", sha256: "b" },
] as never;
const occurrences = [
{ occurrence_id: "occ-a", logical_path: "a" },
{ occurrence_id: "occ-b", logical_path: "b" },
] as never;
const edges = [{ edge_id: "edge-a" }] as never;
const contradictions = [] as never;
const graph = artifactGraphDigest({
nodes,
occurrences,
edges,
contradictions,
});
expect(
artifactGraphDigest({
nodes: [],
occurrences: [],
edges: [],
contradictions: [],
}),
).toBe("d9b34ff5b7099912cce6c39343453ebc7318febd2ed75205ce21caf27113e98f");
expect(
artifactGraphDigest({
nodes: [
{ artifact_id: "art-a", sha256: "changed-bytes" },
nodes[1],
] as never,
occurrences,
edges,
contradictions,
}),
).not.toBe(graph);
expect(
artifactGraphDigest({
nodes,
occurrences,
edges,
contradictions: [{ contradiction_id: "ic-a" }] as never,
}),
).not.toBe(graph);
const orderedContradictions = [
{
contradiction_id: "ic-z",
logical_path: "z",
observed_sha256: "a",
},
{
contradiction_id: "ic-a",
logical_path: "a",
observed_sha256: "b",
},
] as never;
const contradictionGraph = artifactGraphDigest({
nodes: [],
occurrences: [],
edges: [],
contradictions: orderedContradictions,
});
const reorderedContradictionGraph = artifactGraphDigest({
nodes: [],
occurrences: [],
edges: [],
contradictions: [...orderedContradictions].reverse(),
});
expect(contradictionGraph).toBe(
"121f28fb3c5f104e357d502c29994961aea68be22f7f8bcae6c90e540904ddc5",
);
expect(reorderedContradictionGraph).toBe(
"f7ba200f36804975789ee85fab32c4f79922c8a9e603b076d20bab09ba87377e",
);
expect(reorderedContradictionGraph).not.toBe(contradictionGraph);
expect(artifactManifestId("art_root", contradictionGraph)).toBe(
"agm_e25e6ed72612cab4a39efbbcf17e35f119ef944955ad87c0cbecce1a1c126342",
);
expect(artifactManifestId("art_root", reorderedContradictionGraph)).toBe(
"agm_a7631939867608767e773992a4d8fc672202dd1a4a234239eb9d2e50a625fedb",
);
expect(
artifactContradictionId({
rootArtifactId: "root",
logicalPath: "entry",
declaredSha256: "a",
observedSha256: "b",
}),
).not.toBe(
artifactContradictionId({
rootArtifactId: "root",
logicalPath: "entry",
declaredSha256: "a",
observedSha256: "c",
}),
);
expect(
artifactContradictionId({
rootArtifactId: "art_root",
logicalPath: "one.bin",
declaredSha256: "a",
observedSha256: "b",
}),
).toBe("ic_69ee65b6c79c597ca02c91777882edec17064e15a3abeb4ed652adc7781ce4d2");
expect(artifactManifestId("art_root", "b".repeat(64))).toBe(
"agm_3940382b43202a6700426217151879f6274e4c362c3728368c84af993fa1f9a6",
);
expect(
artifactEdgeId({
parent_artifact_id: "art_parent",
child_artifact_id: "art_child",
relation: "contains",
occurrence_id: "occ_child",
logical_path: "child",
}),
).toMatch(/^edge_[a-f0-9]{64}$/u);
});
it("makes Apple projection identity depend on projected facts and evidence", () => {
const projection = {
source_evidence_ids: ["ev-a", "ev-b"],
classification: ["javascript", "native"],
};
expect(appleProjectionId(projection)).toBe(
"aap_eb45cb11ec77d3c98268fa90ce32d3060760b8f70a534fc9255cdb92de316bf9",
);
expect(
appleProjectionId({ ...projection, classification: ["native"] }),
).not.toBe(appleProjectionId(projection));
expect(
appleProjectionId({
...projection,
source_evidence_ids: ["ev-a", "ev-c"],
}),
).not.toBe(appleProjectionId(projection));
});

View File

@ -2,7 +2,6 @@ import { createHash } from "node:crypto";
import { expect, it } from "vitest";
import { browserNetworkBodySchema } from "./browserNetworkEvidence.js";
import { browserScenarioSchema } from "./browserScenario.js";
const bytes = Buffer.from([0xff, 0, 0xfe]);
const retained = {
@ -27,28 +26,3 @@ it("validates the actual retained bytes against canonical base64, count, and dig
browserNetworkBodySchema.safeParse({ ...retained, ...mutation }).success,
).toBe(false);
});
it("defaults content selection independently and rejects unsupported controls", () => {
const input = {
browser: { mode: "launch", executable_path: "/fixture/chrome" },
start_url: { url: "https://example.test" },
actions: [{ step_id: "done", action: "wait_for_timeout", duration_ms: 1 }],
};
expect(browserScenarioSchema.parse(input).capture.network).toEqual({
request_body: false,
response_body: false,
header_values: false,
});
expect(
browserScenarioSchema.parse({
...input,
capture: { network: { response_body: true } },
}).capture.network,
).toEqual({ request_body: false, response_body: true, header_values: false });
expect(
browserScenarioSchema.safeParse({
...input,
capture: { network: { refetch: true } },
}).success,
).toBe(false);
});

View File

@ -3,13 +3,9 @@ import { describe, expect, it } from "vitest";
import {
browserEndpointSchema,
browserOriginSchema,
inspectWebPageInputSchema,
isLiteralLoopbackHostname,
listBrowserTargetsInputSchema,
sanitizeEndpointCandidate,
sanitizeBrowserUrl,
} from "./browserObservation.js";
import { captureWebScreenshotInputSchema } from "./webScreenshot.js";
describe("browser observation contracts", () => {
it("normalizes exact HTTP origins without accepting broader URL scopes", () => {
@ -45,82 +41,9 @@ describe("browser observation contracts", () => {
])
expect(browserEndpointSchema.safeParse(value).success, value).toBe(false);
});
it("recognizes bracketed and normalized IPv6 loopback hostnames", () => {
for (const hostname of ["127.0.0.1", "[::1]", "::1"])
expect(isLiteralLoopbackHostname(hostname), hostname).toBe(true);
for (const hostname of ["localhost", "127.0.0.2", "::2"])
expect(isLiteralLoopbackHostname(hostname), hostname).toBe(false);
});
it("applies defaults and rejects unknown public observation input fields", () => {
expect(
listBrowserTargetsInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
}),
).toEqual({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
});
expect(
inspectWebPageInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
}),
).toMatchObject({
observation_ms: 500,
include_accessibility_text: false,
include_console_text: false,
include_json_body_shapes: false,
include_websocket_shapes: false,
include_script_sources: false,
include_storage_keys: false,
});
expect(
inspectWebPageInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
unknown_field: true,
}).success,
).toBe(false);
});
});
describe("browser observation sensitive surfaces and retention", () => {
it("accepts selected sensitive capture surfaces without extra flags", () => {
const base = {
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
};
for (const input of [
{ include_console_text: true },
{ include_json_body_shapes: true },
{ include_websocket_shapes: true },
])
expect(
inspectWebPageInputSchema.safeParse({ ...base, ...input }).success,
).toBe(true);
expect(
inspectWebPageInputSchema.safeParse({
...base,
include_storage_fingerprints: true,
}).success,
).toBe(false);
});
it("accepts screenshot capture without an extra approval flag", () => {
const input = {
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
};
expect(captureWebScreenshotInputSchema.safeParse(input).success).toBe(true);
});
it("removes only URL userinfo and preserves the original query and fragment", () => {
expect(
sanitizeBrowserUrl(

View File

@ -6,7 +6,7 @@ import { describe, expect } from "vitest";
import { digestCanonicalValue } from "./canonicalDigest.js";
const canonicalizeDigest = (value: unknown): string => {
const legacyDigest = (value: unknown): string => {
const encoded = canonicalize(value);
if (encoded === undefined) throw new TypeError("Unserializable fixture");
return createHash("sha256").update(encoded).digest("hex");
@ -16,7 +16,7 @@ describe("incremental canonical digest", () => {
it.prop([fc.jsonValue()])(
"preserves canonicalize digests for JSON values",
(value) => {
expect(digestCanonicalValue(value)).toBe(canonicalizeDigest(value));
expect(digestCanonicalValue(value)).toBe(legacyDigest(value));
},
);
@ -25,7 +25,7 @@ describe("incremental canonical digest", () => {
const sparse: unknown[] = [undefined, Symbol("omit"), () => undefined];
sparse.length = 6;
const fixtures: unknown[] = [
{ "\ue000": "bmp", 𐀀: "supplementary" },
{ "\uE000": "BMP", "\u{10000}": "supplementary" },
{
"€": "\ud800",
"😀": "\udc00",
@ -41,7 +41,7 @@ describe("incremental canonical digest", () => {
{ nested: { toJSON: () => undefined } },
];
for (const value of fixtures)
expect(digestCanonicalValue(value)).toBe(canonicalizeDigest(value));
expect(digestCanonicalValue(value)).toBe(legacyDigest(value));
});
it("rejects unsupported roots, non-finite numbers, BigInt and ancestor cycles", () => {
@ -56,18 +56,15 @@ describe("incremental canonical digest", () => {
expect(() => digestCanonicalValue(value)).toThrow();
});
it("hashes repeated large leaves with the same byte sequence", () => {
const value = {
payload: Array.from({ length: 64 }, () => "x".repeat(16_384)),
};
expect(digestCanonicalValue(value)).toBe(canonicalizeDigest(value));
});
it("keeps digests identical when buffered parts cross flush boundaries", () => {
it("preserves canonical bytes across large keys, repeated leaves and buffer flushes", () => {
const manyKeys: Record<string, unknown> = {};
for (let i = 0; i < 2000; i += 1) manyKeys[`k${i}`] = i;
const value = { keys: manyKeys, tail: ["€", "😀", "x".repeat(9000)] };
expect(digestCanonicalValue(value)).toBe(canonicalizeDigest(value));
const value = {
keys: manyKeys,
payload: Array.from({ length: 64 }, () => "x".repeat(16_384)),
tail: ["€", "😀", "x".repeat(9000)],
};
expect(digestCanonicalValue(value)).toBe(legacyDigest(value));
});
it("preserves canonicalize property access and toJSON cycle behavior", () => {
@ -81,11 +78,11 @@ describe("incremental canonical digest", () => {
};
};
expect(digestCanonicalValue(createAccessor())).toBe(
canonicalizeDigest(createAccessor()),
legacyDigest(createAccessor()),
);
const shared = { toJSON: () => ({ answer: 42 }) };
expect(digestCanonicalValue([shared, shared])).toBe(
canonicalizeDigest([shared, shared]),
legacyDigest([shared, shared]),
);
});
});

View File

@ -1,6 +1,11 @@
import { createHash } from "node:crypto";
/** Hash canonicalize-compatible JSON without assembling an aggregate string. */
/**
* Streaming SHA-256 over canonical JSON. Hashing owner (avoids assembling
* aggregate strings); `comparisonSemantics.canonicalJson` (canonicalize lib)
* is the string-comparison owner. JSON object keys follow RFC 8785's UTF-16
* code-unit order in both; entity-list ordering is a separate concern.
*/
export const digestCanonicalValue = (
value: unknown,
context = "Comparison",
@ -64,6 +69,7 @@ const emitCanonical = (
} else {
emit("{");
let first = true;
// Match canonicalize/RFC 8785, including supplementary-plane keys.
for (const key of Object.keys(value).sort()) {
if (
Reflect.get(value, key) === undefined ||

View File

@ -2,7 +2,10 @@ import { z } from "zod";
import { artifactComparisonResultSchema } from "./artifactComparison.js";
import { uniqueSorted } from "./canonicalOrdering.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import {
compositeKey,
compareUnicodeCodePoints,
} from "./unicodeCodePointOrder.js";
import { comparisonSourceEvidenceIds } from "./comparisonSourceEvidence.js";
import { evidenceSchema, parseEvidence, type Evidence } from "./evidence.js";
import { functionComparisonResultSchema } from "./functionComparisonSchemas.js";
@ -389,11 +392,9 @@ const count = (
.length;
const compareFindings = (left: Finding, right: Finding): number =>
compareUnicodeCodePoints(left.scope, right.scope) ||
compareUnicodeCodePoints(left.dimension, right.dimension) ||
compareUnicodeCodePoints(
left.source_comparison_id,
right.source_comparison_id,
compositeKey([left.scope, left.dimension, left.source_comparison_id]),
compositeKey([right.scope, right.dimension, right.source_comparison_id]),
);
const assertUnique = (values: readonly string[], label: string): void => {

View File

@ -1,61 +0,0 @@
import { describe, expect, it } from "vitest";
import {
digestSchema,
isDigest,
isPrefixedDigest,
prefixedDigestSchema,
} from "./digests.js";
const DIGEST = "a".repeat(64);
const OTHER_DIGEST = "b".repeat(64);
describe("digest shapes", () => {
it("accepts a lowercase hex sha256", () => {
expect(digestSchema.parse(DIGEST)).toBe(DIGEST);
expect(isDigest(DIGEST)).toBe(true);
});
it.each([
["uppercase", DIGEST.toUpperCase()],
["too short", "a".repeat(63)],
["too long", "a".repeat(65)],
["non-hex", "g".repeat(64)],
["prefixed", `ev_${DIGEST}`],
])("rejects a %s digest", (_label, value) => {
expect(digestSchema.safeParse(value).success).toBe(false);
expect(isDigest(value)).toBe(false);
});
it("rejects non-string values", () => {
for (const value of [null, undefined, 42, {}, []])
expect(isDigest(value)).toBe(false);
});
it("keeps identifier families apart", () => {
expect(prefixedDigestSchema("ev").parse(`ev_${DIGEST}`)).toBe(
`ev_${DIGEST}`,
);
// The point of the prefix: an identifier from one family must not satisfy
// another, or a mis-pasted id would resolve to the wrong object.
expect(prefixedDigestSchema("ev").safeParse(`art_${DIGEST}`).success).toBe(
false,
);
expect(prefixedDigestSchema("ev").safeParse(DIGEST).success).toBe(false);
expect(isPrefixedDigest(`ev_${DIGEST}`, "ev")).toBe(true);
expect(isPrefixedDigest(`ev_${DIGEST}`, "art")).toBe(false);
});
it("distinguishes nested prefixes from their common ancestor", () => {
// `jag_node_` must not validate as `jag_` even though it starts with it.
expect(
prefixedDigestSchema("jag").safeParse(`jag_node_${DIGEST}`).success,
).toBe(false);
expect(prefixedDigestSchema("jag_node").parse(`jag_node_${DIGEST}`)).toBe(
`jag_node_${DIGEST}`,
);
expect(prefixedDigestSchema("jag").parse(`jag_${OTHER_DIGEST}`)).toBe(
`jag_${OTHER_DIGEST}`,
);
});
});

View File

@ -35,8 +35,3 @@ export const prefixedDigestSchema = (prefix: string): z.ZodString =>
/** True when `value` is a bare digest. */
export const isDigest = (value: unknown): value is string =>
typeof value === "string" && SHA256_PATTERN.test(value);
/** True when `value` is a digest carrying `prefix`. */
export const isPrefixedDigest = (value: unknown, prefix: string): boolean =>
typeof value === "string" &&
prefixedDigestSchema(prefix).safeParse(value).success;

View File

@ -1,18 +0,0 @@
import { describe, expect, it } from "vitest";
import { executableFormatHintSchema, validateDosComLength } from "./dosCom.js";
describe("explicit DOS COM interpretation", () => {
it.each([1, 0xff00])("admits the segment extent %i", (length) => {
expect(validateDosComLength(length).ok).toBe(true);
});
it.each([0, -1, 0xff01, 1.5, NaN, Infinity])(
"rejects invalid extent %i",
(length) => {
expect(validateDosComLength(length).ok).toBe(false);
},
);
it("does not turn arbitrary format names into raw COM", () => {
expect(executableFormatHintSchema.safeParse("dos-com").success).toBe(true);
expect(executableFormatHintSchema.safeParse("raw").success).toBe(false);
});
});

View File

@ -2,11 +2,6 @@ import { expect, it } from "vitest";
import { evmInterfaceSchema } from "./evmInterface.js";
import { evmInterfaceFixture } from "../../../tests/fixtures/evm/interface.js";
it("preserves original carrier and decoded byte identity as distinct observations", () => {
const fixture = evmInterfaceFixture();
expect(evmInterfaceSchema.parse(fixture)).toEqual(fixture);
expect(fixture.artifact.sha256).not.toBe(fixture.bytecode.sha256);
});
it.each(["byte-count", "body-offset", "selector", "authenticity"])(
"rejects false interface evidence: %s",
(problem) => {

View File

@ -143,7 +143,7 @@ describe("function comparison normalized identity", () => {
},
);
it.each(["sub_deadbeef", "fcn.00401000", "FUN_00401000", "FUN_deadBEEF"])(
it.each(["sub_deadbeef", "fcn.00401000", "FUN_deadBEEF"])(
"keeps %s as an address-derived name",
(name) => {
const generated = (base: "0x1000" | "0x2000") =>

View File

@ -17,6 +17,7 @@ import {
range,
} from "./javascriptStaticAnalysisHelpers.js";
import { semanticStaticPropertyName } from "./javascriptAstValues.js";
import { compositeKey } from "../unicodeCodePointOrder.js";
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
/** Inspect Electron IPC operations and validation candidates. */
@ -161,7 +162,14 @@ const addValidation = (
};
addLocatedFinding(context, {
collection: context.accumulator.senderValidations,
key: `electron-validation\0${input.subject}\0${input.mechanism}\0${input.expected.status === "literal" ? String(input.expected.value) : input.expected.expression}`,
key: compositeKey([
"electron-validation",
input.subject,
input.mechanism,
input.expected.status === "literal"
? String(input.expected.value)
: input.expected.expression,
]),
node,
value: finding,
});

View File

@ -1,6 +1,9 @@
import * as t from "@babel/types";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
import { stripQueryAndFragment } from "../artifactPathSyntax.js";
import type { ElectronNativeAddonBindingFinding } from "./electronStaticAnalysisTypes.js";
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
@ -141,7 +144,9 @@ const addBinding = (input: NativeBindingInput): void => {
const members = unique.length === 0 ? ["*"] : unique;
addLocatedFinding(context, {
collection: context.accumulator.nativeAddonBindings,
key: `native-addon-binding\0${kind}\0${specifier}\0${members.join("\0")}`,
// Members are a variable-length source-derived list: compositeKey keeps
// ["a\0b"] distinct from ["a", "b"] instead of collapsing one binding.
key: compositeKey(["native-addon-binding", kind, specifier, members]),
node,
value: {
specifier,

View File

@ -1,5 +1,8 @@
import { canonicalJson } from "../comparisonSemantics.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { JsonValue } from "../jsonValue.js";
@ -295,7 +298,7 @@ const uniqueStringProperty = (
const joined = (...values: readonly unknown[]): string | null => {
const strings = values.map(nonEmptyString);
return strings.some((value) => value === null) ? null : strings.join("\0");
return strings.some((value) => value === null) ? null : compositeKey(strings);
};
const nonEmptyString = (value: unknown): string | null =>

View File

@ -40,101 +40,116 @@ const matchingModes = [
describe.each(memberFields)(
"$kind seeds for $nodeKind / $field",
({ kind, nodeKind, field }) => {
it.each(matchingModes)(
"matches scalar and array values with $match / $value / case-sensitive $case_sensitive",
(mode) => {
const values: readonly [JsonValue, string][] = [
["openProject", ""],
[["closeProject", "openProject"], "[1]"],
];
for (const [value, suffix] of values) {
const node = memberNode(nodeKind, { [field]: value });
expect(
findApplicationFeatureSeeds([node], { kind, ...mode }),
).toEqual([propertyMatch(node, `${field}${suffix}`)]);
}
},
);
it("keeps exact, case-sensitive, and non-string exclusions", () => {
const node = memberNode(nodeKind, {
[field]: [null, 42, true, "openProject"],
});
for (const value of ["Project", "OPENPROJECT", "42", "true", "null"])
expect(findApplicationFeatureSeeds([node], seed(kind, value))).toEqual(
[],
);
expect(
findApplicationFeatureSeeds([node], {
...seed(kind, "Project"),
match: null,
}),
).toEqual([]);
});
it("retains actual indexes through nested arrays and object properties", () => {
const properties = {
nested: [{ [field]: [null, 42, false, [["openProject"]]] }],
};
const node = memberNode(nodeKind, properties);
const before = structuredClone(node);
expect(findApplicationFeatureSeeds([node], seed(kind))).toEqual([
propertyMatch(node, `nested[0].${field}[3][0][0]`),
]);
expect(node).toEqual(before);
expect(properties.nested[0]?.[field]).toEqual([
null,
42,
false,
[["openProject"]],
]);
});
it("preserves literal dotted-field suffixes for scalars and arrays", () => {
it("retains scalar and array member locations", () => {
const values: readonly [JsonValue, string][] = [
["openProject", ""],
[["openProject"], "[0]"],
[["closeProject", "openProject"], "[1]"],
];
for (const [value, suffix] of values) {
const node = memberNode(nodeKind, { [`nested.${field}`]: value });
const node = memberNode(nodeKind, { [field]: value });
expect(findApplicationFeatureSeeds([node], seed(kind))).toEqual([
propertyMatch(node, `nested.${field}${suffix}`),
]);
}
});
it("does not select near-fields, literal bracket keys, or object descendants", () => {
const cases = [
{
properties: { [`other_${field}`]: "openProject" },
path: `other_${field}`,
},
{ properties: { [`${field}[0]`]: "openProject" }, path: `${field}[0]` },
{
properties: { [`nested.${field}[0]`]: ["openProject"] },
path: `nested.${field}[0][0]`,
},
{
properties: { [field]: { name: "openProject" } },
path: `${field}.name`,
},
{
properties: { [field]: [{ name: "openProject" }] },
path: `${field}[0].name`,
},
];
for (const { properties, path } of cases) {
const node = memberNode(nodeKind, properties);
expect(findApplicationFeatureSeeds([node], seed(kind))).toEqual([]);
expect(findApplicationFeatureSeeds([node], seed("string"))).toEqual([
propertyMatch(node, path),
propertyMatch(node, `${field}${suffix}`),
]);
}
});
},
);
describe("shared array-member matching semantics", () => {
const kind = "api";
const nodeKind = "context-bridge-api";
const field = "members";
it.each(matchingModes)(
"matches $match / $value / case-sensitive $case_sensitive",
(mode) => {
const node = memberNode(nodeKind, {
members: ["closeProject", "openProject"],
});
expect(findApplicationFeatureSeeds([node], { kind, ...mode })).toEqual([
propertyMatch(node, "members[1]"),
]);
},
);
it("keeps exact, case-sensitive, and non-string exclusions", () => {
const node = memberNode(nodeKind, {
[field]: [null, 42, true, "openProject"],
});
for (const value of ["Project", "OPENPROJECT", "42", "true", "null"])
expect(findApplicationFeatureSeeds([node], seed(kind, value))).toEqual(
[],
);
expect(
findApplicationFeatureSeeds([node], {
...seed(kind, "Project"),
match: null,
}),
).toEqual([]);
});
it("retains actual indexes through nested arrays and object properties", () => {
const properties = {
nested: [{ [field]: [null, 42, false, [["openProject"]]] }],
};
const node = memberNode(nodeKind, properties);
const before = structuredClone(node);
expect(findApplicationFeatureSeeds([node], seed(kind))).toEqual([
propertyMatch(node, `nested[0].${field}[3][0][0]`),
]);
expect(node).toEqual(before);
expect(properties.nested[0]?.[field]).toEqual([
null,
42,
false,
[["openProject"]],
]);
});
it("preserves literal dotted-field suffixes for scalars and arrays", () => {
const values: readonly [JsonValue, string][] = [
["openProject", ""],
[["openProject"], "[0]"],
];
for (const [value, suffix] of values) {
const node = memberNode(nodeKind, { [`nested.${field}`]: value });
expect(findApplicationFeatureSeeds([node], seed(kind))).toEqual([
propertyMatch(node, `nested.${field}${suffix}`),
]);
}
});
it("does not select near-fields, literal bracket keys, or object descendants", () => {
const cases = [
{
properties: { [`other_${field}`]: "openProject" },
path: `other_${field}`,
},
{ properties: { [`${field}[0]`]: "openProject" }, path: `${field}[0]` },
{
properties: { [`nested.${field}[0]`]: ["openProject"] },
path: `nested.${field}[0][0]`,
},
{
properties: { [field]: { name: "openProject" } },
path: `${field}.name`,
},
{
properties: { [field]: [{ name: "openProject" }] },
path: `${field}[0].name`,
},
];
for (const { properties, path } of cases) {
const node = memberNode(nodeKind, properties);
expect(findApplicationFeatureSeeds([node], seed(kind))).toEqual([]);
expect(findApplicationFeatureSeeds([node], seed("string"))).toEqual([
propertyMatch(node, path),
]);
}
});
});
describe("feature seed selection controls", () => {
it.each([
{ kind: "api", nodeKind: "context-bridge-api", field: "label" },

View File

@ -1,21 +0,0 @@
import { describe, expect, it } from "vitest";
import { javascriptRecoveryInputSchema } from "./javascriptRecovery.js";
describe("JavaScript recovery requests", () => {
it.each([
{ path: "", output_directory: "/tmp/recovered" },
{ path: "/tmp/bundle.js" },
{
path: "/tmp/bundle.js",
output_directory: "/tmp/recovered",
approve: true,
},
{
path: "/tmp/bundle.js",
output_directory: "/tmp/recovered",
extraction_mode: "invented",
},
])("rejects malformed input %j", (input) => {
expect(javascriptRecoveryInputSchema.safeParse(input).success).toBe(false);
});
});

View File

@ -52,6 +52,8 @@ const resolveCallables = (
node: t.Node,
context: CallResolutionContext,
): LocalCallableResolution => {
// `unwrapJavaScriptExpression` strips all transparent layers iteratively,
// so a single recursion step suffices (no per-layer stack growth).
const unwrapped = unwrapJavaScriptExpression(node).node;
if (unwrapped !== node) return resolveCallables(unwrapped, context);
const direct = semanticCallableIdForNode(node);

View File

@ -1,13 +0,0 @@
import { expect, it } from "vitest";
import { javaScriptSemanticQueryInputSchema } from "./javascriptSemanticQuerySchemas.js";
it("rejects a source-map authority setting that the query does not consume", () => {
expect(
javaScriptSemanticQueryInputSchema.safeParse({
seed: { kind: "endpoint", value: "/api" },
direction: "forward-influence",
source_map_authority: { authority: "none" },
}).success,
).toBe(false);
});

View File

@ -50,7 +50,11 @@ interface TraversalFrame {
nextIndex: number;
}
/** Return child Babel nodes in visitor-key order. */
/**
* Child Babel nodes in `VISITOR_KEYS` order. Single owner for AST child
* expansion; bespoke `Object.values` / duplicated `childNodes` walkers must
* not be reintroduced (they diverge on loc/comment fields and ordering).
*/
export const childNodes = (node: t.Node): t.Node[] => {
const keys = t.VISITOR_KEYS[node.type];
if (keys === undefined) return [];

View File

@ -3,35 +3,51 @@ import { parse, type ParserPlugin } from "@babel/parser";
/** Babel AST produced by REA's inert JavaScript parser boundary. */
export type ParsedJavaScriptSource = ReturnType<typeof parse>;
/**
* Babel plugins for one source path. Single owner for TypeScript/JSX
* mode selection (tsc + Babel `typescript` plugin semantics).
*
* - `dts:true` only for `.d.ts/.d.mts/.d.cts` ambient declarations;
* ordinary `.ts` keeps the missing-initializer diagnostic.
* - JSX enabled for `.tsx`, `.jsx`, `.js`, and unknown paths; disabled for
* plain `.ts/.cts/.mts` (angle-bracket assertions, not JSX) and for all
* declaration files. `.mts` (including `.d.mts`) sets
* `disallowAmbiguousJSXLike`, matching tsc's `.mts` mode.
* - `decorators-legacy` always present: without it decorated sources fail
* hard instead of reporting recovered syntax, losing every derived fact.
* - Unknown paths retain the existing JSX-capable contract (best-effort
* parse; failures surface via `errorRecovery`, never silent misparse).
*/
export const parserPluginsForPath = (sourcePath?: string): ParserPlugin[] => {
const path = sourcePath ?? "";
const dts = /\.d\.(?:ts|mts|cts)$/iu.test(path);
const isTsLike = /\.(?:ts|cts|mts)$/iu.test(path);
const isTsx = /\.tsx$/iu.test(path);
const isMts = /\.mts$/iu.test(path);
const typescript: ParserPlugin = [
"typescript",
{
dts,
...(isMts ? { disallowAmbiguousJSXLike: true } : {}),
},
];
const jsxCapable = !dts && (isTsx || !isTsLike);
return jsxCapable
? ["decorators-legacy", "jsx", typescript]
: ["decorators-legacy", typescript];
};
/** Parse JavaScript or TypeScript once without attaching comments to AST nodes. */
export const parseJavaScriptSource = (
source: string,
sourcePath?: string,
): ParsedJavaScriptSource | null => {
const path = sourcePath ?? "";
const isMts = /\.mts$/iu.test(path);
const typescript: ParserPlugin = [
"typescript",
{
dts: /\.d\.(?:ts|mts|cts)$/iu.test(path),
...(isMts ? { disallowAmbiguousJSXLike: true } : {}),
},
];
try {
return parse(source, {
sourceType: "unambiguous",
errorRecovery: true,
attachComment: false,
// "decorators-legacy" admits both the standard and the legacy decorator
// forms, including parameter decorators. Without it a decorated
// TypeScript source fails to parse at all rather than reporting
// recovered syntax, which loses every fact derived from that source.
// TypeScript's .mts mode disables JSX and rejects ambiguous angle syntax.
// Plain .ts and .cts artifacts admit angle-bracket type assertions instead of JSX.
// Unknown paths retain the existing JSX-capable parser contract.
plugins: /\.(?:ts|cts|mts)$/iu.test(path)
? ["decorators-legacy", typescript]
: ["decorators-legacy", "jsx", typescript],
plugins: parserPluginsForPath(sourcePath),
});
} catch (cause: unknown) {
// Unparseable source is represented by the null return.

View File

@ -16,7 +16,10 @@ import {
semanticStaticPropertyName,
unwrapJavaScriptExpression,
} from "./javascriptAstValues.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
/** Explicit result for source text that Babel cannot parse. */
export const failedJavaScriptStaticAnalysis = (): JavaScriptStaticAnalysis => ({
@ -674,7 +677,17 @@ export const detectVendors = (source: string): string[] =>
export const registrationKey = (
registration: JavaScriptBundlerRegistration,
): string =>
`${registration.runtime}\0${registration.chunk_keys.join("\0")}\0${String(registration.unknown_chunk_keys)}\0${registration.modules.map(({ module_key: key, source_sha256: digest }) => `${key}:${digest}`).join("\0")}`;
// Collision-free tuple: chunk/module keys are variable-length source-derived
// lists, so NUL-joins would collapse ["a\0b"] with ["a", "b"].
compositeKey([
registration.runtime,
registration.chunk_keys,
String(registration.unknown_chunk_keys),
registration.modules.map(({ module_key: key, source_sha256: digest }) => [
key,
digest,
]),
]);
/** Sort values by a deterministic unique semantic key. */
export const sortedUnique = <Value>(

View File

@ -14,16 +14,10 @@ const absoluteParts = [
["/app", "/shared/preload.js"],
["ignored", "/app", "preload.js"],
["/app", "first", "/shared", "nested", "../preload.js"],
["/app", "preload/"],
["/app", "preload///"],
["/app/", "./sub", "../preload.js"],
["/app", ".."],
["/app", "../../.."],
["/"],
["///"],
["/app", "/", ""],
["", "/app", "", "preload.js", ""],
["/app", "..", "child", "..", "preload.js"],
];
const anchoredExpressions = [
@ -38,21 +32,12 @@ const anchoredExpressions = [
'path.resolve("ignored", path.dirname(__filename), "preload.js")',
"preload.js",
],
['path.resolve(__dirname, "sub", "../preload.js")', "preload.js"],
['path.resolve(__dirname, "../preload.js")', "../preload.js"],
['path.resolve(__dirname, "preload/")', "preload"],
['path.resolve(__dirname, "sub", __dirname, "preload.js")', "preload.js"],
['path.resolve(__dirname, "/shared", "preload.js")', "/shared/preload.js"],
[
'path.resolve(dirname(fileURLToPath(import.meta.url)), "/shared/preload.js")',
"/shared/preload.js",
],
["path.resolve(__dirname)", "."],
['path.resolve(__dirname, "")', "."],
['path.resolve(__dirname, "sub/../")', "."],
['path.posix.resolve(`/app`, `/shared`, "preload.js")', "/shared/preload.js"],
['path.posix.resolve("" + "/app", "preload.js")', "/app/preload.js"],
['path.posix.resolve(`/` + `app`, "preload.js")', "/app/preload.js"],
] as const;
const unknownExpressions = [

View File

@ -1,6 +1,9 @@
import { posix } from "node:path";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { HistoricalSourceGraph } from "../referenceSourceGraph.js";
import {
@ -322,15 +325,15 @@ const uniquePaths = (paths: readonly CurrentPath[]): CurrentPath[] =>
[
...new Map(
paths.map((path) => [
`${path.kind}\0${path.value}\0${String(path.allowSuffix)}`,
compositeKey([path.kind, path.value, String(path.allowSuffix)]),
path,
]),
).values(),
].sort(
(left, right) =>
compareUnicodeCodePoints(left.kind, right.kind) ||
compareUnicodeCodePoints(left.value, right.value) ||
Number(left.allowSuffix) - Number(right.allowSuffix),
].sort((left, right) =>
compareUnicodeCodePoints(
compositeKey([left.kind, left.value]),
compositeKey([right.kind, right.value]),
),
);
const pathSuffixes = (path: string): string[] => {

View File

@ -39,45 +39,19 @@ describe("inferJsonShape", () => {
expect(inferJsonShape("not-json")).toBeNull();
});
it.each([
["é", "e\u0301", "e\u0341"],
["é", "e\u0341", "e\u0301"],
["e\u0301", "é", "e\u0341"],
["e\u0301", "e\u0341", "é"],
["e\u0341", "é", "e\u0301"],
["e\u0341", "e\u0301", "é"],
])("orders distinct collation-equal paths from %j, %j, %j", (...names) => {
for (const name of names) expect(name.localeCompare("é")).toBe(0);
const values: Readonly<Record<string, unknown>> = {
é: 1,
"e\u0301": "value",
"e\u0341": null,
};
const shape = inferJsonShape(
JSON.stringify(
Object.fromEntries(names.map((name) => [name, values[name]])),
),
);
expect(shape).toEqual({
root_type: "object",
node_count: 4,
max_depth_observed: 1,
properties: [
{ path: "/e\u0301", types: ["string"], observations: 1 },
{ path: "/e\u0341", types: ["null"], observations: 1 },
{ path: "/é", types: ["number"], observations: 1 },
],
});
});
it.each([false, true])(
"preserves nested types, counts, depth and pointer names with reversed keys: %s",
(reverse) => {
it("preserves pointer escaping and code-point ordering independently of key insertion order", () => {
for (const reverse of [false, true]) {
const object = (number: number, value: unknown) => {
const entries = [
["é/~", number],
["e\u0301/~", value],
["e\u0341/~", true],
["A", false],
["_", false],
["a", false],
["z", false],
["\u{10000}", false],
["\uE000", false],
];
return Object.fromEntries(reverse ? entries.reverse() : entries);
};
@ -87,31 +61,30 @@ describe("inferJsonShape", () => {
expect(shape).toEqual({
root_type: "object",
node_count: 9,
node_count: 23,
max_depth_observed: 4,
properties: [
{ path: "/nested", types: ["array"], observations: 1 },
{ path: "/nested/*/A", types: ["boolean"], observations: 2 },
{ path: "/nested/*/_", types: ["boolean"], observations: 2 },
{ path: "/nested/*/a", types: ["boolean"], observations: 2 },
{
path: "/nested/*/e\u0301~1~0",
types: ["array", "null"],
observations: 2,
},
{
path: "/nested/*/e\u0341~1~0",
types: ["boolean"],
observations: 2,
},
{ path: "/nested/*/z", types: ["boolean"], observations: 2 },
{ path: "/nested/*/é~1~0", types: ["number"], observations: 2 },
{ path: "/nested/*/\uE000", types: ["boolean"], observations: 2 },
{ path: "/nested/*/\u{10000}", types: ["boolean"], observations: 2 },
],
});
},
);
it("orders paths deterministically by Unicode code point", () => {
const names = ["z", "é", "A", "_", "a"];
// Code-point order (locale-independent): A < _ < a < z < é.
// `localeCompare` order varies by host ICU and must not leak into evidence.
const expected = ["/A", "/_", "/a", "/z", "/é"];
const shape = inferJsonShape(
JSON.stringify(Object.fromEntries(names.map((name) => [name, true]))),
);
expect(shape?.properties.map(({ path }) => path)).toEqual(expected);
}
});
it("retains every parsed property beyond the former shape-node limit", () => {

View File

@ -1,6 +1,9 @@
import { z } from "zod";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
/** Objective-C method type. */
export const objcMethodTypeSchema = z.enum([
@ -397,8 +400,10 @@ export const inspectNativeDispatchMetadata = (
}));
const decodedImplementations = [...implementations.values()].sort(
(left, right) =>
compareUnicodeCodePoints(left.class_name, right.class_name) ||
compareUnicodeCodePoints(left.selector, right.selector),
compareUnicodeCodePoints(
compositeKey([left.class_name, left.selector]),
compositeKey([right.class_name, right.selector]),
),
);
const swift = swiftSymbols.length > 0;

View File

@ -1,10 +1,6 @@
import { expect, it } from "vitest";
import {
omitPrototypeKeys,
omittedPrototypeKeysLimitation,
parseXmlPropertyList,
} from "./propertyListKeys.js";
import { omitPrototypeKeys, parseXmlPropertyList } from "./propertyListKeys.js";
const plist = (body: string) =>
`<?xml version="1.0" encoding="UTF-8"?><plist version="1.0">${body}</plist>`;
@ -67,7 +63,4 @@ it("omits own __proto__ entries that JSON decoding creates", () => {
value: { a: [{ b: 3 }] },
omittedPrototypeKeys: 2,
});
expect(omittedPrototypeKeysLimitation(1)).toBe(
"1 dictionary entry keyed __proto__ was omitted because REA results cannot represent that key.",
);
});

View File

@ -3,7 +3,10 @@ import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
import { z } from "zod";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "./unicodeCodePointOrder.js";
import { evidenceBundleSchema, parseEvidenceBundle } from "./evidenceBundle.js";
import {
@ -405,8 +408,10 @@ const closureStatus = (
};
const reasonOrder = (left: ClosureReason, right: ClosureReason): number =>
compareUnicodeCodePoints(left.code, right.code) ||
compareUnicodeCodePoints(left.subject_id, right.subject_id);
compareUnicodeCodePoints(
compositeKey([left.code, left.subject_id]),
compositeKey([right.code, right.subject_id]),
);
const digest = (value: unknown): string => {
const encoded = canonicalize(value);

View File

@ -1,5 +1,8 @@
import { uniqueSorted } from "./canonicalOrdering.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "./unicodeCodePointOrder.js";
import type {
ReconstructionClaim,
ReconstructionClaimResult,
@ -90,7 +93,7 @@ export const reconstructionProbes = (
const probes = unknowns.flatMap(({ recommended_probes: items }) => items);
const candidates = probes.length > 0 ? probes : [defaultProbe(result.kind)];
for (const probe of candidates) {
const key = `${probe.operation}\0${probe.rationale}`;
const key = compositeKey([probe.operation, probe.rationale]);
const current = output.get(key) ?? {
...probe,
claim_ids: [],
@ -104,10 +107,11 @@ export const reconstructionProbes = (
output.set(key, current);
}
}
const sorted = [...output.values()].sort(
(left, right) =>
compareUnicodeCodePoints(left.operation, right.operation) ||
compareUnicodeCodePoints(left.rationale, right.rationale),
const sorted = [...output.values()].sort((left, right) =>
compareUnicodeCodePoints(
compositeKey([left.operation, left.rationale]),
compositeKey([right.operation, right.rationale]),
),
);
return sorted;
};

View File

@ -1,81 +0,0 @@
import { describe, expect, it } from "vitest";
import { parseReferenceSourceImports } from "./referenceSourceImportParsing.js";
const parse = (source: string) =>
parseReferenceSourceImports(
"main.ts",
new TextEncoder().encode(source),
"TypeScript",
);
describe("historical source dynamic imports", () => {
it.each([
'import("./lazy.js");',
'const module = import("./lazy.js");',
'async function load() { return await import("./lazy.js"); }',
'import("./lazy.js", { with: { type: "json" } });',
])("retains the literal dependency in %s", (source) => {
expect(parse(source)).toEqual({
relationships: [
{
from_path: "main.ts",
to: "./lazy.js",
kind: "imports",
resolution: "internal",
parse_state: "parsed",
},
],
parse_failures: [],
});
});
it.each([
"import(moduleName);",
"async function load(name: string) { return await import(name); }",
"import(`./${moduleName}.js`);",
])("keeps a computed dependency unknown in %s", (source) => {
expect(parse(source)).toEqual({
relationships: [
{
from_path: "main.ts",
to: "<dynamic-import>",
kind: "imports",
resolution: "unknown",
parse_state: "partial",
},
],
parse_failures: [],
});
});
it("retains static import and require relationships alongside dynamic imports", () => {
const result = parse(
'import "./static.js"; require("./common.cjs"); import("node:fs");',
);
expect(result.parse_failures).toEqual([]);
expect(result.relationships).toEqual([
{
from_path: "main.ts",
to: "./static.js",
kind: "imports",
resolution: "internal",
parse_state: "parsed",
},
{
from_path: "main.ts",
to: "./common.cjs",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
},
{
from_path: "main.ts",
to: "node:fs",
kind: "imports",
resolution: "external",
parse_state: "parsed",
},
]);
});
});

View File

@ -1,4 +1,4 @@
import { parse, type ParserPlugin } from "@babel/parser";
import { parse } from "@babel/parser";
import type {
CallExpression,
File,
@ -7,6 +7,7 @@ import type {
StringLiteral,
} from "@babel/types";
import { traverseJavaScriptAst } from "./javascript/javascriptSemanticTraversal.js";
import { parserPluginsForPath } from "./javascript/javascriptSourceParser.js";
import {
isCallExpression,
isExportAllDeclaration,
@ -206,6 +207,9 @@ const extractRequireAndDynamicImports = (
from_path: string,
relationships: ReferenceSourceImportRelationship[],
): void => {
// Single traversal owner: iterative, VISITOR_KEYS-gated, no recursion over
// loc/comment objects. Survives deeply nested generated member chains that
// overflowed the previous hand-rolled Object.values walker.
const expressions: Array<CallExpression | ImportExpression> = [];
for (const statement of body)
traverseJavaScriptAst(statement, {
@ -263,18 +267,11 @@ const parseWithBabel = (
ast: File | undefined;
reasons: readonly string[];
} => {
const plugins: ParserPlugin[] = ["jsx"];
if (language === "TypeScript" || language === "TSX") {
plugins.push([
"typescript",
{
dts:
path.endsWith(".d.ts") ||
path.endsWith(".d.mts") ||
path.endsWith(".d.cts"),
},
]);
}
// Single parser-mode owner: path-driven plugins (dts/JSX/mts) shared with
// the semantic pipeline. `language` only gates non-JS/TS callers upstream;
// TypeScript syntax must parse identically here and in semantic analysis.
void language;
const plugins = parserPluginsForPath(path);
try {
const ast = parse(source, {

View File

@ -1,150 +0,0 @@
import { describe, expect, it } from "vitest";
import { parseReferenceSourceImports } from "./referenceSourceImportParsing.js";
const recoverySource = [
"const missing;",
"let duplicate;",
"let duplicate;",
"let duplicate;",
].join("\n");
const recoveryReasons = [
"Missing initializer in const declaration. (1:13)",
"Identifier 'duplicate' has already been declared. (3:4)",
"Identifier 'duplicate' has already been declared. (4:4)",
];
const moduleSource = [
'import "./static.js";',
'export { value } from "./named.js";',
'export * from "./all.js";',
'require("./common.cjs");',
'require.resolve("node:fs");',
'import("./lazy.js");',
"import(moduleName);",
].join("\n");
const moduleRelationships = [
["./static.js", "imports", "internal", "parsed"],
["./named.js", "imports", "internal", "parsed"],
["./all.js", "imports", "internal", "parsed"],
["./common.cjs", "requires", "internal", "parsed"],
["node:fs", "requires", "external", "parsed"],
["./lazy.js", "imports", "internal", "parsed"],
["<dynamic-import>", "imports", "unknown", "partial"],
].map(([to, kind, resolution, parse_state]) => ({
to,
kind,
resolution,
parse_state,
}));
const parseSource = (path: string, source: string, language: string | null) =>
parseReferenceSourceImports(path, new TextEncoder().encode(source), language);
describe.each([
["main.js", "JavaScript"],
["main.jsx", "JSX"],
["main.ts", "TypeScript"],
["main.tsx", "TSX"],
])("recovered reference imports in %s", (path, language) => {
it("retains every diagnostic and marks recovered relationships partial", () => {
const result = parseSource(
path,
`${recoverySource}\n${moduleSource}`,
language,
);
expect(result.parse_failures).toEqual(
recoveryReasons.map((reason) => ({ path, parser: "babel", reason })),
);
expect(result.relationships).toEqual(
moduleRelationships.map((relationship) => ({
...relationship,
from_path: path,
parse_state: "partial",
})),
);
});
it("preserves valid and computed relationships without diagnostics", () => {
expect(parseSource(path, moduleSource, language)).toEqual({
relationships: moduleRelationships.map((relationship) => ({
...relationship,
from_path: path,
})),
parse_failures: [],
});
});
it("reports recovery diagnostics when there are no relationships", () => {
expect(parseSource(path, recoverySource, language)).toEqual({
relationships: [],
parse_failures: recoveryReasons.map((reason) => ({
path,
parser: "babel",
reason,
})),
});
});
it("returns no relationships when parsing throws", () => {
expect(
parseSource(path, 'import "./before.js";\nconst = ;', language),
).toEqual({
relationships: [],
parse_failures: [
{ path, parser: "babel", reason: "Unexpected token (2:6)" },
],
});
});
});
it.each(["main.ts", "types.d.ts", "types.d.mts", "types.d.cts"])(
"marks recovered TypeScript declarations partial in %s",
(path) => {
const result = parseSource(
path,
[
"let duplicate: number;",
"let duplicate: number;",
'import dep = require("node:path");',
'declare module "ambient-package" {}',
].join("\n"),
"TypeScript",
);
expect(result).toEqual({
relationships: [
{
from_path: path,
to: "node:path",
kind: "requires",
resolution: "external",
parse_state: "partial",
},
{
from_path: path,
to: "ambient-package",
kind: "declares-module",
resolution: "unknown",
parse_state: "partial",
},
],
parse_failures: [
{
path,
parser: "babel",
reason: "Identifier 'duplicate' has already been declared. (2:4)",
},
],
});
},
);
it("keeps unsupported source languages outside Babel parsing", () => {
expect(parseSource("main.py", recoverySource, "Python")).toEqual({
relationships: [],
parse_failures: [],
});
});

View File

@ -1,104 +0,0 @@
import { describe, expect, it } from "vitest";
import { parseReferenceSourceImports } from "./referenceSourceImportParsing.js";
const parseRequire = (source: string) =>
parseReferenceSourceImports(
"main.cjs",
new TextEncoder().encode(source),
"JavaScript",
);
const knownCallees = [
"require",
"require.resolve",
'require["resolve"]',
"require.main",
'require["main"]',
];
describe("historical source require member names", () => {
it.each([
...knownCallees,
'require[("resolve")]',
'require["res\\u006flve"]',
])(
"retains a literal dependency for %s beside computed members",
(callee) => {
expect(
parseRequire(
`require[resolve]("./ignored.js"); ${callee}("./dep.js");`,
),
).toEqual({
relationships: [
{
from_path: "main.cjs",
to: "./dep.js",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
},
],
parse_failures: [],
});
},
);
it.each([
'require[resolve]("./dep.js");',
'require[main]("./dep.js");',
'const resolve = "toString"; require[resolve]("./dep.js");',
'const main = "toString"; require[main]("./dep.js");',
'const resolve = "resolve"; require[resolve]("./dep.js");',
'const main = "main"; require[main]("./dep.js");',
'const method = "resolve"; require[method]("./dep.js");',
'require[`resolve`]("./dep.js");',
'require["re" + "solve"]("./dep.js");',
])("does not infer computed member values in %s", (source) => {
expect(parseRequire(source)).toEqual({
relationships: [],
parse_failures: [],
});
});
it.each([
"require?.",
"require.resolve?.",
'require["resolve"]?.',
"require.main?.",
'require["main"]?.',
"require?.resolve",
'require?.["resolve"]',
"require?.[resolve]",
"require?.main",
'require?.["main"]',
"require?.[main]",
"(require?.resolve)",
"require.resolve.call",
"require.main.require",
"module.require",
"other.resolve",
"other.require",
"require.toString",
'require["toString"]',
])("leaves the unrecognized callee %s unchanged", (callee) => {
expect(parseRequire(`${callee}("./dep.js");`)).toEqual({
relationships: [],
parse_failures: [],
});
});
it.each(knownCallees)("does not guess unknown arguments to %s", (callee) => {
expect(
parseRequire(
[
'const target = "./dep.js";',
`${callee}(target);`,
`${callee}(\`./dep.js\`);`,
`${callee}(getTarget());`,
`${callee}();`,
].join("\n"),
),
).toEqual({ relationships: [], parse_failures: [] });
});
});

View File

@ -2,7 +2,10 @@ import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
import { z } from "zod";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import {
compareUnicodeCodePoints,
compositeKey,
} from "./unicodeCodePointOrder.js";
import { prefixedDigestSchema } from "./../domain/digests.js";
const evidenceIdSchema = prefixedDigestSchema("ev");
@ -293,8 +296,9 @@ const sortedUnique = (values: readonly string[]): string[] =>
const sortedRelationships = (
values: readonly z.infer<typeof relationshipSchema>[],
): z.infer<typeof relationshipSchema>[] =>
[...values].sort(
(left, right) =>
compareUnicodeCodePoints(left.type, right.type) ||
compareUnicodeCodePoints(left.unknown_id, right.unknown_id),
[...values].sort((left, right) =>
compareUnicodeCodePoints(
compositeKey([left.type, left.unknown_id]),
compositeKey([right.type, right.unknown_id]),
),
);

View File

@ -1,11 +0,0 @@
import { expect, it } from "vitest";
import { safeParseJson } from "./safeJson.js";
it("keeps the original JSON parse failure as the cause", () => {
const parsed = safeParseJson("{");
if (parsed.ok) throw new Error("expected invalid JSON to fail");
expect(parsed.error).toContain("JSON");
expect(parsed.cause).toBeInstanceOf(SyntaxError);
});

View File

@ -1,160 +1,9 @@
import { describe, expect, it } from "vitest";
import {
categorizeSwiftTypes,
discoverObjcClasses,
discoverObjcProtocols,
} from "./symbolAnalysis.js";
describe("symbol analysis", () => {
it("deduplicates Objective-C classes and protocols by symbol name", () => {
const names = [
{ address: "0x1", name: "_OBJC_CLASS_$_App" },
{ address: "0x2", name: "_OBJC_CLASS_$_App" },
{ address: "0x3", name: "_OBJC_PROTOCOL_$_Delegate" },
{ address: "0x4", name: "__OBJC_PROTOCOL_REFERENCE_$_Delegate" },
{ address: "0x4", name: "_OBJC_PROTOCOL_$_Delegate" },
];
expect(discoverObjcClasses(names, "App")).toMatchObject({ count: 1 });
expect(discoverObjcProtocols(names)).toMatchObject({ count: 1 });
});
it("excludes Objective-C ivars, metaclasses, properties, and protocols from classes", () => {
const result = discoverObjcClasses(
[
{ address: "0x1", name: "_OBJC_IVAR_$_Fixture.value" },
{ address: "0x2", name: "_OBJC_METACLASS_$_Fixture" },
{ address: "0x3", name: "_OBJC_PROP_$_Fixture.value" },
{ address: "0x4", name: "_OBJC_PROTOCOL_$_FixtureProtocol" },
{ address: "0x5", name: "_OBJC_CLASS_$_Fixture" },
],
"",
);
expect(result).toMatchObject({
count: 1,
classes: [{ address: "0x5", name: "_OBJC_CLASS_$_Fixture" }],
});
});
it("recognizes fragile Apple Objective-C class records without alias duplication", () => {
// Apple clang -target i386-apple-macosx10.5
// -fobjc-runtime=macosx-fragile-10.5 emits these local class records.
// The `.objc_class_name_*` absolute symbols alias the same class and are
// deliberately not selected.
const names = [
{ address: "0x1ac", name: "l_OBJC_CLASS_ProbeClass" },
{ address: "0x0", name: ".objc_class_name_ProbeClass" },
// A class named `NAME_` produces this exact legacy local symbol.
{ address: "0x0", name: "l_OBJC_CLASS_NAME_" },
];
expect(discoverObjcClasses(names, "")).toEqual({
count: 2,
classes: [
{ address: "0x1ac", name: "l_OBJC_CLASS_ProbeClass" },
{ address: "0x0", name: "l_OBJC_CLASS_NAME_" },
],
});
});
it("discovers modern Swift protocol descriptors without treating methods as declarations", () => {
// Current swiftc emits `…Mp` descriptors without the nominal `P` marker.
const names = [
{ address: "0xf0", name: "_$s4main7ScoringMp" },
{ address: "0x100", name: "_$s4main7ScoringTL" },
{ address: "0x110", name: "_$s4main7ScoringP5scoreSiyFTq" },
];
expect(discoverObjcProtocols(names)).toEqual({
count: 1,
protocols: [{ address: "0xf0", name: "_$s4main7ScoringMp" }],
});
});
it("categorizes every Swift mangling family and deduplicates names", () => {
const result = categorizeSwiftTypes([
{ address: "1", name: "_TtCClass" },
{ address: "2", name: "_TtVStruct" },
{ address: "3", name: "_TtOEnum" },
{ address: "4", name: "_TtPProtocol" },
{ address: "5", name: "_TtEExtension" },
{ address: "6", name: "prefix_TtOther" },
{ address: "7", name: "_TtCClass" },
]);
expect(result).toMatchObject({
total: 6,
categories: {
classes: { count: 1 },
structs: { count: 1 },
enums: { count: 1 },
protocols: { count: 1 },
extensions: { count: 1 },
other: { count: 1 },
},
});
});
it("filters Swift categories by a case-sensitive literal name", () => {
const result = categorizeSwiftTypes(
[
{ address: "0x1", name: "_TtCAccount" },
{ address: "0x2", name: "_TtCSession" },
{ address: "0x3", name: "_TtVAccountState" },
],
{ category: "classes", pattern: "Account" },
);
expect(result).toMatchObject({
total: 1,
categories: {
classes: {
count: 1,
items: [{ address: "0x1", name: "_TtCAccount" }],
},
structs: { count: 0, items: [] },
},
});
});
it("classifies modern Swift manglings reported by current providers", () => {
const result = categorizeSwiftTypes([
{ address: "0x1000009f8", name: "$s4main4PairVMa" },
{ address: "0x100000ac4", name: "$s4main6ScorerVMa" },
{ address: "0x100000ab4", name: "_$s4main4PairVMa" },
{ address: "0x1", name: "_TtCClass" },
{ address: "0x1000009f8", name: "$s4main4PairVMa" },
]);
expect(result).toMatchObject({
total: 4,
categories: {
classes: { count: 1 },
structs: {
count: 3,
items: [
{ address: "0x1000009f8", name: "$s4main4PairVMa" },
{ address: "0x100000ac4", name: "$s4main6ScorerVMa" },
{ address: "0x100000ab4", name: "_$s4main4PairVMa" },
],
},
},
});
});
it("keeps non-Swift procedure names out of the Swift inventory", () => {
const result = categorizeSwiftTypes([
{ address: "0x1", name: "printf" },
{ address: "0x2", name: "_OBJC_CLASS_$_App" },
{ address: "0x3", name: "std::vector<int>::push_back" },
]);
expect(result).toMatchObject({ total: 0 });
});
});
import { categorizeSwiftTypes } from "./symbolAnalysis.js";
describe("modern Swift category evidence", () => {
it.each([
["$s4main4PairVMa", "structs"],
["_$s4main6ScorerVMa", "structs"],
["$s4main7ServiceCMa", "classes"],
["$s4main5StateOMa", "enums"],
["$s4main7ScoringPMp", "protocols"],
@ -203,36 +52,6 @@ describe("modern Swift category evidence", () => {
).toMatchObject({ total: 0, unclassified: [] });
});
it("joins Swift aliases only at the exact procedure address", () => {
const result = categorizeSwiftTypes(
[
{ address: "0x1", name: "main::Pair::typeMetadataAccessor" },
{ address: "0x2", name: "std::vector<int>::push_back" },
{ address: "0x3", name: "main::Pair::other" },
],
{ category: "structs", pattern: "Pair" },
[
{ address: "0x1", name: "_$s4main4PairVMa" },
{ address: "0x4", name: "$s4main4PairVMa" },
],
);
expect(result).toMatchObject({
total: 1,
categories: {
structs: {
items: [
{
address: "0x1",
name: "main::Pair::typeMetadataAccessor",
mangled_names: ["_$s4main4PairVMa"],
},
],
},
},
unclassified: [],
});
});
it("deduplicates display names after selecting a category", () => {
expect(
categorizeSwiftTypes(

View File

@ -28,7 +28,8 @@ export const discoverObjcClasses = (
({ name }) =>
name.includes(OBJC_CLASS_MARKER) ||
(name.startsWith(LEGACY_OBJC_CLASS_PREFIX) &&
name.length > LEGACY_OBJC_CLASS_PREFIX.length),
name.length > LEGACY_OBJC_CLASS_PREFIX.length &&
!/^l_OBJC_CLASS_NAME_(?:\.\d+)?$/u.test(name)),
)
.filter(({ name }) => pattern.length === 0 || name.includes(pattern)),
);

View File

@ -16,3 +16,11 @@ export const compareUnicodeCodePoints = (
if (leftIndex === left.length && rightIndex === right.length) return 0;
return leftIndex === left.length ? -1 : 1;
};
/**
* Collision-free composite key for deduplication/sorting. `\0`-joins
* collide when any component contains NUL (valid in JS property names);
* a JSON tuple escapes every component. Single owner for composite keys.
*/
export const compositeKey = (parts: readonly unknown[]): string =>
JSON.stringify(parts);

View File

@ -2,6 +2,7 @@ import { parse } from "@babel/parser";
import * as t from "@babel/types";
import { sanitizeEndpointCandidate } from "./browserObservation.js";
import { compositeKey } from "./unicodeCodePointOrder.js";
import type { WebPageInspection } from "./browserObservationSchemas.js";
import type { WebBundleAnalysis } from "./webBundleAnalysis.js";
import { traverseJavaScriptAst } from "./javascript/javascriptSemanticTraversal.js";
@ -271,7 +272,14 @@ const addWebMcpDeclaration = (
: [],
)
: [];
const key = `webmcp\0${script.script_key}\0${name ?? ""}\0${schemaPropertyNames.join("\0")}`;
// Property names are a variable-length source-derived list: NUL-joins
// would collapse ["a\0b"] with ["a", "b"] and drop a declaration.
const key = compositeKey([
"webmcp",
script.script_key,
name ?? "",
schemaPropertyNames,
]);
addUnique(accumulator, key, () =>
accumulator.webMcp.push({
name: name ?? null,

View File

@ -12,15 +12,6 @@ import {
} from "./webContentArtifact.js";
describe("web content artifacts", () => {
it("binds UTF-8 content, size, and digest inline", () => {
const artifact = createWebTextArtifact(
"const marker = '😀';",
"text/javascript",
);
expect(webTextArtifactSchema.parse(artifact)).toEqual(artifact);
expect(artifact.bytes).toBe(Buffer.byteLength(artifact.text));
});
it("rejects altered content that reuses an artifact digest", () => {
const artifact = createWebTextArtifact("original", "text/javascript");
expect(
@ -83,7 +74,7 @@ describe("stable web inventory", () => {
content_size: 1_024,
},
];
const forward = stableWebResources(inputs);
const forward = stableWebResources([...inputs, ...inputs.slice(0, 1)]);
const reverse = stableWebResources([...inputs].reverse());
expect(reverse).toEqual(forward);
@ -117,20 +108,6 @@ describe("stable web inventory", () => {
});
});
it("deduplicates identical resource observations by stable identity", () => {
const duplicate = {
url: script.url,
origin: "https://app.example.test",
type: "Script",
mime_type: "text/javascript",
content_size: 1_024,
};
expect(stableWebResources([duplicate, duplicate])).toEqual(
stableWebResources([duplicate]),
);
});
it("reconciles raw URLs independently of stable inventory sort order", () => {
const app = {
url: script.url,

View File

@ -1,37 +0,0 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import {
webRuntimeLocationSchema,
webRuntimeSourceSchema,
} from "./webRuntime.js";
const properties = (schema: z.ZodType) =>
z
.object({ properties: z.record(z.string(), z.unknown()) })
.parse(z.toJSONSchema(schema, { io: "output" })).properties;
describe("web runtime coordinate contract", () => {
it("advertises zero-based resource-relative CDP lines and UTF-16 columns", () => {
const location = properties(webRuntimeLocationSchema);
expect(location.line_number).toMatchObject({
description: expect.stringContaining(
"Zero-based line in the resource named by url",
),
});
expect(location.script_id).toMatchObject({
description: expect.stringContaining("no session-script association"),
});
expect(location.column_number).toMatchObject({
description: expect.stringContaining("Zero-based UTF-16 column"),
});
const start = properties(
webRuntimeSourceSchema.shape.resource_start.unwrap(),
);
expect(start.line_number).toMatchObject({
description: expect.stringContaining(
"Zero-based line where the script text starts",
),
});
});
});

View File

@ -1,40 +0,0 @@
import { describe, expect, it } from "vitest";
import {
captureWebScreenshotInputSchema,
compareWebScreenshotsInputSchema,
createWebScreenshotArtifact,
} from "./webScreenshot.js";
describe("inline web screenshots", () => {
it("rejects removed screenshot size and pixel controls", () => {
expect(
captureWebScreenshotInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
maximum_image_bytes: 1,
}).success,
).toBe(false);
const artifact = createWebScreenshotArtifact(Buffer.from("image"));
expect(
compareWebScreenshotsInputSchema.safeParse({
before: artifact,
after: artifact,
maximum_pixels: 1,
}).success,
).toBe(false);
});
it("accepts a caller-supplied target ID without a length ceiling", () => {
const targetId = "target-".repeat(100);
expect(
captureWebScreenshotInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: targetId,
}).target_id,
).toBe(targetId);
});
});

View File

@ -22,18 +22,6 @@ describe("Ghidra headless JVM environment", () => {
});
});
it.each(["linux", "darwin"] as const)(
"passes %s isolated JVM paths through direct argv instead of script options",
(platform) => {
expect(
ghidraHeadlessJavaOptions("/tmp/rea/home", "/tmp/rea/tmp", platform),
).toEqual({
JDK_JAVA_OPTIONS: "",
GHIDRA_HEADLESS_JAVA_OPTIONS: "",
});
},
);
it.each(["%TEMP%", 'quote"', "amp&", "line\n", "nul\0"])(
"rejects interpreter metacharacters in either Windows JVM path: %s",
(suffix) => {

View File

@ -3,9 +3,6 @@ import { describe, expect, it } from "vitest";
import { ghidraSessionRoot } from "./GhidraSessionRoot.js";
const hasDotPrefixElement = (directory: string): boolean =>
directory.split(sep).some((element) => element.startsWith("."));
const POSIX = process.platform !== "win32";
const posixFallback = join(resolve(sep), "tmp");
const posixBase = (...segments: string[]) => join(resolve(sep), ...segments);
@ -28,13 +25,6 @@ describe("ghidraSessionRoot", () => {
);
});
it("relocates a base that is itself a hidden directory", () => {
const base = posixBase("home", "operator", ".tmp");
expect(ghidraSessionRoot({ base, fallback: posixBase("var", "tmp") })).toBe(
posixBase("var", "tmp"),
);
});
it("ignores a fallback that is itself dotted", () => {
const base = posixBase("home", "operator", ".cache", "scratch");
expect(
@ -45,14 +35,6 @@ describe("ghidraSessionRoot", () => {
).toBe(posixBase("home", "operator"));
});
it("never returns a path with a dot-prefixed element", () => {
const directory = ghidraSessionRoot({
base: posixBase("home", "operator", ".cache", "scratch"),
fallback: posixBase("home", "operator", ".cache"),
});
expect(hasDotPrefixElement(directory)).toBe(false);
});
it("uses the nearest safe ancestor when the platform has no fallback", () => {
expect(
ghidraSessionRoot({

View File

@ -84,6 +84,11 @@ describe.skipIf(process.platform !== "darwin")(
"<key>Blob</key><data>AAEC</data>",
"<key>Built</key><date>2020-01-01T00:00:00Z</date>",
"<key>Ratio</key><real>nan</real>",
"<key>Nested</key><array><dict>",
"<key>Ratio</key><real>0.5</real>",
"<key>Unbounded</key><real>-infinity</real>",
"<key></key><string>empty key</string>",
"</dict></array>",
"</dict></plist>",
].join(""),
);
@ -113,6 +118,13 @@ describe.skipIf(process.platform !== "darwin")(
iso8601: "2020-01-01T00:00:00.000Z",
},
Ratio: { $plist_type: "real", value: null },
Nested: [
{
Ratio: 0.5,
Unbounded: { $plist_type: "real", value: null },
"": "empty key",
},
],
},
bundle: { identifier: "com.example.types", executable: "App" },
provenance: [
@ -122,7 +134,7 @@ describe.skipIf(process.platform !== "darwin")(
],
limitations: [
expect.stringContaining("XML conversion"),
expect.stringContaining("1 non-finite real"),
expect.stringContaining("2 non-finite real"),
],
},
});

View File

@ -43,6 +43,24 @@ describe("DOS COM admission", () => {
}),
).toMatchObject({ ok: false, error: { _tag: "BinaryTargetError" } });
});
it.each([1, 0xff00])("admits the actual file extent %i", async (length) => {
const directory = await createTestTempDirectory("rea-com-target-");
const path = join(directory, "fixture.com");
const bytes = Buffer.alloc(length, 0xc3);
await writeFile(path, bytes);
const target = await parseBinaryTarget(path, {
cwd: directory,
hostArchitecture: "x64",
formatHint: "dos-com",
});
if (!target.ok) throw target.error;
expect(target.value).toMatchObject({
format: "dos-com",
architecture: "x86",
sha256: createHash("sha256").update(bytes).digest("hex"),
});
expect(await readFile(path)).toEqual(bytes);
});
it("rejects contradictory target kind", async () => {
const directory = await createTestTempDirectory("rea-com-target-");
const path = join(directory, "fixture.com");

View File

@ -11,7 +11,48 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("distinguishes computed require member values from literal members in source evidence", async () => {
const root = await createTestTempDirectory("rea-reference-require-members-");
const sources = {
const literalCallees = [
"require",
"require.resolve",
'require["resolve"]',
"require.main",
'require["main"]',
'require[("resolve")]',
'require["res\\u006flve"]',
];
const ignoredSources = [
'require[resolve]("./dep.js");',
'require[main]("./dep.js");',
'const resolve = "toString"; require[resolve]("./dep.js");',
'const main = "toString"; require[main]("./dep.js");',
'const resolve = "resolve"; require[resolve]("./dep.js");',
'const main = "main"; require[main]("./dep.js");',
'const method = "resolve"; require[method]("./dep.js");',
'require[`resolve`]("./dep.js");',
'require["re" + "solve"]("./dep.js");',
...[
"require?.",
"require.resolve?.",
'require["resolve"]?.',
"require.main?.",
'require["main"]?.',
"require?.resolve",
'require?.["resolve"]',
"require?.[resolve]",
"require?.main",
'require?.["main"]',
"require?.[main]",
"(require?.resolve)",
"require.resolve.call",
"require.main.require",
"module.require",
"other.resolve",
"other.require",
"require.toString",
'require["toString"]',
].map((callee) => `${callee}("./dep.js");`),
];
const sources: Record<string, string> = {
"computed.cjs": [
'const resolve = "toString";',
'const main = "toString";',
@ -21,15 +62,21 @@ it("distinguishes computed require member values from literal members in source
].join("\n"),
"literal.cjs": 'console.log(require["resolve"]("./dep.js"));\n',
"dep.js": 'throw new Error("The dependency must not execute");\n',
"ignored.cjs": ignoredSources.map((source) => `{ ${source} }`).join("\n"),
"unknown.cjs": [
"function load(target) {",
" require(target);",
" require.resolve(target);",
' require["resolve"](target);',
...literalCallees.flatMap((callee) => [
` ${callee}(target);`,
` ${callee}(\`./dep.js\`);`,
` ${callee}(getTarget());`,
` ${callee}();`,
]),
" return import(target);",
"}",
].join("\n"),
};
for (const [index, callee] of literalCallees.entries())
sources[`member-${index}.cjs`] = `${callee}("./dep.js");\n`;
await Promise.all(
Object.entries(sources).map(([path, source]) =>
writeFile(join(root, path), source),
@ -67,6 +114,13 @@ it("distinguishes computed require member values from literal members in source
resolution: "internal",
parse_state: "parsed",
},
...literalCallees.map((_, index) => ({
from_path: `member-${index}.cjs`,
to: "dep.js",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
})),
{
from_path: "unknown.cjs",
to: "<dynamic-import>",

View File

@ -17,6 +17,8 @@ it("resolves dynamic source imports while retaining computed targets as unknown"
'const lazy = import("./lazy");',
'async function load() { return await import("./data.json", { with: { type: "json" } }); }',
'const name = "./not-guessed.js"; import(name);',
"import(`./${name}.js`);",
'import("node:fs");',
].join("\n"),
),
...["static.ts", "common.js", "lazy.ts"].map((path) =>
@ -32,7 +34,7 @@ it("resolves dynamic source imports while retaining computed targets as unknown"
});
if (!result.ok) throw result.error;
expect(result.value.parse_failures).toEqual([]);
expect(result.value.relationships).toHaveLength(5);
expect(result.value.relationships).toHaveLength(6);
for (const [to, kind] of [
["static.ts", "imports"],
["common.js", "requires"],
@ -53,4 +55,11 @@ it("resolves dynamic source imports while retaining computed targets as unknown"
resolution: "unknown",
parse_state: "partial",
});
expect(result.value.relationships).toContainEqual({
from_path: "main.ts",
to: "node:fs",
kind: "imports",
resolution: "external",
parse_state: "parsed",
});
});

View File

@ -11,27 +11,37 @@ import {
import { historicalSourceGraphSchema } from "../../../src/domain/referenceSourceGraph.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const sources = {
"fatal.js": 'import "./valid";\nconst = ;',
"ignored.py": 'import "./valid"; const missing;\n',
"lazy.js": "export const lazy = true;\n",
"recovered.d.cts": [
"let duplicate: number;",
"let duplicate: number;",
'import dep = require("./valid");',
'declare module "ambient-package" {}',
].join("\n"),
"recovered.js": [
"const missing;",
"let duplicate;",
"let duplicate;",
"let duplicate;",
'import "./valid";',
'require("node:fs");',
'import("./lazy");',
"import(moduleName);",
].join("\n"),
"recovered.ts": [
"let duplicate: number;",
"let duplicate: number;",
'import dep = require("./valid");',
'declare module "ambient-package" {}',
].join("\n"),
"valid.js":
'export { readFile } from "node:fs"; export * from "node:path";\n',
};
it("preserves recovered diagnostics and file evidence through filesystem import", async () => {
const root = await createTestTempDirectory("rea-reference-recovery-");
const sources = {
"fatal.js": 'import "./valid";\nconst = ;',
"recovered.js": [
"const missing;",
"let duplicate;",
"let duplicate;",
"let duplicate;",
'import "./valid";',
'require("node:fs");',
"import(moduleName);",
].join("\n"),
"recovered.ts": [
"let duplicate: number;",
"let duplicate: number;",
'import dep = require("./valid");',
'declare module "ambient-package" {}',
].join("\n"),
"valid.js": 'export * from "node:path";\n',
};
await mkdir(join(root, "src"));
await Promise.all(
Object.entries(sources).map(([path, source]) =>
@ -66,6 +76,11 @@ it("preserves recovered diagnostics and file evidence through filesystem import"
const failures = [
{ path: "src/fatal.js", parser: "babel", reason: "Unexpected token (2:6)" },
{
path: "src/recovered.d.cts",
parser: "babel",
reason: "Identifier 'duplicate' has already been declared. (2:4)",
},
{
path: "src/recovered.js",
parser: "babel",
@ -94,6 +109,20 @@ it("preserves recovered diagnostics and file evidence through filesystem import"
),
).toEqual(failures);
expect(graph.relationships).toEqual([
{
from_path: "src/recovered.d.cts",
to: "ambient-package",
kind: "declares-module",
resolution: "unknown",
parse_state: "partial",
},
{
from_path: "src/recovered.d.cts",
to: "src/valid.js",
kind: "requires",
resolution: "internal",
parse_state: "partial",
},
{
from_path: "src/recovered.js",
to: "<dynamic-import>",
@ -108,6 +137,13 @@ it("preserves recovered diagnostics and file evidence through filesystem import"
resolution: "external",
parse_state: "partial",
},
{
from_path: "src/recovered.js",
to: "src/lazy.js",
kind: "imports",
resolution: "internal",
parse_state: "partial",
},
{
from_path: "src/recovered.js",
to: "src/valid.js",
@ -129,6 +165,13 @@ it("preserves recovered diagnostics and file evidence through filesystem import"
resolution: "internal",
parse_state: "partial",
},
{
from_path: "src/valid.js",
to: "node:fs",
kind: "imports",
resolution: "external",
parse_state: "parsed",
},
{
from_path: "src/valid.js",
to: "node:path",

View File

@ -20,37 +20,69 @@ import { observed as ok } from "../../fixtures/analysisExecution.js";
afterEach(closeEnhancedToolResources);
describe("enhanced MCP tools", () => {
it("discovers every Objective-C class from one complete inventory", async () => {
const client = await connect({
execute: (name) => {
expect(name).toBe("list_names");
return Promise.resolve(
ok([
// Apple clang modern Objective-C ABI `nm` output: the first row
// is a class definition; the remaining rows are method metadata,
// class_ro_t, and a class reference.
{ address: "0x70", value: "_OBJC_CLASS_$_ProbeClass" },
{ address: "0x1a8", value: "__OBJC_$_CLASS_METHODS_ProbeClass" },
{ address: "0x230", value: "__OBJC_CLASS_RO_$_ProbeClass" },
{ address: "0x450", value: "_OBJC_CLASSLIST_REFERENCES_$_" },
{
address: "0x298",
value: "__OBJC_$_CATEGORY_CLASS_METHODS_ProbeClass_$_Category",
},
]),
);
},
});
const result = jsonResult(
await client.callTool({ name: "get_objc_classes", arguments: {} }),
);
expect(result).toEqual({
count: 1,
classes: [{ address: "0x70", name: "_OBJC_CLASS_$_ProbeClass" }],
});
it("discovers declarations without counting duplicates or compiler bookkeeping", async () => {
const client = await connect({
execute: (name) => {
expect(name).toBe("list_names");
return Promise.resolve(
ok([
{ address: "0x1", value: "_OBJC_CLASS_$_First" },
{ address: "0x2", value: "_OBJC_CLASS_$_Last" },
{ address: "0x3", value: "_OBJC_CLASS_$_First" },
{ address: "0x4", value: "__OBJC_CLASS_RO_$_First" },
{ address: "0x5", value: "_OBJC_CLASSLIST_REFERENCES_$_" },
{ address: "0x6", value: "l_OBJC_CLASS_NAME_" },
{ address: "0x7", value: "_OBJC_IVAR_$_First.value" },
{ address: "0x8", value: "_OBJC_METACLASS_$_First" },
{ address: "0x9", value: "_OBJC_PROP_$_First.value" },
{ address: "0xa", value: "__OBJC_PROTOCOL_$_Delegate" },
{ address: "0xb", value: "__OBJC_PROTOCOL_$_Delegate" },
{ address: "0xc", value: "__OBJC_PROTOCOL_REFERENCE_$_Delegate" },
{ address: "0xd", value: "_$s4main7ScoringMp" },
{ address: "0xe", value: "_$s4main7ScoringTL" },
{ address: "0xf", value: "_$s4main7ScoringP5scoreSiyFTq" },
{ address: "0x10", value: "l_OBJC_CLASS_Legacy" },
{ address: "0x11", value: "l_OBJC_CLASS_NAME_.1" },
]),
);
},
});
const result = jsonResult(
await client.callTool({ name: "get_objc_classes", arguments: {} }),
);
expect(result).toMatchObject({
count: 3,
classes: [
{ address: "0x1", name: "_OBJC_CLASS_$_First" },
{ address: "0x2", name: "_OBJC_CLASS_$_Last" },
{ address: "0x10", name: "l_OBJC_CLASS_Legacy" },
],
});
expect(
jsonResult(
await client.callTool({
name: "get_objc_classes",
arguments: { pattern: "First" },
}),
),
).toMatchObject({
count: 1,
classes: [{ address: "0x1", name: "_OBJC_CLASS_$_First" }],
});
expect(
jsonResult(
await client.callTool({ name: "get_objc_protocols", arguments: {} }),
),
).toMatchObject({
count: 2,
protocols: [
{ address: "0xa", name: "__OBJC_PROTOCOL_$_Delegate" },
{ address: "0xd", name: "_$s4main7ScoringMp" },
],
});
});
describe("enhanced MCP tools", () => {
it("returns the complete overview inline with exhaustive totals", async () => {
const client = await connect({
execute: (name) => {

View File

@ -36,7 +36,18 @@ describe("Swift classification through MCP", () => {
arguments: test.arguments,
});
expect(result.isError).not.toBe(true);
expect(jsonResult(result)).toMatchObject({ total: test.total });
const inventory = jsonResult(result);
expect(inventory).toMatchObject({ total: test.total });
if (test.arguments.pattern === undefined)
expect(inventory).toMatchObject({
categories: {
structs: {
items: expect.not.arrayContaining([
expect.objectContaining({ address: "0x100000c0c" }),
]),
},
},
});
if (test.arguments.pattern === undefined)
expect(jsonResult(result)).toMatchObject({
unclassified: [

View File

@ -16,10 +16,14 @@ describe("Ghidra MCP native API evidence", () => {
).structuredContent,
);
expect(inspection.normalized_result).toMatchObject({
procedure: { address: "0x401000", name: "fixture_main" },
boundary: {
available: true,
return_type: { data_type: "int", confidence: "medium" },
jump_tables: [
{
dispatch_address: "0x401010",
data_sources: [{ address: "0x403000" }],
mappings: [{ case_value: 0, target_address: "0x401020" }],
default_targets: [
{ target_address: "0x401030", confidence: "high" },
@ -27,6 +31,7 @@ describe("Ghidra MCP native API evidence", () => {
},
],
},
unsupported_branches: [],
residual_unknowns: [],
});
expect(

View File

@ -1,5 +1,6 @@
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@ -385,13 +386,30 @@ const verifyImport = async (
operation: "import_managed_reconstruction",
provider: { id: "rea-dotnet-workflows" },
confidence: "inferred",
authority: "analyst-inference",
normalized_result: {
phase: "reconstruction-import",
executed: false,
decompiler: { name: "ilspycmd", version: "9.1.0.7988", family: "ilspy" },
summary: { imported_methods: 1, decompiled_csharp_methods: 1 },
methods: [
{
token: method.token,
validation: { canonical_observation: false },
signature_sha256: method.signature.raw_sha256,
normalized_il_sha256: method.body.normalized_il_sha256,
reconstruction: {
kind: "decompiled-csharp",
language: "csharp",
text_sha256: createHash("sha256")
.update("internal static void Main() { }")
.digest("hex"),
},
validation: {
matched_static_member: true,
exact_build_required: true,
canonical_observation: false,
confidence_floor: "inference",
},
},
],
},

View File

@ -1,7 +1,8 @@
import Foundation
// Encode the root under a key that matches the UID marker spelling.
// Encode roots under keys that match both binary and XML UID marker spellings.
let archiver = NSKeyedArchiver(requiringSecureCoding: false)
archiver.encode("payload" as NSString, forKey: "UID")
archiver.encode("payload" as NSString, forKey: "CF$UID")
archiver.finishEncoding()
try archiver.encodedData.write(to: URL(fileURLWithPath: CommandLine.arguments[1]))

View File

@ -42,6 +42,11 @@ export const typeScriptDialectCases = [
kind: "static-import",
source: 'const element = <section />;\nimport "./dep.js";',
},
{
path: "component.d.tsx",
kind: "static-import",
source: 'const element = <section />;\nimport "./dep.js";',
},
{
path: "component.jsx",
kind: "static-import",

View File

@ -102,6 +102,8 @@ describe("OMP integration maintenance", () => {
});
it("preserves Grok Build disabled_mcp_servers during maintenance planning", async () => {
// Strict identity: a user-disabled integration must never be selected
// for maintenance, even with a healthy owned entry underneath.
const registration = `[mcp_servers.rea]\ncommand = ${JSON.stringify(process.execPath)}\nargs = [${JSON.stringify(entryPoint)}, "mcp"]\nstartup_timeout_sec = 30\n`;
const configPath = await writeClient(
"grok_build",