fix(javascript): report located sources with differing digests as modified (#1216)

This commit is contained in:
Jay Hemnani authored and GitHub committed 2026-10-09 16:20:42 +08:00
1 parent c89b3a00dc
commit 01174c4a81
3 files changed
+73 -1

No files matched your search

@@ -136,6 +136,56 @@ describe("historical source to bundle comparison", () => {
});
});
describe("source to bundle digest changes", () => {
it("reports a located source with a different digest as modified", () => {
const node = createJavaScriptApplicationNode({
kind: "source-module",
identity: {
strategy: "source-map-original",
stability: "source-map-exact",
source_map_sha256: HASH.artifact,
original_source: "webpack://fixture/./src/modified.ts",
source_sha256: HASH.unchanged,
},
observations: [
{
label: "src/modified.ts",
properties: { source_sha256: HASH.unchanged },
evidence: artifactEvidence(HASH.artifact, "dist/main.js.map"),
},
],
});
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: [node.node_id],
nodes: [node],
edges: [],
coverage: {
status: "complete",
truncated: false,
omitted_count: 0,
limits: [],
},
limitations: [],
});
const result = compareSourceToBundle({
reference: historicalGraph("complete", ["src/modified.ts"]),
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph,
},
});
expect(result.summary).toMatchObject({ modified: 1, unknown: 0 });
expect(item(result, "src/modified.ts")).toMatchObject({
status: "modified",
confidence: "high",
current_node_ids: [node.node_id],
});
});
});
describe("source to bundle path syntax", () => {
it.each(["src/modified#part.ts", "src/modified?part.ts"])(
"preserves literal filesystem punctuation in %s",
@@ -12,6 +12,7 @@ import {
} from "./sourceToBundleComparisonSchemas.js";
import {
buildSourceToBundleCandidateIndex,
candidateHasDigest,
candidateIdsForSource,
historicalSourceFiles,
scoreSourceToBundleCandidate,
@@ -125,7 +126,12 @@ const compareMappings = (
(candidate): candidate is SourceToBundleCandidate => candidate !== null,
)
.sort(compareCandidates);
const classification = classifyMapping(scored, projection.absenceComplete);
const classification = classifyMapping(
scored,
projection.absenceComplete,
(nodeId) =>
source.sha256 !== null && candidateHasDigest(projection.index, nodeId),
);
const semantic = {
source_path: source.path,
source_sha256: source.sha256,
@@ -175,6 +181,7 @@ const scoringModel = (): SourceToBundleComparisonResult["scoring"] => ({
const classifyMapping = (
candidates: readonly SourceToBundleCandidate[],
absenceComplete: boolean,
digestComparable: (nodeId: string) => boolean,
): ClassifiedMapping => {
const exact = candidates.filter((candidate) =>
hasSignal(candidate, "exact-source-digest"),
@@ -213,6 +220,15 @@ const classifyMapping = (
: pathCandidates.filter(({ score }) => score === topScore);
if (topPathCandidates.length === 1) {
const candidate = topPathCandidates[0];
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.",
],
};
return {
status: "unknown",
confidence: "unknown",
@@ -135,6 +135,12 @@ export const candidateIdsForSource = (
return output;
};
/** Whether an indexed candidate carries any content digest to compare. */
export const candidateHasDigest = (
index: SourceToBundleCandidateIndex,
nodeId: string,
): boolean => (index.nodes.get(nodeId)?.digests.size ?? 0) > 0;
/** Score one indexed candidate from explicit, caller-visible signals. */
export const scoreSourceToBundleCandidate = (
source: SourceFile,