Files
rea/src/domain/javascriptApplicationVersionComparisonSchemas.ts
T

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
>;