From 209c42c34689252d402a8737459fe6ca00f82d4b Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:00:02 +0800 Subject: [PATCH] fix(browser): keep code absence unknown without worker inventory --- docs/javascript-runtime-reconciliation.md | 5 +++ .../javascript/javascriptRuntimeLoadState.ts | 4 ++- .../javascriptRuntimeReconciliationActive.ts | 1 + ...riptRuntimeReconciliationCaptureParsing.ts | 16 +++++++++ .../javascriptRuntimeReconciliation.test.ts | 34 +++++++++++++++++++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/docs/javascript-runtime-reconciliation.md b/docs/javascript-runtime-reconciliation.md index e3236e66bf..decbd0f844 100644 --- a/docs/javascript-runtime-reconciliation.md +++ b/docs/javascript-runtime-reconciliation.md @@ -140,6 +140,11 @@ classification: Nodes beneath a mapped artifact prefix also stay unknown when a runtime script or worker under that mapping has no artifact path. +Script inventory completeness remains a separate reported fact. Unavailable, +truncated, or attach-limited worker inventory prevents negative code-presence +claims even when the selected page's script inventory is complete. Accepted +positive script correspondences remain loaded. + None of these states proves feature execution, initialization order, reachability, or causality. In particular, `not-observed-in-capture` never means globally unloaded. diff --git a/src/domain/javascript/javascriptRuntimeLoadState.ts b/src/domain/javascript/javascriptRuntimeLoadState.ts index 87ba847e8c..49703719d3 100644 --- a/src/domain/javascript/javascriptRuntimeLoadState.ts +++ b/src/domain/javascript/javascriptRuntimeLoadState.ts @@ -70,7 +70,9 @@ const classifyLayer = ( options.reconciliationComplete && layer.graph.coverage.status === "complete" && scopedEntities.length > 0 && - scopedEntities.every(({ capture }) => capture.scriptsCompleteWithinScope); + scopedEntities.every( + ({ capture }) => capture.codeInventoryCompleteWithinScope, + ); const states = nodes.map((node) => { const runtimeNodeIds = uniqueSorted(direct.get(node.node_id) ?? []); if (runtimeNodeIds.length > 0) diff --git a/src/domain/javascript/javascriptRuntimeReconciliationActive.ts b/src/domain/javascript/javascriptRuntimeReconciliationActive.ts index 30ed475fd7..ea8cde18da 100644 --- a/src/domain/javascript/javascriptRuntimeReconciliationActive.ts +++ b/src/domain/javascript/javascriptRuntimeReconciliationActive.ts @@ -67,6 +67,7 @@ export const parseActiveElectronCapture = ( inspection: normalizeInspection(result), captureSha256: digestCanonicalValue(result, "Runtime reconciliation"), scriptsCompleteWithinScope: false, + codeInventoryCompleteWithinScope: false, }; }; diff --git a/src/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.ts b/src/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.ts index eb96918ac0..b1b2d441d1 100644 --- a/src/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.ts +++ b/src/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.ts @@ -39,6 +39,7 @@ interface RuntimeCaptureBase { readonly evidence: Evidence; readonly captureSha256: string; readonly scriptsCompleteWithinScope: boolean; + readonly codeInventoryCompleteWithinScope: boolean; } interface NormalizedV8Inspection { @@ -199,6 +200,9 @@ const parseBrowserCapture = ( inspection, captureSha256: digestCanonicalValue(inspection, "Runtime reconciliation"), scriptsCompleteWithinScope: scriptsComplete(inspection.completeness), + codeInventoryCompleteWithinScope: codeInventoryComplete( + inspection.completeness, + ), }; }; @@ -241,6 +245,9 @@ const parseElectronCapture = ( inspection, captureSha256: digestCanonicalValue(inspection, "Runtime reconciliation"), scriptsCompleteWithinScope: scriptsComplete(inspection.completeness), + codeInventoryCompleteWithinScope: codeInventoryComplete( + inspection.completeness, + ), }; }; @@ -273,6 +280,7 @@ const parseV8Capture = ( inspection: normalizeV8Inspection(result), captureSha256: digestCanonicalValue(result, "Runtime reconciliation"), scriptsCompleteWithinScope: false, + codeInventoryCompleteWithinScope: false, }; }; @@ -505,3 +513,11 @@ const scriptsComplete = (completeness: { !completeness.truncated_sections.includes("scripts") && !completeness.unavailable_sections.includes("scripts") && completeness.dropped_events.scripts === 0; + +// Missing worker inventory cannot establish absence of a worker code asset, +// even when the selected page's own script inventory is complete. +const codeInventoryComplete = (completeness: BrowserCompleteness): boolean => + scriptsComplete(completeness) && + !completeness.unavailable_sections.includes("workers") && + !completeness.truncated_sections.includes("workers") && + !completeness.attach_limited_sections.includes("workers"); diff --git a/tests/boundary/filesystem/javascript-applications/javascriptRuntimeReconciliation.test.ts b/tests/boundary/filesystem/javascript-applications/javascriptRuntimeReconciliation.test.ts index e0da9f01bf..05934d6c57 100644 --- a/tests/boundary/filesystem/javascript-applications/javascriptRuntimeReconciliation.test.ts +++ b/tests/boundary/filesystem/javascript-applications/javascriptRuntimeReconciliation.test.ts @@ -229,6 +229,40 @@ it("keeps static load states unknown when one scoped capture omits scripts", asy ); }); +it("preserves positive script matches without inventing worker absence when workers are unavailable", async () => { + const fixture = await applicationFixture(); + const staticEvidence = await analyzeFixture(fixture); + const reconcile = (workersUnavailable: boolean) => + reconcileInput({ + static_layers: [{ role: "application", analysis: staticEvidence }], + runtime_observations: [ + electronRuntimeEvidence(fixture, SOURCE, { + includeWorker: false, + workersUnavailable, + }), + ], + }); + const complete = reconcile(false); + const incomplete = reconcile(true); + expect(complete.summary.static_not_observed).toBeGreaterThan(0); + expect(incomplete.summary.static_not_observed).toBe(0); + expect(incomplete.runtime_captures[0]?.scripts_complete_within_scope).toBe( + true, + ); + expect(incomplete.static_load_states).toContainEqual( + expect.objectContaining({ + status: "loaded", + reason: "runtime-script-correspondence", + }), + ); + expect(incomplete.static_load_states).toContainEqual( + expect.objectContaining({ + status: "unknown", + reason: "static-or-runtime-coverage-incomplete", + }), + ); +}); + it("reconciles active Electron as a partial target-only runtime capture", async () => { const fixture = await applicationFixture(); const staticEvidence = await analyzeFixture(fixture);