import { z } from "zod"; import { evidenceSchema, providerSchema } from "../evidence.js"; import { javascriptApplicationGraphSchema } from "./javascriptApplicationGraph.js"; import { digestSchema } from "../digests.js"; import { prefixedDigestSchema } from "../digests.js"; const evidenceIdSchema = prefixedDigestSchema("ev"); const nodeIdSchema = prefixedDigestSchema("jag_node"); const edgeIdSchema = prefixedDigestSchema("jag_edge"); const boundedTextSchema = z.string().min(1); /** Matching mode for a literal seed: string seeds default to containment. */ export const featureSeedMatchMode = (seed: { readonly kind: string; readonly match: "exact" | "contains" | null; }): "exact" | "contains" => seed.match ?? (seed.kind === "string" ? "contains" : "exact"); const literalSeedKinds = [ "route", "string", "api", "channel", "module", "native-export", ] as const; /** * Literal starting point for one application feature trace. An empty value, * such as the channel in `ipcMain.handle("")`, must be an explicit exact * literal seed: empty containment would select every node. */ const applicationFeatureSeedSchema = z.union([ z.strictObject({ kind: z.enum(["node-id", ...literalSeedKinds]), value: boundedTextSchema, match: z.enum(["exact", "contains"]).nullable().default(null), case_sensitive: z.boolean().default(false), }), z.strictObject({ kind: z.enum(literalSeedKinds), value: z.literal(""), match: z.literal("exact"), case_sensitive: z.boolean().default(false), }), ]); /** Evidence-backed application graph and feature seed. */ export const traceApplicationFeatureInputSchema = z .strictObject({ application: evidenceSchema, native_observations: z.array(evidenceSchema).default([]), seed: applicationFeatureSeedSchema, direction: z.enum(["outgoing", "incoming", "both"]).default("both"), }) .superRefine((input, context) => { const ids = input.native_observations.map(({ evidence_id: id }) => id); if (new Set(ids).size !== ids.length) context.addIssue({ code: "custom", path: ["native_observations"], message: "Native observations must be unique", }); }); const seedMatchSchema = z.strictObject({ node_id: nodeIdSchema, kind: z.string().min(1), basis: z.enum(["node-id", "label", "identity", "property"]), field: z.string().min(1), }); const tracePathSchema = z.strictObject({ path_id: prefixedDigestSchema("jatp"), start_node_id: nodeIdSchema, end_node_id: nodeIdSchema, end_kind: z.string().min(1), node_ids: z.array(nodeIdSchema).min(1), edge_ids: z.array(edgeIdSchema), authorities: z.array(z.string().min(1)), contains_inference: z.boolean(), }); const nativeHandoffSchema = z.strictObject({ native_node_id: nodeIdSchema, artifact_sha256: digestSchema, artifact_path: z.string().min(1).nullable(), export_node_ids: z.array(nodeIdSchema), requested_exports: z.array(boundedTextSchema), status: z.enum(["evidence-linked", "requires-provider-analysis"]), providers: z.array(providerSchema), evidence_ids: z.array(evidenceIdSchema), recommended_tools: z.array( z.enum([ "open_binary", "binary_overview", "search_procedures", "analyze_function", "xrefs", ]), ), }); /** Evidence-preserving complete reachable subgraph and native-analysis handoffs. */ export const applicationFeatureTraceResultSchema = z.strictObject({ trace_id: prefixedDigestSchema("jatr"), source_evidence_id: evidenceIdSchema, source_graph_id: prefixedDigestSchema("jag"), seed: applicationFeatureSeedSchema, direction: z.enum(["outgoing", "incoming", "both"]), seed_matches: z.array(seedMatchSchema), graph: javascriptApplicationGraphSchema.nullable(), paths: z.array(tracePathSchema), native_handoffs: z.array(nativeHandoffSchema), summary: z.strictObject({ matched_seeds: z.number().int().min(0), traced_nodes: z.number().int().min(0), traced_edges: z.number().int().min(0), terminal_paths: z.number().int().min(0), native_handoffs: z.number().int().min(0), observed_facts: z.number().int().min(0), inferred_facts: z.number().int().min(0), unknown_facts: z.number().int().min(0), unavailable_facts: z.number().int().min(0), }), coverage: z.strictObject({ status: z.enum(["complete-within-source", "partial", "no-match"]), source_graph_status: z.enum([ "complete", "partial", "unknown", "unavailable", ]), total_seed_matches: z.number().int().min(0), }), evidence_links: z.array(evidenceIdSchema).min(1), limitations: z.array(boundedTextSchema), }); export type TraceApplicationFeatureInput = z.infer< typeof traceApplicationFeatureInputSchema >; export type ApplicationFeatureSeed = z.infer< typeof applicationFeatureSeedSchema >; export type ApplicationFeatureTraceResult = z.infer< typeof applicationFeatureTraceResultSchema >;