diff --git a/src/application/native/EnhancedNativeInvestigation.test.ts b/src/application/native/EnhancedNativeInvestigation.test.ts index b598ea2ad8..0dd818372d 100644 --- a/src/application/native/EnhancedNativeInvestigation.test.ts +++ b/src/application/native/EnhancedNativeInvestigation.test.ts @@ -200,7 +200,7 @@ const testAnalysis = ( }); case "list_names": return execution(operation, [ - { address: "0x1000", name: "-[BuildViewController buildTapped:]" }, + { address: "0x1000", value: "-[BuildViewController buildTapped:]" }, ]); case "procedure_callees": return execution( @@ -535,10 +535,6 @@ describe("native dispatch inventory", () => { it.each([ { label: "plain value", entry: { address: "0x1000", value: methodName } }, { label: "value with symbol facts", entry: methodInventoryEntry }, - { - label: "legacy name with symbol facts", - entry: { address: "0x1000", name: methodName, symbol: methodSymbol }, - }, ])( "binds $label inventory to the target without overstating dispatch coverage", async ({ entry }) => { @@ -583,6 +579,10 @@ describe("native dispatch inventory", () => { ); it.each([ + { + label: "legacy name with symbol facts", + entry: { address: "0x1000", name: methodName, symbol: methodSymbol }, + }, { label: "malformed symbol flag", entry: { @@ -836,11 +836,11 @@ describe("native UI trace boundaries", () => { it("matches handlers after the dispatch inventory's former default limit", async () => { const names = Array.from({ length: 5_000 }, (_, index) => ({ address: `0x${(index + 1).toString(16)}`, - name: `unrelated_symbol_${index}`, + value: `unrelated_symbol_${index}`, })); names.push({ address: "0x2000", - name: "-[BuildViewController buildTapped:]", + value: "-[BuildViewController buildTapped:]", }); const result = await new EnhancedTools( testAnalysis({ list_names: () => execution("list_names", names) }), diff --git a/src/application/native/NativeDispatchMetadataInspection.ts b/src/application/native/NativeDispatchMetadataInspection.ts index fd384594a8..c56f64c89b 100644 --- a/src/application/native/NativeDispatchMetadataInspection.ts +++ b/src/application/native/NativeDispatchMetadataInspection.ts @@ -1,5 +1,4 @@ -import { z } from "zod"; -import { addressedValueSchema } from "../../domain/native/nativeInventory.js"; +import { nativeInventoryRowsSchemas } from "../../domain/native/nativeInventory.js"; import { nativeDispatchMetadataResultSchema, inspectNativeDispatchMetadata, @@ -10,6 +9,10 @@ import { err, ok } from "../../domain/result.js"; import type { AnalysisOperationPort } from "../AnalysisProvider.js"; import type { EnhancedResult } from "../EnhancedToolTypes.js"; +const dispatchNameInventorySchema = nativeInventoryRowsSchemas.names.element + .strict() + .array(); + /** Source operations for this workflow, shared with capability discovery. */ export const NATIVE_DISPATCH_METADATA_SOURCES = { binary: "inspect_native_dispatch_metadata", @@ -64,17 +67,7 @@ export const inspectNativeDispatch = async ( signal === undefined ? {} : { signal }, ); if (!execution.ok) return err(execution.error); - const names = z - .array( - z.union([ - addressedValueSchema.strict(), - addressedValueSchema - .omit({ value: true }) - .extend({ name: z.string() }) - .strict(), - ]), - ) - .safeParse(execution.value.result); + const names = dispatchNameInventorySchema.safeParse(execution.value.result); if (!names.success) return err( new AnalysisOutputError( @@ -85,7 +78,7 @@ export const inspectNativeDispatch = async ( const result = inspectNativeDispatchMetadata( names.data.map((entry) => ({ address: entry.address, - name: "value" in entry ? entry.value : entry.name, + name: entry.value, })), maxRecords, ); diff --git a/src/domain/native/objcSwiftMetadata.test.ts b/src/domain/native/objcSwiftMetadata.test.ts index 15327c15f7..7f5dfcc726 100644 --- a/src/domain/native/objcSwiftMetadata.test.ts +++ b/src/domain/native/objcSwiftMetadata.test.ts @@ -20,9 +20,10 @@ describe("symbol-only Objective-C implementation identity", () => { ])( "preserves $source without inventing a numeric method identity", ({ source, canonical, numeric }) => { - const result = inspectNativeDispatchMetadata([ - { address: source, name: "-[StoreController buildTapped:]" }, - ]); + const result = inspectNativeDispatchMetadata( + [{ address: source, name: "-[StoreController buildTapped:]" }], + 1, + ); expect(result.objc_classes[0]?.methods[0]?.address).toBe(numeric); expect(result.objc_dispatch_implementations[0]).toMatchObject({ implementation_address: canonical, @@ -34,10 +35,13 @@ describe("symbol-only Objective-C implementation identity", () => { it("retains unparseable symbol coordinates as reported evidence, without claiming a location", () => { const source = "invalid::0x2000"; - const result = inspectNativeDispatchMetadata([ - { address: source, name: "-[StoreController buildTapped:]" }, - { address: source, name: "$s5Store7launchyyF" }, - ]); + const result = inspectNativeDispatchMetadata( + [ + { address: source, name: "-[StoreController buildTapped:]" }, + { address: source, name: "$s5Store7launchyyF" }, + ], + 2, + ); expect(result.objc_dispatch_implementations[0]).toMatchObject({ implementation_address: null, location: { address: null }, @@ -57,6 +61,7 @@ describe("symbol-only Objective-C implementation identity", () => { address, name: "-[StoreController buildTapped:]", })), + ordered.length, ); expect(result.objc_dispatch_implementations).toHaveLength(1); expect(result.objc_dispatch_implementations[0]).toMatchObject({ @@ -87,6 +92,7 @@ describe("symbol-only Objective-C implementation identity", () => { address, name: "-[StoreController buildTapped:]", })), + 2, ); expect(result.objc_dispatch_implementations).toHaveLength(2); expect(result.objc_classes[0]?.methods).toEqual([ @@ -176,11 +182,14 @@ describe("ObjC/Swift metadata", () => { }); it("retains unlocated Swift and Objective-C symbols with explicit unknown addresses and source evidence", () => { - const result = inspectNativeDispatchMetadata([ - { name: "$s4Test7MyClassC", address: "" }, - { name: "-[StoreController buildTapped:]", address: "" }, - { name: "$s5Store7launchyyF", address: "ram:0x3000" }, - ]); + const result = inspectNativeDispatchMetadata( + [ + { name: "$s4Test7MyClassC", address: "" }, + { name: "-[StoreController buildTapped:]", address: "" }, + { name: "$s5Store7launchyyF", address: "ram:0x3000" }, + ], + 3, + ); expect(result.swift_symbols).toMatchObject([ { mangled_name: "$s4Test7MyClassC", @@ -226,6 +235,7 @@ it("derives classes only from complete class symbol leaves", () => { "_OBJC_METACLASS_$_Local", "OwnController::viewDidLoad", ].map((name, index) => ({ address: `0x${String(index + 1)}`, name })), + 7, ); expect(result.objc_classes.map(({ name }) => name)).toEqual([ "Local", diff --git a/src/domain/native/objcSwiftMetadata.ts b/src/domain/native/objcSwiftMetadata.ts index fc61c26cdf..baa3368ec1 100644 --- a/src/domain/native/objcSwiftMetadata.ts +++ b/src/domain/native/objcSwiftMetadata.ts @@ -474,7 +474,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 = DEFAULT_DISPATCH_METADATA_RECORDS, + maxRecords: number, ): ObjcSwiftMetadata => { const symbolEvidence = ( entry: (typeof names)[number], diff --git a/tests/boundary/mcp/nativeDispatchAvailability.test.ts b/tests/boundary/mcp/nativeDispatchAvailability.test.ts index 793fc28cd0..6ba0abae62 100644 --- a/tests/boundary/mcp/nativeDispatchAvailability.test.ts +++ b/tests/boundary/mcp/nativeDispatchAvailability.test.ts @@ -35,14 +35,17 @@ const identity = { name: "Fixture analysis provider", version: "1", }; -const dispatchMetadata = inspectNativeDispatchMetadata([ - { - address: "0x1000", - name: "-[BuildViewController buildTapped:]", - }, -]); +const dispatchMetadata = inspectNativeDispatchMetadata( + [ + { + address: "0x1000", + name: "-[BuildViewController buildTapped:]", + }, + ], + 1, +); const symbolInventory = [ - { address: "0x1000", name: "-[BuildViewController buildTapped:]" }, + { address: "0x1000", value: "-[BuildViewController buildTapped:]" }, ]; type Operation = "inspect_native_dispatch_metadata" | "list_names"; diff --git a/tests/boundary/mcp/nativeUiAvailability.test.ts b/tests/boundary/mcp/nativeUiAvailability.test.ts index 6e208f4434..113e54894c 100644 --- a/tests/boundary/mcp/nativeUiAvailability.test.ts +++ b/tests/boundary/mcp/nativeUiAvailability.test.ts @@ -64,11 +64,12 @@ const controllerName = "BuildViewController"; const selector = "buildTapped:"; const handlerAddress = "0x1000"; const calleeAddress = "0x2000"; -const dispatchMetadata = inspectNativeDispatchMetadata([ - { address: handlerAddress, name: `-[${controllerName} ${selector}]` }, -]); +const dispatchMetadata = inspectNativeDispatchMetadata( + [{ address: handlerAddress, name: `-[${controllerName} ${selector}]` }], + 1, +); const symbolInventory = [ - { address: handlerAddress, name: `-[${controllerName} ${selector}]` }, + { address: handlerAddress, value: `-[${controllerName} ${selector}]` }, ]; const interfaceBuilderFor = (targetSha256: string) => buildInterfaceBuilderAnalysis({ diff --git a/tests/boundary/mcp/nativeUiPartialCallRoutesMcp.test.ts b/tests/boundary/mcp/nativeUiPartialCallRoutesMcp.test.ts index baa844025e..15e8e16a28 100644 --- a/tests/boundary/mcp/nativeUiPartialCallRoutesMcp.test.ts +++ b/tests/boundary/mcp/nativeUiPartialCallRoutesMcp.test.ts @@ -137,7 +137,7 @@ function publicTrace(plan: Plan, maxNodes = 12, maxEdges = 500) { target_sha256: sha256, provider: identity, analysis_profile_digest: null, - result: inspectNativeDispatchMetadata([]), + result: inspectNativeDispatchMetadata([], 1), }, identity, { subject },