feat(javascript): compare source with shipped bundles

This commit is contained in:
morluto 2026-07-24 13:17:19 +00:00
parent 8271650945
commit 697be942e8
19 changed files with 1692 additions and 16 deletions

View File

@ -3,10 +3,12 @@ import { z } from "zod";
import {
compareApplicationVersionsRequestSchema,
compareJavaScriptExportShapesRequestSchema,
compareSourceToBundleRequestSchema,
traceApplicationFeatureRequestSchema,
traceJavaScriptSemanticsRequestSchema,
type CompareApplicationVersionsRequest,
type CompareJavaScriptExportShapesRequest,
type CompareSourceToBundleRequest,
type TraceApplicationFeatureRequest,
type TraceJavaScriptSemanticsRequest,
} from "../contracts/applicationWorkflowInputContracts.js";
@ -16,6 +18,7 @@ import { compareApplicationVersionsInputSchema } from "../domain/javascriptAppli
import { compareJavaScriptExportShapesInputSchema } from "../domain/javascriptExportShapeComparisonSchemas.js";
import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
import { traceJavaScriptSemanticsInputSchema } from "../domain/javascriptSemanticTraceSchemas.js";
import { compareSourceToBundleInputSchema } from "../domain/sourceToBundleComparisonSchemas.js";
import { err, ok, type Result } from "../domain/result.js";
import {
resolveEvidenceReferences,
@ -48,6 +51,7 @@ type ComparisonInput = z.output<typeof compareApplicationVersionsInputSchema>;
type ExportShapeComparisonInput = z.output<
typeof compareJavaScriptExportShapesInputSchema
>;
type SourceToBundleInput = z.output<typeof compareSourceToBundleInputSchema>;
/** Parse and resolve one trace adapter request into its canonical domain input. */
export const resolveTraceApplicationFeatureRequest = (
@ -180,6 +184,42 @@ export const resolveCompareApplicationVersionsRequestValidated = (
: invalid("compare_application_versions", parsed.error, raw);
};
/** Parse and resolve one historical-source comparison adapter request. */
export const resolveCompareSourceToBundleRequest = (
input: unknown,
lookup?: EvidenceLookup,
): Result<SourceToBundleInput, AnalysisError> => {
const parsed = compareSourceToBundleRequestSchema.safeParse(input);
return parsed.success
? resolveCompareSourceToBundleRequestValidated(parsed.data, lookup)
: invalid("compare_source_to_bundle", parsed.error, input);
};
/** Resolve a historical-source comparison request parsed by its public contract. */
export const resolveCompareSourceToBundleRequestValidated = (
input: CompareSourceToBundleRequest,
lookup?: EvidenceLookup,
): Result<SourceToBundleInput, AnalysisError> => {
const application = graphEvidence(
input.application,
input.application_evidence_id,
lookup,
);
if (!application.ok) return application;
const raw = {
reference: input.reference,
application: application.value,
limits: input.limits,
...(input.unknown_registry_approved === undefined
? {}
: { unknown_registry_approved: input.unknown_registry_approved }),
};
const parsed = compareSourceToBundleInputSchema.safeParse(raw);
return parsed.success
? ok(parsed.data)
: invalid("compare_source_to_bundle", parsed.error, raw);
};
/** Parse and resolve an export-shape adapter request into canonical input. */
export const resolveCompareJavaScriptExportShapesRequest = (
input: unknown,
@ -221,7 +261,8 @@ export const resolveCompareJavaScriptExportShapesRequestValidated = (
const graphEvidence = (
evidence:
| CompareApplicationVersionsRequest["left"]
| CompareJavaScriptExportShapesRequest["left"],
| CompareJavaScriptExportShapesRequest["left"]
| CompareSourceToBundleRequest["application"],
evidenceId: string | undefined,
lookup: EvidenceLookup | undefined,
) => {

View File

@ -8,6 +8,7 @@ import type { JavaScriptExportShapeComparisonResult } from "../domain/javascript
import type { ApplicationFeatureTraceResult } from "../domain/javascriptFeatureTraceSchemas.js";
import type { JavaScriptSemanticTraceResult } from "../domain/javascriptSemanticTraceSchemas.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import type { SourceToBundleComparisonResult } from "../domain/sourceToBundleComparisonSchemas.js";
import { JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER } from "./InvestigationProviders.js";
/** Create derived Evidence for one bounded application-graph feature trace. */
@ -64,6 +65,24 @@ export const createApplicationVersionComparisonEvidence = (
evidenceLinks: result.evidence_links,
});
/** Create derived Evidence for one historical-source to bundle comparison. */
export const createSourceToBundleComparisonEvidence = (
parameters: EvidenceObservation["parameters"],
result: SourceToBundleComparisonResult,
): Evidence =>
createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, {
predicateType: "rea.source-to-bundle-comparison/v1",
operation: "compare_source_to_bundle",
parameters,
result: jsonValueSchema.parse(result),
rawResult: null,
confidence: "inferred",
authority: "analyst-inference",
environment: null,
limitations: result.limitations,
evidenceLinks: result.evidence_links,
});
/** Create derived Evidence for one bounded static export-shape comparison. */
export const createJavaScriptExportShapeComparisonEvidence = (
parameters: EvidenceObservation["parameters"],

View File

@ -14,6 +14,8 @@ import { compareJavaScriptExportShapesInputSchema } from "../domain/javascriptEx
import { traceApplicationFeature } from "../domain/javascriptFeatureTrace.js";
import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
import { err, ok, type Result } from "../domain/result.js";
import { compareSourceToBundle } from "../domain/sourceToBundleComparison.js";
import { compareSourceToBundleInputSchema } from "../domain/sourceToBundleComparisonSchemas.js";
import {
parseApplicationGraphEvidence,
parseNativeApplicationEvidence,
@ -22,6 +24,7 @@ import {
createApplicationFeatureTraceEvidence,
createApplicationVersionComparisonEvidence,
createJavaScriptExportShapeComparisonEvidence,
createSourceToBundleComparisonEvidence,
} from "./JavaScriptApplicationWorkflowEvidence.js";
/** Authenticate Evidence and derive one bounded cross-layer feature trace. */
@ -141,6 +144,37 @@ export const compareApplicationVersionsEvidenceValidated = (
}
};
/** Compare source and bundle inputs already parsed by a trusted adapter. */
export const compareSourceToBundleEvidenceValidated = (
input: z.output<typeof compareSourceToBundleInputSchema>,
): Result<Evidence, AnalysisError> => {
const operation = "compare_source_to_bundle";
try {
const application = parseApplicationGraphEvidence(input.application);
const result = compareSourceToBundle({
reference: input.reference,
application: {
evidenceId: application.evidence.evidence_id,
rootArtifactSha256: application.rootArtifactSha256,
graph: application.graph,
},
limits: input.limits,
});
return ok(
createSourceToBundleComparisonEvidence(
{
reference_root_sha256: input.reference.root_sha256,
application_evidence_id: application.evidence.evidence_id,
limits: input.limits,
},
result,
),
);
} catch (cause: unknown) {
return workflowFailure(operation, cause);
}
};
/**
* Authenticate both graphs and compare one exact export return shape.
* @public

View File

@ -5,6 +5,7 @@ import { compareApplicationVersionsInputSchema } from "../domain/javascriptAppli
import { compareJavaScriptExportShapesInputSchema } from "../domain/javascriptExportShapeComparisonSchemas.js";
import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
import { javaScriptSemanticQueryInputSchema } from "../domain/javascriptSemanticQuerySchemas.js";
import { compareSourceToBundleInputSchema } from "../domain/sourceToBundleComparisonSchemas.js";
const evidenceIdSchema = z
.string()
@ -102,6 +103,25 @@ export const compareApplicationVersionsRequestSchema = z
]);
});
/** Historical-source comparison accepting full application Evidence or a ledger reference. */
export const compareSourceToBundleRequestSchema = z
.strictObject({
reference: compareSourceToBundleInputSchema.shape.reference,
application: evidenceSchema.optional(),
application_evidence_id: evidenceIdSchema.optional(),
limits: compareSourceToBundleInputSchema.shape.limits,
unknown_registry_approved:
compareSourceToBundleInputSchema.shape.unknown_registry_approved,
})
.superRefine((input, context) => {
requireExactlyOne(context, [
"application",
input.application,
"application_evidence_id",
input.application_evidence_id,
]);
});
/** MCP/CLI export-shape request accepting full Evidence or ledger references. */
export const compareJavaScriptExportShapesRequestSchema = z
.strictObject({
@ -145,6 +165,9 @@ export type TraceJavaScriptSemanticsRequest = z.output<
export type CompareApplicationVersionsRequest = z.output<
typeof compareApplicationVersionsRequestSchema
>;
export type CompareSourceToBundleRequest = z.output<
typeof compareSourceToBundleRequestSchema
>;
export type CompareJavaScriptExportShapesRequest = z.output<
typeof compareJavaScriptExportShapesRequestSchema
>;

View File

@ -4,6 +4,7 @@ import {
JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE,
JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE,
} from "./javascriptRuntimeReconciliationExample.js";
import { createHistoricalSourceGraph } from "../domain/referenceSourceGraph.js";
const reconciliation = reconcileJavaScriptRuntime(
JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE,
@ -66,6 +67,48 @@ export const JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE = {
right_evidence_id: SECOND_EXAMPLE_EVIDENCE_ID,
};
/** Historical source inventory compared with one producer-returned graph ID. */
export const SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE = {
reference: createHistoricalSourceGraph({
schema: "HistoricalSourceGraph/v1",
authority: "historical-reference",
root_alias: "$REFERENCE_ROOT",
inventory_state: "complete",
entries: [
{
path: "src",
kind: "directory",
classifications: ["source"],
tree_state: "enumerated",
limitations: [],
},
{
path: "src/main.ts",
kind: "file",
sha256: "2".repeat(64),
size: 128,
language: "TypeScript",
classifications: ["source"],
content_state: "hashed",
limitations: [],
},
],
relationships: [],
parse_failures: [],
exclusions: [],
languages: ["TypeScript"],
manifests: [],
vcs: { kind: "none", head: null, dirty: null },
provenance: {
importer: "rea",
importer_version: "1",
caller: "contract-example",
},
limitations: [],
}),
application_evidence_id: EXAMPLE_EVIDENCE_ID,
};
/** Exact static return-shape comparison using producer-returned Evidence IDs. */
export const JAVASCRIPT_EXPORT_SHAPE_COMPARISON_EXAMPLE = {
left_evidence_id: EXAMPLE_EVIDENCE_ID,

View File

@ -16,7 +16,10 @@ import {
inspectRouteProperty,
inspectRoleProperty,
} from "./javascriptStaticAnalysisCalls.js";
import { inspectBundlerRegistration } from "./javascriptStaticAnalysisBundler.js";
import {
inspectBundlerRegistration,
inspectEsbuildWrapper,
} from "./javascriptStaticAnalysisBundler.js";
import { finalizeLocatedFindings } from "./javascriptStaticAnalysisFindings.js";
import {
createJavaScriptAnalysisAccumulator,
@ -167,8 +170,9 @@ const inspectNode = (
inspectElectronStaticNode(node, findings);
if (t.isCallExpression(node)) {
inspectBundlerRegistration(source, node, accumulator, limits);
inspectCall(node, findings);
} else if (t.isNewExpression(node)) inspectCall(node, findings);
inspectEsbuildWrapper(source, node, accumulator, limits);
inspectCall(source, node, findings);
} else if (t.isNewExpression(node)) inspectCall(source, node, findings);
if (
(t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) &&
node.source !== undefined

View File

@ -22,6 +22,7 @@ import {
import type { JavaScriptAnalysisAccumulator as AnalysisAccumulator } from "./javascriptStaticAnalysisState.js";
import type {
JavaScriptBundlerModule,
JavaScriptBundlerRegistration,
JavaScriptStaticAnalysisLimits,
} from "./javascriptStaticAnalysisTypes.js";
@ -105,6 +106,56 @@ export const inspectBundlerRegistration = (
);
};
/** Inspect esbuild helper wrappers and recover their literal module tables. */
export const inspectEsbuildWrapper = (
source: string,
call: t.CallExpression,
accumulator: AnalysisAccumulator,
limits: JavaScriptStaticAnalysisLimits,
): void => {
const name = calleeName(call.callee);
const wrapperKind =
name === "__commonJS" || name.endsWith(".__commonJS")
? "commonjs"
: name === "__esm" || name.endsWith(".__esm")
? "esm"
: null;
const table = call.arguments[0];
if (wrapperKind === null || !t.isObjectExpression(table)) return;
const recovered = recoverBundlerModules(source, table, accumulator, limits);
if (recovered.modules.length === 0) return;
if (recovered.omittedAsyncChunkKeys > 0) {
accumulator.droppedFindings += recovered.omittedAsyncChunkKeys;
accumulator.structuralTruncation = true;
}
accumulator.unknownFindings += recovered.unknownAsyncChunkKeys;
const registration: JavaScriptBundlerRegistration = {
bundler: "esbuild",
runtime: `esbuild-${wrapperKind}`,
chunk_keys: [`${wrapperKind}@${String(call.start ?? 0)}`],
omitted_chunk_keys: 0,
unknown_chunk_keys: 0,
runtime_require_name: null,
runtime_module_cache_status: "not-observed",
entry_module_keys: [],
omitted_entry_module_keys: 0,
unknown_entry_module_keys: 0,
async_chunk_keys: recovered.asyncChunkKeys,
omitted_async_chunk_keys: recovered.omittedAsyncChunkKeys,
unknown_async_chunk_keys: recovered.unknownAsyncChunkKeys,
modules: recovered.modules.sort((left, right) =>
compareCodePoints(left.module_key, right.module_key),
),
location: range(call),
};
addBoundedFinding(
accumulator,
`registration\0${registrationKey(registration)}`,
limits.maxFindings,
() => accumulator.registrations.push(registration),
);
};
interface RecoveredBundlerModules {
readonly modules: JavaScriptBundlerModule[];
readonly asyncChunkKeys: readonly string[];

View File

@ -30,6 +30,7 @@ import type {
/** Inspect one call or new expression for references, endpoints, storage, and roles. */
export const inspectCall = (
source: string,
node: t.CallExpression | t.NewExpression,
context: FindingContext,
): void => {
@ -52,6 +53,8 @@ export const inspectCall = (
kind: "dynamic-import",
specifier: chunkLoad.specifier,
});
for (const specifier of vitePreloadDependencySpecifiers(source, node, name))
addReference(context, { node, kind: "dynamic-import", specifier });
if (name === "Worker" || name.endsWith(".Worker"))
addReference(context, { node, kind: "worker", specifier: first });
if (name.endsWith("serviceWorker.register"))
@ -118,6 +121,33 @@ const chunkLoadSpecifier = (
return { specifier: prefixedArgument("chunk:", node.arguments[0]) };
};
const vitePreloadDependencySpecifiers = (
source: string,
node: t.CallExpression | t.NewExpression,
name: string,
): readonly string[] => {
if (name !== "__vite__mapDeps" || !t.isArrayExpression(node.arguments[0]))
return [];
const indexes = node.arguments[0].elements.flatMap((element) =>
t.isNumericLiteral(element) ? [element.value] : [],
);
if (indexes.length !== node.arguments[0].elements.length) return [];
const table = vitePreloadDependencyTable(source);
return indexes.flatMap((index) => table[index] ?? []);
};
const vitePreloadDependencyTable = (source: string): readonly string[] => {
const match =
/\.\s*f\s*\|\|\s*\(\s*[^)]*?\.f\s*=\s*\[([\s\S]*?)\]\s*\)/u.exec(source);
const body = match?.[1];
if (body === undefined) return [];
return [
...body.matchAll(/"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gu),
]
.map((literal) => (literal[1] ?? literal[2] ?? "").slice(0, 4_096))
.slice(0, 256);
};
const inspectEndpointCall = (
node: t.CallExpression | t.NewExpression,
name: string,

View File

@ -450,6 +450,8 @@ const vendorPatterns = [
{ name: "webpack", patterns: ["__webpack_require__", "webpackChunk"] },
{ name: "Rspack", patterns: ["__webpack_require__.f", "rspackChunk"] },
{ name: "Vite", patterns: ["__vite__", "import.meta.hot"] },
{ name: "Rollup", patterns: ["ROLLUP_FILE_URL", "import.meta.ROLLUP"] },
{ name: "esbuild", patterns: ["__commonJS", "__esm"] },
{ name: "React", patterns: ["React.createElement", "react/jsx-runtime"] },
{ name: "Vue", patterns: ["__VUE__", "createApp("] },
{ name: "Next.js", patterns: ["__NEXT_DATA__", "/_next/"] },

View File

@ -67,9 +67,9 @@ export interface JavaScriptBundlerModule {
readonly location: JavaScriptSourceRange;
}
/** One statically recognized Webpack/Rspack chunk registration. */
/** One statically recognized bundler chunk or module registration. */
export interface JavaScriptBundlerRegistration {
readonly bundler: "webpack" | "rspack";
readonly bundler: "webpack" | "rspack" | "esbuild";
readonly runtime: string;
readonly chunk_keys: readonly string[];
readonly omitted_chunk_keys: number;

View File

@ -0,0 +1,482 @@
import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import type { HistoricalSourceGraph } from "./referenceSourceGraph.js";
import {
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
sourceToBundleComparisonResultSchema,
type CompareSourceToBundleInput,
type SourceToBundleCandidate,
type SourceToBundleComparisonItem,
type SourceToBundleComparisonResult,
} from "./sourceToBundleComparisonSchemas.js";
import {
buildSourceToBundleCandidateIndex,
candidateIdsForSource,
historicalSourceFiles,
scoreSourceToBundleCandidate,
sourceBearingNodes,
type SourceToBundleCandidateIndex,
} from "./sourceToBundleSignals.js";
interface SourceToBundleComparisonInput {
readonly reference: HistoricalSourceGraph;
readonly application: {
readonly evidenceId: string;
readonly rootArtifactSha256: string;
readonly graph: JavaScriptApplicationGraph;
};
readonly limits: CompareSourceToBundleInput["limits"];
}
interface ClassifiedMapping {
readonly status: SourceToBundleComparisonItem["status"];
readonly confidence: SourceToBundleComparisonItem["confidence"];
readonly currentNodeIds: string[];
readonly limitations: string[];
}
interface ComparisonProjection {
readonly sources: ReturnType<typeof historicalSourceFiles>;
readonly currentNodes: ReturnType<typeof sourceBearingNodes>;
readonly index: SourceToBundleCandidateIndex;
readonly absenceComplete: boolean;
readonly factors: CoverageFactors;
}
interface MappingProjection {
readonly items: SourceToBundleComparisonItem[];
readonly candidateEvaluations: number;
readonly omittedCandidateEvaluations: number;
readonly omittedCandidateReferences: number;
}
/** Compare historical source files to static source-bearing application nodes. */
export const compareSourceToBundle = (
input: SourceToBundleComparisonInput,
): SourceToBundleComparisonResult => {
const projection = projectComparison(input);
const mappings = compareMappings(input, projection);
const factors = {
...projection.factors,
omittedCandidateEvaluations: mappings.omittedCandidateEvaluations,
omittedCandidateReferences: mappings.omittedCandidateReferences,
};
const mappedCurrentIds = new Set(
mappings.items.flatMap(({ current_node_ids: nodeIds }) => nodeIds),
);
const unmappedCurrentNodeIds = projection.currentNodes
.map(({ node_id: nodeId }) => nodeId)
.filter((nodeId) => !mappedCurrentIds.has(nodeId));
const semanticResult = {
schema_version: 1 as const,
reference: {
root_sha256: input.reference.root_sha256,
inventory_state: input.reference.inventory_state,
},
application: {
evidence_id: input.application.evidenceId,
graph_id: input.application.graph.graph_id,
root_artifact_sha256: input.application.rootArtifactSha256,
},
scoring: scoringModel(),
summary: comparisonSummary(mappings.items),
items: mappings.items,
unmapped_current_node_ids: unmappedCurrentNodeIds,
coverage: comparisonCoverage(projection, mappings, factors),
evidence_links: [input.application.evidenceId],
limitations: comparisonLimitations(factors),
};
return sourceToBundleComparisonResultSchema.parse({
...semanticResult,
comparison_id: `stbc_${digestCanonical(semanticResult)}`,
});
};
const projectComparison = (
input: SourceToBundleComparisonInput,
): ComparisonProjection => {
const allSources = historicalSourceFiles(input.reference);
const allCurrentNodes = sourceBearingNodes(input.application.graph.nodes);
const sources = allSources.slice(0, input.limits.max_source_files);
const currentNodes = allCurrentNodes.slice(
0,
input.limits.max_application_nodes,
);
const omittedSourceFiles = allSources.length - sources.length;
const omittedApplicationNodes = allCurrentNodes.length - currentNodes.length;
const index = buildSourceToBundleCandidateIndex(currentNodes);
return {
sources,
currentNodes,
index,
absenceComplete:
input.reference.inventory_state === "complete" &&
input.application.graph.coverage.status === "complete" &&
!input.application.graph.coverage.truncated &&
omittedSourceFiles === 0 &&
omittedApplicationNodes === 0 &&
!index.pathIndexTruncated,
factors: {
input,
omittedSourceFiles,
omittedApplicationNodes,
omittedCandidateEvaluations: 0,
omittedCandidateReferences: 0,
pathIndexTruncated: index.pathIndexTruncated,
},
};
};
const compareMappings = (
input: SourceToBundleComparisonInput,
projection: ComparisonProjection,
): MappingProjection => {
let evaluationsRemaining = input.limits.max_candidate_evaluations;
let candidateEvaluations = 0;
let omittedCandidateEvaluations = 0;
let omittedCandidateReferences = 0;
const provisionalItems: SourceToBundleComparisonItem[] = [];
for (const source of projection.sources) {
const candidateIds = candidateIdsForSource(source, projection.index);
const retainedIds = candidateIds.slice(0, evaluationsRemaining);
const omittedEvaluations = candidateIds.length - retainedIds.length;
evaluationsRemaining -= retainedIds.length;
candidateEvaluations += retainedIds.length;
omittedCandidateEvaluations += omittedEvaluations;
const scored = retainedIds
.map((nodeId) =>
scoreSourceToBundleCandidate(source, nodeId, projection.index),
)
.filter(
(candidate): candidate is SourceToBundleCandidate => candidate !== null,
)
.sort(compareCandidates);
const retainedCandidates = scored.slice(
0,
input.limits.max_candidate_nodes,
);
const omittedCandidates =
omittedEvaluations + scored.length - retainedCandidates.length;
omittedCandidateReferences += omittedCandidates;
const classification = classifyMapping(
retainedCandidates,
omittedEvaluations === 0,
projection.absenceComplete,
);
const semantic = {
source_path: source.path,
source_sha256: source.sha256,
source_language: source.language,
status: classification.status,
confidence: classification.confidence,
current_node_ids: classification.currentNodeIds,
candidates: retainedCandidates,
omitted_candidates: omittedCandidates,
limitations: uniqueSorted([
...source.limitations,
...classification.limitations,
...(omittedCandidates > 0
? [
`${String(omittedCandidates)} candidate references were omitted by comparison limits.`,
]
: []),
]),
};
provisionalItems.push({
mapping_id: mappingId(input, semantic),
...semantic,
});
}
return {
items: markMergedMappings(input, provisionalItems),
candidateEvaluations,
omittedCandidateEvaluations,
omittedCandidateReferences,
};
};
const comparisonCoverage = (
projection: ComparisonProjection,
mappings: MappingProjection,
factors: CoverageFactors,
): SourceToBundleComparisonResult["coverage"] => ({
status: coverageStatus(factors),
reference_inventory_state: factors.input.reference.inventory_state,
application_graph_status: factors.input.application.graph.coverage.status,
retained_source_files: projection.sources.length,
omitted_source_files: factors.omittedSourceFiles,
retained_application_nodes: projection.currentNodes.length,
omitted_application_nodes: factors.omittedApplicationNodes,
candidate_evaluations: mappings.candidateEvaluations,
omitted_candidate_evaluations: mappings.omittedCandidateEvaluations,
omitted_candidate_references: mappings.omittedCandidateReferences,
omitted_unmapped_current_nodes: 0,
});
const scoringModel = (): SourceToBundleComparisonResult["scoring"] => ({
algorithm: "rea-source-to-bundle-signals/v1",
minimum_candidate_score: 20,
weights: Object.entries(SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS).map(
([signal, weight]) => ({
signal: signal as keyof typeof SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
weight,
}),
),
});
const classifyMapping = (
candidates: readonly SourceToBundleCandidate[],
evaluationComplete: boolean,
absenceComplete: boolean,
): ClassifiedMapping => {
if (!evaluationComplete)
return {
status: "unknown",
confidence: "unknown",
currentNodeIds: [],
limitations: [
"Candidate evaluation stopped at the declared limit; a stronger match may be omitted.",
],
};
const exact = candidates.filter((candidate) =>
hasSignal(candidate, "exact-source-digest"),
);
if (exact.length === 1)
return {
status: "unchanged",
confidence: "exact",
currentNodeIds: [exact[0]?.current_node_id ?? ""].filter(Boolean),
limitations: [
"Exact byte identity does not establish equivalent load context or runtime behavior.",
],
};
if (exact.length > 1)
return {
status: "duplicated",
confidence: "exact",
currentNodeIds: exact.map(({ current_node_id: nodeId }) => nodeId),
limitations: [
"The same historical source digest occurs in multiple current graph nodes.",
],
};
const pathCandidates = candidates.filter((candidate) =>
candidate.signals.some(({ kind }) =>
[
"source-map-original-path",
"current-path-exact",
"current-path-suffix",
].includes(kind),
),
);
const topScore = pathCandidates[0]?.score;
const topPathCandidates =
topScore === undefined
? []
: pathCandidates.filter(({ score }) => score === topScore);
if (topPathCandidates.length === 1) {
const candidate = topPathCandidates[0];
return {
status: "modified",
confidence: candidate?.confidence === "high" ? "high" : "medium",
currentNodeIds:
candidate === undefined ? [] : [candidate.current_node_id],
limitations: [
"Location evidence links the source to the current node, but exact source bytes do not match or are unavailable.",
],
};
}
if (topPathCandidates.length > 1)
return {
status: "split",
confidence: "medium",
currentNodeIds: topPathCandidates.map(
({ current_node_id: nodeId }) => nodeId,
),
limitations: [
"Multiple equally scored current nodes retain the historical source path; this is a static split inference, not runtime proof.",
],
};
if (candidates.length > 0)
return {
status: "unknown",
confidence: "unknown",
currentNodeIds: [],
limitations: [
"Only weak basename or extension signals were available; no mapping was forced.",
],
};
return absenceComplete
? {
status: "removed",
confidence: "high",
currentNodeIds: [],
limitations: [
"No retained current node matched exact digest or path signals within complete static inventories.",
],
}
: {
status: "unknown",
confidence: "unknown",
currentNodeIds: [],
limitations: [
"No candidate was observed, but incomplete input or graph coverage prevents a removal claim.",
],
};
};
const markMergedMappings = (
input: SourceToBundleComparisonInput,
items: readonly SourceToBundleComparisonItem[],
): SourceToBundleComparisonItem[] => {
const sourcesByNode = new Map<string, SourceToBundleComparisonItem[]>();
for (const item of items) {
if (item.status !== "unchanged" || item.current_node_ids.length !== 1)
continue;
const nodeId = item.current_node_ids[0];
if (nodeId === undefined) continue;
sourcesByNode.set(nodeId, [...(sourcesByNode.get(nodeId) ?? []), item]);
}
const mergedSources = new Set(
[...sourcesByNode.values()]
.filter((group) => group.length > 1)
.flatMap((group) => group.map(({ source_path: path }) => path)),
);
return items.map((item) => {
if (!mergedSources.has(item.source_path)) return item;
const { mapping_id: previousMappingId, ...base } = item;
void previousMappingId;
const semantic = {
...base,
status: "merged" as const,
confidence: "exact" as const,
limitations: uniqueSorted([
...item.limitations,
"Multiple historical source paths have exact digest identity with the same current graph node.",
]),
};
return { ...semantic, mapping_id: mappingId(input, semantic) };
});
};
const mappingId = (
input: SourceToBundleComparisonInput,
item: Omit<SourceToBundleComparisonItem, "mapping_id">,
): string =>
`stbc_item_${digestCanonical({
reference_root_sha256: input.reference.root_sha256,
application_graph_id: input.application.graph.graph_id,
...item,
})}`;
const comparisonSummary = (
items: readonly SourceToBundleComparisonItem[],
): SourceToBundleComparisonResult["summary"] => ({
unchanged: countStatus(items, "unchanged"),
modified: countStatus(items, "modified"),
removed: countStatus(items, "removed"),
split: countStatus(items, "split"),
merged: countStatus(items, "merged"),
duplicated: countStatus(items, "duplicated"),
unknown: countStatus(items, "unknown"),
});
const countStatus = (
items: readonly SourceToBundleComparisonItem[],
status: SourceToBundleComparisonItem["status"],
): number => items.filter((item) => item.status === status).length;
interface CoverageFactors {
readonly input: SourceToBundleComparisonInput;
readonly omittedSourceFiles: number;
readonly omittedApplicationNodes: number;
readonly omittedCandidateEvaluations: number;
readonly omittedCandidateReferences: number;
readonly pathIndexTruncated: boolean;
}
const coverageStatus = (
factors: CoverageFactors,
): SourceToBundleComparisonResult["coverage"]["status"] => {
if (
factors.omittedSourceFiles > 0 ||
factors.omittedApplicationNodes > 0 ||
factors.omittedCandidateEvaluations > 0 ||
factors.omittedCandidateReferences > 0 ||
factors.pathIndexTruncated
)
return "truncated";
return factors.input.reference.inventory_state === "complete" &&
factors.input.application.graph.coverage.status === "complete"
? "complete-within-inputs"
: "partial";
};
const comparisonLimitations = (factors: CoverageFactors): string[] =>
uniqueSorted([
"Static digest and location signals do not establish runtime behavior, semantic equivalence, or absence of dynamically generated modules.",
...factors.input.reference.limitations,
...factors.input.application.graph.limitations,
...(factors.input.reference.inventory_state === "complete"
? []
: [
`Historical source inventory is ${factors.input.reference.inventory_state}; unmatched files remain unknown.`,
]),
...(factors.input.application.graph.coverage.status === "complete"
? []
: [
`Application graph coverage is ${factors.input.application.graph.coverage.status}; unmatched files remain unknown.`,
]),
...(factors.omittedSourceFiles > 0
? [
`${String(factors.omittedSourceFiles)} historical source files were omitted by max_source_files.`,
]
: []),
...(factors.omittedApplicationNodes > 0
? [
`${String(factors.omittedApplicationNodes)} source-bearing application nodes were omitted by max_application_nodes.`,
]
: []),
...(factors.omittedCandidateEvaluations > 0
? [
`${String(factors.omittedCandidateEvaluations)} candidate evaluations were omitted by max_candidate_evaluations.`,
]
: []),
...(factors.omittedCandidateReferences > 0
? [
`${String(factors.omittedCandidateReferences)} candidate references were omitted from item output.`,
]
: []),
...(factors.pathIndexTruncated
? [
"At least one current path exceeded the 128-segment suffix index bound.",
]
: []),
]);
const hasSignal = (
candidate: SourceToBundleCandidate,
kind: SourceToBundleCandidate["signals"][number]["kind"],
): boolean => candidate.signals.some((signal) => signal.kind === kind);
const compareCandidates = (
left: SourceToBundleCandidate,
right: SourceToBundleCandidate,
): number =>
right.score - left.score ||
compareText(left.current_node_id, right.current_node_id);
const digestCanonical = (value: unknown): string => {
const encoded = canonicalize(value);
if (encoded === undefined)
throw new TypeError("Source-to-bundle comparison could not canonicalize");
return createHash("sha256").update(encoded).digest("hex");
};
const uniqueSorted = (values: readonly string[]): string[] =>
[...new Set(values)].sort(compareText);
const compareText = (left: string, right: string): number =>
left < right ? -1 : left > right ? 1 : 0;

View File

@ -0,0 +1,157 @@
import { z } from "zod";
import { evidenceSchema } from "./evidence.js";
import { JAVASCRIPT_APPLICATION_NODE_KINDS } from "./javascriptApplicationGraphSchemas.js";
import { historicalSourceGraphSchema } from "./referenceSourceGraph.js";
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
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);
export const SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS = {
"exact-source-digest": 100,
"source-map-original-path": 70,
"current-path-exact": 60,
"current-path-suffix": 40,
"basename-match": 20,
"language-extension": 5,
} as const;
const sourceToBundleSignalKindSchema = z.enum([
"exact-source-digest",
"source-map-original-path",
"current-path-exact",
"current-path-suffix",
"basename-match",
"language-extension",
]);
const sourceToBundleLimitsSchema = z.strictObject({
max_source_files: z.number().int().min(1).max(100_000).default(10_000),
max_application_nodes: z.number().int().min(1).max(100_000).default(20_000),
max_candidate_nodes: z.number().int().min(1).max(1_000).default(100),
max_candidate_evaluations: z
.number()
.int()
.min(1)
.max(10_000_000)
.default(1_000_000),
});
/** Historical source inventory, authenticated application Evidence, and bounds. */
export const compareSourceToBundleInputSchema = z.strictObject({
reference: historicalSourceGraphSchema,
application: evidenceSchema,
limits: sourceToBundleLimitsSchema.default({
max_source_files: 10_000,
max_application_nodes: 20_000,
max_candidate_nodes: 100,
max_candidate_evaluations: 1_000_000,
}),
unknown_registry_approved: z.literal(true).optional(),
});
const sourceToBundleSignalSchema = z.strictObject({
kind: sourceToBundleSignalKindSchema,
weight: z.number().int().min(1).max(100),
source_value: boundedTextSchema,
current_values: z.array(boundedTextSchema).min(1).max(64),
});
const sourceToBundleCandidateSchema = z.strictObject({
current_node_id: nodeIdSchema,
current_node_kind: z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS),
score: z.number().int().min(1).max(300),
confidence: z.enum(["exact", "high", "medium", "low"]),
signals: z.array(sourceToBundleSignalSchema).min(1).max(6),
});
const sourceToBundleItemSchema = z.strictObject({
mapping_id: z.string().regex(/^stbc_item_[a-f0-9]{64}$/u),
source_path: boundedTextSchema,
source_sha256: digestSchema.nullable(),
source_language: z.string().min(1).max(100).nullable(),
status: z.enum([
"unchanged",
"modified",
"removed",
"split",
"merged",
"duplicated",
"unknown",
]),
confidence: z.enum(["exact", "high", "medium", "unknown"]),
current_node_ids: z.array(nodeIdSchema).max(1_000),
candidates: z.array(sourceToBundleCandidateSchema).max(1_000),
omitted_candidates: z.number().int().min(0),
limitations: z.array(boundedTextSchema).max(100),
});
/** Deterministic, evidence-bearing historical-source to shipped-bundle comparison. */
export const sourceToBundleComparisonResultSchema = z.strictObject({
schema_version: z.literal(1),
comparison_id: z.string().regex(/^stbc_[a-f0-9]{64}$/u),
reference: z.strictObject({
root_sha256: digestSchema,
inventory_state: z.enum(["complete", "partial", "unknown"]),
}),
application: z.strictObject({
evidence_id: evidenceIdSchema,
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
root_artifact_sha256: digestSchema,
}),
scoring: z.strictObject({
algorithm: z.literal("rea-source-to-bundle-signals/v1"),
minimum_candidate_score: z.literal(20),
weights: z.array(
z.strictObject({
signal: sourceToBundleSignalKindSchema,
weight: z.number().int().min(1).max(100),
}),
),
}),
summary: z.strictObject({
unchanged: z.number().int().min(0),
modified: z.number().int().min(0),
removed: z.number().int().min(0),
split: z.number().int().min(0),
merged: z.number().int().min(0),
duplicated: z.number().int().min(0),
unknown: z.number().int().min(0),
}),
items: z.array(sourceToBundleItemSchema).max(100_000),
unmapped_current_node_ids: z.array(nodeIdSchema).max(100_000),
coverage: z.strictObject({
status: z.enum(["complete-within-inputs", "partial", "truncated"]),
reference_inventory_state: z.enum(["complete", "partial", "unknown"]),
application_graph_status: z.enum([
"complete",
"partial",
"unknown",
"unavailable",
]),
retained_source_files: z.number().int().min(0),
omitted_source_files: z.number().int().min(0),
retained_application_nodes: z.number().int().min(0),
omitted_application_nodes: z.number().int().min(0),
candidate_evaluations: z.number().int().min(0),
omitted_candidate_evaluations: z.number().int().min(0),
omitted_candidate_references: z.number().int().min(0),
omitted_unmapped_current_nodes: z.number().int().min(0),
}),
evidence_links: z.array(evidenceIdSchema).min(1).max(1),
limitations: z.array(boundedTextSchema).max(1_000),
});
export type CompareSourceToBundleInput = z.infer<
typeof compareSourceToBundleInputSchema
>;
export type SourceToBundleComparisonResult = z.infer<
typeof sourceToBundleComparisonResultSchema
>;
export type SourceToBundleComparisonItem =
SourceToBundleComparisonResult["items"][number];
export type SourceToBundleCandidate =
SourceToBundleComparisonItem["candidates"][number];
export type SourceToBundleSignal = SourceToBundleCandidate["signals"][number];

View File

@ -0,0 +1,314 @@
import { posix } from "node:path";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { HistoricalSourceGraph } from "./referenceSourceGraph.js";
import {
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
type SourceToBundleCandidate,
type SourceToBundleSignal,
} from "./sourceToBundleComparisonSchemas.js";
type SourceFile = Extract<
HistoricalSourceGraph["entries"][number],
{ readonly kind: "file" }
>;
type CurrentPathKind =
| "source-map-original"
| "canonical-path"
| "observation-path";
interface CurrentPath {
readonly kind: CurrentPathKind;
readonly value: string;
}
interface CurrentProjection {
readonly node: ApplicationNode;
readonly digests: ReadonlySet<string>;
readonly paths: readonly CurrentPath[];
}
/** Indexed current-node facts used by deterministic source matching. */
export interface SourceToBundleCandidateIndex {
readonly nodes: ReadonlyMap<string, CurrentProjection>;
readonly byDigest: ReadonlyMap<string, ReadonlySet<string>>;
readonly byPathSuffix: ReadonlyMap<string, ReadonlySet<string>>;
readonly byBasename: ReadonlyMap<string, ReadonlySet<string>>;
readonly pathIndexTruncated: boolean;
}
const RELEVANT_NODE_KINDS = new Set([
"javascript-asset",
"javascript-module",
"source-module",
]);
const PATH_PROPERTIES = [
"path",
"source",
"original_source",
"module_path",
"resolved_path",
"declared_path",
] as const;
const MAX_PATH_SUFFIXES = 128;
/** Keep source-bearing application nodes in stable identifier order. */
export const sourceBearingNodes = (
nodes: readonly ApplicationNode[],
): ApplicationNode[] =>
nodes
.filter(({ kind }) => RELEVANT_NODE_KINDS.has(kind))
.sort((left, right) => compareText(left.node_id, right.node_id));
/** Keep historical source files in stable path order. */
export const historicalSourceFiles = (
graph: HistoricalSourceGraph,
): SourceFile[] =>
graph.entries
.filter(
(entry): entry is SourceFile =>
entry.kind === "file" && entry.classifications.includes("source"),
)
.sort((left, right) => compareText(left.path, right.path));
/** Build bounded-key indices without assigning fuzzy matches. */
export const buildSourceToBundleCandidateIndex = (
nodes: readonly ApplicationNode[],
): SourceToBundleCandidateIndex => {
const projections = nodes.map(projectCurrentNode);
const byDigest = new Map<string, Set<string>>();
const byPathSuffix = new Map<string, Set<string>>();
const byBasename = new Map<string, Set<string>>();
let pathIndexTruncated = false;
for (const projection of projections) {
for (const digest of projection.digests)
addIndexValue(byDigest, digest, projection.node.node_id);
for (const path of projection.paths) {
const suffixes = pathSuffixes(path.value);
pathIndexTruncated ||= suffixes.truncated;
for (const suffix of suffixes.values)
addIndexValue(byPathSuffix, suffix, projection.node.node_id);
addIndexValue(
byBasename,
posix.basename(path.value),
projection.node.node_id,
);
}
}
return {
nodes: new Map(
projections.map((projection) => [projection.node.node_id, projection]),
),
byDigest,
byPathSuffix,
byBasename,
pathIndexTruncated,
};
};
/** Candidate identifiers ordered by strongest available signal, then ID. */
export const candidateIdsForSource = (
source: SourceFile,
index: SourceToBundleCandidateIndex,
): string[] => {
const groups = [
source.sha256 === null ? undefined : index.byDigest.get(source.sha256),
index.byPathSuffix.get(source.path),
index.byBasename.get(posix.basename(source.path)),
];
const seen = new Set<string>();
const output: string[] = [];
for (const group of groups) {
if (group === undefined) continue;
for (const nodeId of [...group].sort(compareText)) {
if (seen.has(nodeId)) continue;
seen.add(nodeId);
output.push(nodeId);
}
}
return output;
};
/** Score one indexed candidate from explicit, caller-visible signals. */
export const scoreSourceToBundleCandidate = (
source: SourceFile,
nodeId: string,
index: SourceToBundleCandidateIndex,
): SourceToBundleCandidate | null => {
const projection = index.nodes.get(nodeId);
if (projection === undefined) return null;
const signals = candidateSignals(source, projection);
const score = signals.reduce((total, signal) => total + signal.weight, 0);
if (score < 20) return null;
return {
current_node_id: projection.node.node_id,
current_node_kind: projection.node.kind,
score,
confidence: candidateConfidence(signals),
signals,
};
};
const projectCurrentNode = (node: ApplicationNode): CurrentProjection => {
const digests = new Set<string>();
const paths: CurrentPath[] = [];
if (node.identity.strategy === "content-digest")
digests.add(node.identity.sha256);
if (node.identity.strategy === "source-map-original") {
if (node.identity.source_sha256 !== null)
digests.add(node.identity.source_sha256);
addPath(paths, "source-map-original", node.identity.original_source);
}
if (node.identity.strategy === "canonical-path")
addPath(paths, "canonical-path", node.identity.path);
for (const observation of node.observations) {
const sourceDigest = observation.properties.source_sha256;
if (typeof sourceDigest === "string" && isDigest(sourceDigest))
digests.add(sourceDigest);
for (const key of PATH_PROPERTIES)
addJsonPath(paths, observation.properties[key]);
}
return {
node,
digests,
paths: uniquePaths(paths),
};
};
const candidateSignals = (
source: SourceFile,
current: CurrentProjection,
): SourceToBundleSignal[] => {
const signals: SourceToBundleSignal[] = [];
if (source.sha256 !== null && current.digests.has(source.sha256))
signals.push(signal("exact-source-digest", source.sha256, [source.sha256]));
const sourceMapPaths = matchingPaths(
source.path,
current.paths.filter(({ kind }) => kind === "source-map-original"),
);
if (sourceMapPaths.length > 0)
signals.push(
signal("source-map-original-path", source.path, sourceMapPaths),
);
const otherPaths = current.paths.filter(
({ kind }) => kind !== "source-map-original",
);
const exactPaths = otherPaths
.filter(({ value }) => value === source.path)
.map(({ value }) => value);
if (exactPaths.length > 0)
signals.push(signal("current-path-exact", source.path, exactPaths));
const suffixPaths = otherPaths
.filter(
({ value }) => value !== source.path && value.endsWith(`/${source.path}`),
)
.map(({ value }) => value);
if (suffixPaths.length > 0)
signals.push(signal("current-path-suffix", source.path, suffixPaths));
const basename = posix.basename(source.path);
const basenamePaths = current.paths
.filter(({ value }) => posix.basename(value) === basename)
.map(({ value }) => value);
if (basenamePaths.length > 0)
signals.push(signal("basename-match", basename, basenamePaths));
const extension = posix.extname(source.path).toLowerCase();
const extensionPaths = current.paths
.filter(({ value }) => posix.extname(value).toLowerCase() === extension)
.map(({ value }) => value);
if (extension !== "" && extensionPaths.length > 0)
signals.push(signal("language-extension", extension, extensionPaths));
return signals;
};
const signal = (
kind: SourceToBundleSignal["kind"],
sourceValue: string,
currentValues: readonly string[],
): SourceToBundleSignal => ({
kind,
weight: SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS[kind],
source_value: sourceValue,
current_values: [...new Set(currentValues)].sort(compareText).slice(0, 64),
});
const candidateConfidence = (
signals: readonly SourceToBundleSignal[],
): SourceToBundleCandidate["confidence"] => {
const kinds = new Set(signals.map(({ kind }) => kind));
if (kinds.has("exact-source-digest")) return "exact";
if (kinds.has("source-map-original-path") || kinds.has("current-path-exact"))
return "high";
return kinds.has("current-path-suffix") ? "medium" : "low";
};
const matchingPaths = (
sourcePath: string,
currentPaths: readonly CurrentPath[],
): string[] =>
currentPaths
.filter(
({ value }) => value === sourcePath || value.endsWith(`/${sourcePath}`),
)
.map(({ value }) => value);
const addJsonPath = (paths: CurrentPath[], value: unknown): void => {
if (typeof value === "string") addPath(paths, "observation-path", value);
};
const addPath = (
paths: CurrentPath[],
kind: CurrentPathKind,
raw: string,
): void => {
const value = normalizeCurrentPath(raw);
if (value !== null) paths.push({ kind, value });
};
const normalizeCurrentPath = (raw: string): string | null => {
const withoutQuery = raw.split(/[?#]/u, 1)[0] ?? "";
const withoutScheme = withoutQuery.replace(/^[a-z][a-z0-9+.-]*:\/\/+/iu, "");
const parts = withoutScheme
.replaceAll("\\", "/")
.split("/")
.filter((part) => part !== "" && part !== ".");
if (parts.length === 0 || parts.includes("..")) return null;
return parts.join("/");
};
const uniquePaths = (paths: readonly CurrentPath[]): CurrentPath[] =>
[
...new Map(
paths.map((path) => [`${path.kind}\0${path.value}`, path]),
).values(),
].sort((left, right) =>
compareText(`${left.kind}\0${left.value}`, `${right.kind}\0${right.value}`),
);
const pathSuffixes = (
path: string,
): { readonly values: readonly string[]; readonly truncated: boolean } => {
const parts = path.split("/");
const start = Math.max(0, parts.length - MAX_PATH_SUFFIXES);
return {
values: parts
.slice(start)
.map((_, index) => parts.slice(start + index).join("/")),
truncated: start > 0,
};
};
const addIndexValue = (
index: Map<string, Set<string>>,
key: string,
nodeId: string,
): void => {
const values = index.get(key) ?? new Set<string>();
values.add(nodeId);
index.set(key, values);
};
const isDigest = (value: string): boolean => /^[a-f0-9]{64}$/u.test(value);
const compareText = (left: string, right: string): number =>
left < right ? -1 : left > right ? 1 : 0;

View File

@ -0,0 +1,59 @@
import type { McpServer } from "@modelcontextprotocol/server";
import { compareSourceToBundleEvidenceValidated } from "../../application/JavaScriptApplicationWorkflowService.js";
import { resolveCompareSourceToBundleRequestValidated } from "../../application/ApplicationWorkflowEvidenceResolver.js";
import { applicationToolContract } from "../../contracts/applicationToolContracts.js";
import { compareSourceToBundleRequestSchema } from "../../contracts/applicationWorkflowInputContracts.js";
import { sourceToBundleComparisonResultSchema } from "../../domain/sourceToBundleComparisonSchemas.js";
import { logToolExecution } from "../toolLogging.js";
import { safeParseToolInput } from "../toolInputValidation.js";
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
import { toCallToolResult } from "../toolResult.js";
import { recordResult, recordSources } from "./helpers.js";
import type { ApplicationToolRegistration } from "./types.js";
const contract = applicationToolContract("compare_source_to_bundle");
/** Register conservative historical-source to bundle comparison. */
export const registerCompareSourceToBundleTool = (
server: McpServer,
options: ApplicationToolRegistration,
): void => {
server.registerTool(
contract.name,
toolRegistrationOptions(contract),
async (input) => {
const parsedInput = safeParseToolInput(
compareSourceToBundleRequestSchema,
input,
contract.name,
);
if (!parsedInput.ok) return toCallToolResult(parsedInput, contract);
const resolved = resolveCompareSourceToBundleRequestValidated(
parsedInput.value,
options.evidenceLookup,
);
if (!resolved.ok) return toCallToolResult(resolved, contract);
const result = await logToolExecution(options.logger, contract.name, () =>
Promise.resolve(compareSourceToBundleEvidenceValidated(resolved.value)),
);
if (!result.ok) return toCallToolResult(result, contract);
const recorded = recordSources(options.recordEvidence, [
resolved.value.application,
]);
if (!recorded.ok) return toCallToolResult(recorded, contract);
const comparison = sourceToBundleComparisonResultSchema.parse(
result.value.normalized_result,
);
return recordResult(
options,
contract,
result.value,
resolved.value.unknown_registry_approved === true &&
comparison.summary.unknown > 0
? "source-to-bundle-comparison"
: undefined,
);
},
);
};

View File

@ -24,7 +24,10 @@ export const recordResult = (
options: ApplicationToolRegistration,
contract: (typeof APPLICATION_TOOL_CONTRACTS)[number],
evidence: Evidence,
unknownKind?: "application-version-comparison" | "javascript-export-shape",
unknownKind?:
| "application-version-comparison"
| "javascript-export-shape"
| "source-to-bundle-comparison",
) => {
const recorded =
unknownKind === undefined
@ -42,13 +45,18 @@ export const recordResult = (
const unknownRegistration = (
evidence: Evidence,
kind: "application-version-comparison" | "javascript-export-shape",
kind:
| "application-version-comparison"
| "javascript-export-shape"
| "source-to-bundle-comparison",
) => ({
approved: true as const,
question:
kind === "application-version-comparison"
? "Which application entities remain unmatched or ambiguous across these versions?"
: "Which selected JavaScript export return shapes remain dynamic, incomplete, or ambiguously paired?",
: kind === "source-to-bundle-comparison"
? "Which historical source files remain unmatched or ambiguously mapped to the shipped bundle?"
: "Which selected JavaScript export return shapes remain dynamic, incomplete, or ambiguously paired?",
severity: "medium" as const,
domain: kind,
supporting_evidence_ids: [evidence.evidence_id],
@ -70,13 +78,15 @@ const unknownRegistration = (
"Add approved passive runtime Evidence without promoting it to static fact.",
},
]
: [
{
operation: "run_controlled_replay",
rationale:
"Validate exact approved module behavior separately when runtime semantics are required.",
},
]),
: kind === "source-to-bundle-comparison"
? []
: [
{
operation: "run_controlled_replay",
rationale:
"Validate exact approved module behavior separately when runtime semantics are required.",
},
]),
],
relationships: [],
});

View File

@ -12,6 +12,7 @@ import {
JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE,
JAVASCRIPT_FEATURE_TRACE_EXAMPLE,
JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE,
SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE,
} from "../src/contracts/javascriptApplicationWorkflowExamples.js";
import {
createEvidenceBundle,
@ -65,6 +66,18 @@ describe("application workflow CLI parity", () => {
summary: { unknown: expect.any(Number) },
},
});
const sourceCompared = await runCli([
"compare-source-to-bundle",
JSON.stringify({
reference: SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE.reference,
application: JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE.application,
}),
"--json",
]);
expect(sourceCompared).toMatchObject({
operation: "compare_source_to_bundle",
normalized_result: { schema_version: 1 },
});
}, 20_000);
it("traces the same authenticated semantic graph through the CLI", async () => {

View File

@ -12,6 +12,7 @@ import {
JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE,
JAVASCRIPT_FEATURE_TRACE_EXAMPLE,
JAVASCRIPT_VERSION_COMPARISON_FULL_EVIDENCE_EXAMPLE,
SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE,
} from "../src/contracts/javascriptApplicationWorkflowExamples.js";
import { createEvidence } from "../src/domain/evidence.js";
import { createServer } from "../src/server/createServer.js";
@ -202,6 +203,25 @@ describe("application workflow MCP parity", () => {
coverage: { status: expect.any(String) },
},
});
const sourceCompared = await client.callTool({
name: "compare_source_to_bundle",
arguments: {
reference: SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE.reference,
application:
JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE.application,
},
});
expect(sourceCompared.isError).not.toBe(true);
expect(sourceCompared.structuredContent).toMatchObject({
result: {
schema_version: 1,
reference: {
root_sha256:
SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE.reference.root_sha256,
},
scoring: { algorithm: "rea-source-to-bundle-signals/v1" },
},
});
expect(session.exportEvidenceBundle().records.length).toBeGreaterThan(2);
await new Promise<void>((resolve) => setImmediate(resolve));
@ -243,6 +263,16 @@ describe("application workflow MCP parity", () => {
},
},
});
const sourceComparedById = await client.callTool({
name: "compare_source_to_bundle",
arguments: {
...SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE,
application_evidence_id:
JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE.application
.evidence_id,
},
});
expect(sourceComparedById.isError).not.toBe(true);
const missing = await client.callTool({
name: "trace_application_feature",

View File

@ -104,6 +104,40 @@ describe("Webpack and Rspack runtime adapters", () => {
await rm(root, { recursive: true, force: true });
}
});
it("recovers esbuild wrapper modules and Vite preload dependencies", async () => {
const root = await esmRuntimeFixture();
try {
const { graph } = await reconstructJavaScriptArtifact({
input_path: root,
});
const commonJsModule = moduleNode(graph, "src/dep.js");
const esmModule = moduleNode(graph, "src/core.js");
expect(commonJsModule?.observations[0]?.properties).toMatchObject({
bundler: "esbuild",
runtime: "esbuild-commonjs",
module_key: "src/dep.js",
});
expect(esmModule?.observations[0]?.properties).toMatchObject({
bundler: "esbuild",
runtime: "esbuild-esm",
module_key: "src/core.js",
});
expect(
graph.edges.filter(
({ relation, properties }) =>
relation === "imports" &&
properties.kind === "dynamic-import" &&
["./feature.js", "./main.css"].includes(
String(properties.specifier),
),
),
).toHaveLength(2);
} finally {
await rm(root, { recursive: true, force: true });
}
});
});
const bundlerFixture = async (): Promise<string> => {
@ -159,6 +193,33 @@ const bundlerFixture = async (): Promise<string> => {
return root;
};
const esmRuntimeFixture = async (): Promise<string> => {
const root = await createTestTempDirectory("rea-esm-runtime-adapters-");
await writeFile(
join(root, "main.js"),
`
const __vite__mapDeps = (indexes, map = __vite__mapDeps, dependencies =
(map.f || (map.f = ["./feature.js", "./main.css"]))) =>
indexes.map((index) => dependencies[index]);
const require_dep = __commonJS({
"src/dep.js"(exports, module) {
module.exports = { value: 1 };
}
});
const init_core = __esm({
"src/core.js"() {
require_dep();
}
});
__vite__mapDeps([0, 1]);
init_core();
`,
);
await writeFile(join(root, "feature.js"), "export const feature = true;");
await writeFile(join(root, "main.css"), ".app { color: green; }");
return root;
};
const chunk = (
graph: JavaScriptApplicationGraph,
runtime: string,

View File

@ -0,0 +1,303 @@
import { describe, expect, it } from "vitest";
import {
createJavaScriptApplicationGraph,
createJavaScriptApplicationNode,
} from "../src/domain/javascriptApplicationGraph.js";
import { createHistoricalSourceGraph } from "../src/domain/referenceSourceGraph.js";
import { compareSourceToBundle } from "../src/domain/sourceToBundleComparison.js";
import { sourceToBundleComparisonResultSchema } from "../src/domain/sourceToBundleComparisonSchemas.js";
import { artifactEvidence } from "./fixtures/javascriptApplicationGraph.js";
const HASH = {
artifact: "1".repeat(64),
unchanged: "2".repeat(64),
modified: "3".repeat(64),
removed: "4".repeat(64),
split: "5".repeat(64),
merged: "6".repeat(64),
duplicated: "7".repeat(64),
} as const;
const EVIDENCE_ID = `ev_${"a".repeat(64)}`;
describe("historical source to bundle comparison", () => {
it("classifies exact, changed, absent, split, and merged mappings stably", () => {
const reference = historicalGraph("complete");
const graph = applicationGraph("complete");
const input = {
reference,
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph,
},
limits: limits(),
};
const first = compareSourceToBundle(input);
const second = compareSourceToBundle(input);
expect(first).toEqual(second);
expect(() =>
sourceToBundleComparisonResultSchema.parse(first),
).not.toThrow();
expect(first.summary).toEqual({
unchanged: 1,
modified: 1,
removed: 1,
split: 1,
merged: 2,
duplicated: 1,
unknown: 0,
});
expect(item(first, "src/unchanged.ts")).toMatchObject({
status: "unchanged",
confidence: "exact",
candidates: [
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({
kind: "exact-source-digest",
weight: 100,
}),
]),
}),
],
});
expect(item(first, "src/modified.ts")).toMatchObject({
status: "modified",
confidence: "high",
candidates: [
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({ kind: "current-path-exact", weight: 60 }),
]),
}),
],
});
expect(item(first, "src/removed.ts")).toMatchObject({
status: "removed",
current_node_ids: [],
});
expect(item(first, "src/split.ts")).toMatchObject({
status: "split",
confidence: "medium",
current_node_ids: [expect.any(String), expect.any(String)],
});
expect(item(first, "src/merged-a.ts").status).toBe("merged");
expect(item(first, "src/merged-b.ts").status).toBe("merged");
expect(item(first, "src/duplicated.ts")).toMatchObject({
status: "duplicated",
confidence: "exact",
current_node_ids: [expect.any(String), expect.any(String)],
});
expect(first.coverage.status).toBe("complete-within-inputs");
});
it("keeps absence unknown when either inventory is incomplete", () => {
const reference = historicalGraph("partial", ["src/removed.ts"]);
const graph = applicationGraph("partial");
const result = compareSourceToBundle({
reference,
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph,
},
limits: limits(),
});
expect(result.summary).toMatchObject({ removed: 0, unknown: 1 });
expect(result.items[0]).toMatchObject({
source_path: "src/removed.ts",
status: "unknown",
confidence: "unknown",
});
expect(result.coverage.status).toBe("partial");
});
it("reports the candidate frontier instead of deciding past a limit", () => {
const result = compareSourceToBundle({
reference: historicalGraph("complete", ["src/split.ts"]),
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph: applicationGraph("complete"),
},
limits: { ...limits(), max_candidate_evaluations: 1 },
});
expect(result.items[0]).toMatchObject({
status: "unknown",
confidence: "unknown",
omitted_candidates: 1,
});
expect(result.coverage).toMatchObject({
status: "truncated",
candidate_evaluations: 1,
omitted_candidate_evaluations: 1,
});
});
});
const historicalGraph = (
inventoryState: "complete" | "partial",
paths = [
"src/duplicated.ts",
"src/merged-a.ts",
"src/merged-b.ts",
"src/modified.ts",
"src/removed.ts",
"src/split.ts",
"src/unchanged.ts",
],
) =>
createHistoricalSourceGraph({
schema: "HistoricalSourceGraph/v1",
authority: "historical-reference",
root_alias: "$REFERENCE_ROOT",
inventory_state: inventoryState,
entries: [
{
path: "src",
kind: "directory",
classifications: ["source"],
tree_state: inventoryState === "complete" ? "enumerated" : "partial",
limitations:
inventoryState === "complete"
? []
: ["Fixture inventory is partial."],
},
...paths.map((path) => ({
path,
kind: "file" as const,
sha256: sourceDigest(path),
size: 10,
language: "TypeScript",
classifications: ["source"] as const,
content_state: "hashed" as const,
limitations: [],
})),
],
relationships: [],
parse_failures: [],
exclusions: [],
languages: ["TypeScript"],
manifests: [],
vcs: { kind: "none", head: null, dirty: null },
provenance: {
importer: "rea-test",
importer_version: "1",
caller: "source-to-bundle-test",
},
limitations:
inventoryState === "complete" ? [] : ["Fixture inventory is partial."],
});
const applicationGraph = (coverage: "complete" | "partial") => {
const nodes = [
moduleNode("duplicated-a", {
path: "bundle-a.js",
source_sha256: HASH.duplicated,
}),
moduleNode("duplicated-b", {
path: "bundle-b.js",
source_sha256: HASH.duplicated,
}),
moduleNode("unchanged", {
path: "bundle.js",
source_sha256: HASH.unchanged,
}),
createJavaScriptApplicationNode({
kind: "javascript-asset",
identity: {
strategy: "canonical-path",
stability: "artifact-version",
artifact_sha256: HASH.artifact,
path: "src/modified.ts",
},
observations: [
{
label: "src/modified.ts",
properties: { path: "src/modified.ts" },
evidence: artifactEvidence(HASH.artifact, "src/modified.ts"),
},
],
}),
moduleNode("split-a", { path: "bundle-a/src/split.ts" }),
moduleNode("split-b", { path: "bundle-b/src/split.ts" }),
moduleNode("merged", {
path: "bundle.js",
source_sha256: HASH.merged,
}),
];
return createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
schema_version: 1,
root_node_ids: nodes.map(({ node_id: nodeId }) => nodeId),
nodes,
edges: [],
coverage:
coverage === "complete"
? {
status: "complete",
truncated: false,
omitted_count: 0,
limits: [],
}
: {
status: "partial",
truncated: true,
omitted_count: 1,
limits: [{ name: "fixture", value: 5, unit: "items" }],
},
limitations:
coverage === "complete" ? [] : ["Fixture graph coverage is partial."],
});
};
const moduleNode = (key: string, properties: Record<string, string>) =>
createJavaScriptApplicationNode({
kind: "javascript-module",
identity: {
strategy: "artifact-local-key",
stability: "artifact-version",
artifact_sha256: HASH.artifact,
namespace: "test",
key,
},
observations: [
{
label: key,
properties,
evidence: artifactEvidence(HASH.artifact, "bundle.js"),
},
],
});
const sourceDigest = (path: string): string => {
if (path.includes("duplicated")) return HASH.duplicated;
if (path.includes("merged")) return HASH.merged;
if (path.includes("modified")) return HASH.modified;
if (path.includes("removed")) return HASH.removed;
if (path.includes("split")) return HASH.split;
return HASH.unchanged;
};
const limits = () => ({
max_source_files: 100,
max_application_nodes: 100,
max_candidate_nodes: 100,
max_candidate_evaluations: 10_000,
});
const item = (
result: ReturnType<typeof compareSourceToBundle>,
sourcePath: string,
) => {
const value = result.items.find(
({ source_path: path }) => path === sourcePath,
);
if (value === undefined) throw new TypeError(`Missing item: ${sourcePath}`);
return value;
};