diff --git a/knip.json b/knip.json index 9eb05c02dc..2d5837f975 100644 --- a/knip.json +++ b/knip.json @@ -11,7 +11,6 @@ "src/composition/webBundleAnalysisWorkerProcess.ts", "src/contracts/errorSchemas.ts", "src/evaluation/AgentEvaluationFixtures.ts", - "src/application/RealHopperAssertions.ts", "scripts/verify/fridaBindingsCompatibility.ts", "scripts/verify/termux/browser.mjs", "scripts/verify-real-ghidra-noreturn.mjs", diff --git a/scripts/lib/real-hopper-assertions.d.mts b/scripts/lib/real-hopper-assertions.d.mts new file mode 100644 index 0000000000..b0e3afb179 --- /dev/null +++ b/scripts/lib/real-hopper-assertions.d.mts @@ -0,0 +1,35 @@ +/** Extract one real procedure address from the complete inline inventory. */ +export function firstProcedureAddress(input: unknown): string; + +/** Reject empty and success-shaped embedded decompilation failures. */ +export function requirePseudocode(input: unknown, operation: string): string; + +/** Validate a complete Hopper function dossier semantically. */ +export function requireFunctionDossier( + input: unknown, + expectedAddress: string, +): Record; + +/** Positive semantic facts required from one source-owned Hopper fixture. */ +export interface FunctionDossierOracle { + readonly procedure_address: string; + readonly callee_address?: string; + readonly caller_address?: string; + readonly referenced_string?: string; + readonly referenced_name?: string; + readonly comment?: string; + readonly require_cfg_successor?: boolean; + readonly require_assembly?: boolean; +} + +/** Reject a structurally valid dossier that omits fixture-proven semantics. */ +export function requireFunctionDossierOracle( + input: unknown, + oracle: FunctionDossierOracle, +): Record; + +/** Require every relationship to be an actual hexadecimal address. */ +export function requireAddressArray( + input: unknown, + operation: string, +): string[]; diff --git a/src/application/RealHopperAssertions.ts b/scripts/lib/real-hopper-assertions.mjs similarity index 79% rename from src/application/RealHopperAssertions.ts rename to scripts/lib/real-hopper-assertions.mjs index 5f2be5a7fc..db49b6abde 100644 --- a/src/application/RealHopperAssertions.ts +++ b/scripts/lib/real-hopper-assertions.mjs @@ -3,7 +3,7 @@ import { z } from "zod"; const addressSchema = z.string().regex(/^0x[0-9a-f]+$/iu); const identitySchema = z.object({ address: addressSchema, name: z.string() }); /** Extract one real procedure address from the complete inline inventory. */ -export const firstProcedureAddress = (input: unknown): string => { +export const firstProcedureAddress = (input) => { const first = z .array(z.object({ address: addressSchema })) .min(1) @@ -13,10 +13,7 @@ export const firstProcedureAddress = (input: unknown): string => { }; /** Reject empty and success-shaped embedded decompilation failures. */ -export const requirePseudocode = ( - input: unknown, - operation: string, -): string => { +export const requirePseudocode = (input, operation) => { if (typeof input !== "string" || input.trim().length === 0) throw new TypeError(`${operation} returned empty pseudocode`); if (input === "No output" || input.startsWith("Error:")) @@ -25,10 +22,7 @@ export const requirePseudocode = ( }; /** Validate a complete Hopper function dossier semantically. */ -export const requireFunctionDossier = ( - input: unknown, - expectedAddress: string, -): Record => { +export const requireFunctionDossier = (input, expectedAddress) => { const value = objectValue( input, "analyze_function returned no function dossier", @@ -40,29 +34,14 @@ export const requireFunctionDossier = ( throw new TypeError("analyze_function omitted the procedure name"); requirePseudocode(value.pseudocode, "analyze_function"); for (const field of COLLECTION_FIELDS) list(value, field); - for (const field of ["callers", "callees"] as const) + for (const field of ["callers", "callees"]) z.array(identitySchema).parse(list(value, field)); validateBlocks(list(value, "basic_blocks")); return value; }; -/** Positive semantic facts required from one source-owned Hopper fixture. */ -export interface FunctionDossierOracle { - readonly procedure_address: string; - readonly callee_address?: string; - readonly caller_address?: string; - readonly referenced_string?: string; - readonly referenced_name?: string; - readonly comment?: string; - readonly require_cfg_successor?: boolean; - readonly require_assembly?: boolean; -} - /** Reject a structurally valid dossier that omits fixture-proven semantics. */ -export const requireFunctionDossierOracle = ( - input: unknown, - oracle: FunctionDossierOracle, -): Record => { +export const requireFunctionDossierOracle = (input, oracle) => { const value = requireFunctionDossier(input, oracle.procedure_address); if (oracle.callee_address !== undefined) { const callees = z.array(identitySchema).parse(list(value, "callees")); @@ -158,12 +137,7 @@ const referenceEdgeSchema = z.object({ target_address: addressSchema, }); -const requireReferencedValue = ( - dossier: Record, - field: "referenced_strings" | "referenced_names", - expected: string, - normalize: (value: string) => string, -): void => { +const requireReferencedValue = (dossier, field, expected, normalize) => { const values = z.array(referencedValueSchema).parse(list(dossier, field)); const references = z .array(referenceEdgeSchema) @@ -183,10 +157,7 @@ const requireReferencedValue = ( }; /** Require every relationship to be an actual hexadecimal address. */ -export const requireAddressArray = ( - input: unknown, - operation: string, -): string[] => { +export const requireAddressArray = (input, operation) => { const parsed = z.array(addressSchema).parse(input); if (parsed.some((address) => address.length === 0)) throw new TypeError(`${operation} returned an invalid address list`); @@ -203,21 +174,17 @@ const COLLECTION_FIELDS = [ "referenced_strings", "referenced_names", "basic_blocks", -] as const; +]; -const list = (value: Record, field: string): unknown[] => - z.array(z.unknown()).parse(value[field]); +const list = (value, field) => z.array(z.unknown()).parse(value[field]); -const validateBlocks = (items: readonly unknown[]): void => { +const validateBlocks = (items) => { for (const block of items) if (!Array.isArray(objectValue(block, "Invalid basic block").successors)) throw new TypeError("analyze_function omitted CFG successor evidence"); }; -const objectValue = ( - input: unknown, - message: string, -): Record => { +const objectValue = (input, message) => { const parsed = z.record(z.string(), z.unknown()).safeParse(input); if (!parsed.success) throw new TypeError(message); return parsed.data; diff --git a/scripts/lib/real-hopper-semantic.mjs b/scripts/lib/real-hopper-semantic.mjs index 1f520a690f..d171a48da6 100644 --- a/scripts/lib/real-hopper-semantic.mjs +++ b/scripts/lib/real-hopper-semantic.mjs @@ -1,17 +1,6 @@ import assert from "node:assert/strict"; -/** - * `requireFunctionDossierOracle` is only used inside `verifyRealHopperFixture`, - * which runs against the real Hopper bridge after `build:cached`. Loading it - * lazily keeps this module importable from `test:fast` (which docs promise to - * run "without build" — see docs/testing.md § test scripts) so that mock-only - * callers such as `requireCurrentDocument` and `resolveFixtureProcedure` do - * not require `dist/`. - */ -const loadFunctionDossierOracle = () => - import("../../dist/application/RealHopperAssertions.js").then( - (module) => module.requireFunctionDossierOracle, - ); +import { requireFunctionDossierOracle } from "./real-hopper-assertions.mjs"; /** Require truthful tri-state permissions from real Hopper memory regions. */ export const requireTruthfulMemoryRegions = (segments) => { @@ -99,7 +88,6 @@ export async function verifyRealHopperFixture({ oracle, normalizedResult, }) { - const requireFunctionDossierOracle = await loadFunctionDossierOracle(); const main = await resolveFixtureProcedure( client, options, diff --git a/scripts/verify-real-hopper-deadlines.mjs b/scripts/verify-real-hopper-deadlines.mjs index 31b2d856fc..af2213ec6f 100644 --- a/scripts/verify-real-hopper-deadlines.mjs +++ b/scripts/verify-real-hopper-deadlines.mjs @@ -4,7 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; -import { firstProcedureAddress } from "../dist/application/RealHopperAssertions.js"; +import { firstProcedureAddress } from "./lib/real-hopper-assertions.mjs"; import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js"; import { ArtifactResourceScope } from "../dist/artifacts/ArtifactResourceScope.js"; import { AnalysisCleanupLease } from "../dist/artifacts/AnalysisCleanupLease.js"; diff --git a/scripts/verify-real-hopper.mjs b/scripts/verify-real-hopper.mjs index 17ddde21c8..5f31565b4a 100644 --- a/scripts/verify-real-hopper.mjs +++ b/scripts/verify-real-hopper.mjs @@ -10,7 +10,7 @@ import { requireAddressArray, requireFunctionDossier, requirePseudocode, -} from "../dist/application/RealHopperAssertions.js"; +} from "./lib/real-hopper-assertions.mjs"; import { HOPPER_PROVIDER_IDENTITY } from "../dist/hopper/HopperProviderCapabilities.js"; import { HOPPER_TARGET_LEASE_DIRECTORY } from "../dist/process/HopperLease.js"; import { REA_WORKFLOW_PROVIDER } from "../dist/domain/investigationProviders.js"; diff --git a/tests/conformance/providers/hopper/realHopperAssertions.test.ts b/tests/conformance/providers/hopper/realHopperAssertions.test.ts index feff8fd503..ad0d474ff7 100644 --- a/tests/conformance/providers/hopper/realHopperAssertions.test.ts +++ b/tests/conformance/providers/hopper/realHopperAssertions.test.ts @@ -5,7 +5,7 @@ import { requireFunctionDossierOracle, requireFunctionDossier, requirePseudocode, -} from "../../../../src/application/RealHopperAssertions.js"; +} from "../../../../scripts/lib/real-hopper-assertions.mjs"; describe("Hopper verifier semantic rejection", () => { it("rejects empty and embedded per-item decompilation failures", () => {