2026-10-09 23:17:17 +08:00
import { uniqueSorted } from "../canonicalOrdering.js" ;
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js" ;
import { absenceClaimable } from "../comparisonSemantics.js" ;
import { digestCanonicalValue } from "../canonicalDigest.js" ;
2026-07-24 13:17:19 +00:00
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js" ;
2026-10-07 17:16:58 +08:00
import type { HistoricalSourceGraph } from "../referenceSourceGraph.js" ;
2026-07-24 13:17:19 +00:00
import {
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS ,
sourceToBundleComparisonResultSchema ,
type SourceToBundleCandidate ,
type SourceToBundleComparisonItem ,
type SourceToBundleComparisonResult ,
} from "./sourceToBundleComparisonSchemas.js" ;
import {
buildSourceToBundleCandidateIndex ,
2026-10-09 13:50:42 +05:30
candidateHasDigest ,
2026-07-24 13:17:19 +00:00
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 ;
};
}
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 ;
}
/** 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 ,
};
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 = {
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 ,
2026-10-09 23:17:17 +08:00
comparison_id : `stbc_ ${ digestCanonicalValue ( semanticResult , "Source-to-bundle comparison" ) } ` ,
2026-07-24 13:17:19 +00:00
});
};
const projectComparison = (
input : SourceToBundleComparisonInput ,
) : ComparisonProjection => {
const allSources = historicalSourceFiles ( input . reference );
const allCurrentNodes = sourceBearingNodes ( input . application . graph . nodes );
2026-09-29 11:22:31 +08:00
const sources = allSources ;
const currentNodes = allCurrentNodes ;
2026-07-24 13:17:19 +00:00
const index = buildSourceToBundleCandidateIndex ( currentNodes );
return {
sources ,
currentNodes ,
index ,
absenceComplete :
input.reference.inventory_state === "complete" &&
2026-10-06 00:50:38 +08:00
absenceClaimable ( input . application . graph . coverage ),
2026-07-24 13:17:19 +00:00
factors : {
input ,
},
};
};
const compareMappings = (
input : SourceToBundleComparisonInput ,
projection : ComparisonProjection ,
) : MappingProjection => {
let candidateEvaluations = 0 ;
const provisionalItems : SourceToBundleComparisonItem [] = [];
for ( const source of projection . sources ) {
const candidateIds = candidateIdsForSource ( source , projection . index );
2026-09-29 11:22:31 +08:00
candidateEvaluations += candidateIds . length ;
const scored = candidateIds
2026-07-24 13:17:19 +00:00
. map (( nodeId ) =>
scoreSourceToBundleCandidate ( source , nodeId , projection . index ),
)
. filter (
( candidate ) : candidate is SourceToBundleCandidate => candidate !== null ,
)
. sort ( compareCandidates );
2026-10-09 13:50:42 +05:30
const classification = classifyMapping (
scored ,
projection . absenceComplete ,
( nodeId ) =>
source . sha256 !== null && candidateHasDigest ( projection . index , nodeId ),
);
2026-07-24 13:17:19 +00:00
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 ,
2026-09-29 11:22:31 +08:00
candidates : scored ,
2026-07-24 13:17:19 +00:00
limitations : uniqueSorted ([
... source . limitations ,
... classification . limitations ,
]),
};
provisionalItems . push ({
mapping_id : mappingId ( input , semantic ),
... semantic ,
});
}
return {
items : markMergedMappings ( input , provisionalItems ),
candidateEvaluations ,
};
};
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 ,
retained_application_nodes : projection.currentNodes.length ,
candidate_evaluations : mappings.candidateEvaluations ,
});
const scoringModel = () : SourceToBundleComparisonResult [ "scoring" ] => ({
2026-09-29 13:57:24 +08:00
algorithm : "rea-source-to-bundle-signals" ,
2026-07-24 13:17:19 +00:00
minimum_candidate_score : 20 ,
2026-08-10 02:28:11 +08:00
weights : SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS.map (([ signal , weight ]) => ({
signal ,
weight ,
})),
2026-07-24 13:17:19 +00:00
});
const classifyMapping = (
candidates : readonly SourceToBundleCandidate [],
absenceComplete : boolean ,
2026-10-09 13:50:42 +05:30
digestComparable : ( nodeId : string ) => boolean ,
2026-07-24 13:17:19 +00:00
) : ClassifiedMapping => {
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 ];
2026-10-09 13:50:42 +05:30
if ( candidate !== undefined && digestComparable ( candidate . current_node_id ))
return {
status : "modified" ,
confidence : candidate.confidence === "high" ? "high" : "medium" ,
currentNodeIds : [ candidate . current_node_id ],
limitations : [
"Location evidence links the source to the current node, but their source digests differ." ,
],
};
2026-07-24 13:17:19 +00:00
return {
2026-07-24 14:14:40 +00:00
status : "unknown" ,
confidence : "unknown" ,
2026-07-24 13:17:19 +00:00
currentNodeIds :
candidate === undefined ? [] : [ candidate . current_node_id ],
limitations : [
2026-07-24 14:14:40 +00:00
"Location evidence links the source to the current node, but comparable source digests are unavailable." ,
2026-07-24 13:17:19 +00:00
],
};
}
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 : [
2026-10-13 13:48:24 +08:00
candidates . some (( candidate ) =>
hasSignal ( candidate , "unicode-equivalent-path" ),
)
? "Unicode-equivalent paths are inferred candidates only; filesystem paths with distinct Unicode spellings may identify different files, so no mapping was forced."
: "Only weak basename or extension signals were available; no mapping was forced." ,
2026-07-24 13:17:19 +00:00
],
};
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 =>
2026-10-09 23:17:17 +08:00
`stbc_item_ ${ digestCanonicalValue (
2026-10-06 04:55:26 +08:00
{
reference_root_sha256 : input.reference.root_sha256 ,
application_graph_id : input.application.graph.graph_id ,
... item ,
} ,
"Source-to-bundle comparison",
)}` ;
2026-07-24 13:17:19 +00:00
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 ;
}
const coverageStatus = (
factors : CoverageFactors ,
) : SourceToBundleComparisonResult [ "coverage" ][ "status" ] => {
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.` ,
]),
]);
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 ||
2026-10-09 23:17:17 +08:00
compareUnicodeCodePoints ( left . current_node_id , right . current_node_id );