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.
This commit is contained in:
morluto committed 2026-10-27 06:53:47 +08:00
1 parent fc7f042c8c
commit ace4dbf36e
10 files changed
+377 -116

No files matched your search

+13 -10
View File
@@ -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 }
: {}),
});
+3 -54
View File
@@ -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<Record<string, z.ZodObject>>;
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,
@@ -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({
+2 -4
View File
@@ -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";
+4 -11
View File
@@ -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;
})
+85
View File
@@ -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,
);
+3 -3
View File
@@ -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<PowerPcElfMetadata> = {}): BinaryTarget => ({
const target = (change: Partial<ElfFamilyMetadata> = {}): 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<PowerPcElfMetadata>, string][])(
] satisfies [Partial<ElfFamilyMetadata>, string][])(
"refuses an unverified PowerPC interpretation %j",
async (change, reason) => {
const ghidra = new GhidraProvider(config, silentLogger, {}, host);
+3 -3
View File
@@ -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<RiscvElfMetadata> = {}): BinaryTarget => ({
const target = (change: Partial<ElfFamilyMetadata> = {}): 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<RiscvElfMetadata>, string][])(
] satisfies [Partial<ElfFamilyMetadata>, string][])(
"refuses an unverified RISC-V interpretation %j",
async (change, reason) => {
const provider = ghidra();
@@ -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);
}
},
);
+2 -2
View File
@@ -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"