From b529bd84d727fcd5d1bd91fa4428a29a96ac780e Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 06:48:55 +0800 Subject: [PATCH] refactor(contracts): preserve named output schema types Keep exact readonly schema maps and constrain contract factories to their owned keys. Verify named catalog lookups retain each schema type. --- .../binary/AnalysisSnapshotCache.test.ts | 2 +- src/contracts/artifactToolContracts.ts | 4 +-- src/contracts/enhancedToolContracts.ts | 7 ++-- .../functionWorkflowToolContracts.ts | 2 +- src/contracts/managed/managedToolContracts.ts | 2 +- .../managed/managedWorkflowToolContracts.ts | 2 +- src/contracts/native/nativeToolContracts.ts | 7 ++-- src/contracts/officialToolContracts.ts | 7 ++-- src/contracts/sessionToolContracts.ts | 2 +- src/contracts/toolFamilies.test.ts | 32 +++++++++++++++-- src/contracts/toolOutputSchemaGroups.ts | 34 +++++++++---------- 11 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/application/binary/AnalysisSnapshotCache.test.ts b/src/application/binary/AnalysisSnapshotCache.test.ts index 39441cf936..86640eb2aa 100644 --- a/src/application/binary/AnalysisSnapshotCache.test.ts +++ b/src/application/binary/AnalysisSnapshotCache.test.ts @@ -924,7 +924,7 @@ it("does not replay pre-envelope call graphs under the revised workflow profile" }), ).toBeUndefined(); expect( - enhancedOutputSchemas.get_call_graph?.safeParse({ + enhancedOutputSchemas.get_call_graph.safeParse({ normalized_result: oldResult, }).success, ).toBe(false); diff --git a/src/contracts/artifactToolContracts.ts b/src/contracts/artifactToolContracts.ts index f82da9bd89..6b299aad20 100644 --- a/src/contracts/artifactToolContracts.ts +++ b/src/contracts/artifactToolContracts.ts @@ -1,3 +1,4 @@ +import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { z } from "zod"; import type { ToolContract } from "./toolContractTypes.js"; @@ -5,7 +6,6 @@ import { artifactOutputSchemas } from "./toolOutputSchemaGroups.js"; import { artifactIntegrityPolicySchema } from "../domain/artifactIntegrityPolicy.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { toolContractMetadata } from "./toolEffects.js"; -import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { appleAssetCatalogInputSchema } from "../domain/apple/appleAssetCatalog.js"; import { dylibResolutionInputSchema } from "../domain/apple/dylibResolution.js"; import { keyedArchiveInputSchema } from "../domain/apple/keyedArchive.js"; @@ -52,7 +52,7 @@ const examples: Readonly>>> = { }; const artifact = < - Name extends string, + Name extends keyof typeof artifactOutputSchemas, Schema extends z.ZodType>>, >( name: Name, diff --git a/src/contracts/enhancedToolContracts.ts b/src/contracts/enhancedToolContracts.ts index 1bc48dab46..144828d54a 100644 --- a/src/contracts/enhancedToolContracts.ts +++ b/src/contracts/enhancedToolContracts.ts @@ -1,15 +1,18 @@ +import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { analysisDetailSchema } from "./analysisDetail.js"; import { z } from "zod"; import { enhancedInputSchemas } from "./enhancedInputs.js"; import { enhancedOutputSchemas } from "./toolOutputSchemaGroups.js"; -import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { examplesFor } from "./toolContractHelpers.js"; import type { ToolContract } from "./toolContractTypes.js"; import { toolContractMetadata } from "./toolEffects.js"; import { FUNCTION_WORKFLOW_TOOL_CONTRACTS } from "./functionWorkflowToolContracts.js"; -const enhanced = ( +const enhanced = < + Name extends keyof typeof enhancedOutputSchemas, + Schema extends z.ZodObject, +>( name: Name, description: string, inputSchema: Schema, diff --git a/src/contracts/functionWorkflowToolContracts.ts b/src/contracts/functionWorkflowToolContracts.ts index dd3b1c2ae7..cc360d916b 100644 --- a/src/contracts/functionWorkflowToolContracts.ts +++ b/src/contracts/functionWorkflowToolContracts.ts @@ -1,3 +1,4 @@ +import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { jsonObjectSchema } from "../domain/jsonValue.js"; import { analysisDetailSummaryDefaultSchema } from "./analysisDetail.js"; import { enhancedInputSchemas } from "./enhancedInputs.js"; @@ -5,7 +6,6 @@ import { TOOL_EXAMPLE_OVERRIDES } from "./toolContractExamples.js"; import type { ToolContract } from "./toolContractTypes.js"; import { toolContractMetadata } from "./toolEffects.js"; import { enhancedOutputSchemas } from "./toolOutputSchemaGroups.js"; -import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; type FunctionWorkflowName = "analyze_function" | "inspect_native_api"; diff --git a/src/contracts/managed/managedToolContracts.ts b/src/contracts/managed/managedToolContracts.ts index 627f2db50e..2ae07e1042 100644 --- a/src/contracts/managed/managedToolContracts.ts +++ b/src/contracts/managed/managedToolContracts.ts @@ -1,9 +1,9 @@ +import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; import { z } from "zod"; import type { ToolContract } from "../toolContractTypes.js"; import { managedOutputSchemas } from "../toolOutputSchemaGroups.js"; import { toolContractMetadata } from "../toolEffects.js"; -import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; import { absoluteLocalPathStringSchema } from "../../domain/localPath.js"; import { analysisDetailSchema } from "../analysisDetail.js"; diff --git a/src/contracts/managed/managedWorkflowToolContracts.ts b/src/contracts/managed/managedWorkflowToolContracts.ts index 4db8335ae5..dff0b0c55b 100644 --- a/src/contracts/managed/managedWorkflowToolContracts.ts +++ b/src/contracts/managed/managedWorkflowToolContracts.ts @@ -1,3 +1,4 @@ +import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; import { z } from "zod"; import { evidenceSchema } from "../../domain/evidence.js"; import { compareManagedMembersInputSchema } from "../../domain/managed/managedMemberComparison.js"; @@ -13,7 +14,6 @@ import { MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE, } from "./managedWorkflowExamples.js"; import { toolContractMetadata } from "../toolEffects.js"; -import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; const managedApplicationGraphReferenceShape = z.strictObject({ managed_artifact: evidenceSchema.optional(), diff --git a/src/contracts/native/nativeToolContracts.ts b/src/contracts/native/nativeToolContracts.ts index dc64d451dd..0fde5e4420 100644 --- a/src/contracts/native/nativeToolContracts.ts +++ b/src/contracts/native/nativeToolContracts.ts @@ -1,3 +1,4 @@ +import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; import { z } from "zod"; import { nativeCallObservationInputSchema } from "../../domain/native/nativeCallObservation.js"; import { @@ -10,7 +11,6 @@ import { nativeOutputSchemas } from "../toolOutputSchemaGroups.js"; import { jsonValueSchema } from "../../domain/jsonValue.js"; import { localPathStringSchema } from "../../domain/localPath.js"; import { toolContractMetadata } from "../toolEffects.js"; -import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; import { analysisDetailSchema } from "../analysisDetail.js"; import { nativeCommandTimeoutField } from "../../domain/native/nativeDeadlines.js"; @@ -41,7 +41,10 @@ const examples: Readonly>>> = { }, }; -const native = ( +const native = < + Name extends keyof typeof nativeOutputSchemas, + Schema extends z.ZodObject, +>( name: Name, description: string, inputSchema: Schema, diff --git a/src/contracts/officialToolContracts.ts b/src/contracts/officialToolContracts.ts index 27b87841a9..a81bc86bb5 100644 --- a/src/contracts/officialToolContracts.ts +++ b/src/contracts/officialToolContracts.ts @@ -1,10 +1,10 @@ +import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { analysisDetailSchema } from "./analysisDetail.js"; import { z } from "zod"; import { jsonRecordSchema } from "../domain/jsonRecord.js"; import { nativeFunctionAnnotationsInputSchema } from "../domain/native/nativeFunctionAnnotations.js"; import { officialOutputSchemas } from "./toolOutputSchemaGroups.js"; -import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { address, document, @@ -23,7 +23,10 @@ import { nativeSearchInput } from "../domain/native/nativeSearch.js"; const hopperRegexDeadline = "Hopper regex mode uses ECMAScript Unicode syntax in a cancellable worker. The matching deadline is REA_REGEX_MATCHING_DEADLINE_MS (a positive decimal integer of milliseconds from 1 to 2147483647, default 5000). Unset keeps the default. A deadline failure names the effective milliseconds, source (default or setting), the default, and the maximum, and leaves the API usable. The worker old-generation request is REA_REGEX_WORKER_HEAP_REQUEST_MIB (a decimal integer of mebibytes from 7 to 16384, default 128). Unset keeps the default. A positive Node --max-old-space-size, including NODE_OPTIONS, overrides that request and is not limited by the setting's floor or maximum. A heap failure names the effective request, its source (default or setting), the default, the floor, and the maximum."; -const official = ( +const official = < + Name extends keyof typeof officialOutputSchemas, + Schema extends z.ZodObject, +>( name: Name, description: string, inputSchema: Schema, diff --git a/src/contracts/sessionToolContracts.ts b/src/contracts/sessionToolContracts.ts index e5884f8643..df1b443942 100644 --- a/src/contracts/sessionToolContracts.ts +++ b/src/contracts/sessionToolContracts.ts @@ -1,3 +1,4 @@ +import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { matchNativeFunctionsInputSchema } from "./native/functionMatchingContracts.js"; import { z } from "zod"; @@ -14,7 +15,6 @@ import { import { reconstructionVerificationInputSchema } from "../domain/reconstructionVerificationSchemas.js"; import { staticRuntimeCorrelationInputSchema } from "../domain/staticRuntimeCorrelation.js"; import { sessionOutputSchemas } from "./toolOutputSchemaGroups.js"; -import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; import { closeBinaryInputSchema, openBundleResourcesInputSchema, diff --git a/src/contracts/toolFamilies.test.ts b/src/contracts/toolFamilies.test.ts index a10f40f105..9d639445a9 100644 --- a/src/contracts/toolFamilies.test.ts +++ b/src/contracts/toolFamilies.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, expectTypeOf, it } from "vitest"; import { TOOL_FAMILY_DEFINITIONS, @@ -6,7 +6,35 @@ import { toolFamilyId, } from "./toolFamilies.js"; import { TOOL_FAMILY_IDS } from "./toolFamilyIds.js"; -import { TOOL_CONTRACTS } from "./toolContracts.js"; +import { TOOL_CONTRACTS, toolContract } from "./toolContracts.js"; +import type { + officialOutputSchemas, + enhancedOutputSchemas, + nativeOutputSchemas, + artifactOutputSchemas, + managedOutputSchemas, + managedWorkflowOutputSchemas, +} from "./toolOutputSchemaGroups.js"; + +// Catalog resolution must retain the exact schema owned by each named family. +expectTypeOf( + toolContract("inspect_native_instruction").outputSchema, +).toEqualTypeOf(); +expectTypeOf(toolContract("analyze_function").outputSchema).toEqualTypeOf< + typeof enhancedOutputSchemas.analyze_function +>(); +expectTypeOf(toolContract("inspect_macho").outputSchema).toEqualTypeOf< + typeof nativeOutputSchemas.inspect_macho +>(); +expectTypeOf(toolContract("inspect_artifact").outputSchema).toEqualTypeOf< + typeof artifactOutputSchemas.inspect_artifact +>(); +expectTypeOf( + toolContract("inspect_managed_artifact").outputSchema, +).toEqualTypeOf(); +expectTypeOf( + toolContract("compare_managed_members").outputSchema, +).toEqualTypeOf(); describe("tool families", () => { it("declares exactly one definition per published id", () => { diff --git a/src/contracts/toolOutputSchemaGroups.ts b/src/contracts/toolOutputSchemaGroups.ts index 4dd8f6b25e..9816a89ab5 100644 --- a/src/contracts/toolOutputSchemaGroups.ts +++ b/src/contracts/toolOutputSchemaGroups.ts @@ -25,13 +25,15 @@ import { functionDossierSchema, functionInstructionWindowSchema, } from "../domain/native/functionDossier.js"; -import { analysisBookmarkSchema } from "../domain/native/nativeInventory.js"; +import { + analysisBookmarkSchema, + nativeInventoryRowsSchemas, +} from "../domain/native/nativeInventory.js"; import { referenceEdgeSchema, unresolvedCallSchema, } from "../domain/native/nativeReference.js"; import { procedureIdentitySchema } from "../domain/native/procedureInfo.js"; -import { nativeInventoryRowsSchemas } from "../domain/native/nativeInventory.js"; import { z } from "zod"; import { jsonRecordSchema } from "../domain/jsonRecord.js"; import { byteSearchResultSchema } from "../domain/native/bytePattern.js"; @@ -134,7 +136,7 @@ const bookmarkFacetSchema = z.discriminatedUnion("state", [ ]); /** Exact structured-content schemas shared by direct analysis providers. */ -export const officialOutputSchemas: Readonly> = { +export const officialOutputSchemas = { search_bytes: evidenceResultOf(byteSearchResultSchema), annotate_native_function: evidenceResultOf(nativeFunctionAnnotationsSchema), inspect_native_load_image: analysisDetailOutputSchemaOf( @@ -252,7 +254,7 @@ export const officialOutputSchemas: Readonly> = { set_inline_comment: evidenceResultOf(z.boolean()), unset_bookmark: evidenceResultOf(z.boolean()), xrefs: evidenceResultOf(nativeAddressListSchema), -}; +} as const satisfies Readonly>; const literalTraceOutput = evidenceResultOf( z.object({ @@ -325,7 +327,7 @@ const batchProcedureIdentity = z.discriminatedUnion("status", [ ]); /** Exact structured-content schemas for composed analysis workflows. */ -export const enhancedOutputSchemas: Readonly> = { +export const enhancedOutputSchemas = { inspect_native_dispatch_metadata: evidenceResultOf( nativeDispatchMetadataResultSchema, ), @@ -432,7 +434,7 @@ export const enhancedOutputSchemas: Readonly> = { inspect_native_api: evidenceResultOf(nativeApiInspectionResultSchema), trace_feature: literalTraceOutput, trace_call_path: callPathTraceOutput, -}; +} as const satisfies Readonly>; /** Complete entropy Evidence or its retained summary, shared by the named contract. */ export const byteEntropyOutputSchema = analysisDetailOutputSchemaOf( @@ -441,7 +443,7 @@ export const byteEntropyOutputSchema = analysisDetailOutputSchemaOf( ); /** Exact Evidence schemas for provider-neutral native inspection. */ -export const nativeOutputSchemas: Readonly> = { +export const nativeOutputSchemas = { inspect_byte_entropy: byteEntropyOutputSchema, observe_native_ui: evidenceResultOf(nativeUiResultSchema), capture_native_ui_scenario: evidenceResultOf(nativeUiResultSchema), @@ -453,10 +455,10 @@ export const nativeOutputSchemas: Readonly> = { inspect_plist: evidenceResultOf(inspectPlistSchema), list_architectures: evidenceResultOf(listArchitecturesSchema), demangle_swift: evidenceResultOf(demangleSwiftSchema), -}; +} as const satisfies Readonly>; /** Exact Evidence schemas for provider-neutral artifact graph operations. */ -export const artifactOutputSchemas: Readonly> = { +export const artifactOutputSchemas = { inspect_artifact: evidenceResultOf( z.union([ artifactInspectionResultSchema, @@ -473,10 +475,10 @@ export const artifactOutputSchemas: Readonly> = { inspect_keyed_archive: evidenceResultOf(keyedArchiveResultSchema), inspect_asset_catalog: evidenceResultOf(appleAssetCatalogResultSchema), trace_dylib_resolution: evidenceResultOf(dylibResolutionResultSchema), -}; +} as const satisfies Readonly>; /** Exact Evidence schema for execution-free managed static analysis. */ -export const managedOutputSchemas: Readonly> = { +export const managedOutputSchemas = { inspect_managed_artifact: evidenceResultOf( z.union([ managedArtifactInspectionSchema, @@ -492,12 +494,10 @@ export const managedOutputSchemas: Readonly> = { inspect_managed_native_boundaries: evidenceResultOf( managedNativeBoundaryInspectionSchema, ), -}; +} as const satisfies Readonly>; /** Exact Evidence schema for provider-neutral managed workflows. */ -export const managedWorkflowOutputSchemas: Readonly< - Record -> = { +export const managedWorkflowOutputSchemas = { compare_managed_members: evidenceResultOf( managedMemberComparisonResultSchema, ), @@ -510,7 +510,7 @@ export const managedWorkflowOutputSchemas: Readonly< project_managed_application_graph: evidenceResultOf( managedApplicationGraphResultSchema, ), -}; +} as const satisfies Readonly>; const lifecycleTargetFields = { path: z.string(), @@ -677,7 +677,7 @@ export const sessionOutputSchemas = { unknown: residualUnknownSchema, }), ), -} satisfies Readonly>; +} as const satisfies Readonly>; /** Exact Evidence schema for read-only Electron fuse inspection. */ export const electronFuseOutputSchemas = {