refactor(native): centralize procedure and metadata input contracts
This commit is contained in:
1 parent
0d5c7b6d79
commit
44a4c1ba87
8 files changed
+76
-74
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { nativeProcedureInputSchema } from "../domain/native/nativeProcedureInput.js";
|
||||
import { CALL_RELATIONSHIP_TRAVERSALS } from "./CallRelationshipTraversal.js";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -9,7 +10,6 @@ import type {
|
||||
} from "./AnalysisProvider.js";
|
||||
import {
|
||||
enhancedInputSchemas,
|
||||
MAX_ENHANCED_RECORDS,
|
||||
type CallRelationshipDirection,
|
||||
type EnhancedToolName,
|
||||
} from "../contracts/enhancedInputs.js";
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
functionDossierSchema,
|
||||
parseFunctionDossier,
|
||||
parseListCount,
|
||||
parseRelatedAddresses,
|
||||
@@ -40,7 +39,10 @@ import { traceNativeValues } from "./native/NativeValueTrace.js";
|
||||
import { traceNativeUiAction } from "./native/NativeUiActionTrace.js";
|
||||
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import { addressedValueSchema } from "../domain/native/nativeInventory.js";
|
||||
import { nativeDispatchMetadataResultSchema } from "../domain/native/objcSwiftMetadata.js";
|
||||
import {
|
||||
nativeDispatchMetadataResultSchema,
|
||||
MAX_DISPATCH_METADATA_RECORDS,
|
||||
} from "../domain/native/objcSwiftMetadata.js";
|
||||
import { isMachOFormat } from "../domain/binaryTargetTypes.js";
|
||||
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
||||
import {
|
||||
@@ -269,12 +271,7 @@ export class EnhancedTools {
|
||||
input: unknown,
|
||||
signal?: AbortSignal,
|
||||
): EnhancedResult {
|
||||
if (name === "analyze_function") {
|
||||
const parsed = enhancedInputSchemas.analyze_function.safeParse(input);
|
||||
if (!parsed.success) return invalidEnhancedInput(name, parsed.error);
|
||||
return this.executeValidated({ name, input: parsed.data }, signal);
|
||||
}
|
||||
const parsed = enhancedInputSchemas.inspect_native_api.safeParse(input);
|
||||
const parsed = nativeProcedureInputSchema.safeParse(input);
|
||||
if (!parsed.success) return invalidEnhancedInput(name, parsed.error);
|
||||
return this.executeValidated({ name, input: parsed.data }, signal);
|
||||
}
|
||||
@@ -288,18 +285,14 @@ export class EnhancedTools {
|
||||
}
|
||||
|
||||
async #inspectNativeApi(
|
||||
input: z.output<typeof enhancedInputSchemas.inspect_native_api>,
|
||||
input: z.output<typeof nativeProcedureInputSchema>,
|
||||
signal?: AbortSignal,
|
||||
): EnhancedResult {
|
||||
const analysisInput = enhancedInputSchemas.analyze_function.parse({
|
||||
procedure: input.procedure,
|
||||
});
|
||||
const result = await this.#call("analyze_function", analysisInput, signal);
|
||||
const result = await this.#call("analyze_function", input, signal);
|
||||
if (!result.ok) return result;
|
||||
const parsed = parseFunctionDossier(result.value);
|
||||
if (!parsed.ok) return parsed;
|
||||
const dossier = functionDossierSchema.parse(parsed.value);
|
||||
return ok(jsonValueSchema.parse(projectNativeApiInspection(dossier)));
|
||||
return ok(jsonValueSchema.parse(projectNativeApiInspection(parsed.value)));
|
||||
}
|
||||
|
||||
async #objcClasses(pattern: string, signal?: AbortSignal): EnhancedResult {
|
||||
@@ -320,14 +313,14 @@ export class EnhancedTools {
|
||||
);
|
||||
|
||||
const addresses = [...new Set(candidates.map(({ address }) => address))];
|
||||
const scannedAddresses = addresses.slice(0, MAX_ENHANCED_RECORDS);
|
||||
const scannedAddresses = addresses.slice(0, MAX_DISPATCH_METADATA_RECORDS);
|
||||
const bindingsExecution = await this.#observe(
|
||||
"inspect_native_dispatch_metadata",
|
||||
{
|
||||
max_records: MAX_ENHANCED_RECORDS,
|
||||
max_records: MAX_DISPATCH_METADATA_RECORDS,
|
||||
objc_class_import_addresses: scannedAddresses,
|
||||
objc_class_import_addresses_complete:
|
||||
addresses.length <= MAX_ENHANCED_RECORDS,
|
||||
addresses.length <= MAX_DISPATCH_METADATA_RECORDS,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
|
||||
@@ -2,11 +2,11 @@ import type {
|
||||
AnalysisOperationPort,
|
||||
ExecutionOptions,
|
||||
} from "./AnalysisProvider.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import { nativeProcedureInputSchema } from "../domain/native/nativeProcedureInput.js";
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import { createEvidence } from "../domain/evidence.js";
|
||||
import { parseFunctionDossier } from "../domain/hopperValues.js";
|
||||
import { jsonObjectSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { err } from "../domain/result.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
@@ -25,8 +25,7 @@ export const executeFunctionAnalysisEvidence = async (
|
||||
cause: options.signal.reason,
|
||||
}),
|
||||
);
|
||||
const input =
|
||||
toolContract("analyze_function").inputSchema.safeParse(parameters);
|
||||
const input = nativeProcedureInputSchema.safeParse(parameters);
|
||||
if (!input.success)
|
||||
return err(
|
||||
analysisInputErrorFromIssues(
|
||||
@@ -36,7 +35,7 @@ export const executeFunctionAnalysisEvidence = async (
|
||||
{ cause: input.error },
|
||||
),
|
||||
);
|
||||
const arguments_ = jsonObjectSchema.parse(input.data);
|
||||
const arguments_ = input.data;
|
||||
const execution = await analysis.execute(
|
||||
"analyze_function",
|
||||
arguments_,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
|
||||
import { nativeDispatchMetadataInputSchema } from "../domain/native/objcSwiftMetadata.js";
|
||||
import { z } from "incur";
|
||||
|
||||
import type { DirectAnalysis } from "../composition/directAnalysis.js";
|
||||
@@ -266,16 +266,10 @@ const registerNativeDispatchMetadataCommand = (
|
||||
path: z.string().describe("Target path used to bind the result evidence"),
|
||||
}),
|
||||
options: z.object({
|
||||
maxRecords: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(20_000)
|
||||
.default(5_000)
|
||||
.describe("Maximum symbol records to inspect"),
|
||||
maxInputBytes:
|
||||
enhancedInputSchemas.inspect_native_dispatch_metadata.shape
|
||||
.max_input_bytes,
|
||||
maxRecords: nativeDispatchMetadataInputSchema.shape.max_records.describe(
|
||||
"Maximum symbol records to inspect",
|
||||
),
|
||||
maxInputBytes: nativeDispatchMetadataInputSchema.shape.max_input_bytes,
|
||||
snapshot: snapshotSelectionOption.describe(
|
||||
"Load or update the local analysis snapshot",
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { MAXIMUM_DISPATCH_METADATA_INPUT_BYTES } from "../domain/native/objcSwiftMetadata.js";
|
||||
import { nativeDispatchMetadataInputSchema } from "../domain/native/objcSwiftMetadata.js";
|
||||
import { nativeProcedureInputSchema } from "../domain/native/nativeProcedureInput.js";
|
||||
import { nativeValueTraceInputSchema } from "../domain/native/nativeValueTrace.js";
|
||||
const traceLiteralInputSchema = z.strictObject({
|
||||
query: z.string().min(1),
|
||||
@@ -7,7 +8,6 @@ const traceLiteralInputSchema = z.strictObject({
|
||||
});
|
||||
|
||||
export const DEFAULT_CALL_RELATIONSHIP_DIRECTION = "forward";
|
||||
export const MAX_ENHANCED_RECORDS = 20_000;
|
||||
const callRelationshipDirectionSchema = z
|
||||
.enum(["forward", "backward"])
|
||||
.default(DEFAULT_CALL_RELATIONSHIP_DIRECTION);
|
||||
@@ -18,31 +18,7 @@ export type CallRelationshipDirection = z.output<
|
||||
/** Input schemas shared by MCP registration and enhanced application dispatch. */
|
||||
export const enhancedInputSchemas = {
|
||||
trace_native_values: nativeValueTraceInputSchema,
|
||||
inspect_native_dispatch_metadata: z.strictObject({
|
||||
max_records: z.int().min(1).max(MAX_ENHANCED_RECORDS).default(5_000),
|
||||
objc_class_import_addresses: z
|
||||
.array(z.string())
|
||||
.max(MAX_ENHANCED_RECORDS)
|
||||
.describe(
|
||||
"Optional exact pointer-slot addresses from the selected name inventory; used to report decoded Objective-C class import bindings.",
|
||||
)
|
||||
.optional(),
|
||||
objc_class_import_addresses_complete: z
|
||||
.boolean()
|
||||
.describe(
|
||||
"Whether the candidate addresses include every selected class-label row; false keeps binding coverage partial when the existing record budget truncates the scan.",
|
||||
)
|
||||
.optional(),
|
||||
max_input_bytes: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(MAXIMUM_DISPATCH_METADATA_INPUT_BYTES)
|
||||
.optional()
|
||||
.describe(
|
||||
"Largest target file REA reads into memory for binary metadata, in bytes. Omitted: 67108864 (64 MiB). The whole file counts, including every slice of a universal Mach-O. At most 1073741824 (1 GiB).",
|
||||
),
|
||||
}),
|
||||
inspect_native_dispatch_metadata: nativeDispatchMetadataInputSchema,
|
||||
get_objc_classes: z.strictObject({ pattern: z.string().default("") }),
|
||||
get_objc_protocols: z.strictObject({}),
|
||||
batch_decompile: z.strictObject({
|
||||
@@ -74,12 +50,8 @@ export const enhancedInputSchemas = {
|
||||
}),
|
||||
find_xrefs_to_name: z.strictObject({ name: z.string() }),
|
||||
binary_overview: z.strictObject({}),
|
||||
analyze_function: z.strictObject({
|
||||
procedure: z.string().describe("A procedure name or address"),
|
||||
}),
|
||||
inspect_native_api: z.strictObject({
|
||||
procedure: z.string().describe("A procedure name or address"),
|
||||
}),
|
||||
analyze_function: nativeProcedureInputSchema,
|
||||
inspect_native_api: nativeProcedureInputSchema,
|
||||
trace_feature: traceLiteralInputSchema,
|
||||
trace_call_path: z.strictObject({
|
||||
start: z.string().describe("A starting procedure symbol or address"),
|
||||
|
||||
@@ -435,7 +435,7 @@ export type FunctionDossier = z.infer<typeof functionDossierSchema>;
|
||||
/** Strictly parse a complete provider-neutral function dossier. */
|
||||
export const parseFunctionDossier = (
|
||||
value: JsonValue,
|
||||
): Result<JsonValue, AnalysisOutputError> => {
|
||||
): Result<FunctionDossier, AnalysisOutputError> => {
|
||||
const parsed = functionDossierSchema.safeParse(value);
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** Selected procedure for function dossier and native API analysis. */
|
||||
export const nativeProcedureInputSchema = z.strictObject({
|
||||
procedure: z.string().describe("A procedure name or address"),
|
||||
});
|
||||
@@ -356,7 +356,6 @@ export const objcSwiftMetadataSchema = z.strictObject({
|
||||
});
|
||||
export type ObjcSwiftMetadata = z.infer<typeof objcSwiftMetadataSchema>;
|
||||
|
||||
/** Provider and target identity for one normalized dispatch-metadata result. */
|
||||
/** Retained-image budget when the caller selects none. */
|
||||
export const DEFAULT_DISPATCH_METADATA_INPUT_BYTES = 64 * 1024 * 1024;
|
||||
|
||||
@@ -367,6 +366,42 @@ export const DEFAULT_DISPATCH_METADATA_INPUT_BYTES = 64 * 1024 * 1024;
|
||||
*/
|
||||
export const MAXIMUM_DISPATCH_METADATA_INPUT_BYTES = 1024 * 1024 * 1024;
|
||||
|
||||
/** Shared metadata row allowance for provider reads and class-import discovery. */
|
||||
export const MAX_DISPATCH_METADATA_RECORDS = 20_000;
|
||||
export const DEFAULT_DISPATCH_METADATA_RECORDS = 5_000;
|
||||
|
||||
/** Selected dispatch facts and retained-image budget, shared by providers and adapters. */
|
||||
export const nativeDispatchMetadataInputSchema = z.strictObject({
|
||||
max_records: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(MAX_DISPATCH_METADATA_RECORDS)
|
||||
.default(DEFAULT_DISPATCH_METADATA_RECORDS),
|
||||
objc_class_import_addresses: z
|
||||
.array(z.string())
|
||||
.max(MAX_DISPATCH_METADATA_RECORDS)
|
||||
.describe(
|
||||
"Optional exact pointer-slot addresses from the selected name inventory; used to report decoded Objective-C class import bindings.",
|
||||
)
|
||||
.optional(),
|
||||
objc_class_import_addresses_complete: z
|
||||
.boolean()
|
||||
.describe(
|
||||
"Whether the candidate addresses include every selected class-label row; false keeps binding coverage partial when the existing record budget truncates the scan.",
|
||||
)
|
||||
.optional(),
|
||||
max_input_bytes: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(MAXIMUM_DISPATCH_METADATA_INPUT_BYTES)
|
||||
.optional()
|
||||
.describe(
|
||||
"Largest target file REA reads into memory for binary metadata, in bytes. Omitted: 67108864 (64 MiB). The whole file counts, including every slice of a universal Mach-O. At most 1073741824 (1 GiB).",
|
||||
),
|
||||
});
|
||||
|
||||
/** Effective input budget of one binary metadata read and the bytes it retained. */
|
||||
export const dispatchMetadataInputAdmissionSchema = z.strictObject({
|
||||
maximum_bytes: z
|
||||
@@ -381,6 +416,7 @@ export type DispatchMetadataInputAdmission = z.infer<
|
||||
typeof dispatchMetadataInputAdmissionSchema
|
||||
>;
|
||||
|
||||
/** Provider and target identity for one normalized dispatch-metadata result. */
|
||||
export const nativeDispatchMetadataResultSchema = z.strictObject({
|
||||
target_sha256: z
|
||||
.string()
|
||||
@@ -401,7 +437,7 @@ export const nativeDispatchMetadataResultSchema = z.strictObject({
|
||||
/** Decode only facts visible in a provider's existing name inventory. */
|
||||
export const inspectNativeDispatchMetadata = (
|
||||
names: readonly { readonly address: string; readonly name: string }[],
|
||||
maxRecords = 5_000,
|
||||
maxRecords = DEFAULT_DISPATCH_METADATA_RECORDS,
|
||||
): ObjcSwiftMetadata => {
|
||||
const symbolEvidence = (
|
||||
entry: (typeof names)[number],
|
||||
|
||||
@@ -46,8 +46,10 @@ import {
|
||||
isMachOFormat,
|
||||
type BinaryTarget,
|
||||
} from "../domain/binaryTargetTypes.js";
|
||||
import { isAppleDispatchMetadataTarget } from "../domain/native/objcSwiftMetadata.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import {
|
||||
isAppleDispatchMetadataTarget,
|
||||
nativeDispatchMetadataInputSchema,
|
||||
} from "../domain/native/objcSwiftMetadata.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import type { EvidenceLocation } from "../domain/evidence.js";
|
||||
import {
|
||||
@@ -270,7 +272,7 @@ class NativeMacOSClient implements AnalysisClient {
|
||||
),
|
||||
);
|
||||
if (operation === "inspect_native_dispatch_metadata") {
|
||||
const input = toolContract(operation).inputSchema.safeParse(parameters);
|
||||
const input = nativeDispatchMetadataInputSchema.safeParse(parameters);
|
||||
if (!input.success)
|
||||
return err(
|
||||
analysisInputErrorFromIssues(
|
||||
|
||||
Reference in new issue
Block a user