refactor(test): keep coverage at behavioral boundaries (#474)

* refactor(test): keep coverage at behavioral boundaries

* fix(ci): restore package workflow temp-root classification
This commit is contained in:
morluto 2026-08-11 18:34:32 +08:00 committed by GitHub
parent d65730c4c2
commit d4dc940bd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 1519 additions and 2419 deletions

View File

@ -25,10 +25,10 @@ registries but do not cross production filesystem, process, socket, or browser b
Boundary tests cross one production boundary. Only acceptance tests assemble
the complete runtime or invoke the compiled product surface.
Focused immutable builders shared by a domain test family live beside their
production owner as `src/domain/*.fixture.ts`. They are typechecked with the
suite and excluded from package builds; broader runtime and provider fixtures
remain under `tests/fixtures/**`.
Focused immutable builders and recording ports shared by one test family live
beside their production owner as `src/**/*.fixture.ts`. They are typechecked
with the suite and excluded from package builds; broader runtime and provider
fixtures remain under `tests/fixtures/**`.
`tests/process-global/**` is reserved for cases with a demonstrated dependency
on process-global state. Those tests run without file parallelism. Reusable,
@ -70,8 +70,10 @@ Local full-suite Vitest runs are intentionally capped at one worker and one
project at a time. Boundary fixtures own real subprocesses, and this local cap
keeps aggregate memory predictable; CI retains the existing two-worker budget.
The pure domain/contracts and recording-port service projects share one worker
module context because their tests own no mutable runtime resources; adapter,
composition, and boundary projects retain per-file isolation.
module context because their tests own no mutable runtime resources. MCP
boundary files also share the immutable server module graph while creating and
closing independent in-memory sessions. Adapter, composition, acceptance,
process-global, and other boundary projects retain per-file isolation.
`npm test`, `npm run docs:check`, and `npm run docs:generate` share
repository-local locks and fail fast when the same class of command is already
running. The `npm test` build is inside that lock. `check:pr` runs its test task

View File

@ -1,6 +1,6 @@
{
"$schema": "./node_modules/knip/schema.json",
"entry": ["src/main.ts", "src/cli.ts"],
"entry": ["src/main.ts", "src/cli.ts", "src/contracts/errorSchemas.ts"],
"project": ["src/**/*.ts", "tests/**/*.ts", "scripts/**/*.mjs"],
"ignoreDependencies": ["electron"],
"ignoreBinaries": ["ps"],

View File

@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import {
AnalysisProviderRegistry,
type AnalysisProviderBinding,
} from "../../../src/application/AnalysisProviderRegistry.js";
} from "./AnalysisProviderRegistry.js";
import {
createAnalysisExecution,
type AnalysisClient,
@ -12,15 +12,15 @@ import {
type ProviderAvailability,
type ProviderIdentity,
type ProviderTargetSupport,
} from "../../../src/application/AnalysisProvider.js";
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
import type { BinaryTarget } from "../../../src/domain/binaryTarget.js";
} from "./AnalysisProvider.js";
import { createAnalysisProfile } from "../domain/analysisProfile.js";
import type { BinaryTarget } from "../domain/binaryTarget.js";
import {
ProviderAdapterError,
ProviderSelectionError,
projectAnalysisError,
} from "../../../src/domain/errors.js";
import { err, ok } from "../../../src/domain/result.js";
} from "../domain/errors.js";
import { err, ok } from "../domain/result.js";
const DATABASE_TARGET: BinaryTarget = {
path: "/tmp/fixture.hop",

View File

@ -0,0 +1,32 @@
import { CATALOG_IDENTITY } from "../catalogIdentity.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import type { DoctorHost } from "./Doctor.js";
/** Healthy recording host for focused doctor and projection tests. */
export const createDoctorHostFixture = (
overrides: Partial<DoctorHost> = {},
): DoctorHost => ({
platform: "darwin",
architecture: "x64",
nodeVersion: "24.18.0",
macosVersion: () => Promise.resolve("14.0"),
linuxDistribution: () => Promise.resolve(undefined),
validTarget: (path) => Promise.resolve(path.includes("Hopper")),
executable: (path) => Promise.resolve(path.includes("Hopper")),
supportedLinuxHopper: () => Promise.resolve(true),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: true,
classification: "healthy",
}),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
installedSkillIdentity: () =>
Promise.resolve({
version: PRODUCT_IDENTITY.skillVersion,
toolCount: CATALOG_IDENTITY.counts.mcp_tools,
catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
}),
...overrides,
});

View File

@ -1,34 +1,7 @@
import { describe, expect, it } from "vitest";
import { runDoctor, type DoctorHost } from "../../../src/application/Doctor.js";
import { CATALOG_IDENTITY } from "../../../src/catalogIdentity.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
const host = (overrides: Partial<DoctorHost> = {}): DoctorHost => ({
platform: "darwin",
architecture: "x64",
nodeVersion: "24.18.0",
macosVersion: () => Promise.resolve("12.0"),
linuxDistribution: () => Promise.resolve(undefined),
validTarget: (path) => Promise.resolve(path.includes("Hopper")),
executable: (path) => Promise.resolve(path.includes("Hopper")),
supportedLinuxHopper: () => Promise.resolve(true),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: true,
classification: "healthy",
}),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
installedSkillIdentity: () =>
Promise.resolve({
version: PRODUCT_IDENTITY.skillVersion,
toolCount: CATALOG_IDENTITY.counts.mcp_tools,
catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
}),
...overrides,
});
import { createDoctorHostFixture as host } from "./Doctor.fixture.js";
import { runDoctor } from "./Doctor.js";
describe("doctor runtime executable diagnostics", () => {
it("reports a broken shadowed Node candidate without hiding the healthy launcher", async () => {

View File

@ -1,34 +1,7 @@
import { describe, expect, it } from "vitest";
import type { ClientRegistrationStatus } from "../../../src/application/ClientRegistrationStatus.js";
import { runDoctor, type DoctorHost } from "../../../src/application/Doctor.js";
import { CATALOG_IDENTITY } from "../../../src/catalogIdentity.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
const host = (overrides: Partial<DoctorHost> = {}): DoctorHost => ({
platform: "darwin",
architecture: "x64",
nodeVersion: "24.18.0",
macosVersion: () => Promise.resolve("12.0"),
linuxDistribution: () => Promise.resolve(undefined),
validTarget: (path) => Promise.resolve(path.includes("Hopper")),
executable: (path) => Promise.resolve(path.includes("Hopper")),
supportedLinuxHopper: () => Promise.resolve(true),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: true,
classification: "healthy",
}),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
installedSkillIdentity: () =>
Promise.resolve({
version: PRODUCT_IDENTITY.skillVersion,
toolCount: CATALOG_IDENTITY.counts.mcp_tools,
catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
}),
...overrides,
});
import type { ClientRegistrationStatus } from "./ClientRegistrationStatus.js";
import { createDoctorHostFixture as host } from "./Doctor.fixture.js";
import { runDoctor } from "./Doctor.js";
describe("doctor", () => {
it("returns exact recovery for every failed diagnostic", async () => {

View File

@ -12,7 +12,7 @@ import {
type LinuxHopperInstallHost,
type LinuxHopperLauncherStatus,
type LinuxPackageFamily,
} from "../../../../src/application/LinuxHopper.js";
} from "./LinuxHopper.js";
class RecordingLinuxHost implements LinuxHopperInstallHost {
distributionValue: LinuxDistribution | undefined = {

View File

@ -6,7 +6,7 @@ import {
installMacHopper,
macHopperInstallDisclosure,
type MacHopperInstallHost,
} from "../../../../src/application/MacHopper.js";
} from "./MacHopper.js";
class FakeMacHopperHost implements MacHopperInstallHost {
readonly archive = new TextEncoder().encode("hopper-dmg");

View File

@ -2,14 +2,14 @@ import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import { importManagedReconstructionEvidence } from "../../../../src/application/ManagedReconstructionService.js";
import { MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE } from "../../../../src/contracts/managedWorkflowExamples.js";
import { MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE } from "../contracts/managedWorkflowExamples.js";
import {
importManagedReconstruction,
managedReconstructionImportInputSchema,
} from "../../../../src/domain/managedReconstruction.js";
import { managedMemberInspectionSchema } from "../../../../src/domain/managedArtifact.js";
import { createEvidence } from "../../../../src/domain/evidence.js";
} from "../domain/managedReconstruction.js";
import { managedMemberInspectionSchema } from "../domain/managedArtifact.js";
import { createEvidence } from "../domain/evidence.js";
import { importManagedReconstructionEvidence } from "./ManagedReconstructionService.js";
const exampleInput = () =>
managedReconstructionImportInputSchema.parse(

View File

@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest";
import { projectNativeApiInspection } from "../../../../src/application/NativeApiInspection.js";
import { functionDossierSchema } from "../../../../src/domain/hopperValues.js";
import { nativeApiBoundarySchema } from "../../../../src/domain/nativeApiBoundary.js";
import { ghidraFunctionDossier } from "../../../fixtures/ghidraFunction.js";
import { functionDossierSchema } from "../domain/hopperValues.js";
import { ghidraFunctionDossier } from "../domain/hopperValues.fixture.js";
import { nativeApiBoundarySchema } from "../domain/nativeApiBoundary.js";
import { projectNativeApiInspection } from "./NativeApiInspection.js";
describe("native API inspection", () => {
it("projects structured boundary evidence through inspectable substeps", () => {

View File

@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { projectAnalysisError } from "../domain/errors.js";
import {
ProcessCaptureError,
processCaptureCancelled,
} from "./ProcessCaptureError.js";
describe("process capture error projection", () => {
it("reports incomplete cleanup resources without exposing its cause", () => {
const projected = projectAnalysisError(
new ProcessCaptureError("terminal cleanup failed", {
cause: new Error("secret-token"),
reason: "cleanup_incomplete",
cleanupResources: ["process_group"],
}),
);
expect(projected).toMatchObject({
code: "cleanup_incomplete",
details: { cleanup: "incomplete", resources: ["process_group"] },
});
expect(JSON.stringify(projected)).not.toContain("secret-token");
});
it("projects caller cancellation as completed cleanup", () => {
expect(projectAnalysisError(processCaptureCancelled())).toMatchObject({
code: "cancelled",
category: "cancelled",
details: { operation: "process_capture", cleanup: "complete" },
});
});
});

View File

@ -0,0 +1,35 @@
import { expect, it } from "vitest";
import type { ProcessCaptureEventJournalEntry } from "../domain/processCapture.js";
import { createProcessCaptureJournal } from "./ProcessCaptureJournal.js";
it("publishes journal entries in capture order and supports nested recording", () => {
const journal = createProcessCaptureJournal();
const observed: ProcessCaptureEventJournalEntry[] = [];
const unsubscribe = journal.subscribe((entry) => observed.push(entry));
journal.recordEvent("lifecycle", 0);
const settlementEntry = journal.record("lifecycle", 1);
unsubscribe();
journal.recordEvent("frames", 0);
expect(journal.entries).toEqual([
{ capture_order: 0, collection: "lifecycle", index: 0 },
{ capture_order: 1, collection: "lifecycle", index: 1 },
{ capture_order: 2, collection: "frames", index: 0 },
]);
expect(observed).toEqual(journal.entries.slice(0, 2));
expect(settlementEntry).toEqual(journal.entries[1]);
const nestedJournal = createProcessCaptureJournal();
const notifications: string[] = [];
nestedJournal.subscribe((entry) => {
notifications.push(`a:${String(entry.capture_order)}`);
if (entry.capture_order === 0) nestedJournal.record("frames", 1);
});
nestedJournal.subscribe((entry) =>
notifications.push(`b:${String(entry.capture_order)}`),
);
nestedJournal.record("frames", 0);
expect(notifications).toEqual(["a:0", "b:0", "a:1", "b:1"]);
});

View File

@ -1,26 +1,23 @@
import { expect, it } from "vitest";
import { snapshotRoots } from "../../../src/application/FilesystemSnapshot.js";
import { snapshotRoots } from "./FilesystemSnapshot.js";
import {
buildCaptureResult,
prepareProcessCapture,
type ProcessPreparationHost,
} from "../../../src/application/ProcessCaptureLifecycle.js";
import { ProcessCheckpoints } from "../../../src/application/ProcessCheckpoints.js";
import { normalizeProcessSamples } from "../../../src/application/ProcessNormalization.js";
import {
isInitializedPtyRoot,
readLinuxChildren,
} from "../../../src/application/ProcessSampling.js";
import { TerminalRenderer } from "../../../src/application/TerminalRenderer.js";
} from "./ProcessCaptureLifecycle.js";
import { ProcessCheckpoints } from "./ProcessCheckpoints.js";
import { normalizeProcessSamples } from "./ProcessNormalization.js";
import { isInitializedPtyRoot, readLinuxChildren } from "./ProcessSampling.js";
import { TerminalRenderer } from "./TerminalRenderer.js";
import {
authorizeProcessScenario,
compareProcessCaptures,
parseProcessCapture,
parseProcessScenario,
type ProcessCapture,
} from "../../../src/domain/processCapture.js";
import { emptyProcessCapture as emptyCapture } from "../../fixtures/processCapture.js";
} from "../domain/processCapture.js";
import { emptyProcessCapture as emptyCapture } from "../domain/processCapture.fixture.js";
const base = {
approved: true as const,

View File

@ -4,7 +4,7 @@ import {
runPairedProcessExperiment,
type ProcessCapturePort,
} from "./ProcessPairedExperiment.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js";
import {
parseProcessCapture,
type EnabledProcessExecutionPolicy,

View File

@ -5,7 +5,7 @@ import {
buildReconstructionObligationLedgerEvidenceValidated,
resolveReconstructionObligationLedgerRequest,
} from "./ReconstructionObligationLedgerService.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js";
import { createEvidence, type Evidence } from "../domain/evidence.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";

View File

@ -5,7 +5,7 @@ import {
buildReconstructionObligationLedgerEvidenceValidated,
resolveReconstructionObligationLedgerRequest,
} from "./ReconstructionObligationLedgerService.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js";
import { createEvidence, type Evidence } from "../domain/evidence.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";

View File

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { runSetup } from "../../../src/application/Setup.js";
import { FakeSetupHost, options } from "./setupTestSupport.js";
import { FakeSetupHost, options } from "./Setup.fixture.js";
import { runSetup } from "./Setup.js";
describe("setup workflow", () => {
it("omits an aligned managed skill from an otherwise empty plan", async () => {

View File

@ -6,14 +6,11 @@ import {
type SetupOptions,
type SetupHopperInstallResult,
type SetupProviderEnvironment,
} from "../../../src/application/Setup.js";
import type {
DoctorCheck,
DoctorReport,
DoctorScope,
} from "../../../src/application/Doctor.js";
import type { LinuxDistribution } from "../../../src/application/LinuxHopper.js";
} from "./Setup.js";
import type { DoctorCheck, DoctorReport, DoctorScope } from "./Doctor.js";
import type { LinuxDistribution } from "./LinuxHopper.js";
/** Recording setup host for service-level planning and recovery tests. */
export class FakeSetupHost implements SetupHost {
readonly platform: NodeJS.Platform;
nodeVersion = "25.1.0";
@ -160,6 +157,7 @@ export class FakeSetupHost implements SetupHost {
};
}
/** Build setup options for approved or planning-only service tests. */
export const options = (
approved: boolean,
installHopper = false,

View File

@ -4,8 +4,8 @@ import {
registrationPermissionEnvironment,
runSetup,
type SetupProgressEvent,
} from "../../../src/application/Setup.js";
import { FakeSetupHost, options } from "./setupTestSupport.js";
} from "./Setup.js";
import { FakeSetupHost, options } from "./Setup.fixture.js";
describe("setup workflow", () => {
it("propagates only the explicit non-secret investigation root policy", () => {

View File

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { runSetup } from "../../../src/application/Setup.js";
import { FakeSetupHost, options } from "./setupTestSupport.js";
import { FakeSetupHost, options } from "./Setup.fixture.js";
import { runSetup } from "./Setup.js";
describe("setup workflow", () => {
it("records every detected client outcome after an earlier failure", async () => {

View File

@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import {
setupInstallFailure,
type HopperInstallFailureReason,
} from "../../../src/application/SetupInstallFailure.js";
} from "./SetupInstallFailure.js";
describe("setup installer failure projection", () => {
it.each([

View File

@ -1,35 +1,9 @@
import { describe, expect, it } from "vitest";
import { runCapabilityStatus, runProviderStatus } from "./DirectAnalysis.js";
import { createDoctorHostFixture } from "./Doctor.fixture.js";
import { projectDoctorReport } from "./DoctorProjection.js";
import { runDoctor, type DoctorHost } from "./Doctor.js";
import { CATALOG_IDENTITY } from "../catalogIdentity.js";
import { PRODUCT_IDENTITY } from "../identity.js";
const doctorHost = (): DoctorHost => ({
platform: "darwin",
architecture: "x64",
nodeVersion: "24.18.0",
macosVersion: () => Promise.resolve("14.0"),
linuxDistribution: () => Promise.resolve(undefined),
validTarget: () => Promise.resolve(true),
executable: (path) => Promise.resolve(path.includes("Hopper")),
supportedLinuxHopper: () => Promise.resolve(true),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: true,
classification: "healthy",
}),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
installedSkillIdentity: () =>
Promise.resolve({
version: PRODUCT_IDENTITY.skillVersion,
toolCount: CATALOG_IDENTITY.counts.mcp_tools,
catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
}),
});
import { runDoctor } from "./Doctor.js";
describe("purpose-specific CLI projections", () => {
it("keeps provider and capability summaries concise with explicit full detail", async () => {
@ -59,7 +33,10 @@ describe("purpose-specific CLI projections", () => {
});
it("omits the catalog tool array only from the default doctor projection", async () => {
const report = await runDoctor(undefined, doctorHost());
const report = await runDoctor(
undefined,
createDoctorHostFixture({ validTarget: () => Promise.resolve(true) }),
);
const summary = projectDoctorReport(report, "summary");
const full = projectDoctorReport(report, "full");

View File

@ -6,7 +6,7 @@ import {
type UpgradeHost,
type UpgradeOutput,
type NpmInstallationHost,
} from "../../../src/application/Upgrade.js";
} from "./Upgrade.js";
class FakeUpgradeHost implements UpgradeHost {
latest: string | undefined = "0.6.0";

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { CdpCaptureCompleteness } from "../../../src/browser/CdpCaptureCompleteness.js";
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
describe("CdpCaptureCompleteness", () => {
it("reports attach-window limitations without claiming a complete history", () => {

View File

@ -5,8 +5,8 @@ import {
captureDom,
captureFrames,
captureResources,
} from "../../../src/browser/CdpCaptureDocuments.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
} from "./CdpCaptureDocuments.js";
import { inspectWebPageInputSchema } from "../domain/browserObservation.js";
const origin = "https://app.example.test";

View File

@ -1,19 +1,19 @@
import { describe, expect, it } from "vitest";
import type { BrowserScenarioSessionPort } from "../../../src/browser/BrowserScenarioSessionPort.js";
import { PlaywrightBrowserScenarioProvider } from "../../../src/browser/PlaywrightBrowserScenarioProvider.js";
import { BrowserScenarioCaptureBudget } from "../../../src/browser/PlaywrightScenarioArtifacts.js";
import { sanitizeBrowserUrl } from "../../../src/domain/browserObservation.js";
import type { BrowserScenarioSessionPort } from "./BrowserScenarioSessionPort.js";
import { PlaywrightBrowserScenarioProvider } from "./PlaywrightBrowserScenarioProvider.js";
import { BrowserScenarioCaptureBudget } from "./PlaywrightScenarioArtifacts.js";
import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
import {
browserScenarioSchema,
type BrowserScenario,
type BrowserScenarioAction,
} from "../../../src/domain/browserScenario.js";
} from "../domain/browserScenario.js";
import {
browserStepArtifactsSchema,
type BrowserScenarioEvent,
type BrowserStepArtifacts,
} from "../../../src/domain/browserScenarioCapture.js";
} from "../domain/browserScenarioCapture.js";
const scenario = (
options: {

View File

@ -3,9 +3,9 @@ import { expect, it } from "vitest";
import {
electronActiveObservationInputSchema,
electronActiveObservationResultSchema,
} from "../../../src/domain/electronActiveObservation.js";
import { runElectronActions } from "../../../src/browser/PlaywrightElectronActiveActions.js";
import { createElectronActiveObservationFixtureResult } from "../../fixtures/electronActiveObservationResult.js";
} from "../domain/electronActiveObservation.js";
import { createElectronActiveObservationFixtureResult } from "../domain/electronActiveObservation.fixture.js";
import { runElectronActions } from "./PlaywrightElectronActiveActions.js";
it("parses bounded window, renderer, and deep-link actions for agents", () => {
const input = electronActiveObservationInputSchema.parse({

View File

@ -1,10 +1,10 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { withPlaywrightExecutionBoundary } from "../../../src/browser/PlaywrightExecutionBoundary.js";
import { withPlaywrightExecutionBoundary } from "./PlaywrightExecutionBoundary.js";
import {
AnalysisCancelledError,
AnalysisTimeoutError,
} from "../../../src/domain/errors.js";
} from "../domain/errors.js";
describe("withPlaywrightExecutionBoundary", () => {
afterEach(() => vi.useRealTimers());

View File

@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import type { Page } from "playwright-core";
import { BrowserScenarioSecrets } from "../../../src/browser/BrowserScenarioSecrets.js";
import { BrowserScenarioCaptureBudget } from "../../../src/browser/PlaywrightScenarioArtifacts.js";
import { PlaywrightScenarioEvents } from "../../../src/browser/PlaywrightScenarioEvents.js";
import { browserScenarioSchema } from "../../../src/domain/browserScenario.js";
import { browserScenarioSchema } from "../domain/browserScenario.js";
import { BrowserScenarioSecrets } from "./BrowserScenarioSecrets.js";
import { BrowserScenarioCaptureBudget } from "./PlaywrightScenarioArtifacts.js";
import { PlaywrightScenarioEvents } from "./PlaywrightScenarioEvents.js";
describe("PlaywrightScenarioEvents", () => {
it("bounds oversized page errors before validating captured events", () => {

View File

@ -1,18 +1,8 @@
import { afterEach, describe, expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import {
isCliOperationFailure,
logCliCommand,
} from "../../../src/cliLogging.js";
import { silentLogger } from "../../../src/logger.js";
import { isCliOperationFailure } from "./cliLogging.js";
const originalExitCode = process.exitCode;
afterEach(() => {
process.exitCode = originalExitCode;
});
describe("CLI operation status", () => {
describe("CLI operation status classification", () => {
it.each([
["typed error", { error: "Analysis failed", category: "timeout" }],
["unhealthy diagnostic", { healthy: false, checks: [] }],
@ -34,20 +24,4 @@ describe("CLI operation status", () => {
])("keeps %s successful", (_label, value) => {
expect(isCliOperationFailure(value)).toBe(false);
});
it("sets a nonzero process status without replacing structured output", async () => {
const output = {
error: "Analysis failed",
category: "integrity_mismatch",
message: "Artifact integrity check failed.",
details: { logical_path: "main.js" },
};
await expect(
logCliCommand(silentLogger, "inventory-artifact", () =>
Promise.resolve(output),
),
).resolves.toBe(output);
expect(process.exitCode).toBe(1);
});
});

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { parseConfig } from "../../../src/config.js";
import { parseConfig } from "./config.js";
describe("configuration parsing integration", () => {
it("returns defaults when no environment variables are set", () => {

View File

@ -1,72 +1,2 @@
import type { JsonValue } from "../domain/jsonValue.js";
import { digestProcessCommitment } from "../domain/processCapture.js";
const normalization = {
paths: true,
pids: true,
ports: true,
time_bucket_ms: 10,
patterns: [],
};
const scenario = { executable_sha256: "0".repeat(64) };
const comparisonContract = {};
const shimPlan: JsonValue[] = [];
const replayPlan = {};
/** Minimal valid process capture used only in public contract examples. */
export const EMPTY_PROCESS_CAPTURE_EXAMPLE = {
schema_version: 4,
manifest: {
rea_version: "1.1.0",
provider_version: "3",
platform: "fixture",
architecture: "fixture",
pty_backend: "node-pty",
started_at: "2026-01-01T00:00:00.000Z",
completed_at: "2026-01-01T00:00:00.001Z",
scenario,
comparison_contract: comparisonContract,
shim_plan: shimPlan,
replay_plan: replayPlan,
full_scenario_sha256: digestProcessCommitment(scenario),
comparison_contract_sha256: digestProcessCommitment(comparisonContract),
executable_sha256: "0".repeat(64),
normalization_sha256: digestProcessCommitment(normalization),
shim_plan_sha256: digestProcessCommitment(shimPlan),
replay_plan_sha256: digestProcessCommitment(replayPlan),
},
normalization,
frames: [],
rendered_frames: [],
interaction_events: [],
exit: { code: 0, signal: null, reason: "exited" },
settlement: {
state: "quiesced",
elapsed_ms: 50,
cleanup_outcome: "not_required",
},
process_samples: [],
filesystem_checkpoints: [
{ name: "before", at_ms: 0, files: [], effects: [], truncated: false },
{
name: "after_settlement",
at_ms: 50,
files: [],
effects: [],
truncated: false,
},
],
shim_events: [],
protocol_events: [],
replay_transitions: [],
files_before: [],
files_after: [],
filesystem_effects: [],
truncated: false,
limitations: [],
residual_unknowns: [],
cleanup: {
owned_process_group: "verified",
temporary_root: "removed",
},
} satisfies JsonValue;
/** Minimal valid process capture retained as a public contract example. */
export { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCaptureExample.js";

View File

@ -0,0 +1,219 @@
import { describe, expect, it } from "vitest";
import {
AnalysisCapabilityUnavailableError,
AnalysisInputError,
ArtifactOperationError,
BinaryTargetError,
HopperProcessError,
PermissionRequiredError,
ProviderAdapterError,
ReplayPlanStaleError,
UnknownRegistryError,
projectAnalysisError,
} from "./errors.js";
describe("analysis error projection: provider failures", () => {
it("preserves detached, actionable provider diagnostics", () => {
const diagnostics = {
runtime_root: "/tmp/rea-ghidra-fixture",
profile_digest: "a".repeat(64),
exit_code: 1,
};
const error = new ProviderAdapterError("ghidra", "health", {
diagnostics,
});
expect(error.diagnostics).not.toBe(diagnostics);
expect(projectAnalysisError(error)).toMatchObject({
details: {
provider_id: "ghidra",
operation: "health",
diagnostics,
},
});
});
it("maps the Linux startup compatibility exit codes without host internals", () => {
const expected = [
[70, "private_display_unavailable"],
[71, "x11_authorization_failed"],
[72, "unsupported_hopper_build"],
[73, "invalid_launch_command"],
[74, "process_ownership_mismatch"],
[75, "hopper_exited_during_startup"],
[76, "unsupported_demo_dialog"],
[77, "unexpected_display_geometry"],
[78, "x11_input_failed"],
[79, "runtime_dependency_unavailable"],
[80, "x11_socket_directory_unusable"],
] as const;
for (const [exitCode, code] of expected) {
const projected = projectAnalysisError(new HopperProcessError(exitCode));
expect(projected).toMatchObject({
code: "provider_unavailable",
details: { failure_code: code, exit_code: exitCode },
});
expect(projected.message.length).toBeGreaterThan(20);
expect(JSON.stringify(projected)).not.toContain("/proc/");
}
});
});
describe("analysis error projection: caller contract", () => {
it("maps representative failures without exposing causes", () => {
const secretCause = new Error("secret-token");
const projected = [
projectAnalysisError(
new AnalysisInputError("overview", { cause: secretCause }),
),
projectAnalysisError(
new AnalysisCapabilityUnavailableError("fixture", "overview", "absent"),
),
projectAnalysisError(
new BinaryTargetError("/local/targets/app", "invalid", {
cause: secretCause,
}),
),
projectAnalysisError(
new ReplayPlanStaleError("a".repeat(64), "b".repeat(64)),
),
];
expect(projected.map(({ code }) => code)).toEqual([
"invalid_request",
"capability_unavailable",
"target_unavailable",
"plan_stale",
]);
expect(projected[1]).toMatchObject({ category: "unsupported_provider" });
expect(projected[2]).toMatchObject({
details: { path: "/local/targets/app" },
});
expect(JSON.stringify(projected)).not.toContain("secret-token");
});
it("preserves exact artifact-integrity coordinates", () => {
expect(
projectAnalysisError(
new ArtifactOperationError("inventory_artifact", "integrity", {
logicalPath: "main.js",
declaredSha256: "a".repeat(64),
calculatedSha256: "b".repeat(64),
unpacked: true,
}),
),
).toMatchObject({
category: "integrity_mismatch",
details: {
logical_path: "main.js",
declared_sha256: "a".repeat(64),
calculated_sha256: "b".repeat(64),
unpacked: true,
},
});
});
it.each([
[
"configure",
false,
false,
"Add the exact missing scope beneath the administrator ceiling, then retry.",
],
[
"elicit",
false,
true,
"Approve the exact missing scope, then retry the operation.",
],
[
"restart",
true,
false,
"Add the exact missing scope to the administrator configuration, then restart the registered MCP server or client.",
],
] as const)(
"projects %s permission remediation into stable protocol fields",
(remediation, restartRequired, elicitationSupported, action) => {
const projected = projectAnalysisError(
new PermissionRequiredError({
requested: {
capability: "evidence_read",
roots: ["/workspace/evidence.json"],
executables: [],
environment_names: [],
network: "none",
mount: false,
operation_identity: "read:evidence",
},
missing: { roots: ["/workspace/evidence.json"] },
ceiling: null,
remediation,
}),
);
expect(projected.remediation).toEqual({
action,
restart_required: restartRequired,
elicitation_supported: elicitationSupported,
});
},
);
it("gives missing residual unknowns lookup-specific remediation", () => {
expect(
projectAnalysisError(new UnknownRegistryError("not-found")),
).toMatchObject({
code: "execution_failure",
message:
"The requested residual unknown does not exist in this session. Check the unknown_id and try again.",
remediation: {
action:
"Check that the unknown_id belongs to this session, then retry.",
},
details: { reason: "not-found" },
});
});
it("retains schema-authored input guidance", () => {
expect(
projectAnalysisError(
new AnalysisInputError("find_changed_behavior", undefined, [
{
path: [],
reason: "invalid_value",
message:
"Supply either existing comparisons or one investigation_run",
},
]),
),
).toMatchObject({
details: {
issues: [
{
path: [],
reason: "invalid_value",
message:
"Supply either existing comparisons or one investigation_run",
},
],
},
});
});
it("distinguishes disabled integrity continuation from format support", () => {
expect(
projectAnalysisError(
new ArtifactOperationError("inventory_artifact", "policy"),
),
).toMatchObject({
code: "artifact_operation_failed",
category: "unavailable",
message: expect.stringContaining(
"REA_ARTIFACT_INTEGRITY_CONTINUE_ENABLED=true",
),
});
});
});

View File

@ -7,8 +7,7 @@ import {
detectArtifactFormat,
detectBytecodeFamily,
type BytecodeArtifact,
type BytecodeSymbol,
} from "../../../../src/domain/bytecodeProvider.js";
} from "./bytecodeProvider.js";
describe("bytecode provider", () => {
it("detects JVM family from extensions", () => {
@ -137,16 +136,4 @@ describe("bytecode provider", () => {
const parsed = bytecodeAnalysisSchema.safeParse(result);
expect(parsed.success).toBe(true);
});
it("uses BytecodeSymbol type", () => {
const sym: BytecodeSymbol = {
name: "com.example.Test",
raw_location: "0x100",
kind: "class",
bytecode_version: 52,
provenance: "application",
is_public: true,
};
expect(sym.provenance).toBe("application");
});
});

View File

@ -37,7 +37,6 @@ export const bytecodeSymbolSchema = z.strictObject({
/** Whether the symbol is accessible (public). */
is_public: z.boolean().default(false),
});
export type BytecodeSymbol = z.infer<typeof bytecodeSymbolSchema>;
/** A discovered bytecode artifact (class file, wheel, etc). */
export const bytecodeArtifactSchema = z.strictObject({

View File

@ -10,12 +10,8 @@ import {
looksLikeCredentialFrame,
successfulAuthEvents,
type AuthFlowEvent,
type FrameDirection,
type AuthStage,
type TokenLifecycleEvent,
type CustomProtocolCapture,
type ProtocolFrame,
} from "../../../src/domain/customProtocolCapture.js";
} from "./customProtocolCapture.js";
const sampleFrames: ProtocolFrame[] = [
{
@ -137,21 +133,4 @@ describe("custom protocol capture", () => {
it("returns null for empty auth events", () => {
expect(authFlowDuration([])).toBeNull();
});
it("uses all exported types", () => {
const dir: FrameDirection = "sent";
expect(dir).toBe("sent");
const stage: AuthStage = "init";
expect(stage).toBe("init");
const event: TokenLifecycleEvent = "issued";
expect(event).toBe("issued");
const capture: CustomProtocolCapture = {
transport: "tcp",
frames: sampleFrames,
auth_events: sampleAuthEvents,
has_truncated: false,
credentials_detected: false,
};
expect(capture.transport).toBe("tcp");
});
});

View File

@ -15,7 +15,6 @@ export type TransportType = z.infer<typeof transportTypeSchema>;
/** Direction of a protocol frame. */
export const frameDirectionSchema = z.enum(["sent", "received", "intercepted"]);
export type FrameDirection = z.infer<typeof frameDirectionSchema>;
/** A captured protocol frame. */
export const protocolFrameSchema = z.strictObject({
@ -57,7 +56,6 @@ export const authStageSchema = z.enum([
"failure",
"success",
]);
export type AuthStage = z.infer<typeof authStageSchema>;
/** Token lifecycle event. */
export const tokenLifecycleEventSchema = z.enum([
@ -67,7 +65,6 @@ export const tokenLifecycleEventSchema = z.enum([
"revoked",
"renewed",
]);
export type TokenLifecycleEvent = z.infer<typeof tokenLifecycleEventSchema>;
/** A captured authentication flow event. */
export const authFlowEventSchema = z.strictObject({
@ -107,7 +104,6 @@ export const customProtocolCaptureSchema = z.strictObject({
/** Whether credentials were detected. */
credentials_detected: z.boolean().default(false),
});
export type CustomProtocolCapture = z.infer<typeof customProtocolCaptureSchema>;
/** Correlate a frame with process identity. */
export function correlateProcessIdentity(

View File

@ -1,4 +1,4 @@
import { electronActiveObservationResultSchema } from "../../src/domain/electronActiveObservation.js";
import { electronActiveObservationResultSchema } from "./electronActiveObservation.js";
/** Create a bounded active-capture result for MCP contract tests. */
export const createElectronActiveObservationFixtureResult = (

View File

@ -6,10 +6,7 @@ import {
maxTreeDepth,
reconstructProcessTree,
type ProcessTreeEvent,
type ProcessEventType,
type ProcessNode,
type ProcessTreeReconstruction,
} from "../../../src/domain/eventProcessTree.js";
} from "./eventProcessTree.js";
describe("event-backed process tree reconstruction", () => {
it("reconstructs a simple process tree", () => {
@ -388,37 +385,4 @@ describe("event-backed process tree depth and types", () => {
const result = reconstructProcessTree(events, 1);
expect(maxTreeDepth(result.processes, 1)).toBe(2);
});
it("uses exported types", () => {
const node: ProcessNode = {
pid: 1,
ppid: null,
process_name: "test",
executable: "/test",
arguments: [],
children: [],
spawn_time_ms: 0,
exit_time_ms: null,
exit_code: null,
is_re_exec: false,
is_reparented: false,
previous_ppid: null,
};
expect(node.pid).toBe(1);
const recon: ProcessTreeReconstruction = {
root_pid: 1,
processes: [node],
events_consumed: 1,
events_unmatched: 0,
short_lived_descendants: 0,
has_re_exec: false,
has_reparenting: false,
};
expect(recon.root_pid).toBe(1);
});
it("uses ProcessEventType", () => {
const t: ProcessEventType = "spawn";
expect(t).toBe("spawn");
});
});

View File

@ -11,7 +11,6 @@ export const processEventTypeSchema = z.enum([
"thread_create",
"thread_exit",
]);
export type ProcessEventType = z.infer<typeof processEventTypeSchema>;
/** A single event-backed process-tree event. */
export const processTreeEventSchema = z.strictObject({

View File

@ -1,4 +1,4 @@
import type { JsonValue } from "../../src/domain/jsonValue.js";
import type { JsonValue } from "./jsonValue.js";
export const ghidraFunctionClassification = () => ({
external: false,

View File

@ -8,7 +8,7 @@ import {
parseProcedures,
parseRelatedAddresses,
parseSegments,
} from "../../../../src/domain/hopperValues.js";
} from "./hopperValues.js";
describe("Hopper boundary values", () => {
it("parses Hopper's address-keyed name and string maps", () => {

View File

@ -0,0 +1,286 @@
import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import {
compareManagedMembers,
managedMemberComparisonResultSchema,
} from "./managedMemberComparison.js";
import { inspectManagedMembersBytes } from "../dotnet/ManagedMemberInspector.js";
import {
buildManagedPeFixture,
managedPeFixtureTarget,
MANAGED_MEMBER_FIXTURE_LIMITS,
} from "../dotnet/ManagedPe.fixture.js";
const comparisonLimits = {
max_method_matches: 100,
max_field_matches: 100,
max_candidates: 10,
};
describe("managed member comparison", () => {
it("remaps renamed methods by exact CIL/signature without using names", () => {
const leftBytes = buildManagedPeFixture();
const rightBytes = buildManagedPeFixture({
mvid: Buffer.from("00112233445566778899aabbccddeefe", "hex"),
typeName: "A",
typeNamespace: "X",
methodName: "b",
fieldName: "c",
});
const left = inspect(leftBytes, "/tmp/left.dll");
const right = inspect(rightBytes, "/tmp/right.dll");
const result = compareManagedMembers(
{ evidenceId: left.evidenceId, result: left.result },
{ evidenceId: right.evidenceId, result: right.result },
comparisonLimits,
);
expect(result.algorithm.name_matching).toBe("not-used");
expect(result.left.mvid).toBe("00112233-4455-6677-8899-aabbccddeeff");
expect(result.right.mvid).toBe("33221100-5544-7766-8899-aabbccddeefe");
expect(result.matching.exact_il_signature).toBe(1);
expect(result.methods).toEqual([
expect.objectContaining({
status: "unchanged",
left: expect.objectContaining({ token: "0x06000001", name: "Main" }),
right: expect.objectContaining({ token: "0x06000001", name: "b" }),
match: expect.objectContaining({
status: "matched",
basis: "exact-il-signature",
confidence: "exact",
}),
dimensions: [],
}),
]);
expect(result.limitations).toContain(
"Names are reported as observations but are not used as a matching basis.",
);
});
it("uses structural method shape when build-local token operands drift", () => {
const leftBytes = buildManagedPeFixture();
const rightBytes = buildManagedPeFixture({
ilBody: Buffer.from([
0x32, 0x02, 0x7b, 0x02, 0x00, 0x00, 0x04, 0x28, 0x02, 0x00, 0x00, 0x0a,
0x2a,
]),
});
const left = inspect(leftBytes, "/tmp/left.dll");
const right = inspect(rightBytes, "/tmp/right.dll");
const result = compareManagedMembers(
{ evidenceId: left.evidenceId, result: left.result },
{ evidenceId: right.evidenceId, result: right.result },
comparisonLimits,
);
expect(result.matching.exact_il_signature).toBe(0);
expect(result.matching.structural_method_shape).toBe(1);
expect(result.methods[0]).toMatchObject({
status: "changed",
match: { status: "matched", basis: "structural-method-shape" },
dimensions: ["cil"],
});
});
it("rejects member states that disagree with their observed sides or match", () => {
const left = inspect(buildManagedPeFixture(), "/tmp/left.dll");
const right = inspect(buildManagedPeFixture(), "/tmp/right.dll");
const result = compareManagedMembers(
{ evidenceId: left.evidenceId, result: left.result },
{ evidenceId: right.evidenceId, result: right.result },
comparisonLimits,
);
const method = result.methods[0];
expect(method).toBeDefined();
if (method === undefined) return;
expect(
managedMemberComparisonResultSchema.safeParse({
...result,
methods: [{ ...method, status: "unchanged", right: null }],
}).success,
).toBe(false);
expect(
managedMemberComparisonResultSchema.safeParse({
...result,
methods: [
{
...method,
match: {
...method.match,
basis: "none",
confidence: "unknown",
},
},
],
}).success,
).toBe(false);
});
it("does not match identical instruction-limited method prefixes", () => {
const limited = {
...MANAGED_MEMBER_FIXTURE_LIMITS,
maxMethodInstructions: 1,
};
const left = inspect(
buildManagedPeFixture(),
"/tmp/left-partial.dll",
limited,
);
const right = inspect(
buildManagedPeFixture(),
"/tmp/right-partial.dll",
limited,
);
const result = compareManagedMembers(
{ evidenceId: left.evidenceId, result: left.result },
{ evidenceId: right.evidenceId, result: right.result },
comparisonLimits,
);
expect(result.matching.exact_il_signature).toBe(0);
expect(result.matching.structural_method_shape).toBe(0);
expect(result.coverage).toMatchObject({
status: "partial",
left_status: "partial",
right_status: "partial",
});
});
});
describe("managed member comparison uncertainty", () => {
it("keeps unmatched members unknown when the opposite page is incomplete", () => {
const left = inspect(buildManagedPeFixture(), "/tmp/left-paged.dll");
const right = inspect(buildManagedPeFixture(), "/tmp/right-paged.dll");
const leftPartial = {
...left.result,
fields: {
...left.result.fields,
items: [],
returned: 0,
dropped: 1,
complete: false,
},
};
const rightPartial = {
...right.result,
methods: {
...right.result.methods,
items: [],
returned: 0,
dropped: 1,
complete: false,
},
};
const result = compareManagedMembers(
{ evidenceId: left.evidenceId, result: leftPartial },
{ evidenceId: right.evidenceId, result: rightPartial },
comparisonLimits,
);
expect(result.summary).toMatchObject({
added: 0,
removed: 0,
unknown: 2,
});
expect(result.methods[0]).toMatchObject({
status: "unknown",
left: { token: "0x06000001" },
right: null,
limitations: [expect.stringContaining("unknown-within-unobserved-page")],
});
expect(result.fields[0]).toMatchObject({
status: "unknown",
left: null,
right: { token: "0x04000001" },
limitations: [expect.stringContaining("unknown-within-unobserved-page")],
});
expect(result.coverage).toEqual({
status: "truncated",
left_status: "partial",
right_status: "partial",
omitted_methods: 1,
omitted_fields: 1,
omitted_candidates: 0,
});
});
it("does not guess ambiguous field signature matches", () => {
const left = inspect(buildManagedPeFixture(), "/tmp/left.dll");
const leftField = left.result.fields.items[0];
expect(leftField).toBeDefined();
if (leftField === undefined) return;
const duplicatedLeft = {
...left.result,
fields: {
...left.result.fields,
items: [
leftField,
{
...leftField,
token: "0x04000002",
name: "other",
},
],
total: 2,
returned: 2,
},
};
const right = inspect(buildManagedPeFixture(), "/tmp/right.dll");
const rightField = right.result.fields.items[0];
expect(rightField).toBeDefined();
if (rightField === undefined) return;
const duplicatedRight = {
...right.result,
fields: {
...right.result.fields,
items: [
rightField,
{
...rightField,
token: "0x04000002",
name: "renamed",
},
],
total: 2,
returned: 2,
},
};
const result = compareManagedMembers(
{ evidenceId: left.evidenceId, result: duplicatedLeft },
{ evidenceId: right.evidenceId, result: duplicatedRight },
comparisonLimits,
);
expect(result.matching.ambiguous).toBe(1);
expect(result.fields).toEqual([
expect.objectContaining({
status: "unknown",
match: expect.objectContaining({
status: "ambiguous",
basis: "field-signature",
candidate_left_tokens: ["0x04000001", "0x04000002"],
candidate_right_tokens: ["0x04000001", "0x04000002"],
}),
}),
]);
});
});
const inspect = (
bytes: Buffer,
path: string,
limits: typeof MANAGED_MEMBER_FIXTURE_LIMITS = MANAGED_MEMBER_FIXTURE_LIMITS,
) => {
const target = managedPeFixtureTarget(bytes, path);
const result = inspectManagedMembersBytes(bytes, target, limits);
return {
result,
evidenceId: `ev_${hash(Buffer.from(path))}`,
};
};
const hash = (bytes: Buffer): string =>
createHash("sha256").update(bytes).digest("hex");

View File

@ -13,7 +13,7 @@ import {
type SigningInfo,
type NativeLibrary,
type MobileResource,
} from "../../../src/domain/mobileApplicationInvestigation.js";
} from "./mobileApplicationInvestigation.js";
describe("mobile application investigation", () => {
it("detects mobile format from extension", () => {

View File

@ -10,14 +10,10 @@ import {
swiftDeclsByKind,
type ObjcClass,
type ObjcMethod,
type ObjcProperty,
type DbOperation,
type ObjcSwiftMetadata,
type ObjcProtocol,
type ObjcPropertyAttribute,
type SwiftDecl,
type DbSaveResult,
} from "../../../../src/domain/objcSwiftMetadata.js";
} from "./objcSwiftMetadata.js";
describe("ObjC/Swift metadata", () => {
it("identifies getter selectors", () => {
@ -105,25 +101,4 @@ describe("ObjC/Swift metadata", () => {
const result = objcSwiftMetadataSchema.safeParse(meta);
expect(result.success).toBe(true);
});
it("uses all exported types", () => {
const prop: ObjcProperty = {
name: "title",
type_encoding: 'T@"NSString"&,N,V_title',
attributes: [] as ObjcPropertyAttribute[],
is_readonly: false,
getter: "title",
setter: "setTitle:",
};
expect(prop.name).toBe("title");
const op: DbOperation = "save";
expect(op).toBe("save");
const meta: ObjcSwiftMetadata = {
objc_classes: [],
objc_protocols: [],
swift_decls: [],
db_save_result: null,
};
expect(meta.objc_classes).toHaveLength(0);
});
});

View File

@ -10,8 +10,7 @@ import {
resourcesByType,
totalResourceSize,
type PackageManifest,
type DigitalSignature,
} from "../../../src/domain/packageAnalysis.js";
} from "./packageAnalysis.js";
describe("package analysis", () => {
it("detects package format from extension", () => {
@ -99,9 +98,4 @@ describe("package analysis", () => {
};
expect(hasValidSignature(unsigned)).toBe(false);
});
it("uses DigitalSignature type", () => {
const sig: DigitalSignature = validManifest.signature;
expect(sig.is_signed).toBe(true);
});
});

View File

@ -44,7 +44,6 @@ export const digitalSignatureSchema = z.strictObject({
/** Timestamp signer if present. */
timestamp_signer: z.string().nullable(),
});
export type DigitalSignature = z.infer<typeof digitalSignatureSchema>;
/** A Windows package manifest for static analysis. */
export const packageManifestSchema = z.strictObject({

View File

@ -11,13 +11,7 @@ import {
parseSectionCharacteristics,
peManifestSchema,
type PeManifest,
type PeSection,
type PeImport,
type PeExport,
type PeResource,
type PeDebugEntry,
type PeTlsCallback,
} from "../../../../src/domain/peInspection.js";
} from "./peInspection.js";
const validManifest: PeManifest = {
dos_magic: 0x5a4d,
@ -196,19 +190,4 @@ describe("PE/COFF static inspection", () => {
it("counts executable sections", () => {
expect(countExecutableSections(validManifest)).toBe(1);
});
it("uses all exported types", () => {
const section: PeSection = validManifest.sections[0]!;
expect(section.name).toBe(".text");
const imp: PeImport = validManifest.imports[0]!;
expect(imp.dll).toBe("KERNEL32.DLL");
const exp: PeExport = validManifest.exports[0]!;
expect(exp.name).toBe("DllMain");
const res: PeResource = validManifest.resources[0]!;
expect(res.type).toBe("RT_VERSION");
const dbg: PeDebugEntry = validManifest.debug_entries[0]!;
expect(dbg.type).toBe("CODEVIEW");
const tls: PeTlsCallback = validManifest.tls_callbacks[0]!;
expect(tls.index).toBe(0);
});
});

View File

@ -25,7 +25,6 @@ export const peSectionSchema = z.strictObject({
raw_offset: z.number().int().nonnegative(),
characteristics: sectionCharacteristicsSchema,
});
export type PeSection = z.infer<typeof peSectionSchema>;
/** PE import entry. */
export const peImportSchema = z.strictObject({
@ -34,7 +33,6 @@ export const peImportSchema = z.strictObject({
ordinal: z.number().int().nullable(),
is_delay_import: z.boolean().default(false),
});
export type PeImport = z.infer<typeof peImportSchema>;
/** PE export entry. */
export const peExportSchema = z.strictObject({
@ -43,7 +41,6 @@ export const peExportSchema = z.strictObject({
rva: z.number().int().nonnegative(),
forwarder: z.string().nullable(),
});
export type PeExport = z.infer<typeof peExportSchema>;
/** PE/COFF resource entry. */
export const peResourceSchema = z.strictObject({
@ -53,7 +50,6 @@ export const peResourceSchema = z.strictObject({
rva: z.number().int().nonnegative(),
size: z.number().int().nonnegative(),
});
export type PeResource = z.infer<typeof peResourceSchema>;
/** PE debug directory entry. */
export const peDebugEntrySchema = z.strictObject({
@ -67,14 +63,12 @@ export const peDebugEntrySchema = z.strictObject({
/** PDB path if this is a CodeView entry. */
pdb_path: z.string().nullable(),
});
export type PeDebugEntry = z.infer<typeof peDebugEntrySchema>;
/** TLS callback entry. */
export const peTlsCallbackSchema = z.strictObject({
callback_rva: z.number().int().nonnegative(),
index: z.number().int().nonnegative(),
});
export type PeTlsCallback = z.infer<typeof peTlsCallbackSchema>;
/** Architecture/machine type. */
export const peMachineTypeSchema = z.enum([

View File

@ -3,7 +3,7 @@ import { expect, it } from "vitest";
import {
compareUnverifiedProcessCaptures as compareProcessCaptures,
emptyProcessCapture as emptyCapture,
} from "../../fixtures/processCapture.js";
} from "./processCapture.fixture.js";
it("classifies missing observations as unknown and one-sided evidence as added", () => {
const base = {

View File

@ -1,17 +1,22 @@
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../src/contracts/processCaptureExample.js";
import {
compareProcessCaptures,
parseProcessCapture,
processCaptureSchema,
type UnverifiedProcessCapture,
} from "../../src/domain/processCapture.js";
} from "./processCapture.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js";
export { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js";
/** Parse a detached valid empty capture for tests that need trusted evidence. */
export const emptyProcessCapture = () =>
parseProcessCapture(EMPTY_PROCESS_CAPTURE_EXAMPLE);
/** Parse a mutable unverified capture for boundary-validation tests. */
export const emptyUnverifiedProcessCapture = (): UnverifiedProcessCapture =>
processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE);
/** Project validation failures without coupling tests to Zod internals. */
export const processCaptureIssues = (
capture: UnverifiedProcessCapture,
): readonly { readonly path: string; readonly message: string }[] => {
@ -24,6 +29,7 @@ export const processCaptureIssues = (
}));
};
/** Compare unverified fixture values through the production parser. */
export const compareUnverifiedProcessCaptures = (
left: UnverifiedProcessCapture,
right: UnverifiedProcessCapture,

View File

@ -1,10 +1,10 @@
import { expect, it } from "vitest";
import { digestProcessCommitment } from "../../../src/domain/processCapture.js";
import { digestProcessCommitment } from "./processCapture.js";
import {
compareUnverifiedProcessCaptures as compareProcessCaptures,
emptyProcessCapture as emptyCapture,
} from "../../fixtures/processCapture.js";
} from "./processCapture.fixture.js";
it("detects changes in normalized process sample metadata", () => {
const capture = {

View File

@ -1,19 +1,17 @@
import { expect, it } from "vitest";
import { createProcessCaptureJournal } from "../../../src/application/ProcessCaptureJournal.js";
import {
digestProcessCommitment,
LEGACY_PROCESS_CAPTURE_MESSAGE,
parseProcessCapture,
processCaptureSchema,
type ProcessCaptureEventJournalEntry,
type UnverifiedProcessCapture,
} from "../../../src/domain/processCapture.js";
} from "./processCapture.js";
import {
compareUnverifiedProcessCaptures as compareProcessCaptures,
emptyUnverifiedProcessCapture as emptyCapture,
processCaptureIssues,
} from "../../fixtures/processCapture.js";
} from "./processCapture.fixture.js";
it("never considers truncated captures equivalent", () => {
const capture = {
@ -102,33 +100,6 @@ it("rejects settlement and cleanup combinations that cannot occur", () => {
});
it("accepts old captures without a journal and validates complete journals", () => {
const journal = createProcessCaptureJournal();
const observed: ProcessCaptureEventJournalEntry[] = [];
const unsubscribe = journal.subscribe((entry) => observed.push(entry));
journal.recordEvent("lifecycle", 0);
const settlementEntry = journal.record("lifecycle", 1);
unsubscribe();
journal.recordEvent("frames", 0);
expect(journal.entries).toEqual([
{ capture_order: 0, collection: "lifecycle", index: 0 },
{ capture_order: 1, collection: "lifecycle", index: 1 },
{ capture_order: 2, collection: "frames", index: 0 },
]);
expect(observed).toEqual(journal.entries.slice(0, 2));
expect(settlementEntry).toEqual(journal.entries[1]);
const nestedJournal = createProcessCaptureJournal();
const notifications: string[] = [];
nestedJournal.subscribe((entry) => {
notifications.push(`a:${String(entry.capture_order)}`);
if (entry.capture_order === 0) nestedJournal.record("frames", 1);
});
nestedJournal.subscribe((entry) =>
notifications.push(`b:${String(entry.capture_order)}`),
);
nestedJournal.record("frames", 0);
expect(notifications).toEqual(["a:0", "b:0", "a:1", "b:1"]);
const capture = emptyCapture();
const { event_journal: _eventJournal, ...oldCapture } = capture;
expect(parseProcessCapture(oldCapture).event_journal).toEqual([]);

View File

@ -0,0 +1,72 @@
import type { JsonValue } from "./jsonValue.js";
import { digestProcessCommitment } from "./processCapture.js";
const normalization = {
paths: true,
pids: true,
ports: true,
time_bucket_ms: 10,
patterns: [],
};
const scenario = { executable_sha256: "0".repeat(64) };
const comparisonContract = {};
const shimPlan: JsonValue[] = [];
const replayPlan = {};
/** Minimal valid process capture retained as a public contract example. */
export const EMPTY_PROCESS_CAPTURE_EXAMPLE = {
schema_version: 4,
manifest: {
rea_version: "1.1.0",
provider_version: "3",
platform: "fixture",
architecture: "fixture",
pty_backend: "node-pty",
started_at: "2026-01-01T00:00:00.000Z",
completed_at: "2026-01-01T00:00:00.001Z",
scenario,
comparison_contract: comparisonContract,
shim_plan: shimPlan,
replay_plan: replayPlan,
full_scenario_sha256: digestProcessCommitment(scenario),
comparison_contract_sha256: digestProcessCommitment(comparisonContract),
executable_sha256: "0".repeat(64),
normalization_sha256: digestProcessCommitment(normalization),
shim_plan_sha256: digestProcessCommitment(shimPlan),
replay_plan_sha256: digestProcessCommitment(replayPlan),
},
normalization,
frames: [],
rendered_frames: [],
interaction_events: [],
exit: { code: 0, signal: null, reason: "exited" },
settlement: {
state: "quiesced",
elapsed_ms: 50,
cleanup_outcome: "not_required",
},
process_samples: [],
filesystem_checkpoints: [
{ name: "before", at_ms: 0, files: [], effects: [], truncated: false },
{
name: "after_settlement",
at_ms: 50,
files: [],
effects: [],
truncated: false,
},
],
shim_events: [],
protocol_events: [],
replay_transitions: [],
files_before: [],
files_after: [],
filesystem_effects: [],
truncated: false,
limitations: [],
residual_unknowns: [],
cleanup: {
owned_process_group: "verified",
temporary_root: "removed",
},
} satisfies JsonValue;

View File

@ -1,14 +1,14 @@
import { describe, expect, it } from "vitest";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCapture.fixture.js";
import {
processCaptureSchema,
type UnverifiedProcessCapture,
} from "../../../src/domain/processCapture.js";
} from "./processCapture.js";
import {
createProcessObservation,
projectProcessObservation,
} from "../../../src/domain/processObservation.js";
} from "./processObservation.js";
const emptyCapture = processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE);

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCapture.fixture.js";
import {
compareProcessCaptures,
parseProcessCapture,

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCapture.fixture.js";
import { parseProcessCapture, type ProcessCapture } from "./processCapture.js";
import {
compareProcessTraces,

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCapture.fixture.js";
import { parseProcessCapture, type ProcessCapture } from "./processCapture.js";
import {
compareProcessTraces,

View File

@ -9,8 +9,7 @@ import {
looksLikeCredential,
protocolCaptureSchema,
type ProtocolMessage,
type ProtocolCapture,
} from "../../../src/domain/protocolCapture.js";
} from "./protocolCapture.js";
describe("protocol capture", () => {
it("classifies gRPC from content type", () => {
@ -179,19 +178,6 @@ describe("schema inference", () => {
});
});
describe("uses ProtocolCapture type", () => {
it("validates a well-formed capture typed value", () => {
const capture: ProtocolCapture = {
family: "grpc",
messages: [],
inferred_schema: [],
has_truncated: false,
credentials_detected: false,
};
expect(capture.family).toBe("grpc");
});
});
describe("protocol capture schema validation", () => {
it("validates a well-formed capture", () => {
const capture = {

View File

@ -77,7 +77,6 @@ export const protocolCaptureSchema = z.strictObject({
/** Whether credentials were detected and redacted. */
credentials_detected: z.boolean().default(false),
});
export type ProtocolCapture = z.infer<typeof protocolCaptureSchema>;
/** Classification of a byte sequence as a known protocol family. */
export function classifyProtocolFamily(

View File

@ -7,7 +7,7 @@ import {
verifyReconstruction,
} from "./reconstructionVerification.js";
import type { JsonValue } from "./jsonValue.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../contracts/processCaptureExample.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCapture.fixture.js";
import { ARTIFACT_COMPARISON_EXAMPLE } from "../contracts/artifactComparisonExample.js";
import { createResidualUnknown } from "./residualUnknown.js";

View File

@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import {
replayMachineRunInputSchema,
runReplayMachine,
} from "../../../../src/domain/replayMachineRun.js";
} from "./replayMachineRun.js";
const loginRun = () =>
replayMachineRunInputSchema.parse({

View File

@ -1,7 +1,7 @@
import { expect, it } from "vitest";
import { replayMachineSchema } from "../../../../src/domain/replayMachine.js";
import { ReplayMachineRuntime } from "../../../../src/domain/replayMachineRuntime.js";
import { replayMachineSchema } from "./replayMachine.js";
import { ReplayMachineRuntime } from "./replayMachineRuntime.js";
const machine = replayMachineSchema.parse({
initial_state: "login",

View File

@ -1,7 +1,7 @@
import { expect, it } from "vitest";
import { replayMachineSchema } from "../../../../src/domain/replayMachine.js";
import { ReplayMachineRuntime } from "../../../../src/domain/replayMachineRuntime.js";
import { replayMachineSchema } from "./replayMachine.js";
import { ReplayMachineRuntime } from "./replayMachineRuntime.js";
const machine = replayMachineSchema.parse({
initial_state: "login",

View File

@ -0,0 +1,61 @@
import { describe, expect, it } from "vitest";
import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js";
import {
buildManagedPeFixture,
buildNativePeFixture,
managedPeFixtureTarget,
MANAGED_ARTIFACT_FIXTURE_LIMITS,
} from "./ManagedPe.fixture.js";
describe("managed artifact failure classification", () => {
it("distinguishes native, malformed, unsupported, and bounded metadata", () => {
const nativeBytes = buildNativePeFixture();
const native = inspectManagedArtifactBytes(
nativeBytes,
managedPeFixtureTarget(nativeBytes),
MANAGED_ARTIFACT_FIXTURE_LIMITS,
);
expect(native).toMatchObject({
classification: { status: "not-managed" },
metadata: { status: "absent" },
coverage: { state: "unavailable" },
});
const malformedBytes = buildManagedPeFixture({
corruptMetadataSignature: true,
});
const malformed = inspectManagedArtifactBytes(
malformedBytes,
managedPeFixtureTarget(malformedBytes),
MANAGED_ARTIFACT_FIXTURE_LIMITS,
);
expect(malformed.classification.status).toBe("malformed");
expect(malformed.coverage.issues).toEqual([
expect.objectContaining({ code: "invalid-metadata-root" }),
]);
const unsupportedTableBytes = buildManagedPeFixture({
metadataValidMaskExtra: 1n << 50n,
});
const unsupportedTable = inspectManagedArtifactBytes(
unsupportedTableBytes,
managedPeFixtureTarget(unsupportedTableBytes),
MANAGED_ARTIFACT_FIXTURE_LIMITS,
);
expect(unsupportedTable.coverage.issues).toEqual([
expect.objectContaining({ code: "invalid-tables" }),
]);
const limitedBytes = buildManagedPeFixture();
const limited = inspectManagedArtifactBytes(
limitedBytes,
managedPeFixtureTarget(limitedBytes),
{ ...MANAGED_ARTIFACT_FIXTURE_LIMITS, maxMetadataBytes: 256 },
);
expect(limited.metadata.status).toBe("partial");
expect(limited.coverage.issues).toEqual([
expect.objectContaining({ code: "limit-exceeded" }),
]);
});
});

View File

@ -2,60 +2,26 @@ import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { inspectManagedArtifactBytes } from "../../../../src/dotnet/ManagedArtifactInspector.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { inspectManagedNativeBoundariesBytes } from "../../../../src/dotnet/ManagedNativeBoundaryInspector.js";
import { buildManagedPeFixture } from "../../../fixtures/managedPe.js";
const limits = {
referenceOffset: 0,
referenceLimit: 100,
resourceOffset: 0,
resourceLimit: 100,
attributeOffset: 0,
attributeLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
};
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
const nativeBoundaryLimits = {
moduleRefOffset: 0,
moduleRefLimit: 100,
importOffset: 0,
importLimit: 100,
implementationOffset: 0,
implementationLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
};
import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js";
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js";
import {
buildManagedPeFixture,
managedPeFixtureTarget,
MANAGED_ARTIFACT_FIXTURE_LIMITS,
MANAGED_MEMBER_FIXTURE_LIMITS,
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
} from "./ManagedPe.fixture.js";
describe("managed artifact inventory", () => {
it("inventories module, assembly, framework, references, and resources without loading CLR code", () => {
const resource = Buffer.from("source-owned resource");
const bytes = buildManagedPeFixture({ resourceData: resource });
const result = inspectManagedArtifactBytes(bytes, target(bytes), limits);
const result = inspectManagedArtifactBytes(
bytes,
managedPeFixtureTarget(bytes),
MANAGED_ARTIFACT_FIXTURE_LIMITS,
);
expect(result.classification).toMatchObject({
status: "managed",
@ -100,10 +66,14 @@ describe("managed artifact inventory", () => {
const bytes = buildManagedPeFixture({
references: ["System.Runtime", "UnityEngine.CoreModule"],
});
const result = inspectManagedArtifactBytes(bytes, target(bytes), {
...limits,
referenceLimit: 1,
});
const result = inspectManagedArtifactBytes(
bytes,
managedPeFixtureTarget(bytes),
{
...MANAGED_ARTIFACT_FIXTURE_LIMITS,
referenceLimit: 1,
},
);
expect(result.classification.runtime_family).toBe("unity-mono");
expect(result.references).toMatchObject({
@ -119,16 +89,20 @@ describe("managed artifact inventory", () => {
const bytes = buildManagedPeFixture({
mvid: Buffer.from("3aebc60edc4a544b1f458b4ed40b33b1", "hex"),
});
const result = inspectManagedArtifactBytes(bytes, target(bytes), limits);
const result = inspectManagedArtifactBytes(
bytes,
managedPeFixtureTarget(bytes),
MANAGED_ARTIFACT_FIXTURE_LIMITS,
);
const members = inspectManagedMembersBytes(
bytes,
target(bytes),
memberLimits,
managedPeFixtureTarget(bytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
const boundaries = inspectManagedNativeBoundariesBytes(
bytes,
target(bytes),
nativeBoundaryLimits,
managedPeFixtureTarget(bytes),
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
);
expect(result.module?.mvid).toBe("0ec6eb3a-4adc-4b54-1f45-8b4ed40b33b1");
@ -146,8 +120,8 @@ describe("managed artifact inventory", () => {
});
const result = inspectManagedMembersBytes(
bytes,
target(bytes),
memberLimits,
managedPeFixtureTarget(bytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(result.fields.items[0]?.signature).toMatchObject({
@ -161,13 +135,3 @@ describe("managed artifact inventory", () => {
});
});
});
const target = (bytes: Buffer): BinaryTarget => ({
path: "/fixture.exe",
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});

View File

@ -2,28 +2,12 @@ import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { buildManagedPeFixture } from "../../../fixtures/managedPe.js";
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import {
buildManagedPeFixture,
managedPeFixtureTarget,
MANAGED_MEMBER_FIXTURE_LIMITS,
} from "./ManagedPe.fixture.js";
describe("managed CIL decoding", () => {
it("reads fat method header size from the full flags-and-size word", () => {
@ -41,8 +25,8 @@ describe("managed CIL decoding", () => {
});
const result = inspectManagedMembersBytes(
bytes,
target(bytes),
memberLimits,
managedPeFixtureTarget(bytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(result.methods.items[0]?.body).toMatchObject({
@ -68,8 +52,8 @@ describe("managed CIL decoding", () => {
});
const result = inspectManagedMembersBytes(
bytes,
target(bytes),
memberLimits,
managedPeFixtureTarget(bytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(result.methods.items[0]?.body).toMatchObject({
@ -96,13 +80,13 @@ describe("managed CIL decoding", () => {
});
const tiny = inspectManagedMembersBytes(
tinyBytes,
target(tinyBytes),
memberLimits,
managedPeFixtureTarget(tinyBytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
const fat = inspectManagedMembersBytes(
fatBytes,
target(fatBytes),
memberLimits,
managedPeFixtureTarget(fatBytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(tiny.methods.items[0]?.body).toMatchObject({
@ -128,13 +112,3 @@ describe("managed CIL decoding", () => {
});
});
});
const target = (bytes: Buffer): BinaryTarget => ({
path: "/fixture.exe",
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});

View File

@ -2,36 +2,24 @@ import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { buildManagedPeFixture } from "../../../fixtures/managedPe.js";
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import {
buildManagedPeFixture,
managedPeFixtureTarget,
MANAGED_MEMBER_FIXTURE_LIMITS,
} from "./ManagedPe.fixture.js";
describe("managed CIL limits", () => {
it("marks instruction-limited CIL as partial without assigning normalized identity", () => {
const bytes = buildManagedPeFixture();
const result = inspectManagedMembersBytes(bytes, target(bytes), {
...memberLimits,
maxMethodInstructions: 1,
});
const result = inspectManagedMembersBytes(
bytes,
managedPeFixtureTarget(bytes),
{
...MANAGED_MEMBER_FIXTURE_LIMITS,
maxMethodInstructions: 1,
},
);
const il = bytes.subarray(0x0a01, 0x0a0d);
expect(result.metadata.status).toBe("complete");
@ -63,13 +51,3 @@ describe("managed CIL limits", () => {
);
});
});
const target = (bytes: Buffer): BinaryTarget => ({
path: "/fixture.exe",
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});

View File

@ -1,42 +1,24 @@
import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { buildManagedPeFixture } from "../../../fixtures/managedPe.js";
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import {
buildManagedPeFixture,
managedPeFixtureTarget,
MANAGED_MEMBER_FIXTURE_LIMITS,
} from "./ManagedPe.fixture.js";
describe("managed member inspection", () => {
it("inspects metadata members, signatures, CIL hashes, call edges, and field anchors", () => {
const bytes = buildManagedPeFixture();
const result = inspectManagedMembersBytes(
bytes,
target(bytes),
memberLimits,
managedPeFixtureTarget(bytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(result.identity_scope).toEqual({
token_identity: "build-local",
requires_artifact_sha256: target(bytes).sha256,
requires_artifact_sha256: managedPeFixtureTarget(bytes).sha256,
requires_mvid: "00112233-4455-6677-8899-aabbccddeeff",
});
expect(result.types.items).toEqual([
@ -130,13 +112,3 @@ describe("managed member inspection", () => {
expect(result.coverage).toMatchObject({ state: "complete", issues: [] });
});
});
const target = (bytes: Buffer): BinaryTarget => ({
path: "/fixture.exe",
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});

View File

@ -1,45 +1,14 @@
import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { inspectManagedNativeBoundariesBytes } from "../../../../src/dotnet/ManagedNativeBoundaryInspector.js";
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js";
import {
buildManagedPeFixture,
buildNativePeFixture,
} from "../../../fixtures/managedPe.js";
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
const nativeBoundaryLimits = {
moduleRefOffset: 0,
moduleRefLimit: 100,
importOffset: 0,
importLimit: 100,
implementationOffset: 0,
implementationLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
};
managedPeFixtureTarget,
MANAGED_MEMBER_FIXTURE_LIMITS,
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
} from "./ManagedPe.fixture.js";
describe("managed native boundaries", () => {
it("inspects managed/native PInvoke declarations without verifying native exports", () => {
@ -53,8 +22,8 @@ describe("managed native boundaries", () => {
});
const result = inspectManagedNativeBoundariesBytes(
bytes,
target(bytes),
nativeBoundaryLimits,
managedPeFixtureTarget(bytes),
MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS,
);
expect(result.cli_native).toMatchObject({
@ -109,11 +78,15 @@ describe("managed native boundaries", () => {
it("keeps member evidence bounded and typed for pagination and unavailable metadata", () => {
const bytes = buildManagedPeFixture();
const paged = inspectManagedMembersBytes(bytes, target(bytes), {
...memberLimits,
methodLimit: 0 + 1,
instructionAnchorLimit: 1,
});
const paged = inspectManagedMembersBytes(
bytes,
managedPeFixtureTarget(bytes),
{
...MANAGED_MEMBER_FIXTURE_LIMITS,
methodLimit: 0 + 1,
instructionAnchorLimit: 1,
},
);
expect(paged.methods).toMatchObject({
total: 1,
returned: 1,
@ -124,8 +97,8 @@ describe("managed native boundaries", () => {
const nativeBytes = buildNativePeFixture();
const native = inspectManagedMembersBytes(
nativeBytes,
target(nativeBytes),
memberLimits,
managedPeFixtureTarget(nativeBytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(native.metadata.status).toBe("absent");
expect(native.coverage.state).toBe("unavailable");
@ -135,8 +108,8 @@ describe("managed native boundaries", () => {
});
const malformed = inspectManagedMembersBytes(
malformedBytes,
target(malformedBytes),
memberLimits,
managedPeFixtureTarget(malformedBytes),
MANAGED_MEMBER_FIXTURE_LIMITS,
);
expect(malformed.metadata.status).toBe("malformed");
expect(malformed.coverage.issues).toEqual([
@ -144,13 +117,3 @@ describe("managed native boundaries", () => {
]);
});
});
const target = (bytes: Buffer): BinaryTarget => ({
path: "/fixture.exe",
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});

View File

@ -1,3 +1,7 @@
import { createHash } from "node:crypto";
import type { BinaryTarget } from "../domain/binaryTarget.js";
interface ManagedPeFixtureOptions {
readonly cliFlags?: number;
readonly corruptMetadataSignature?: boolean;
@ -69,6 +73,73 @@ const DEFAULT_MVID = Buffer.from([
0xdd, 0xee, 0xff,
]);
/** Generous bounds for inventory assertions over the source-owned PE fixture. */
export const MANAGED_ARTIFACT_FIXTURE_LIMITS = {
referenceOffset: 0,
referenceLimit: 100,
resourceOffset: 0,
resourceLimit: 100,
attributeOffset: 0,
attributeLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
};
/** Generous bounds for member assertions over the source-owned PE fixture. */
export const MANAGED_MEMBER_FIXTURE_LIMITS = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
/** Member bounds for fixture tests that inspect a real local path. */
export const MANAGED_MEMBER_PATH_FIXTURE_LIMITS = {
...MANAGED_MEMBER_FIXTURE_LIMITS,
maxFileBytes: 1024 * 1024,
};
/** Generous native-boundary bounds for the source-owned PE fixture. */
export const MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS = {
moduleRefOffset: 0,
moduleRefLimit: 100,
importOffset: 0,
importLimit: 100,
implementationOffset: 0,
implementationLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
};
/** Describe fixture bytes as the managed PE target consumed by static inspectors. */
export const managedPeFixtureTarget = (
bytes: Buffer,
path = "/fixture.exe",
): BinaryTarget => ({
path,
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});
const guidHeap = (mvid: Buffer = DEFAULT_MVID): Buffer => {
if (mvid.length !== 16)
throw new RangeError("Managed PE fixture MVID must be 16 bytes");

View File

@ -1,17 +1,13 @@
import { describe, expect, it } from "vitest";
import {
runDoctor,
type DoctorHost,
} from "../../../../src/application/Doctor.js";
import { runDoctor, type DoctorHost } from "../application/Doctor.js";
import { createDoctorHostFixture } from "../application/Doctor.fixture.js";
import {
inspectGhidraInstallation,
type GhidraInstallationHost,
type GhidraInstallationInspection,
} from "../../../../src/ghidra/GhidraInstallation.js";
import { projectGhidraDoctorInspection } from "../../../../src/ghidra/GhidraDoctor.js";
import { CATALOG_IDENTITY } from "../../../../src/catalogIdentity.js";
import { PRODUCT_IDENTITY } from "../../../../src/identity.js";
} from "./GhidraInstallation.js";
import { projectGhidraDoctorInspection } from "./GhidraDoctor.js";
const INSTALL = "/opt/ghidra_12.1.2_PUBLIC";
const installationHost = (
@ -51,39 +47,34 @@ const inspection = (
const doctorHost = (
ghidra: GhidraInstallationInspection,
hopperAvailable = false,
): DoctorHost => ({
platform: "linux",
architecture: "x64",
nodeVersion: "24.18.0",
macosVersion: () => Promise.resolve(undefined),
linuxDistribution: () =>
Promise.resolve({
id: "ubuntu",
versionId: "24.04",
packageFamily: "deb",
supported: true,
}),
validTarget: () => Promise.resolve(true),
executable: (path) =>
Promise.resolve(hopperAvailable && path.includes("Hopper")),
supportedLinuxHopper: () => Promise.resolve(true),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: true,
classification: "healthy",
}),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
providerInspections: () =>
Promise.resolve([projectGhidraDoctorInspection(ghidra)]),
installedSkillIdentity: () =>
Promise.resolve({
version: PRODUCT_IDENTITY.skillVersion,
toolCount: CATALOG_IDENTITY.counts.mcp_tools,
catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
}),
});
): DoctorHost =>
createDoctorHostFixture({
platform: "linux",
architecture: "x64",
nodeVersion: "24.18.0",
macosVersion: () => Promise.resolve(undefined),
linuxDistribution: () =>
Promise.resolve({
id: "ubuntu",
versionId: "24.04",
packageFamily: "deb",
supported: true,
}),
validTarget: () => Promise.resolve(true),
executable: (path) =>
Promise.resolve(hopperAvailable && path.includes("Hopper")),
supportedLinuxHopper: () => Promise.resolve(true),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: true,
classification: "healthy",
}),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
providerInspections: () =>
Promise.resolve([projectGhidraDoctorInspection(ghidra)]),
});
describe("Ghidra doctor integration", () => {
it("accepts a valid BYO Ghidra engine when Hopper is absent", async () => {

View File

@ -1,13 +1,13 @@
import { describe, expect, it } from "vitest";
import type { JsonValue } from "../../../../src/domain/jsonValue.js";
import type { JsonValue } from "../domain/jsonValue.js";
import {
GHIDRA_FUNCTION_OPERATIONS,
isGhidraFunctionOperation,
parseGhidraFunctionInput,
parseGhidraFunctionResult,
type GhidraFunctionOperation,
} from "../../../../src/ghidra/GhidraFunctionValues.js";
} from "./GhidraFunctionValues.js";
import {
ghidraBounded,
ghidraFunctionClassification,
@ -15,7 +15,7 @@ import {
ghidraFunctionIdentity,
ghidraNativeApiBoundary,
ghidraReferenceEdge,
} from "../../../fixtures/ghidraFunction.js";
} from "../domain/hopperValues.fixture.js";
describe("Ghidra function-analysis boundary values", () => {
it("defaults bounded inputs and rejects undeclared or implicit addresses", () => {

View File

@ -7,7 +7,7 @@ import {
SUPPORTED_GHIDRA_VERSION,
type GhidraInstallationHost,
type GhidraJavaObservation,
} from "../../../../src/ghidra/GhidraInstallation.js";
} from "./GhidraInstallation.js";
const INSTALL = "/opt/ghidra";
const PROPERTIES = `${INSTALL}/Ghidra/application.properties`;

View File

@ -6,8 +6,8 @@ import {
parseGhidraInventoryInput,
parseGhidraInventoryResult,
type GhidraInventoryOperation,
} from "../../../../src/ghidra/GhidraInventoryValues.js";
import type { JsonValue } from "../../../../src/domain/jsonValue.js";
} from "./GhidraInventoryValues.js";
import type { JsonValue } from "../domain/jsonValue.js";
describe("Ghidra inventory boundary values", () => {
it("defaults bounded provider inputs without accepting undeclared fields", () => {

View File

@ -1,18 +1,18 @@
import { describe, expect, it } from "vitest";
import { parseConfig } from "../../../../src/config.js";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { parseConfig } from "../config.js";
import type { BinaryTarget } from "../domain/binaryTarget.js";
import {
GHIDRA_PROVIDER_IDENTITY,
GHIDRA_PROVIDER_TOOL_CONTRACTS,
GhidraProvider,
type GhidraProviderClientFactory,
} from "../../../../src/ghidra/GhidraProvider.js";
import type { GhidraInstallationHost } from "../../../../src/ghidra/GhidraInstallation.js";
import { GHIDRA_SESSION_CAPABILITIES } from "../../../../src/ghidra/GhidraSessionValues.js";
import { err, ok } from "../../../../src/domain/result.js";
import { GhidraSessionError } from "../../../../src/ghidra/GhidraSessionError.js";
import { silentLogger } from "../../../../src/logger.js";
} from "./GhidraProvider.js";
import type { GhidraInstallationHost } from "./GhidraInstallation.js";
import { GHIDRA_SESSION_CAPABILITIES } from "./GhidraSessionValues.js";
import { err, ok } from "../domain/result.js";
import { GhidraSessionError } from "./GhidraSessionError.js";
import { silentLogger } from "../logger.js";
const INSTALL = "/opt/ghidra_12.1.2_PUBLIC";
const installationHost = (): GhidraInstallationHost => ({
@ -61,7 +61,6 @@ describe("Ghidra provider", () => {
GHIDRA_PROVIDER_TOOL_CONTRACTS.map(({ name }) => name),
);
expect(GHIDRA_PROVIDER_TOOL_CONTRACTS).toHaveLength(19);
expect(Object.isFrozen(ghidra.capabilities())).toBe(true);
expect(ghidra.capabilities()).toEqual(
expect.arrayContaining([
expect.objectContaining({

View File

@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest";
import { parseConfig } from "../../../src/config.js";
import type { BinaryTarget } from "../../../src/domain/binaryTarget.js";
import { HopperProvider } from "../../../src/hopper/HopperProvider.js";
import { silentLogger } from "../../../src/logger.js";
import { parseConfig } from "../config.js";
import type { BinaryTarget } from "../domain/binaryTarget.js";
import { silentLogger } from "../logger.js";
import { HopperProvider } from "./HopperProvider.js";
describe("Hopper provider discovery", () => {
it("reports bounded launcher availability without starting Hopper", () => {

View File

@ -4,7 +4,7 @@ import {
observeOwnedProcessLineage,
verifyNoTokenOwnedProcesses,
type ProcessOwnershipHost,
} from "../../../src/process/ProcessOwnership.js";
} from "./ProcessOwnership.js";
const ownership = {
runId: "run-token",
leaderPid: 100,

View File

@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest";
import {
observeOwnedProcessLineage,
type ProcessOwnershipHost,
} from "../../../src/process/ProcessOwnership.js";
} from "./ProcessOwnership.js";
const ownership = {
runId: "run-token",
leaderPid: 100,

View File

@ -4,8 +4,8 @@ import {
parseProcessEnvironment,
selectCapturedProcessGroupIds,
type ProcessOwnershipHost,
} from "../../../src/process/ProcessOwnership.js";
import { matchesOwnedProcessCommand } from "../../../src/process/ProcessCommandIdentity.js";
} from "./ProcessOwnership.js";
import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
const ownership = {
runId: "run-token",
leaderPid: 100,

View File

@ -3,7 +3,7 @@ import {
cleanupOwnedProcessGroup,
observeOwnedProcessLineage,
type ProcessOwnershipHost,
} from "../../../src/process/ProcessOwnership.js";
} from "./ProcessOwnership.js";
const ownership = {
runId: "run-token",
leaderPid: 100,

View File

@ -3,7 +3,7 @@ import {
cleanupOwnedProcessGroup,
observeOwnedProcessGroup,
type ProcessOwnershipHost,
} from "../../../src/process/ProcessOwnership.js";
} from "./ProcessOwnership.js";
const ownership = {
runId: "run-token",
leaderPid: 100,

View File

@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import {
cleanupWindowsProcessTree,
type WindowsProcessTreeHost,
} from "../../../src/process/ProcessOwnership.js";
} from "./ProcessOwnership.js";
describe("Windows P0 process-tree cleanup", () => {
it("reports whether taskkill signaled or found an exited tree", async () => {
const terminated: WindowsProcessTreeHost = {

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { parseReplayWorkerResponse } from "../../../../src/replay/ReplayWorkerProtocol.js";
import { parseReplayWorkerResponse } from "./ReplayWorkerProtocol.js";
const digest = "1".repeat(64);
const cases = [{ case_id: "case-1", sha256: digest }];

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { runDerivedOperation } from "../../../src/server/runDerivedOperation.js";
import { runDerivedOperation } from "./runDerivedOperation.js";
const context = (
controller: AbortController,

View File

@ -30,7 +30,7 @@ afterEach(async () => {
);
});
describe("agent lifecycle", () => {
describe("client configuration filesystem lifecycle", () => {
it("detects every supported client and skips absent clients", async () => {
const home = await createTestTempDirectory("rea-detect-");
roots.push(home);
@ -170,7 +170,7 @@ describe("agent lifecycle", () => {
});
});
describe("agent lifecycle removal", () => {
describe("client configuration filesystem removal", () => {
it("uninstalls only owned entries and refuses purge symlinks", async () => {
const home = await createTestTempDirectory("rea-uninstall-");
roots.push(home);
@ -280,7 +280,7 @@ describe("agent lifecycle removal", () => {
});
});
describe("agent lifecycle failure reporting", () => {
describe("client configuration filesystem failure reporting", () => {
it.each([
[
"read",

View File

@ -27,7 +27,7 @@ afterEach(async () => {
vi.unstubAllEnvs();
});
describe("direct analysis snapshot permissions", () => {
describe("direct analysis snapshot filesystem permissions", () => {
it("forwards the CLI provider selector ahead of the environment preference", async () => {
const directory = await createTestTempDirectory("rea-direct-provider-");
const targetPath = join(directory, "fixture.hop");

View File

@ -1,4 +1,3 @@
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@ -15,29 +14,13 @@ import { MANAGED_STATIC_PROVIDER } from "../../../src/application/InvestigationP
import { createPermissionAuthority } from "../../../src/application/PermissionAuthority.js";
import { createEvidence } from "../../../src/domain/evidence.js";
import { managedRuntimeCorrelationResultSchema } from "../../../src/domain/managedRuntimeCorrelation.js";
import type { BinaryTarget } from "../../../src/domain/binaryTarget.js";
import type { PermissionCeiling } from "../../../src/domain/permissionPolicy.js";
import { inspectManagedMembersBytes } from "../../../src/dotnet/ManagedMemberInspector.js";
import { buildManagedPeFixture } from "../../fixtures/managedPe.js";
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
import {
buildManagedPeFixture,
managedPeFixtureTarget,
MANAGED_MEMBER_FIXTURE_LIMITS,
} from "../../../src/dotnet/ManagedPe.fixture.js";
describe("managed runtime correlation planning", () => {
it("fails closed while the managed runtime authority is disabled", async () => {
@ -157,7 +140,7 @@ describe("managed runtime correlation planning", () => {
await writeFile(artifactPath, bytes);
await writeFile(executablePath, "#!/bin/sh\n");
const fixture = inspect(bytes, artifactPath, {
...memberLimits,
...MANAGED_MEMBER_FIXTURE_LIMITS,
maxMethodInstructions: 1,
});
const authority = await authorityFor(directory, executablePath, true);
@ -247,18 +230,9 @@ const inputFor = (
const inspect = (
bytes: Buffer,
path: string,
limits: typeof memberLimits = memberLimits,
limits: typeof MANAGED_MEMBER_FIXTURE_LIMITS = MANAGED_MEMBER_FIXTURE_LIMITS,
) => {
const target: BinaryTarget = {
path,
sha256: hash(bytes),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
};
const target = managedPeFixtureTarget(bytes, path);
const result = inspectManagedMembersBytes(bytes, target, limits);
const method = result.methods.items[0];
if (method === undefined) throw new Error("fixture has no method");
@ -274,6 +248,3 @@ const inspect = (
}),
};
};
const hash = (bytes: Buffer): string =>
createHash("sha256").update(bytes).digest("hex");

View File

@ -17,7 +17,7 @@ import {
type PermissionScope,
} from "../../../src/domain/permissionPolicy.js";
describe("permission authority", () => {
describe("permission authority filesystem boundaries", () => {
it("distinguishes an elicitable grant from administrator reconfiguration", async () => {
const sandbox = await createTestTempDirectory(
"rea-permission-remediation-",

View File

@ -21,7 +21,7 @@ import {
type FakeCdpBrowser,
} from "../../fixtures/fakeCdpBrowser.js";
import { writeElectronBoundaryFixture } from "../../fixtures/electronBoundaryApplication.js";
import { createElectronActiveObservationFixtureResult } from "../../fixtures/electronActiveObservationResult.js";
import { createElectronActiveObservationFixtureResult } from "../../../src/domain/electronActiveObservation.fixture.js";
const browsers: FakeCdpBrowser[] = [];
const resources: Array<{ close(): Promise<unknown> }> = [];

View File

@ -21,7 +21,7 @@ import {
ghidraFunctionDossier,
ghidraFunctionIdentity,
ghidraReferenceEdge,
} from "../../fixtures/ghidraFunction.js";
} from "../../../src/domain/hopperValues.fixture.js";
const INSTALL = "/opt/ghidra_12.1.2_PUBLIC";

View File

@ -17,7 +17,7 @@ import { MANAGED_NATIVE_VERIFICATION_EXAMPLE } from "../../../src/contracts/mana
import type { PermissionCeiling } from "../../../src/domain/permissionPolicy.js";
import { ManagedStaticProvider } from "../../../src/dotnet/ManagedStaticProvider.js";
import { createServer } from "../../../src/server/createServer.js";
import { buildManagedPeFixture } from "../../fixtures/managedPe.js";
import { buildManagedPeFixture } from "../../../src/dotnet/ManagedPe.fixture.js";
it("opens a managed PE and executes the managed static provider through MCP", async () => {
const directory = await createTestTempDirectory("rea-managed-mcp-");

View File

@ -12,7 +12,7 @@ import {
parseProcessScenario,
type ProcessCapture,
} from "../../../src/domain/processCapture.js";
import { processCaptureIssues } from "../../fixtures/processCapture.js";
import { processCaptureIssues } from "../../../src/domain/processCapture.fixture.js";
type EventSource = "process" | "filesystem" | "http" | "websocket" | "shim";

View File

@ -11,7 +11,7 @@ import {
parseProcessScenario,
type ProcessCapture,
} from "../../../src/domain/processCapture.js";
import { processCaptureIssues } from "../../fixtures/processCapture.js";
import { processCaptureIssues } from "../../../src/domain/processCapture.fixture.js";
function createInteractiveScenario(root: string, script: string) {
return parseProcessScenario({

View File

@ -1,6 +1,5 @@
import { spawn, type ChildProcess } from "node:child_process";
import { createHash } from "node:crypto";
import { getEventListeners } from "node:events";
import { readFileSync } from "node:fs";
import { access, mkdir } from "node:fs/promises";
import { join } from "node:path";
@ -394,12 +393,6 @@ describe("GhidraClient cleanup and diagnostics", () => {
expect(exited(process_)).toBe(true);
if (process_ === undefined)
throw new Error("Fixture process was not captured");
expect(getEventListeners(process_, "exit")).toHaveLength(0);
expect(getEventListeners(process_, "close")).toHaveLength(0);
expect(getEventListeners(process_, "error")).toHaveLength(0);
expect(process_.stdout).not.toBeNull();
if (process_.stdout !== null)
expect(getEventListeners(process_.stdout, "data")).toHaveLength(0);
});
it("retains actionable runtime coordinates after successful cleanup", async () => {

View File

@ -1,5 +1,4 @@
import { spawn, type ChildProcess } from "node:child_process";
import { getEventListeners } from "node:events";
import { access } from "node:fs/promises";
import { fileURLToPath } from "node:url";
@ -286,12 +285,6 @@ describe("HopperClient cleanup", () => {
if (child === undefined)
throw new Error("Fixture process was not captured");
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
expect(getEventListeners(child, "exit")).toHaveLength(0);
expect(getEventListeners(child, "close")).toHaveLength(0);
expect(getEventListeners(child, "error")).toHaveLength(0);
expect(child.stderr).not.toBeNull();
if (child.stderr === null) throw new Error("Fixture stderr is unavailable");
expect(getEventListeners(child.stderr, "data")).toHaveLength(0);
});
});
const stopUnrelatedFixture = async (child: ChildProcess): Promise<void> => {

View File

@ -7,10 +7,10 @@ import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js
import { runProviderAnalysis } from "../../../../src/application/DirectAnalysis.js";
import { parseEvidence } from "../../../../src/domain/evidence.js";
import { buildManagedPeFixture } from "../../../fixtures/managedPe.js";
import { buildManagedPeFixture } from "../../../../src/dotnet/ManagedPe.fixture.js";
describe("managed CLI evidence", () => {
it("has the same evidence shape through direct CLI analysis plumbing", async () => {
describe("managed direct-analysis path boundary", () => {
it("returns provider evidence through the shared analysis service", async () => {
const directory = await createTestTempDirectory("rea-managed-cli-");
const path = join(directory, "fixture.exe");
await writeFile(path, buildManagedPeFixture());

View File

@ -1,4 +1,3 @@
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@ -7,292 +6,11 @@ import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
import { compareManagedMemberPaths } from "../../../../src/application/ManagedMemberComparisonService.js";
import { MANAGED_STATIC_PROVIDER } from "../../../../src/application/InvestigationProviders.js";
import { managedMemberComparisonResultSchema } from "../../../../src/domain/managedMemberComparison.js";
import {
compareManagedMembers,
managedMemberComparisonResultSchema,
} from "../../../../src/domain/managedMemberComparison.js";
import { createEvidence } from "../../../../src/domain/evidence.js";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { buildManagedPeFixture } from "../../../fixtures/managedPe.js";
const memberLimits = {
typeOffset: 0,
typeLimit: 100,
methodOffset: 0,
methodLimit: 100,
fieldOffset: 0,
fieldLimit: 100,
memberRefOffset: 0,
memberRefLimit: 100,
edgeOffset: 0,
edgeLimit: 100,
instructionAnchorLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
maxMethodBodyBytes: 1024 * 1024,
maxMethodInstructions: 1_000,
};
const pathMemberLimits = {
...memberLimits,
maxFileBytes: 1024 * 1024,
};
const comparisonLimits = {
max_method_matches: 100,
max_field_matches: 100,
max_candidates: 10,
};
describe("managed member comparison", () => {
it("remaps renamed methods by exact CIL/signature without using names", () => {
const leftBytes = buildManagedPeFixture();
const rightBytes = buildManagedPeFixture({
mvid: Buffer.from("00112233445566778899aabbccddeefe", "hex"),
typeName: "A",
typeNamespace: "X",
methodName: "b",
fieldName: "c",
});
const left = inspect(leftBytes, "/tmp/left.dll");
const right = inspect(rightBytes, "/tmp/right.dll");
const result = compareManagedMembers(
{ evidenceId: left.evidence.evidence_id, result: left.result },
{ evidenceId: right.evidence.evidence_id, result: right.result },
comparisonLimits,
);
expect(result.algorithm.name_matching).toBe("not-used");
expect(result.left.mvid).toBe("00112233-4455-6677-8899-aabbccddeeff");
expect(result.right.mvid).toBe("33221100-5544-7766-8899-aabbccddeefe");
expect(result.matching.exact_il_signature).toBe(1);
expect(result.methods).toEqual([
expect.objectContaining({
status: "unchanged",
left: expect.objectContaining({ token: "0x06000001", name: "Main" }),
right: expect.objectContaining({ token: "0x06000001", name: "b" }),
match: expect.objectContaining({
status: "matched",
basis: "exact-il-signature",
confidence: "exact",
}),
dimensions: [],
}),
]);
expect(result.limitations).toContain(
"Names are reported as observations but are not used as a matching basis.",
);
});
it("uses structural method shape when build-local token operands drift", () => {
const leftBytes = buildManagedPeFixture();
const rightBytes = buildManagedPeFixture({
ilBody: Buffer.from([
0x32, 0x02, 0x7b, 0x02, 0x00, 0x00, 0x04, 0x28, 0x02, 0x00, 0x00, 0x0a,
0x2a,
]),
});
const left = inspect(leftBytes, "/tmp/left.dll");
const right = inspect(rightBytes, "/tmp/right.dll");
const result = compareManagedMembers(
{ evidenceId: left.evidence.evidence_id, result: left.result },
{ evidenceId: right.evidence.evidence_id, result: right.result },
comparisonLimits,
);
expect(result.matching.exact_il_signature).toBe(0);
expect(result.matching.structural_method_shape).toBe(1);
expect(result.methods[0]).toMatchObject({
status: "changed",
match: { status: "matched", basis: "structural-method-shape" },
dimensions: ["cil"],
});
});
it("rejects member states that disagree with their observed sides or match", () => {
const left = inspect(buildManagedPeFixture(), "/tmp/left.dll");
const right = inspect(buildManagedPeFixture(), "/tmp/right.dll");
const result = compareManagedMembers(
{ evidenceId: left.evidence.evidence_id, result: left.result },
{ evidenceId: right.evidence.evidence_id, result: right.result },
comparisonLimits,
);
const method = result.methods[0];
expect(method).toBeDefined();
if (method === undefined) return;
expect(
managedMemberComparisonResultSchema.safeParse({
...result,
methods: [{ ...method, status: "unchanged", right: null }],
}).success,
).toBe(false);
expect(
managedMemberComparisonResultSchema.safeParse({
...result,
methods: [
{
...method,
match: {
...method.match,
basis: "none",
confidence: "unknown",
},
},
],
}).success,
).toBe(false);
});
it("does not match identical instruction-limited method prefixes", () => {
const limited = { ...memberLimits, maxMethodInstructions: 1 };
const left = inspect(
buildManagedPeFixture(),
"/tmp/left-partial.dll",
limited,
);
const right = inspect(
buildManagedPeFixture(),
"/tmp/right-partial.dll",
limited,
);
const result = compareManagedMembers(
{ evidenceId: left.evidence.evidence_id, result: left.result },
{ evidenceId: right.evidence.evidence_id, result: right.result },
comparisonLimits,
);
expect(result.matching.exact_il_signature).toBe(0);
expect(result.matching.structural_method_shape).toBe(0);
expect(result.coverage).toMatchObject({
status: "partial",
left_status: "partial",
right_status: "partial",
});
});
});
describe("managed member comparison uncertainty", () => {
it("keeps unmatched members unknown when the opposite page is incomplete", () => {
const left = inspect(buildManagedPeFixture(), "/tmp/left-paged.dll");
const right = inspect(buildManagedPeFixture(), "/tmp/right-paged.dll");
const leftPartial = {
...left.result,
fields: {
...left.result.fields,
items: [],
returned: 0,
dropped: 1,
complete: false,
},
};
const rightPartial = {
...right.result,
methods: {
...right.result.methods,
items: [],
returned: 0,
dropped: 1,
complete: false,
},
};
const result = compareManagedMembers(
{ evidenceId: left.evidence.evidence_id, result: leftPartial },
{ evidenceId: right.evidence.evidence_id, result: rightPartial },
comparisonLimits,
);
expect(result.summary).toMatchObject({
added: 0,
removed: 0,
unknown: 2,
});
expect(result.methods[0]).toMatchObject({
status: "unknown",
left: { token: "0x06000001" },
right: null,
limitations: [expect.stringContaining("unknown-within-unobserved-page")],
});
expect(result.fields[0]).toMatchObject({
status: "unknown",
left: null,
right: { token: "0x04000001" },
limitations: [expect.stringContaining("unknown-within-unobserved-page")],
});
expect(result.coverage).toEqual({
status: "truncated",
left_status: "partial",
right_status: "partial",
omitted_methods: 1,
omitted_fields: 1,
omitted_candidates: 0,
});
});
it("does not guess ambiguous field signature matches", () => {
const left = inspect(buildManagedPeFixture(), "/tmp/left.dll");
const leftField = left.result.fields.items[0];
expect(leftField).toBeDefined();
if (leftField === undefined) return;
const duplicatedLeft = {
...left.result,
fields: {
...left.result.fields,
items: [
leftField,
{
...leftField,
token: "0x04000002",
name: "other",
},
],
total: 2,
returned: 2,
},
};
const right = inspect(buildManagedPeFixture(), "/tmp/right.dll");
const rightField = right.result.fields.items[0];
expect(rightField).toBeDefined();
if (rightField === undefined) return;
const duplicatedRight = {
...right.result,
fields: {
...right.result.fields,
items: [
rightField,
{
...rightField,
token: "0x04000002",
name: "renamed",
},
],
total: 2,
returned: 2,
},
};
const result = compareManagedMembers(
{ evidenceId: left.evidence.evidence_id, result: duplicatedLeft },
{ evidenceId: right.evidence.evidence_id, result: duplicatedRight },
comparisonLimits,
);
expect(result.matching.ambiguous).toBe(1);
expect(result.fields).toEqual([
expect.objectContaining({
status: "unknown",
match: expect.objectContaining({
status: "ambiguous",
basis: "field-signature",
candidate_left_tokens: ["0x04000001", "0x04000002"],
candidate_right_tokens: ["0x04000001", "0x04000002"],
}),
}),
]);
});
});
buildManagedPeFixture,
MANAGED_MEMBER_PATH_FIXTURE_LIMITS,
} from "../../../../src/dotnet/ManagedPe.fixture.js";
describe("managed member comparison path workflow", () => {
it("compares two local paths and returns derived Evidence", async () => {
@ -308,49 +26,24 @@ describe("managed member comparison path workflow", () => {
const result = await compareManagedMemberPaths({
leftPath,
rightPath,
memberLimits: pathMemberLimits,
comparisonLimits,
memberLimits: MANAGED_MEMBER_PATH_FIXTURE_LIMITS,
comparisonLimits: {
max_method_matches: 100,
max_field_matches: 100,
max_candidates: 10,
},
});
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.operation).toBe("compare_managed_members");
expect(result.value.confidence).toBe("inferred");
expect(result.value.authority).toBe("analyst-inference");
expect(result.value).toMatchObject({
operation: "compare_managed_members",
confidence: "inferred",
authority: "analyst-inference",
});
expect(
managedMemberComparisonResultSchema.parse(result.value.normalized_result)
.matching.exact_il_signature,
).toBe(1);
});
});
const inspect = (
bytes: Buffer,
path: string,
limits: typeof memberLimits = memberLimits,
) => {
const target: BinaryTarget = {
path,
sha256: hash(bytes),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
};
const result = inspectManagedMembersBytes(bytes, target, limits);
return {
result,
evidence: createEvidence(target, MANAGED_STATIC_PROVIDER, {
operation: "inspect_managed_members",
parameters: {},
result,
rawResult: null,
limitations: result.limitations,
}),
};
};
const hash = (bytes: Buffer): string =>
createHash("sha256").update(bytes).digest("hex");

View File

@ -1,4 +1,3 @@
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@ -7,79 +6,16 @@ import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
import type { AnalysisExecution } from "../../../../src/application/AnalysisProvider.js";
import type { BinaryTarget } from "../../../../src/domain/binaryTarget.js";
import { parseBinaryTarget } from "../../../../src/domain/binaryTarget.js";
import {
managedArtifactInspectionSchema,
managedMemberInspectionSchema,
managedNativeBoundaryInspectionSchema,
} from "../../../../src/domain/managedArtifact.js";
import { inspectManagedArtifactBytes } from "../../../../src/dotnet/ManagedArtifactInspector.js";
import { ManagedStaticProvider } from "../../../../src/dotnet/ManagedStaticProvider.js";
import {
buildManagedPeFixture,
buildNativePeFixture,
} from "../../../fixtures/managedPe.js";
const limits = {
referenceOffset: 0,
referenceLimit: 100,
resourceOffset: 0,
resourceLimit: 100,
attributeOffset: 0,
attributeLimit: 100,
maxMetadataBytes: 1024 * 1024,
maxTableRows: 1_000,
maxHeapItemBytes: 1024 * 1024,
};
describe("managed artifact failures", () => {
it("reports native PE, malformed metadata, and bounded metadata failures as typed results", () => {
const native = inspectManagedArtifactBytes(
buildNativePeFixture(),
target(buildNativePeFixture()),
limits,
);
expect(native.classification.status).toBe("not-managed");
expect(native.metadata.status).toBe("absent");
expect(native.coverage.state).toBe("unavailable");
const malformedBytes = buildManagedPeFixture({
corruptMetadataSignature: true,
});
const malformed = inspectManagedArtifactBytes(
malformedBytes,
target(malformedBytes),
limits,
);
expect(malformed.classification.status).toBe("malformed");
expect(malformed.coverage.issues).toEqual([
expect.objectContaining({ code: "invalid-metadata-root" }),
]);
const unsupportedTableBytes = buildManagedPeFixture({
metadataValidMaskExtra: 1n << 50n,
});
const unsupportedTable = inspectManagedArtifactBytes(
unsupportedTableBytes,
target(unsupportedTableBytes),
limits,
);
expect(unsupportedTable.coverage.issues).toEqual([
expect.objectContaining({ code: "invalid-tables" }),
]);
const limited = inspectManagedArtifactBytes(
buildManagedPeFixture(),
target(buildManagedPeFixture()),
{ ...limits, maxMetadataBytes: 256 },
);
expect(limited.metadata.status).toBe("partial");
expect(limited.coverage.issues).toEqual([
expect.objectContaining({ code: "limit-exceeded" }),
]);
});
import { buildManagedPeFixture } from "../../../../src/dotnet/ManagedPe.fixture.js";
describe("managed static provider path boundary", () => {
it("executes as a read-only auxiliary provider with digest, cancellation, and format boundaries", async () => {
const directory = await createTestTempDirectory("rea-managed-provider-");
const bytes = buildManagedPeFixture();
@ -161,17 +97,6 @@ describe("managed artifact failures", () => {
});
});
});
const target = (bytes: Buffer): BinaryTarget => ({
path: "/fixture.exe",
sha256: createHash("sha256").update(bytes).digest("hex"),
kind: "executable",
format: "pe",
architecture: "x86",
availableArchitectures: ["x86"],
executableRole: "application",
managed: true,
});
const asManagedResult = (execution: AnalysisExecution) =>
managedArtifactInspectionSchema.parse(execution.result);

View File

@ -1,96 +1,17 @@
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import type {
AnalysisProvider,
CapabilityDescriptor,
} from "../../../src/application/AnalysisProvider.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
import {
createBinarySessionTargets,
createCacheProvider,
createTestBinarySession,
} from "../../fixtures/binarySession.js";
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
import { createInvestigationWorkspace } from "../../../src/domain/investigationWorkspace.js";
import { ok as resultOk } from "../../../src/domain/result.js";
import { observed as ok } from "../../fixtures/analysisExecution.js";
const cacheProvider = (
calls: string[],
mayWriteFilesystem = false,
): AnalysisProvider => {
const identity = {
id: "fixture",
name: "Fixture analysis provider",
version: "1",
} as const;
return {
identity: () => identity,
resolveAnalysisProfile: () =>
Promise.resolve(
resultOk({
profile: createAnalysisProfile(identity, 1, { fixture: true }),
compatibility: {},
}),
),
capabilities: () => [
cacheCapability(identity, "address_name", false, mayWriteFilesystem),
cacheCapability(identity, "set_address_name", true),
],
createClient: () => ({
execute: (operation) => {
calls.push(operation);
return Promise.resolve(ok(operation));
},
close: () => Promise.resolve(),
}),
};
};
const cacheCapability = (
provider: CapabilityDescriptor["provider"],
operation: "address_name" | "set_address_name",
mutatesArtifact: boolean,
mayWriteFilesystem = false,
): CapabilityDescriptor => ({
provider,
operation,
inputContractVersion: 1,
outputContractVersion: 1,
available: true,
reason: null,
pagination: "none",
exhaustive: true,
effects: {
mutatesArtifact,
launchesProcess: false,
mayShowUi: false,
mayAccessNetwork: false,
mayWriteFilesystem,
changesPermissions: false,
requiresRoot: false,
},
limits: {
maxResults: null,
maxPayloadBytes: null,
timeoutMs: null,
},
limitations: [],
});
const targets = async (): Promise<readonly [string, string]> => {
const directory = await createTestTempDirectory("bb-session-");
const first = join(directory, "first.hop");
const second = join(directory, "second.hop");
await writeFile(first, "one");
await writeFile(second, "two");
return [first, second];
};
describe("binary session", () => {
it("returns detached provider, target, and workspace metadata", async () => {
const [first] = await targets();
const session = createTestBinarySession(cacheProvider([]));
const [first] = await createBinarySessionTargets();
const session = createTestBinarySession(createCacheProvider([]));
expect((await session.open(first)).ok).toBe(true);
expect(session.status()).toMatchObject({
analysis_run: {

Some files were not shown because too many files have changed in this diff Show More