import { posix } from "node:path"; import { z } from "zod"; import { canonicalJson } from "./comparisonSemantics.js"; import { digestCanonicalValue } from "./canonicalDigest.js"; import { createReferenceSourcePathLookup, isPortableAbsoluteReferenceTarget, } from "./referenceSourcePathIdentity.js"; import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js"; import { digestSchema } from "./../domain/digests.js"; import { prefixedDigestSchema } from "./../domain/digests.js"; const boundedTextSchema = z.string().min(1); const relativePathSchema = z .string() .min(1) .refine( (path) => !path.startsWith("/") && !path.includes("\0") && path .split("/") .every((part) => part !== "" && part !== "." && part !== ".."), "Expected a normalized relative POSIX path without traversal", ); const classificationSchema = z.enum([ "source", "test", "config", "manifest", "generated", "vendor", "documentation", "unknown", ]); const entryBaseShape = { path: relativePathSchema, classifications: z.array(classificationSchema).min(1), limitations: z.array(boundedTextSchema), }; const sourceFileBaseShape = { ...entryBaseShape, kind: z.literal("file"), language: z.string().min(1).nullable(), }; const sourceFileSchema = z.union([ z.strictObject({ ...sourceFileBaseShape, sha256: digestSchema, size: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER), content_state: z.literal("hashed"), }), z.strictObject({ ...sourceFileBaseShape, sha256: z.null(), size: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(), content_state: z.enum([ "redacted-secret", "excluded", "unreadable", "unknown", ]), }), ]); const sourceDirectorySchema = z.strictObject({ ...entryBaseShape, kind: z.literal("directory"), tree_state: z.enum([ "enumerated", "partial", "excluded", "unreadable", "unknown", ]), }); const symlinkTargetSchema = z .string() .min(1) .refine( (target) => !target.includes("\0") && (isPortableAbsoluteReferenceTarget(target) || (!target.startsWith("/") && posix.normalize(target) === target)), "Expected a normalized POSIX symlink target", ); const sourceSymlinkSchema = z.union([ z.strictObject({ ...entryBaseShape, kind: z.literal("symlink"), target: symlinkTargetSchema, target_state: z.enum(["internal", "external", "missing"]), }), z.strictObject({ ...entryBaseShape, kind: z.literal("symlink"), target: z.null(), target_state: z.enum(["unreadable", "unknown"]), }), ]); const sourceEntrySchema = z.union([ sourceFileSchema, sourceDirectorySchema, sourceSymlinkSchema, z.strictObject({ ...entryBaseShape, kind: z.literal("unknown"), }), ]); const sourceRelationshipSchema = z.strictObject({ from_path: relativePathSchema, to: z.string().min(1), kind: z.enum(["imports", "requires", "references", "declares-module"]), resolution: z.enum(["internal", "external", "unresolved", "unknown"]), parse_state: z.enum(["parsed", "partial", "unknown"]), }); const parseFailureSchema = z.strictObject({ path: relativePathSchema, parser: z.string().min(1), reason: boundedTextSchema, }); const exclusionSchema = z.discriminatedUnion("reason", [ z.strictObject({ path: relativePathSchema, entry_kind: z .enum(["file", "directory", "symlink", "other", "unknown"]) .exactOptional(), reason: z.enum([ "configured-secret", "project-ignored", "default-ignored", "caller-excluded", ]), pattern: boundedTextSchema, }), z.strictObject({ path: relativePathSchema, entry_kind: z .enum(["file", "directory", "symlink", "other", "unknown"]) .exactOptional(), reason: z.enum(["symlink-escape", "unreadable"]), }), ]); const vcsSchema = z .discriminatedUnion("kind", [ z.strictObject({ kind: z.literal("git"), head: z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u), dirty: z.boolean().nullable(), }), z.strictObject({ kind: z.literal("none"), head: z.null(), dirty: z.null(), }), z.strictObject({ kind: z.literal("unknown"), head: z.null(), dirty: z.null(), }), ]) .nullable(); const graphShape = { schema: z.literal("HistoricalSourceGraph"), authority: z.literal("historical-reference"), root_alias: z.literal("$REFERENCE_ROOT"), root_sha256: digestSchema, inventory_state: z.enum(["complete", "partial", "unknown"]), entries: z.array(sourceEntrySchema), relationships: z.array(sourceRelationshipSchema), parse_failures: z.array(parseFailureSchema), exclusions: z.array(exclusionSchema), languages: z.array(z.string().min(1)), manifests: z.array(relativePathSchema), vcs: vcsSchema, provenance: z.strictObject({ importer: z.string().min(1), importer_version: z.string().min(1).nullable(), caller: z .string() .min(1) .regex(/^[\w .:@\x2f+\x2d]+$/u), }), limitations: z.array(boundedTextSchema), }; const historicalSourceGraphStructureSchema = z .strictObject(graphShape) .superRefine(checkGraphInvariants); export const historicalSourceGraphSchema = historicalSourceGraphStructureSchema.superRefine((graph, context) => { if ( computeRootSha256(graph.entries, graph.exclusions) !== graph.root_sha256 ) context.addIssue({ code: "custom", message: "Historical source root commitment does not match entries", path: ["root_sha256"], }); }); const historicalSourceGraphInputSchema = z.strictObject({ ...graphShape, root_sha256: z.never().optional(), }); const historicalSourceManifestBaseSchema = z.strictObject({ schema: z.literal("HistoricalSourceManifest"), authority: z.literal("historical-reference"), manifest_id: prefixedDigestSchema("hsm"), graph_sha256: digestSchema, root_sha256: digestSchema, inventory_state: z.enum(["complete", "partial", "unknown"]), entry_count: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER), relationship_count: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER), }); export type HistoricalSourceGraph = z.infer; export type HistoricalSourceGraphInput = z.infer< typeof historicalSourceGraphInputSchema >; export type HistoricalSourceManifest = z.infer< typeof historicalSourceManifestBaseSchema >; /** Stable identity used to sort and deduplicate source parse failures. */ export const historicalSourceParseFailureKey = ( failure: HistoricalSourceGraphInput["parse_failures"][number], ): string => `${failure.path}\u0000${failure.parser}\u0000${failure.reason}`; const relationshipKey = ( relationship: z.infer, ): string => `${relationship.from_path}\u0000${relationship.to}\u0000${relationship.kind}\u0000${relationship.resolution}\u0000${relationship.parse_state}`; const checkSortedUnique = ( values: readonly string[], field: string, context: z.RefinementCtx, ): void => { for (let index = 1; index < values.length; index += 1) if ( compareUnicodeCodePoints(values[index - 1] ?? "", values[index] ?? "") >= 0 ) { context.addIssue({ code: "custom", message: `${field} must be sorted and unique by Unicode code point`, path: [field, index], }); return; } }; /** Derive the canonical language index from inventoried source files. */ export const historicalSourceLanguages = ( entries: readonly z.infer[], ): string[] => [ ...new Set( entries.flatMap((entry) => entry.kind === "file" && entry.language !== null ? [entry.language] : [], ), ), ].sort(compareUnicodeCodePoints); /** Derive the canonical manifest path index from inventoried source files. */ export const historicalSourceManifests = ( entries: readonly z.infer[], ): string[] => entries .flatMap((entry) => entry.kind === "file" && entry.classifications.includes("manifest") ? [entry.path] : [], ) .sort(compareUnicodeCodePoints); type GraphStructure = z.infer; const checkRelationships = ( graph: GraphStructure, paths: ReadonlySet, context: z.RefinementCtx, ): void => { const files = new Set( graph.entries.flatMap((entry) => entry.kind === "file" ? [entry.path] : [], ), ); for (const [index, relationship] of graph.relationships.entries()) { if (!files.has(relationship.from_path)) context.addIssue({ code: "custom", message: "Relationship source must name an inventoried file", path: ["relationships", index, "from_path"], }); if (relationship.resolution !== "internal") continue; const parsed = relativePathSchema.safeParse(relationship.to); if (!parsed.success || !paths.has(parsed.data)) context.addIssue({ code: "custom", message: "Internal relationship target must be normalized and inventoried", path: ["relationships", index, "to"], }); } }; const checkSymlinks = ( graph: GraphStructure, paths: ReadonlySet, context: z.RefinementCtx, ): void => { const lookupPath = createReferenceSourcePathLookup(paths); const excludedPaths = createReferenceSourcePathLookup( graph.exclusions.map(({ path }) => path), ); const excludedKinds = new Map< string, Set<"file" | "directory" | "symlink" | "other" | "unknown" | undefined> >(); for (const { path, entry_kind } of graph.exclusions) { const kinds = excludedKinds.get(path) ?? new Set(); kinds.add(entry_kind); excludedKinds.set(path, kinds); } for (const [index, entry] of graph.entries.entries()) { if (entry.kind !== "symlink") continue; if (entry.target === null) continue; const resolved = posix.normalize( posix.join(posix.dirname(entry.path), entry.target), ); const escapesRoot = resolved.startsWith("../") || resolved === ".."; // The root is implicit; every other internal target needs an exact or // uniquely Unicode-equivalent recorded inventory identity. const inventoried = resolved === "." || lookupPath.resolve(resolved) !== undefined; const exactExcluded = excludedPaths.resolve(resolved); let excluded = exactExcluded !== undefined; for ( let candidate = posix.dirname(resolved); candidate !== "." && candidate !== "/"; candidate = posix.dirname(candidate) ) { const excludedAncestor = excludedPaths.resolve(candidate); if (excludedAncestor === undefined) continue; const kinds = excludedKinds.get(excludedAncestor); if (kinds?.size !== 1 || !kinds.has("directory")) { excluded = false; break; } excluded = true; } const ownedRelativeTarget = !entry.target.startsWith("/") && !escapesRoot && inventoried; const absolute = isPortableAbsoluteReferenceTarget(entry.target) && !(entry.target_state === "internal" && ownedRelativeTarget); if ( (entry.target_state === "external" && !absolute) || (entry.target_state === "internal" && absolute) ) context.addIssue({ code: "custom", message: "Symlink target path does not match its target state", path: ["entries", index, "target"], }); if (entry.target_state !== "internal") continue; if (escapesRoot || (!inventoried && !excluded)) context.addIssue({ code: "custom", message: "Internal symlink target must resolve to an inventoried entry or a recorded exclusion", path: ["entries", index, "target"], }); } }; const checkCompleteInventory = ( graph: GraphStructure, context: z.RefinementCtx, ): void => { if (graph.inventory_state !== "complete") return; const partialEntry = graph.entries.some( (entry) => entry.limitations.length > 0 || (entry.kind === "file" && entry.content_state !== "hashed") || (entry.kind === "directory" && entry.tree_state !== "enumerated") || (entry.kind === "symlink" && entry.target_state !== "internal") || entry.kind === "unknown", ); const partialRelationship = graph.relationships.some( ({ parse_state, resolution }) => parse_state !== "parsed" || ["unresolved", "unknown"].includes(resolution), ); if ( !partialEntry && !partialRelationship && graph.parse_failures.length === 0 && graph.exclusions.length === 0 && graph.limitations.length === 0 ) return; context.addIssue({ code: "custom", message: "Complete inventory cannot contain partial states, exclusions, failures, or limitations", path: ["inventory_state"], }); }; function checkGraphInvariants( graph: GraphStructure, context: z.RefinementCtx, ): void { checkSortedUnique( graph.entries.map(({ path }) => path), "entries", context, ); for (const [index, entry] of graph.entries.entries()) checkSortedUnique( entry.classifications, `entries.${index}.classifications`, context, ); checkSortedUnique(graph.languages, "languages", context); checkSortedUnique(graph.manifests, "manifests", context); checkSortedUnique( graph.relationships.map(relationshipKey), "relationships", context, ); checkSortedUnique( graph.parse_failures.map(historicalSourceParseFailureKey), "parse_failures", context, ); checkSortedUnique( graph.exclusions.map( (exclusion) => `${exclusion.path}\u0000${exclusion.reason}\u0000${"pattern" in exclusion ? exclusion.pattern : ""}`, ), "exclusions", context, ); if ( canonicalJson(graph.languages, "Reference source graph") !== canonicalJson( historicalSourceLanguages(graph.entries), "Reference source graph", ) ) context.addIssue({ code: "custom", message: "languages must be derived from file entries", path: ["languages"], }); if ( canonicalJson(graph.manifests, "Reference source graph") !== canonicalJson( historicalSourceManifests(graph.entries), "Reference source graph", ) ) context.addIssue({ code: "custom", message: "manifests must be derived from classified file entries", path: ["manifests"], }); const paths = new Set(graph.entries.map(({ path }) => path)); checkRelationships(graph, paths, context); checkSymlinks(graph, paths, context); checkCompleteInventory(graph, context); } const rootCommitment = ( entries: readonly z.infer[], exclusions: readonly z.infer[], ): unknown => ({ entries: entries.map((entry) => { switch (entry.kind) { case "file": return { kind: entry.kind, path: entry.path, sha256: entry.sha256, size: entry.size, content_state: entry.content_state, }; case "directory": return { kind: entry.kind, path: entry.path, tree_state: entry.tree_state, }; case "symlink": return { kind: entry.kind, path: entry.path, target: entry.target, target_state: entry.target_state, }; case "unknown": return { kind: entry.kind, path: entry.path, }; } }), exclusions, }); const computeRootSha256 = ( entries: readonly z.infer[], exclusions: readonly z.infer[], ): string => digestCanonicalValue( rootCommitment(entries, exclusions), "Reference source graph", ); /** Build and internally commit a normalized historical source graph. */ export const createHistoricalSourceGraph = ( input: unknown, ): HistoricalSourceGraph => { const parsed = historicalSourceGraphInputSchema.parse(input); return historicalSourceGraphSchema.parse({ ...parsed, root_sha256: computeRootSha256(parsed.entries, parsed.exclusions), }); }; /** Parse a graph and reject stale or caller-invented root commitments. */ export const parseHistoricalSourceGraph = ( input: unknown, ): HistoricalSourceGraph => historicalSourceGraphSchema.parse(input); /** Compute a deterministic graph commitment containing no absolute root path. */ export const computeHistoricalSourceGraphSha256 = (input: unknown): string => digestCanonicalValue( parseHistoricalSourceGraph(input), "Reference source graph", ); /** Build a deterministic, relocation-independent manifest for a source graph. */ export const createHistoricalSourceManifest = ( input: unknown, ): HistoricalSourceManifest => { const graph = parseHistoricalSourceGraph(input); const semantic = { schema: "HistoricalSourceManifest" as const, authority: "historical-reference" as const, graph_sha256: computeHistoricalSourceGraphSha256(graph), root_sha256: graph.root_sha256, inventory_state: graph.inventory_state, entry_count: graph.entries.length, relationship_count: graph.relationships.length, }; return historicalSourceManifestBaseSchema.parse({ ...semantic, manifest_id: `hsm_${digestCanonicalValue(semantic, "Reference source graph")}`, }); }; /** Parse and verify a historical source manifest's semantic identifier. */ export const parseHistoricalSourceManifest = ( input: unknown, ): HistoricalSourceManifest => { const manifest = historicalSourceManifestBaseSchema.parse(input); const { manifest_id: manifestId, ...semantic } = manifest; if ( `hsm_${digestCanonicalValue(semantic, "Reference source graph")}` !== manifestId ) throw new TypeError("Historical source manifest identifier does not match"); return manifest; };