From ace4dbf36e8c8f36a0e192aff9a11484ed428358 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 06:53:47 +0800 Subject: [PATCH] fix(contracts): align live target classification with admitted facts Share exact live target variants across open and session responses, preserving historical Evidence and snapshot admission. Consolidate equivalent ELF metadata aliases at their owner. --- src/application/binary/BinaryTargetSummary.ts | 23 +- src/contracts/toolOutputSchemaGroups.ts | 57 +--- src/contracts/toolOutputSchemaPrimitives.ts | 29 -- src/domain/binaryTarget.ts | 6 +- src/domain/binaryTargetTypes.ts | 15 +- src/domain/liveBinaryTargetSummary.ts | 85 ++++++ src/ghidra/GhidraPowerPcProfile.test.ts | 6 +- src/ghidra/GhidraRiscvProfile.test.ts | 6 +- .../mcp/liveBinaryTargetSummary.test.ts | 262 ++++++++++++++++++ tests/conformance/ghidra/ppc-checks.d.mts | 4 +- 10 files changed, 377 insertions(+), 116 deletions(-) create mode 100644 src/domain/liveBinaryTargetSummary.ts create mode 100644 tests/boundary/mcp/liveBinaryTargetSummary.test.ts diff --git a/src/application/binary/BinaryTargetSummary.ts b/src/application/binary/BinaryTargetSummary.ts index a0a4f9e7bd..018c0af679 100644 --- a/src/application/binary/BinaryTargetSummary.ts +++ b/src/application/binary/BinaryTargetSummary.ts @@ -1,13 +1,16 @@ import type { BinaryTarget } from "../../domain/binaryTargetTypes.js"; +import { liveBinaryTargetSummarySchema } from "../../domain/liveBinaryTargetSummary.js"; + /** Preserve the admitted target interpretation in public lifecycle results. */ -export const binaryTargetSummary = (target: BinaryTarget) => ({ - path: target.path, - format: target.format, - kind: target.kind, - sha256: target.sha256, - architecture: target.architecture ?? null, - ...(target.format === "raw-binary" || target.format === "gamecube-dol" - ? { entry_point: target.entryPoint } - : {}), -}); +export const binaryTargetSummary = (target: BinaryTarget) => + liveBinaryTargetSummarySchema.parse({ + path: target.path, + format: target.format, + kind: target.kind, + sha256: target.sha256, + architecture: target.architecture ?? null, + ...(target.format === "raw-binary" || target.format === "gamecube-dol" + ? { entry_point: target.entryPoint } + : {}), + }); diff --git a/src/contracts/toolOutputSchemaGroups.ts b/src/contracts/toolOutputSchemaGroups.ts index 9816a89ab5..63907f51a5 100644 --- a/src/contracts/toolOutputSchemaGroups.ts +++ b/src/contracts/toolOutputSchemaGroups.ts @@ -38,14 +38,7 @@ import { z } from "zod"; import { jsonRecordSchema } from "../domain/jsonRecord.js"; import { byteSearchResultSchema } from "../domain/native/bytePattern.js"; import { evidenceBundleSummarySchema } from "../domain/evidenceRecordSummary.js"; -import { - BINARY_ARCHITECTURES, - RAW_ONLY_ARCHITECTURES, -} from "../domain/binaryTargetTypes.js"; -import { - binaryArchitectureSchema, - rawBinaryEntryPointSchema, -} from "../domain/rawBinary.js"; +import { liveBinaryTargetSummaryVariants } from "../domain/liveBinaryTargetSummary.js"; import { nativeFunctionAnnotationsSchema } from "../domain/native/nativeFunctionAnnotations.js"; import { nativeLoadImageSchema } from "../domain/native/nativeLoadImage.js"; import { nativeUiResultSchema } from "../domain/native/nativeUiObservation.js"; @@ -109,8 +102,6 @@ import { sessionProviderFacts, sessionStatusDetailShapes, symbolDiscoveryOutput, - targetFormatSchema, - targetKindSchema, } from "./toolOutputSchemaPrimitives.js"; import { processCaptureSchema } from "../domain/process/processCapture.js"; @@ -512,52 +503,10 @@ export const managedWorkflowOutputSchemas = { ), } as const satisfies Readonly>; -const lifecycleTargetFields = { - path: z.string(), - kind: targetKindSchema, - sha256: z.string().regex(/^[a-f0-9]{64}$/u), -}; -const detectedLifecycleTargetSchema = z.object({ - ...lifecycleTargetFields, - format: targetFormatSchema.exclude(["raw-binary", "gamecube-dol"]), - architecture: z - .enum(BINARY_ARCHITECTURES) - .exclude([...RAW_ONLY_ARCHITECTURES]) - .nullable(), -}); -const rawLifecycleTargetSchema = z.object({ - ...lifecycleTargetFields, - format: z.literal("raw-binary"), - kind: z.literal("executable"), - architecture: binaryArchitectureSchema, - entry_point: rawBinaryEntryPointSchema.describe( - "Canonical caller-selected address where raw file offset 0 is mapped and execution is seeded.", - ), -}); -const dolLifecycleTargetSchema = z.object({ - ...lifecycleTargetFields, - format: z.literal("gamecube-dol"), - kind: z.literal("executable"), - architecture: z.literal("powerpc"), - entry_point: rawBinaryEntryPointSchema.describe( - "Canonical entry address taken from the caller-selected DOL header.", - ), -}); - -/** - * Every open-target variant. open_binary and binary_session share this list so - * a target that open_binary returns is always valid in binary_session status. - */ -const openLifecycleTargetVariants = [ - detectedLifecycleTargetSchema, - rawLifecycleTargetSchema, - dolLifecycleTargetSchema, -] as const; - /** Exact structured-content schemas for target lifecycle operations. */ export const sessionOutputSchemas = { open_binary: lifecycleResultOf( - z.discriminatedUnion("format", [...openLifecycleTargetVariants]), + z.discriminatedUnion("format", [...liveBinaryTargetSummaryVariants]), ), open_bundle_resources: lifecycleResultOf(bundleResourceRootSchema), close_binary: lifecycleResultOf( @@ -595,7 +544,7 @@ export const sessionOutputSchemas = { resource_root: bundleResourceRootSchema, ...detail, }), - ...openLifecycleTargetVariants.map((variant) => + ...liveBinaryTargetSummaryVariants.map((variant) => sessionProviderFacts.extend({ open: z.literal(true), ...variant.shape, diff --git a/src/contracts/toolOutputSchemaPrimitives.ts b/src/contracts/toolOutputSchemaPrimitives.ts index f66d02beab..1b1e42c73b 100644 --- a/src/contracts/toolOutputSchemaPrimitives.ts +++ b/src/contracts/toolOutputSchemaPrimitives.ts @@ -19,7 +19,6 @@ import { PROVIDER_RETRY_ACTIONS, } from "../domain/providerOperationHealth.js"; import { analysisErrorProjectionSchema } from "./errorSchemas.js"; -import { ZIP_PACKAGE_FORMATS } from "../domain/zipPackageFormat.js"; import { objcClassImportIdentitySchema } from "../domain/native/objcSwiftMetadata.js"; import { BINARY_SESSION_SUMMARY_OMITTED_FIELDS } from "./sessionStatusContract.js"; @@ -47,34 +46,6 @@ export const requireOutputSchema = < return schema; }; -export const targetFormatSchema = z.enum([ - "hopper", - "analysis-database", - "mach-o", - "mach-o-universal", - "elf", - "pe", - "dos-mz", - "dos-com", - "raw-binary", - "gamecube-dol", - ...ZIP_PACKAGE_FORMATS, - "asar", - "godot-pck", - "dmg", - "pkg", - "plist", - "javascript", - "source-map", -]); - -export const targetKindSchema = z.enum([ - "executable", - "database", - "archive", - "artifact", -]); - const providerCapabilityFacts = { operation: z.string(), effects: z.object({ diff --git a/src/domain/binaryTarget.ts b/src/domain/binaryTarget.ts index 35bd9faf72..acf7e8ca1e 100644 --- a/src/domain/binaryTarget.ts +++ b/src/domain/binaryTarget.ts @@ -4,8 +4,6 @@ import type { HeaderArchitecture, MipsElfMetadata, PeCodeViewRecord, - RiscvElfMetadata, - PowerPcElfMetadata, UniversalMachOSlice, } from "./binaryTargetTypes.js"; import { err, ok, type Result } from "./result.js"; @@ -48,13 +46,13 @@ export type ExecutableMetadata = readonly format: "elf"; readonly architecture: "riscv"; readonly availableArchitectures: readonly "riscv"[]; - readonly riscv: RiscvElfMetadata; + readonly riscv: ElfFamilyMetadata; } | { readonly format: "elf"; readonly architecture: "powerpc"; readonly availableArchitectures: readonly "powerpc"[]; - readonly powerpc: PowerPcElfMetadata; + readonly powerpc: ElfFamilyMetadata; } | { readonly format: "elf"; diff --git a/src/domain/binaryTargetTypes.ts b/src/domain/binaryTargetTypes.ts index d7fb7fb940..7763b23618 100644 --- a/src/domain/binaryTargetTypes.ts +++ b/src/domain/binaryTargetTypes.ts @@ -110,15 +110,6 @@ export interface MipsGnuAttributes { readonly msa: number | null; } -/** - * ELF class and flags retain the width and ABI declarations used by this lane. - * ISA extensions in `.riscv.attributes` are not inspected by this header parser. - */ -export type RiscvElfMetadata = ElfFamilyMetadata; - -/** Raw PowerPC declarations; family identity does not select a processor variant. */ -export type PowerPcElfMetadata = ElfFamilyMetadata; - /** Byte range of the selected slice inside its universal Mach-O container. */ export interface UniversalMachOSlice { readonly offset: number; @@ -167,7 +158,8 @@ export type BinaryTarget = readonly format: "elf"; readonly architecture: "riscv"; readonly availableArchitectures: readonly "riscv"[]; - readonly riscv: RiscvElfMetadata; + /** ISA extensions in `.riscv.attributes` are not inspected by the header parser. */ + readonly riscv: ElfFamilyMetadata; readonly executableRole?: never; readonly managed?: never; }) @@ -176,7 +168,8 @@ export type BinaryTarget = readonly format: "elf"; readonly architecture: "powerpc"; readonly availableArchitectures: readonly "powerpc"[]; - readonly powerpc: PowerPcElfMetadata; + /** Family identity does not select a processor variant. */ + readonly powerpc: ElfFamilyMetadata; readonly executableRole?: never; readonly managed?: never; }) diff --git a/src/domain/liveBinaryTargetSummary.ts b/src/domain/liveBinaryTargetSummary.ts new file mode 100644 index 0000000000..633154baf9 --- /dev/null +++ b/src/domain/liveBinaryTargetSummary.ts @@ -0,0 +1,85 @@ +import { z } from "zod"; + +import { + BINARY_ARCHITECTURES, + ELF_METADATA_ARCHITECTURES, + RAW_ONLY_ARCHITECTURES, +} from "./binaryTargetTypes.js"; +import { digestSchema } from "./digests.js"; +import { + binaryArchitectureSchema, + rawBinaryEntryPointSchema, +} from "./rawBinary.js"; +import { ZIP_PACKAGE_FORMATS } from "./zipPackageFormat.js"; + +const architectureSchema = z.enum(BINARY_ARCHITECTURES); +const headerArchitectureSchema = architectureSchema.exclude([ + ...ELF_METADATA_ARCHITECTURES, + ...RAW_ONLY_ARCHITECTURES, + "hexagon", + "arm64_32", +]); +const identity = { path: z.string(), sha256: digestSchema }; +const executable = { ...identity, kind: z.literal("executable") }; + +/** Live target classifications; historical Evidence and snapshots retain their own admission. */ +export const liveBinaryTargetSummaryVariants = [ + z.object({ + ...executable, + format: z.literal("elf"), + architecture: architectureSchema.exclude([ + ...RAW_ONLY_ARCHITECTURES, + "arm64_32", + ]), + }), + z.object({ + ...executable, + format: z.literal("mach-o"), + architecture: z.enum([...headerArchitectureSchema.options, "arm64_32"]), + }), + z.object({ + ...executable, + format: z.enum(["mach-o-universal", "pe", "dos-mz", "dos-com"]), + architecture: headerArchitectureSchema, + }), + z.object({ + ...executable, + format: z.literal("raw-binary"), + architecture: binaryArchitectureSchema, + entry_point: rawBinaryEntryPointSchema.describe( + "Canonical caller-selected address where raw file offset 0 is mapped and execution is seeded.", + ), + }), + z.object({ + ...executable, + format: z.literal("gamecube-dol"), + architecture: z.literal("powerpc"), + entry_point: rawBinaryEntryPointSchema.describe( + "Canonical entry address taken from the caller-selected DOL header.", + ), + }), + z.object({ + ...identity, + kind: z.literal("database"), + format: z.literal("analysis-database"), + architecture: z.null(), + }), + z.object({ + ...identity, + kind: z.literal("archive"), + format: z.enum([...ZIP_PACKAGE_FORMATS, "asar", "godot-pck", "dmg", "pkg"]), + architecture: z.null(), + }), + z.object({ + ...identity, + kind: z.literal("artifact"), + format: z.enum(["plist", "javascript", "source-map"]), + architecture: z.null(), + }), +] as const; + +/** Exact wire projection of the currently admitted canonical target variants. */ +export const liveBinaryTargetSummarySchema = z.discriminatedUnion( + "format", + liveBinaryTargetSummaryVariants, +); diff --git a/src/ghidra/GhidraPowerPcProfile.test.ts b/src/ghidra/GhidraPowerPcProfile.test.ts index 72fb0ac545..01f9e00779 100644 --- a/src/ghidra/GhidraPowerPcProfile.test.ts +++ b/src/ghidra/GhidraPowerPcProfile.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it, onTestFinished } from "vitest"; import { parseConfig } from "../config/parseConfig.js"; import type { BinaryTarget, - PowerPcElfMetadata, + ElfFamilyMetadata, } from "../domain/binaryTargetTypes.js"; import { silentLogger } from "../logger.js"; import { HopperProvider } from "../hopper/HopperProvider.js"; @@ -38,7 +38,7 @@ const configFor = (environment: NodeJS.ProcessEnv) => { return parsed.value; }; const config = configFor({}); -const target = (change: Partial = {}): BinaryTarget => ({ +const target = (change: Partial = {}): BinaryTarget => ({ path: "/fixture.elf", sha256: "a".repeat(64), kind: "executable", @@ -143,7 +143,7 @@ describe("bounded Ghidra PowerPC admission", () => { [{ type: 1 }, "ET_EXEC"], [{ type: 3 }, "ET_EXEC"], [{ flags: 0x80000000 }, "EF_PPC_EMB"], - ] satisfies [Partial, string][])( + ] satisfies [Partial, string][])( "refuses an unverified PowerPC interpretation %j", async (change, reason) => { const ghidra = new GhidraProvider(config, silentLogger, {}, host); diff --git a/src/ghidra/GhidraRiscvProfile.test.ts b/src/ghidra/GhidraRiscvProfile.test.ts index 4ae4c7e59d..31f7ed04ed 100644 --- a/src/ghidra/GhidraRiscvProfile.test.ts +++ b/src/ghidra/GhidraRiscvProfile.test.ts @@ -5,7 +5,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { parseConfig } from "../config/parseConfig.js"; import type { BinaryTarget, - RiscvElfMetadata, + ElfFamilyMetadata, } from "../domain/binaryTargetTypes.js"; import { silentLogger } from "../logger.js"; import { HopperProvider } from "../hopper/HopperProvider.js"; @@ -60,7 +60,7 @@ const configOf = ( if (!parsed.ok) throw parsed.error; return parsed.value; }; -const target = (change: Partial = {}): BinaryTarget => ({ +const target = (change: Partial = {}): BinaryTarget => ({ path: "/fixture.elf", sha256: "a".repeat(64), kind: "executable", @@ -170,7 +170,7 @@ const registerRiscvAdmissionAndCapabilityCases = () => { [{ flags: 0x7 }, "quad-float"], [{ flags: 0x8000_0005 }, "(0x80000000)"], [{ flags: 0x24 }, "bits outside the verified lane"], - ] satisfies [Partial, string][])( + ] satisfies [Partial, string][])( "refuses an unverified RISC-V interpretation %j", async (change, reason) => { const provider = ghidra(); diff --git a/tests/boundary/mcp/liveBinaryTargetSummary.test.ts b/tests/boundary/mcp/liveBinaryTargetSummary.test.ts new file mode 100644 index 0000000000..dc593ac3ec --- /dev/null +++ b/tests/boundary/mcp/liveBinaryTargetSummary.test.ts @@ -0,0 +1,262 @@ +import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv"; +import { expect, it } from "vitest"; + +import type { BinaryTarget } from "../../../src/domain/binaryTargetTypes.js"; +import { ZIP_PACKAGE_FORMATS } from "../../../src/domain/zipPackageFormat.js"; +import { binaryTargetSummary } from "../../../src/application/binary/BinaryTargetSummary.js"; +import { toolContract } from "../../../src/contracts/toolContracts.js"; +import { toolOutputSchemaWithMetadata } from "../../../src/contracts/toolSchemaMetadata.js"; +import { sessionOutputSchemas } from "../../../src/contracts/toolOutputSchemaGroups.js"; + +const identity = { path: "/fixture/target", sha256: "a".repeat(64) }; +const executable = { ...identity, kind: "executable" as const }; +const elf = { + elfClass: 32 as const, + byteOrder: "little" as const, + type: 2, + flags: 0, +}; +const targets: readonly BinaryTarget[] = [ + ...(["mach-o", "elf", "dos-mz", "dos-com"] as const).map((format) => ({ + ...executable, + format, + architecture: "x86" as const, + availableArchitectures: ["x86" as const], + })), + { + ...executable, + format: "elf", + architecture: "mips", + availableArchitectures: ["mips"], + mips: elf, + }, + { + ...executable, + format: "elf", + architecture: "riscv", + availableArchitectures: ["riscv"], + riscv: elf, + }, + { + ...executable, + format: "elf", + architecture: "powerpc", + availableArchitectures: ["powerpc"], + powerpc: elf, + }, + { + ...executable, + format: "elf", + architecture: "hexagon", + availableArchitectures: ["hexagon"], + }, + { + ...executable, + format: "mach-o", + architecture: "arm64_32", + availableArchitectures: ["arm64_32"], + }, + { + ...executable, + format: "mach-o-universal", + architecture: "arm64", + availableArchitectures: ["arm64"], + universalSlice: { offset: 32, size: 32 }, + }, + { + ...executable, + format: "pe", + architecture: "x86_64", + availableArchitectures: ["x86_64"], + managed: false, + executableRole: "application", + }, + ...(["x86", "x86_64", "arm", "arm64", "6502", "m68k"] as const).map( + (architecture) => ({ + ...executable, + format: "raw-binary" as const, + architecture, + availableArchitectures: [architecture], + entryPoint: "0x1000", + entryPointSource: "0x00001000", + }), + ), + { + ...executable, + format: "gamecube-dol", + architecture: "powerpc", + availableArchitectures: ["powerpc"], + byteOrder: "big", + bits: 32, + entryPoint: "0x80003100", + entryPointSource: "dol-header", + dol: { + sections: [], + bss: { address: 0, size: 0 }, + bssGaps: [], + bssOverlaps: [], + entryPoint: 0x80003100, + }, + }, + { ...identity, kind: "database", format: "analysis-database" }, + ...([...ZIP_PACKAGE_FORMATS, "asar", "godot-pck", "dmg", "pkg"] as const).map( + (format) => ({ ...identity, kind: "archive" as const, format }), + ), + ...(["plist", "javascript", "source-map"] as const).map((format) => ({ + ...identity, + kind: "artifact" as const, + format, + })), +]; +const impossible = [ + { kind: "database", format: "elf", architecture: "x86" }, + { kind: "executable", format: "zip", architecture: "arm64" }, + { kind: "archive", format: "zip", architecture: "arm64" }, + { kind: "executable", format: "pe", architecture: null }, + { kind: "executable", format: "mach-o", architecture: "hexagon" }, + { kind: "executable", format: "elf", architecture: "arm64_32" }, + { kind: "executable", format: "pe", architecture: "mips" }, + { kind: "database", format: "hopper", architecture: null }, + { + kind: "executable", + format: "raw-binary", + architecture: "riscv", + entry_point: "0x1000", + }, + { kind: "executable", format: "gamecube-dol", architecture: "powerpc" }, + { + kind: "executable", + format: "gamecube-dol", + architecture: "arm", + entry_point: "0x1000", + }, +]; + +it.each(["draft-2020-12", "draft-07"] as const)( + "preserves canonical live summaries and rejects impossible classifications through %s clients", + (target) => { + const advertised = toolOutputSchemaWithMetadata( + toolContract("open_binary"), + )["~standard"].jsonSchema.output({ target }); + const validate = new AjvJsonSchemaValidator().getValidator(advertised); + for (const target of targets) { + const summary = binaryTargetSummary(target); + expect(summary).toEqual({ + ...identity, + kind: target.kind, + format: target.format, + architecture: target.architecture ?? null, + ...(target.format === "raw-binary" || target.format === "gamecube-dol" + ? { entry_point: target.entryPoint } + : {}), + }); + expect( + sessionOutputSchemas.open_binary.safeParse({ result: summary }).success, + ).toBe(true); + expect(validate({ result: summary }).valid).toBe(true); + } + for (const classification of impossible) { + const result = { ...identity, ...classification }; + expect( + sessionOutputSchemas.open_binary.safeParse({ result }).success, + ).toBe(false); + expect(validate({ result }).valid).toBe(false); + } + }, +); + +const sessionFacts = { + open: true, + cleanup_pending: false, + capabilities: [], + analysis_run: null, + analysis_activity: { status: "not_observed", providers: [] }, + provider_operation_health: null, + analysis_provider_binding: null, + analysis_provider_candidates: [], + client_features: { + elicitation_form: false, + elicitation_url: false, + roots: false, + sampling: false, + }, + server_identity: { + package: { + name: "rea", + version: "fixture", + root_path: "/fixture", + build_commit: null, + }, + server: { + name: "rea", + version: "fixture", + started_at: "2026-01-01T00:00:00Z", + command_path: null, + }, + sdk: { server: "fixture", client_test: "fixture", core: "fixture" }, + negotiated_protocol_version: null, + client: null, + skill: { name: "rea", expected_version: "fixture" }, + catalog: { + counts: { cli_commands: 0, mcp_tools: 0, mcp_prompts: 0 }, + digests: { + tools_sha256: identity.sha256, + prompts_sha256: identity.sha256, + combined_sha256: identity.sha256, + }, + }, + protocol_features: { + progress: false, + cancellation: false, + elicitation: false, + }, + alignment: { state: "unknown", reasons: [], remediation: null }, + }, + detail: "complete", + tool_availability: { + client_requirements: { + required: [], + optional: [], + missing_required: [], + missing_optional: [], + }, + groups: [], + }, +}; + +it.each(["draft-2020-12", "draft-07"] as const)( + "shares live classification admission with binary_session in %s", + (target) => { + const advertised = toolOutputSchemaWithMetadata( + toolContract("binary_session"), + )["~standard"].jsonSchema.output({ target }); + const validate = new AjvJsonSchemaValidator().getValidator(advertised); + // One representative per distinct projection shape; the complete target matrix is above. + for (const format of [ + "elf", + "raw-binary", + "gamecube-dol", + "zip", + ] as const) { + const selected = targets.find((candidate) => candidate.format === format); + if (selected === undefined) + throw new Error(`Missing canonical fixture: ${format}`); + const value = { + result: { ...sessionFacts, ...binaryTargetSummary(selected) }, + }; + expect(sessionOutputSchemas.binary_session.safeParse(value).success).toBe( + true, + ); + expect(validate(value).valid).toBe(true); + } + for (const classification of impossible.slice(0, 4)) { + const value = { + result: { ...sessionFacts, ...identity, ...classification }, + }; + expect(sessionOutputSchemas.binary_session.safeParse(value).success).toBe( + false, + ); + expect(validate(value).valid).toBe(false); + } + }, +); diff --git a/tests/conformance/ghidra/ppc-checks.d.mts b/tests/conformance/ghidra/ppc-checks.d.mts index 38609de454..ef27c3ddd0 100644 --- a/tests/conformance/ghidra/ppc-checks.d.mts +++ b/tests/conformance/ghidra/ppc-checks.d.mts @@ -1,7 +1,7 @@ -import type { PowerPcElfMetadata } from "../../../src/domain/binaryTargetTypes.js"; +import type { ElfFamilyMetadata } from "../../../src/domain/binaryTargetTypes.js"; export interface PowerPcReadelfFacts { - powerpc: PowerPcElfMetadata; + powerpc: ElfFamilyMetadata; symbols: Record< | "rea_ppc_entry" | "rea_ppc_leaf"