import { z } from "zod"; import { evidenceSchema } from "../domain/evidence.js"; import { processCaptureComparisonSchema, processCaptureSchema, } from "../domain/processCapture.js"; import { evidenceBundleSchema } from "../domain/evidenceBundle.js"; import { residualUnknownSchema } from "../domain/residualUnknown.js"; import { functionDossierSchema } from "../domain/hopperValues.js"; import { demangleSwiftSchema, inspectMachoSchema, inspectPlistSchema, inspectSignatureSchema, listArchitecturesSchema, } from "../domain/nativeInspection.js"; import { artifactExtractionResultSchema, artifactInventoryResultSchema, } from "../domain/artifactGraph.js"; import { artifactComparisonResultSchema } from "../domain/artifactComparison.js"; import { functionComparisonResultSchema } from "../domain/functionComparison.js"; import { bundleComparisonResultSchema } from "../domain/bundleComparison.js"; import { changedBehaviorResultSchema } from "../domain/changedBehavior.js"; import { callPathResultSchema } from "../domain/callPath.js"; import { staticRuntimeCorrelationResultSchema } from "../domain/staticRuntimeCorrelation.js"; import { reconstructionVerificationResultSchema } from "../domain/reconstructionVerification.js"; import { analysisErrorProjectionSchema } from "./errorSchemas.js"; const resultOf = (schema: z.ZodType) => evidenceSchema .omit({ normalized_result: true }) .extend({ normalized_result: schema }); const lifecycleResultOf = (schema: z.ZodType) => z.object({ result: schema }); /** Resolve a required named output schema or reject contract drift. */ export const requireOutputSchema = ( schemas: Readonly>, name: string, ): z.ZodObject => { const schema = schemas[name]; if (schema === undefined) throw new Error(`Missing output schema for ${name}`); return schema; }; const targetFormatSchema = z.enum([ "hopper", "mach-o", "elf", "pe", "zip", "ipa", "apk", "asar", "dmg", "pkg", "plist", "javascript", "source-map", ]); const targetKindSchema = z.enum([ "executable", "database", "archive", "artifact", ]); const providerCapability = z.object({ operation: z.string(), available: z.boolean(), reason: z.string().nullable(), input_contract_version: z.number().int().min(1), output_contract_version: z.number().int().min(1), pagination: z.enum(["none", "offset", "cursor"]), exhaustive: z.boolean(), effects: z.object({ mutates_artifact: z.boolean(), launches_process: z.boolean(), may_show_ui: z.boolean(), may_access_network: z.boolean(), may_write_filesystem: z.boolean(), changes_permissions: z.boolean(), requires_root: z.boolean(), }), limits: z.object({ max_results: z.number().int().min(0).nullable(), max_payload_bytes: z.number().int().min(0).nullable(), timeout_ms: z.number().int().min(0).nullable(), }), limitations: z.array(z.string()), }); const providerIdentity = z.object({ id: z.string(), name: z.string(), version: z.string().nullable(), }); const sessionProvider = z.object({ provider: providerIdentity, providers: z.array(providerIdentity), capabilities: z.array(providerCapability), tool_availability: z.array( z.object({ name: z.string(), surface: z.string(), available: z.boolean(), reason: z.enum([ "available", "target_required", "provider_missing", "provider_unavailable", "target_unsupported", "unsupported_host", "policy_disabled", ]), remediation: z.string().nullable(), annotations: z.object({ read_only: z.boolean(), destructive: z.boolean(), idempotent: z.boolean(), open_world: z.boolean(), }), }), ), client_features: z.object({ elicitation_form: z.boolean(), elicitation_url: z.boolean(), roots: z.boolean(), sampling: z.boolean(), }), server_identity: z.object({ package: z.object({ name: z.string(), version: z.string(), root_path: z.string(), build_commit: z.string().nullable(), }), server: z.object({ name: z.string(), version: z.string(), started_at: z.string(), command_path: z.string(), }), sdk: z.object({ server: z.string(), client_test: z.string(), core: z.string(), }), negotiated_protocol_version: z.string().nullable(), client: z.object({ name: z.string(), version: z.string() }).nullable(), skill: z.object({ name: z.string(), expected_version: z.string() }), catalog: z.record(z.string(), z.json()), protocol_features: z.object({ progress: z.boolean(), cancellation: z.boolean(), evidence_resources: z.boolean(), elicitation: z.boolean(), }), alignment: z.object({ state: z.enum(["aligned", "mcp_server_restart_required", "unknown"]), reasons: z.array(z.string()), remediation: z.string().nullable(), }), }), }); const nullableText = z.string().nullable(); const addressList = z.array(z.string()); const addressedEntry = z.object({ address: z.string(), name: z.string() }); const procedureIdentity = z.object({ address: z.string(), name: z.string() }); const localVariable = z.object({ description: z.string(), provenance: z.literal("hopper-public-python-api"), }); const containingProcedureResolution = z.discriminatedUnion("found", [ z.object({ query_address: z.string(), found: z.literal(true), procedure: procedureIdentity, }), z.object({ query_address: z.string(), found: z.literal(false), procedure: z.null(), reason: z.enum(["outside_segments", "not_in_procedure"]), }), ]); const unavailable = z.object({ available: z.literal(false), reason: z.string(), }); const bounded = (item: z.ZodType) => z.object({ items: z.array(item), total: z.number().int().min(0).nullable(), returned: z.number().int().min(0), truncated: z.boolean(), next_offset: z.number().int().min(0).nullable(), }); const pageOutput = z.object({ items: z.array(z.object({ address: z.string(), value: z.string() })), offset: z.number().int().min(0), limit: z.number().int().min(1), total: z.number().int().min(0), next_offset: z.number().int().min(0).nullable(), has_more: z.boolean(), }); const searchPageOutput = pageOutput.extend({ items: z.array( z.object({ address: z.string(), value: z.string(), value_truncated: z.boolean(), }), ), }); const memoryRegionOutput = z.object({ name: z.string(), start: z.string(), end: z.string(), readable: z.boolean().nullable(), writable: z.boolean().nullable(), executable: z.boolean().nullable(), permissions: unavailable, provenance: z.literal("hopper-public-python-api"), }); const segmentOutput = resultOf( z.array(memoryRegionOutput.extend({ sections: z.array(memoryRegionOutput) })), ); const procedureInfoOutput = resultOf( z.object({ name: z.string(), entrypoint: z.string(), basicblock_count: z.number().int().min(0), length: z.number().min(0), signature: nullableText, locals: z.array(localVariable), }), ); const symbolDiscoveryOutput = (property: "classes" | "protocols") => resultOf( z.object({ count: z.number().int().min(0), [property]: z.array(addressedEntry), }), ); const graphNode = z.discriminatedUnion("status", [ z.object({ address: z.string(), status: z.literal("ok"), calls: z.array(z.string()), }), z.object({ address: z.string(), status: z.literal("error"), error: analysisErrorProjectionSchema, }), ]); const functionDossierOutput = resultOf(functionDossierSchema); /** Exact structured-content schemas for the direct Hopper operations. */ export const officialOutputSchemas: Readonly> = { address_name: resultOf(nullableText), comment: resultOf(nullableText), current_address: resultOf(z.string()), current_procedure: resultOf(z.string()), current_document: resultOf(z.string()), goto_address: resultOf(z.string()), inline_comment: resultOf(nullableText), list_bookmarks: resultOf(z.array(addressedEntry)), list_documents: resultOf(z.array(z.string())), list_names: resultOf(pageOutput), list_procedures: resultOf(pageOutput), list_segments: segmentOutput, list_strings: resultOf(pageOutput), next_address: resultOf(z.string()), prev_address: resultOf(z.string()), procedure_address: resultOf(z.string()), procedure_assembly: resultOf(z.string()), procedure_callees: resultOf(addressList), procedure_callers: resultOf(addressList), procedure_info: procedureInfoOutput, procedure_references: resultOf( z.object({ procedure: procedureIdentity, direction: z.enum(["incoming", "outgoing"]), references: bounded( z.object({ source_address: z.string(), target_address: z.string(), source_procedure: procedureIdentity.nullable(), target_procedure: procedureIdentity.nullable(), kind: unavailable, }), ), instructions_scanned: z.number().int().min(0), instruction_scan_truncated: z.boolean(), }), ), procedure_pseudo_code: resultOf(nullableText), resolve_containing_procedure: resultOf(containingProcedureResolution), search_procedures: resultOf(searchPageOutput), search_strings: resultOf(searchPageOutput), set_address_name: resultOf(z.boolean()), set_addresses_names: resultOf(z.record(z.string(), z.boolean())), set_bookmark: resultOf(z.boolean()), set_comment: resultOf(z.boolean()), set_current_document: resultOf(z.string()), set_inline_comment: resultOf(z.boolean()), unset_bookmark: resultOf(z.boolean()), xrefs: resultOf(addressList), }; /** Exact structured-content schemas for composed analysis workflows. */ export const enhancedOutputSchemas: Readonly> = { swift_classes: symbolDiscoveryOutput("classes"), get_objc_classes: symbolDiscoveryOutput("classes"), get_objc_protocols: symbolDiscoveryOutput("protocols"), batch_decompile: resultOf( z.object({ items: z.array( z.discriminatedUnion("status", [ z.object({ address: z.string(), status: z.literal("ok"), pseudocode: z.string().min(1), }), z.object({ address: z.string(), status: z.literal("error"), error: analysisErrorProjectionSchema, }), ]), ), total: z.number().int().min(0), succeeded: z.number().int().min(0), failed: z.number().int().min(0), }), ), get_call_graph: resultOf(z.record(z.string(), z.array(graphNode))), analyze_swift_types: resultOf( z.object({ total: z.number().int().min(0), categories: z.record( z.string(), z.object({ count: z.number().int().min(0), items: z.array(addressedEntry), }), ), }), ), find_xrefs_to_name: resultOf( z.discriminatedUnion("status", [ z.object({ status: z.literal("resolved"), name: z.string(), address: z.string(), xrefs: addressList, }), z.object({ status: z.literal("unresolved"), name: z.string(), reason: z.literal("name_not_found"), }), ]), ), binary_overview: resultOf( z.object({ document: z.string(), detail: z.enum(["concise", "detailed"]), segments: z.array( z.object({ name: z.string(), start: z.string(), end: z.string(), length: z.number().min(0).optional(), }), ), segment_count: z.number().int().min(0), procedure_count: z.number().int().min(0), string_count: z.number().int().min(0), }), ), analyze_function: functionDossierOutput, trace_feature: resultOf( z.object({ query: z.string(), search_mode: z.literal("literal"), operations_used: z.number().int().min(0), operation_budget: z.number().int().min(1), matches: z.array( z.object({ type: z.enum(["string", "procedure"]), address: z.string(), value: z.string(), }), ), references: z.array( z.object({ target_address: z.string(), source_address: z.string(), containing_procedure: containingProcedureResolution, }), ), truncated: z.boolean(), residual_unknowns: z.array(z.string()), }), ), }; /** Exact Evidence v2 schemas for provider-neutral native inspection. */ export const nativeOutputSchemas: Readonly> = { inspect_macho: resultOf(inspectMachoSchema), inspect_signature: resultOf(inspectSignatureSchema), inspect_plist: resultOf(inspectPlistSchema), list_architectures: resultOf(listArchitecturesSchema), demangle_swift: resultOf(demangleSwiftSchema), }; /** Exact Evidence v2 schemas for provider-neutral artifact graph operations. */ export const artifactOutputSchemas: Readonly> = { inventory_artifact: resultOf(artifactInventoryResultSchema), extract_artifact: resultOf(artifactExtractionResultSchema), }; /** Exact structured-content schemas for target lifecycle operations. */ export const sessionOutputSchemas: Readonly> = { open_binary: lifecycleResultOf( z.object({ path: z.string(), format: targetFormatSchema, kind: targetKindSchema, loaderArgs: z.array(z.string()), sha256: z.string().regex(/^[a-f0-9]{64}$/u), architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(), }), ), close_binary: lifecycleResultOf( z.union([ z.null(), z.object({ path: z.string(), bytes: z.number().int().min(0), entries: z.number().int().min(0), }), ]), ), binary_session: lifecycleResultOf( z.union([ sessionProvider.extend({ open: z.literal(false) }), sessionProvider.extend({ open: z.literal(true), path: z.string(), format: targetFormatSchema, kind: targetKindSchema, sha256: z.string().regex(/^[a-f0-9]{64}$/u), architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(), }), ]), ), export_evidence_bundle: lifecycleResultOf( z.union([ evidenceBundleSchema, z.object({ path: z.string(), bytes: z.number().int().min(0), records: z.number().int().min(0), }), ]), ), import_evidence_bundle: lifecycleResultOf( z.object({ imported: z.number().int().min(0), total: z.number().int().min(0), }), ), capture_process_scenario: lifecycleResultOf( evidenceSchema .omit({ normalized_result: true }) .extend({ normalized_result: processCaptureSchema }), ), compare_process_captures: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: processCaptureComparisonSchema, }), ), compare_artifacts: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: artifactComparisonResultSchema, }), ), compare_functions: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: functionComparisonResultSchema, }), ), compare_bundles: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: bundleComparisonResultSchema, }), ), find_changed_behavior: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: changedBehaviorResultSchema, }), ), build_call_path: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: callPathResultSchema, }), ), correlate_static_and_runtime: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: staticRuntimeCorrelationResultSchema, }), ), verify_reconstruction: lifecycleResultOf( evidenceSchema.omit({ normalized_result: true }).extend({ normalized_result: reconstructionVerificationResultSchema, }), ), list_unknowns: lifecycleResultOf(z.array(residualUnknownSchema)), record_unknown: lifecycleResultOf(residualUnknownSchema), update_unknown: lifecycleResultOf(residualUnknownSchema), verify_unknown_resolution: lifecycleResultOf( z.object({ valid: z.boolean(), truthVerified: z.boolean(), unknown: residualUnknownSchema, }), ), };