mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
Remove forwarding wrappers and shadow aliases, migrate callers and verification scripts to their owners, and share one exhaustive semantic resource-bound projection.
164 lines
6.6 KiB
TypeScript
164 lines
6.6 KiB
TypeScript
import { digestCanonicalValue } from "../canonicalDigest.js";
|
|
import { uniqueSorted } from "../canonicalOrdering.js";
|
|
import type { Evidence } from "../evidence.js";
|
|
import { buildJavaScriptApplicationChangeGraph } from "./javascriptApplicationChangeGraph.js";
|
|
import {
|
|
applicationVersionComparisonResultSchema,
|
|
type ApplicationVersionComparisonResult,
|
|
} from "./javascriptApplicationVersionComparisonSchemas.js";
|
|
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
|
|
import { classifyJavaScriptApplicationVersions } from "./javascriptApplicationVersionItems.js";
|
|
import { matchJavaScriptApplicationVersions } from "./javascriptApplicationVersionKeys.js";
|
|
|
|
/** Pure inputs after application-layer Evidence authentication. */
|
|
export interface ApplicationVersionComparisonProjectionInput {
|
|
readonly left: {
|
|
readonly evidenceId: string;
|
|
readonly rootArtifactSha256: string;
|
|
readonly graph: JavaScriptApplicationGraph;
|
|
};
|
|
readonly right: {
|
|
readonly evidenceId: string;
|
|
readonly rootArtifactSha256: string;
|
|
readonly graph: JavaScriptApplicationGraph;
|
|
};
|
|
readonly leftNativeEvidence: readonly Evidence[];
|
|
readonly rightNativeEvidence: readonly Evidence[];
|
|
}
|
|
|
|
/** Compare application entities with ordered unique-only identity tiers. */
|
|
export const compareJavaScriptApplicationVersions = (
|
|
input: ApplicationVersionComparisonProjectionInput,
|
|
): ApplicationVersionComparisonResult => {
|
|
const matching = matchJavaScriptApplicationVersions(
|
|
input.left.graph.nodes,
|
|
input.right.graph.nodes,
|
|
);
|
|
const projection = classifyJavaScriptApplicationVersions(matching, {
|
|
leftGraph: input.left.graph,
|
|
rightGraph: input.right.graph,
|
|
leftEvidenceId: input.left.evidenceId,
|
|
rightEvidenceId: input.right.evidenceId,
|
|
leftNativeEvidence: input.leftNativeEvidence,
|
|
rightNativeEvidence: input.rightNativeEvidence,
|
|
});
|
|
const changeGraph = buildJavaScriptApplicationChangeGraph({
|
|
left: input.left.graph,
|
|
right: input.right.graph,
|
|
leftEvidenceId: input.left.evidenceId,
|
|
rightEvidenceId: input.right.evidenceId,
|
|
items: projection.items,
|
|
});
|
|
const evidenceLinks = uniqueSorted(
|
|
projection.items.flatMap(({ evidence_links: links }) => links),
|
|
);
|
|
const omissions = {
|
|
left_graph_omitted_count: input.left.graph.coverage.omitted_count,
|
|
right_graph_omitted_count: input.right.graph.coverage.omitted_count,
|
|
};
|
|
const coverage = comparisonCoverage(input, omissions);
|
|
const semantic = {
|
|
left: {
|
|
evidence_id: input.left.evidenceId,
|
|
graph_id: input.left.graph.graph_id,
|
|
root_artifact_sha256: input.left.rootArtifactSha256,
|
|
},
|
|
right: {
|
|
evidence_id: input.right.evidenceId,
|
|
graph_id: input.right.graph.graph_id,
|
|
root_artifact_sha256: input.right.rootArtifactSha256,
|
|
},
|
|
summary: summary(projection.items),
|
|
matching: matchingSummary(projection.items),
|
|
items: projection.items,
|
|
graph: changeGraph.graph,
|
|
coverage,
|
|
evidence_links: evidenceLinks,
|
|
limitations: comparisonLimitations(input),
|
|
};
|
|
return applicationVersionComparisonResultSchema.parse({
|
|
...semantic,
|
|
comparison_id: `javc_${digestCanonicalValue(semantic, "Application version comparison")}`,
|
|
});
|
|
};
|
|
|
|
const summary = (
|
|
items: readonly ApplicationVersionComparisonResult["items"][number][],
|
|
): ApplicationVersionComparisonResult["summary"] => ({
|
|
unchanged: countStatus(items, "unchanged"),
|
|
added: countStatus(items, "added"),
|
|
removed: countStatus(items, "removed"),
|
|
changed: countStatus(items, "changed"),
|
|
unknown: countStatus(items, "unknown"),
|
|
});
|
|
|
|
const countStatus = (
|
|
items: readonly ApplicationVersionComparisonResult["items"][number][],
|
|
status: ApplicationVersionComparisonResult["items"][number]["status"],
|
|
): number => items.filter((item) => item.status === status).length;
|
|
|
|
const matchingSummary = (
|
|
items: readonly ApplicationVersionComparisonResult["items"][number][],
|
|
): ApplicationVersionComparisonResult["matching"] => ({
|
|
exact_node_identity: countBasis(items, "exact-node-identity"),
|
|
exact_content_digest: countBasis(items, "exact-content-digest"),
|
|
exact_module_source_digest: countBasis(items, "exact-module-source-digest"),
|
|
source_map_identity: countBasis(items, "source-map-identity"),
|
|
structural_fingerprint: countBasis(items, "structural-fingerprint"),
|
|
semantic_key: countBasis(items, "semantic-key"),
|
|
ambiguous: items.filter(({ match }) => match.status === "ambiguous").length,
|
|
unmatched: items.filter(({ match }) => match.status === "unmatched").length,
|
|
});
|
|
|
|
const countBasis = (
|
|
items: readonly ApplicationVersionComparisonResult["items"][number][],
|
|
basis: ApplicationVersionComparisonResult["items"][number]["match"]["basis"],
|
|
): number => items.filter(({ match }) => match.basis === basis).length;
|
|
|
|
type ComparisonOmissions = ApplicationVersionComparisonResult["coverage"];
|
|
type ComparisonOmissionCounts = Pick<
|
|
ComparisonOmissions,
|
|
"left_graph_omitted_count" | "right_graph_omitted_count"
|
|
>;
|
|
|
|
const comparisonCoverage = (
|
|
input: ApplicationVersionComparisonProjectionInput,
|
|
omissions: ComparisonOmissionCounts,
|
|
): ApplicationVersionComparisonResult["coverage"] => {
|
|
const context = {
|
|
left_graph_status: input.left.graph.coverage.status,
|
|
right_graph_status: input.right.graph.coverage.status,
|
|
};
|
|
const sourceGraphsComplete =
|
|
input.left.graph.coverage.status === "complete" &&
|
|
input.right.graph.coverage.status === "complete";
|
|
const sourceGraphsTruncated =
|
|
input.left.graph.coverage.truncated || input.right.graph.coverage.truncated;
|
|
return {
|
|
...context,
|
|
...omissions,
|
|
status: sourceGraphsComplete
|
|
? "complete-within-inputs"
|
|
: sourceGraphsTruncated
|
|
? "truncated"
|
|
: "partial",
|
|
};
|
|
};
|
|
|
|
const comparisonLimitations = (
|
|
input: ApplicationVersionComparisonProjectionInput,
|
|
): string[] =>
|
|
uniqueSorted([
|
|
"Application comparison never uses bundler module ordinals as persistent identity and performs no fuzzy pairing.",
|
|
"Exact digest equality proves byte identity only; source-map, structural-fingerprint, and semantic-key matches retain inferred confidence.",
|
|
"Minified names and chunk locations are not assumed stable across versions.",
|
|
"The comparison reads retained graphs and Evidence only; it does not execute either application.",
|
|
"Native Evidence is linked only by exact subject digest; provider snapshot reuse remains target/provider/profile exact.",
|
|
...(input.left.graph.coverage.status === "complete" &&
|
|
input.right.graph.coverage.status === "complete"
|
|
? []
|
|
: [
|
|
"At least one source graph is incomplete; unmatched entities on the opposite side remain unknown rather than added or removed.",
|
|
]),
|
|
]);
|