298 lines
9.1 KiB
TypeScript
298 lines
9.1 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { evidenceSchema } from "../evidence.js";
|
|
import { prefixedDigestSchema } from "../digests.js";
|
|
|
|
const evidenceIdSchema = prefixedDigestSchema("ev");
|
|
const graphIdSchema = prefixedDigestSchema("jag");
|
|
const nodeIdSchema = prefixedDigestSchema("jag_node");
|
|
const textSchema = z.string().min(1);
|
|
const selectorTextSchema = textSchema;
|
|
const semanticPrimitiveSchema = z.union([
|
|
z.string(),
|
|
z.number().finite(),
|
|
z.boolean(),
|
|
z.null(),
|
|
]);
|
|
|
|
function isJsonPointer(value: string): boolean {
|
|
if (value === "") return true;
|
|
if (!value.startsWith("/")) return false;
|
|
for (let index = 0; index < value.length; index += 1) {
|
|
if (value[index] !== "~") continue;
|
|
const escaped = value[index + 1];
|
|
if (escaped !== "0" && escaped !== "1") return false;
|
|
index += 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const jsonPointerSchema = z
|
|
.string()
|
|
.refine(isJsonPointer, "Expected an RFC 6901 JSON Pointer");
|
|
|
|
const sourcePointSchema = z.strictObject({
|
|
line: z.number().int().min(1),
|
|
column: z.number().int().min(0),
|
|
});
|
|
|
|
const sourceRangeSchema = z
|
|
.strictObject({
|
|
start: sourcePointSchema,
|
|
end: sourcePointSchema,
|
|
})
|
|
.superRefine((range, context) => {
|
|
if (
|
|
range.end.line < range.start.line ||
|
|
(range.end.line === range.start.line &&
|
|
range.end.column < range.start.column)
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["end"],
|
|
message: "Source range end must not precede its start",
|
|
});
|
|
});
|
|
|
|
/** Two authenticated application graphs and exact export selectors. */
|
|
export const compareJavaScriptExportShapesInputSchema = z
|
|
.strictObject({
|
|
left: evidenceSchema,
|
|
right: evidenceSchema,
|
|
left_module_path: selectorTextSchema,
|
|
left_export_name: selectorTextSchema,
|
|
right_module_path: selectorTextSchema,
|
|
right_export_name: selectorTextSchema,
|
|
})
|
|
.superRefine((input, context) => {
|
|
if (input.left.evidence_id === input.right.evidence_id)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["right"],
|
|
message: "JavaScript export shape Evidence must be distinct",
|
|
});
|
|
});
|
|
|
|
const projectedReturnFieldShape = { path: jsonPointerSchema };
|
|
const projectedReturnFieldSchema = z.discriminatedUnion("state", [
|
|
z.strictObject({
|
|
...projectedReturnFieldShape,
|
|
state: z.literal("literal"),
|
|
value: semanticPrimitiveSchema,
|
|
reason: z.null(),
|
|
}),
|
|
z.strictObject({
|
|
...projectedReturnFieldShape,
|
|
state: z.literal("union"),
|
|
value: z.array(semanticPrimitiveSchema).min(1),
|
|
reason: z.null(),
|
|
}),
|
|
z.strictObject({
|
|
...projectedReturnFieldShape,
|
|
state: z.literal("unknown"),
|
|
value: z.null(),
|
|
reason: textSchema,
|
|
}),
|
|
]);
|
|
export type ProjectedReturnField = z.infer<typeof projectedReturnFieldSchema>;
|
|
|
|
const projectedPropertyCoverageSchema = z.discriminatedUnion("status", [
|
|
z.strictObject({
|
|
path: jsonPointerSchema,
|
|
status: z.literal("complete"),
|
|
omitted: z.literal(0),
|
|
}),
|
|
z.strictObject({
|
|
path: jsonPointerSchema,
|
|
status: z.literal("partial"),
|
|
omitted: z.union([z.number().int().positive(), z.null()]),
|
|
}),
|
|
]);
|
|
export type ProjectedPropertyCoverage = z.infer<
|
|
typeof projectedPropertyCoverageSchema
|
|
>;
|
|
|
|
const projectedReturnShapeSchema = z
|
|
.strictObject({
|
|
source_range: sourceRangeSchema,
|
|
value_status: z.enum([
|
|
"literal",
|
|
"union",
|
|
"object",
|
|
"array",
|
|
"unknown",
|
|
"ambiguous",
|
|
"cycle",
|
|
]),
|
|
fields: z.array(projectedReturnFieldSchema),
|
|
property_coverage: z.array(projectedPropertyCoverageSchema),
|
|
})
|
|
.superRefine((shape, context) => {
|
|
const fieldPaths = shape.fields.map(({ path }) => path);
|
|
if (new Set(fieldPaths).size !== fieldPaths.length)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["fields"],
|
|
message: "Projected return field paths must be unique",
|
|
});
|
|
const coveragePaths = shape.property_coverage.map(({ path }) => path);
|
|
if (new Set(coveragePaths).size !== coveragePaths.length)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["property_coverage"],
|
|
message: "Projected property coverage paths must be unique",
|
|
});
|
|
});
|
|
|
|
/** Strict graph-observation payload produced by static return-shape recovery. */
|
|
export const projectedExportReturnShapesSchema = z
|
|
.strictObject({
|
|
semantic_role: z.literal("export-return-shapes"),
|
|
module_path: selectorTextSchema,
|
|
exported_name: selectorTextSchema,
|
|
callable_id: textSchema,
|
|
callable_kind: z.enum(["function", "class", "method"]),
|
|
static_return_shapes: z.array(projectedReturnShapeSchema),
|
|
return_shape_coverage: z.strictObject({
|
|
status: z.enum(["complete", "partial", "truncated"]),
|
|
retained_return_sites: z.number().int().min(0),
|
|
omitted_return_sites: z.number().int().min(0).nullable(),
|
|
omitted_fields: z.number().int().min(0),
|
|
omitted_property_coverage: z.number().int().min(0),
|
|
projection_complete: z.boolean(),
|
|
}),
|
|
})
|
|
.superRefine((projection, context) => {
|
|
if (
|
|
projection.return_shape_coverage.retained_return_sites !==
|
|
projection.static_return_shapes.length
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["return_shape_coverage", "retained_return_sites"],
|
|
message: "Retained return-site count must match projected shapes",
|
|
});
|
|
});
|
|
|
|
const exportCandidateSchema = z.strictObject({
|
|
node_id: nodeIdSchema,
|
|
module_path: selectorTextSchema,
|
|
export_name: selectorTextSchema,
|
|
matches_requested_module: z.boolean(),
|
|
matches_requested_export: z.boolean(),
|
|
});
|
|
|
|
const selectorResultShape = {
|
|
evidence_id: evidenceIdSchema,
|
|
graph_id: graphIdSchema,
|
|
requested_module_path: selectorTextSchema,
|
|
requested_export_name: selectorTextSchema,
|
|
candidates: z.array(exportCandidateSchema),
|
|
omitted_candidates: z.number().int().min(0),
|
|
};
|
|
const selectorResultSchema = z.union([
|
|
z.strictObject({
|
|
...selectorResultShape,
|
|
status: z.literal("selected"),
|
|
selected_node_id: nodeIdSchema,
|
|
}),
|
|
z.strictObject({
|
|
...selectorResultShape,
|
|
status: z.enum(["missing", "ambiguous"]),
|
|
selected_node_id: z.null(),
|
|
}),
|
|
z.strictObject({
|
|
...selectorResultShape,
|
|
status: z.literal("unavailable"),
|
|
selected_node_id: nodeIdSchema.nullable(),
|
|
}),
|
|
]);
|
|
|
|
const valueAvailabilitySchema = z.discriminatedUnion("availability", [
|
|
z.strictObject({ availability: z.literal("absent") }),
|
|
z.strictObject({
|
|
availability: z.literal("literal"),
|
|
value: semanticPrimitiveSchema,
|
|
}),
|
|
z.strictObject({
|
|
availability: z.literal("union"),
|
|
values: z.array(semanticPrimitiveSchema).min(1),
|
|
}),
|
|
z.strictObject({
|
|
availability: z.literal("unknown"),
|
|
reason: textSchema,
|
|
}),
|
|
]);
|
|
|
|
const discriminantSchema = z.strictObject({
|
|
path: jsonPointerSchema,
|
|
value: semanticPrimitiveSchema,
|
|
});
|
|
|
|
const comparisonChangeSchema = z.strictObject({
|
|
change_id: prefixedDigestSchema("jesc_change"),
|
|
status: z.enum(["added", "removed", "changed", "unknown"]),
|
|
path: jsonPointerSchema,
|
|
discriminant: discriminantSchema.nullable(),
|
|
left: valueAvailabilitySchema,
|
|
right: valueAvailabilitySchema,
|
|
left_source_range: sourceRangeSchema.nullable(),
|
|
right_source_range: sourceRangeSchema.nullable(),
|
|
evidence_links: z.array(evidenceIdSchema).length(2),
|
|
limitations: z.array(textSchema),
|
|
});
|
|
|
|
/** Static export-return comparison with explicit unknown semantics. */
|
|
export const javaScriptExportShapeComparisonResultSchema = z.strictObject({
|
|
comparison_id: prefixedDigestSchema("jesc"),
|
|
left: selectorResultSchema,
|
|
right: selectorResultSchema,
|
|
summary: z.strictObject({
|
|
added: z.number().int().min(0),
|
|
removed: z.number().int().min(0),
|
|
changed: z.number().int().min(0),
|
|
unknown: z.number().int().min(0),
|
|
}),
|
|
changes: z.array(comparisonChangeSchema),
|
|
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",
|
|
]),
|
|
paired_variants: z.number().int().min(0),
|
|
unpaired_left_variants: z.number().int().min(0),
|
|
unpaired_right_variants: z.number().int().min(0),
|
|
omitted_left_variants: z.number().int().min(0),
|
|
omitted_right_variants: z.number().int().min(0),
|
|
left_source_omitted_variants: z.number().int().min(0).nullable(),
|
|
right_source_omitted_variants: z.number().int().min(0).nullable(),
|
|
left_omitted_fields: z.number().int().min(0),
|
|
right_omitted_fields: z.number().int().min(0),
|
|
left_omitted_property_coverage: z.number().int().min(0),
|
|
right_omitted_property_coverage: z.number().int().min(0),
|
|
omitted_candidates: z.number().int().min(0),
|
|
omitted_changes: z.number().int().min(0),
|
|
}),
|
|
evidence_links: z.array(evidenceIdSchema).length(2),
|
|
limitations: z.array(textSchema),
|
|
});
|
|
|
|
export type ProjectedExportReturnShapes = z.output<
|
|
typeof projectedExportReturnShapesSchema
|
|
>;
|
|
export type JavaScriptExportShapeComparisonResult = z.output<
|
|
typeof javaScriptExportShapeComparisonResultSchema
|
|
>;
|
|
export type JavaScriptExportShapeComparisonChange = z.output<
|
|
typeof comparisonChangeSchema
|
|
>;
|