162 lines
5.6 KiB
TypeScript
162 lines
5.6 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { evidenceSchema } from "./evidence.js";
|
|
import { javascriptApplicationGraphSchema } from "./javascriptApplicationGraph.js";
|
|
import { JAVASCRIPT_APPLICATION_NODE_KINDS } from "./javascriptApplicationGraphSchemas.js";
|
|
|
|
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
const nodeIdSchema = z.string().regex(/^jag_node_[a-f0-9]{64}$/u);
|
|
const boundedTextSchema = z.string().min(1).max(4_096);
|
|
|
|
const comparisonLimitsSchema = z.strictObject({
|
|
max_comparison_items: z.number().int().min(1).max(50_000).default(20_000),
|
|
max_candidate_nodes: z.number().int().min(1).max(1_000).default(100),
|
|
max_graph_nodes: z.number().int().min(1).max(100_000).default(20_000),
|
|
max_graph_edges: z.number().int().min(1).max(200_000).default(40_000),
|
|
});
|
|
|
|
/** Two authenticated application versions and deterministic output bounds. */
|
|
export const compareApplicationVersionsInputSchema = z
|
|
.strictObject({
|
|
left: evidenceSchema,
|
|
right: evidenceSchema,
|
|
left_native_observations: z.array(evidenceSchema).max(64).default([]),
|
|
right_native_observations: z.array(evidenceSchema).max(64).default([]),
|
|
limits: comparisonLimitsSchema.default({
|
|
max_comparison_items: 20_000,
|
|
max_candidate_nodes: 100,
|
|
max_graph_nodes: 20_000,
|
|
max_graph_edges: 40_000,
|
|
}),
|
|
unknown_registry_approved: z.literal(true).optional(),
|
|
})
|
|
.superRefine((input, context) => {
|
|
if (input.left.evidence_id === input.right.evidence_id)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["right"],
|
|
message: "Application version Evidence must be distinct",
|
|
});
|
|
for (const side of [
|
|
"left_native_observations",
|
|
"right_native_observations",
|
|
] as const) {
|
|
const ids = input[side].map(({ evidence_id: id }) => id);
|
|
if (new Set(ids).size !== ids.length)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: [side],
|
|
message: "Native observations must be unique on each side",
|
|
});
|
|
}
|
|
});
|
|
|
|
const applicationVersionMatchBasisSchema = z.enum([
|
|
"exact-node-identity",
|
|
"exact-content-digest",
|
|
"exact-module-source-digest",
|
|
"source-map-identity",
|
|
"structural-fingerprint",
|
|
"semantic-key",
|
|
"none",
|
|
]);
|
|
|
|
const comparisonItemSchema = z.strictObject({
|
|
item_id: z.string().regex(/^javc_item_[a-f0-9]{64}$/u),
|
|
status: z.enum(["unchanged", "added", "removed", "changed", "unknown"]),
|
|
node_kind: z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS),
|
|
left_node_id: nodeIdSchema.nullable(),
|
|
right_node_id: nodeIdSchema.nullable(),
|
|
match: z.strictObject({
|
|
status: z.enum(["matched", "unmatched", "ambiguous"]),
|
|
basis: applicationVersionMatchBasisSchema,
|
|
confidence: z.enum(["exact", "high", "medium", "unknown"]),
|
|
candidate_left_node_ids: z.array(nodeIdSchema).max(1_000),
|
|
candidate_right_node_ids: z.array(nodeIdSchema).max(1_000),
|
|
}),
|
|
dimensions: z
|
|
.array(
|
|
z.enum([
|
|
"content",
|
|
"location",
|
|
"properties",
|
|
"relationships",
|
|
"availability",
|
|
"coverage",
|
|
]),
|
|
)
|
|
.max(6),
|
|
evidence_links: z.array(evidenceIdSchema).min(2).max(130),
|
|
limitations: z.array(boundedTextSchema).max(100),
|
|
});
|
|
|
|
/** Tiered module/entity matching plus a bounded cross-version change graph. */
|
|
export const applicationVersionComparisonResultSchema = z.strictObject({
|
|
schema_version: z.literal(1),
|
|
comparison_id: z.string().regex(/^javc_[a-f0-9]{64}$/u),
|
|
left: z.strictObject({
|
|
evidence_id: evidenceIdSchema,
|
|
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
root_artifact_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
}),
|
|
right: z.strictObject({
|
|
evidence_id: evidenceIdSchema,
|
|
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
root_artifact_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
}),
|
|
summary: z.strictObject({
|
|
unchanged: z.number().int().min(0),
|
|
added: z.number().int().min(0),
|
|
removed: z.number().int().min(0),
|
|
changed: z.number().int().min(0),
|
|
unknown: z.number().int().min(0),
|
|
}),
|
|
matching: z.strictObject({
|
|
exact_node_identity: z.number().int().min(0),
|
|
exact_content_digest: z.number().int().min(0),
|
|
exact_module_source_digest: z.number().int().min(0),
|
|
source_map_identity: z.number().int().min(0),
|
|
structural_fingerprint: z.number().int().min(0),
|
|
semantic_key: z.number().int().min(0),
|
|
ambiguous: z.number().int().min(0),
|
|
unmatched: z.number().int().min(0),
|
|
}),
|
|
items: z.array(comparisonItemSchema).max(50_000),
|
|
graph: javascriptApplicationGraphSchema,
|
|
coverage: z.strictObject({
|
|
status: z.enum(["complete-within-inputs", "partial", "truncated"]),
|
|
left_graph_status: z.enum([
|
|
"complete",
|
|
"partial",
|
|
"unknown",
|
|
"unavailable",
|
|
]),
|
|
right_graph_status: z.enum([
|
|
"complete",
|
|
"partial",
|
|
"unknown",
|
|
"unavailable",
|
|
]),
|
|
omitted_comparison_items: z.number().int().min(0),
|
|
omitted_candidate_references: z.number().int().min(0),
|
|
omitted_graph_nodes: z.number().int().min(0),
|
|
omitted_graph_edges: z.number().int().min(0),
|
|
omitted_graph_observations: z.number().int().min(0),
|
|
}),
|
|
evidence_links: z.array(evidenceIdSchema).min(2).max(130),
|
|
limitations: z.array(boundedTextSchema).max(1_000),
|
|
});
|
|
|
|
export type CompareApplicationVersionsInput = z.infer<
|
|
typeof compareApplicationVersionsInputSchema
|
|
>;
|
|
export type ApplicationVersionComparisonItem = z.infer<
|
|
typeof comparisonItemSchema
|
|
>;
|
|
export type ApplicationVersionComparisonResult = z.infer<
|
|
typeof applicationVersionComparisonResultSchema
|
|
>;
|
|
export type ApplicationVersionMatchBasis = z.infer<
|
|
typeof applicationVersionMatchBasisSchema
|
|
>;
|