From cb0d8a9fef2b907cff2ba27f1e017493e1173eb2 Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:51:53 +0800 Subject: [PATCH] fix(javascript): preserve recorded POSIX path case on Windows Follow up on #2422 and the host-relative path diagnosis by @Aurantium-47. Select the recorded path grammar at the shared root matcher, preserving Windows case semantics and both matching and active-layer scope. Verify real npm module data and passive Node Inspector Evidence through Windows CLI and same-connection SDK MCP. Positive normalized results remain unchanged; rejected POSIX roots leave load state unknown. All 101 related tests and static/docs gates pass. Register the existing workflow tests in Windows CI execution and package scope. No IDA or Ghidra engine was used; scoped resources and delivery costs are recorded in experiments/analysis-efficiency/windows-posix-reconciliation.md. --- .github/workflows/ci.yml | 1 + .../windows-posix-reconciliation.md | 92 +++++++++++++++++++ scripts/ci/scopes.mjs | 1 + .../javascriptRuntimeStaticCandidates.ts | 4 +- .../javascriptApplicationWorkflows.test.ts | 92 +++++++++++++++++++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 experiments/analysis-efficiency/windows-posix-reconciliation.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e6521b412..09f0d6e36b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -510,6 +510,7 @@ jobs: tests/boundary/filesystem/setupUnavailableProvider.test.ts tests/boundary/filesystem/windowsNpxRegistration.test.ts tests/acceptance/setup/homeDirectoryFallback.test.ts + tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts tests/boundary/filesystem/webScriptManifestPaths.test.ts tests/boundary/process/ProcessEvidence.host.test.ts tests/acceptance/webModuleCli.test.ts diff --git a/experiments/analysis-efficiency/windows-posix-reconciliation.md b/experiments/analysis-efficiency/windows-posix-reconciliation.md new file mode 100644 index 0000000000..d3f99a2fe7 --- /dev/null +++ b/experiments/analysis-efficiency/windows-posix-reconciliation.md @@ -0,0 +1,92 @@ +# Windows reconciliation of recorded POSIX paths + +Follow-up to [#2422](https://github.com/morluto/rea/pull/2422), including +@Aurantium-47's diagnosis of the host-dependent relative-path operation. + +## Source pins and real data + +The initial reproduction and passive producer used main +fa4b8288655188426222773ead2ccbdaf76334fd. Before branching, main was updated +to 8b181716aca2cb467be499cba226abfb5ae19141. The owning matcher and its +affected reconciliation consumers were unchanged between those revisions. +The newer baseline again reproduced both errors on the real Windows host. + +Input: npm 11.16.0's published lib/utils/validate-lockfile.js, 1,023 bytes, +SHA-256 2295e2dac1c5816e1eae2d417804960ebbef17b64a0a44db4f3483ce6e6d9526. +The installed copy was independently checked against the exact module in the +[official registry package](https://registry.npmjs.org/npm/11.16.0). +The 2,945,850-byte tarball matched its published SHA-512 SRI before reading +that member; no archive extraction or installation occurred, and the tarball +was not retained. + +Two actual Linux filesystem directories, Runtime/App and Runtime/app, +held byte-identical module copies and had distinct directory identities. +A controlled Node 24.18.0 process loaded the public module and ran its real +function against 587 locked dependency records: compatible trees returned zero +errors, and one controlled version mismatch returned one error. A passive REA +Inspector capture recorded 114 loaded scripts, including the selected module. +The provider did not evaluate, pause, resume or read source. The owned process +exited normally. Function execution is established by the harness receipt; +REA's passive script metadata establishes loaded status. + +Production static analysis inspected both directories plus an independent +Static/public-module copy. The latter prevents implicit root matching from +hiding the explicit mapping result. Complete static/runtime Evidence was then +consumed by the actual **Windows** CLI and SDK 2.3.1 stdio MCP server, using +Windows 11 10.0.22631.0 and official Windows x64 Node 24.18.0. +No Electron GUI, IDA or Ghidra engine was used. + +## Correctness and provenance + +| Windows reconciliation | Baseline | Candidate | +| -------------------------------------- | ------------------------------ | ----------------------------------- | +| Exact POSIX input root | Matched, artifact path | Same complete normalized result | +| Case-mismatched POSIX input root | Incorrectly matched and loaded | Unknown; static load states unknown | +| Exact explicit POSIX mapping | Matched, operator file mapping | Same complete normalized result | +| Case-mismatched explicit POSIX mapping | Incorrectly matched and loaded | Unknown; static load states unknown | + +All four CLI and same-connection MCP normalized results agree. The input +Evidence, recorded runtime facts and caller-selected root spellings are +preserved. Rejected location membership does not establish that a module was +absent or unexecuted. The operation remains offline and performs no target or +filesystem inspection. + +The shared matcher now chooses posix.relative after identifying POSIX input; +Windows coordinates continue to use win32.relative. Both location matching +and active-layer scope use this owner. The obsolete host-default import is +removed; no downstream repair or new path abstraction is introduced. + +The related suite passed 101 tests: 15 workflow tests, 84 CI scope tests and +two curated-path checks. Four simulated Electron Evidence controls cover +POSIX mismatch/exact matching, Windows case-insensitive matching, the resulting +asset load state and retained mapping spelling. They are service regressions, +not real Electron-provider verification. The existing workflow test file is +registered in both the Windows execution lane and its package-change scope. + +## Delivery and resource observations + +Probes ran serially at BelowNormal priority with one CPU and 512 MiB Node heap +settings for CLI/server/client work. Heap settings do not bound aggregate memory. +Four corrected CLI exit-time observations reported 7.343–15.827 seconds CPU and +322,308–324,280 KiB peak working set, excluding the probe parent. Wall times +ranged from 7.785 to 17.601 seconds. There is no paired resource baseline. + +One complete MCP trial connected in 18,296 ms and discovered 189 tools in +875 ms (2,588,336 serialized bytes). The four subsequent same-connection calls +took 879, 320, 353 and 422 ms. Their serialized replies were 918,133 / 913,765 / +918,571 / 914,171 bytes; inputs totalled 681,312 bytes. Discovery plus these +inputs and replies totalled 6,934,288 bytes, excluding initialization, framing +and notifications. This is delivery volume, not model token usage. Actual +model context and total model task cost were not measured. + +A process-generation sampler recorded 233 observations over 25,671 ms: +15,797 ms aggregate observed CPU as a lower bound and a peak sampled working-set +sum of 491,597,824 bytes (about 469 MiB). Exited-process tails can be missed; +shared pages can be counted twice. This excludes the observer and is not an +exact physical-memory peak. The server wrote four structured completion records +to stderr; tool replies and normalized results passed the recorded checks. + +Cold/warm caches and observer differences were not normalized. No latency, +CPU, memory or model-token improvement is claimed. The fix adds no lookup I/O, +engine invocation, retry or cache. Complete default replies remain available; +only compact selected findings and hashes were sent to the investigating agent. diff --git a/scripts/ci/scopes.mjs b/scripts/ci/scopes.mjs index 4493fe512c..2a2d703bee 100644 --- a/scripts/ci/scopes.mjs +++ b/scripts/ci/scopes.mjs @@ -56,6 +56,7 @@ export const SCOPES = { "tests/boundary/filesystem/setupUnavailableProvider.test.ts", "tests/boundary/filesystem/windowsNpxRegistration.test.ts", "tests/acceptance/setup/homeDirectoryFallback.test.ts", + "tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts", "tests/boundary/filesystem/webScriptManifestPaths.test.ts", "tests/boundary/process/ProcessEvidence.host.test.ts", "tests/acceptance/webModuleCli.test.ts", diff --git a/src/domain/javascript/javascriptRuntimeStaticCandidates.ts b/src/domain/javascript/javascriptRuntimeStaticCandidates.ts index 1f02c820dc..810ab7f5e1 100644 --- a/src/domain/javascript/javascriptRuntimeStaticCandidates.ts +++ b/src/domain/javascript/javascriptRuntimeStaticCandidates.ts @@ -1,4 +1,4 @@ -import { posix, relative, win32 } from "node:path"; +import { posix, win32 } from "node:path"; import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js"; import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js"; @@ -216,7 +216,7 @@ const pathBelowRoot = (root: string, value: string): string | null => { const remainder = rootStyle === "windows" ? win32.relative(root, value) - : relative(root, value); + : posix.relative(root, value); const portableRemainder = remainder.replaceAll("\\", "/"); if ( remainder === "" || diff --git a/tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts b/tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts index 17c3f00487..8393bad540 100644 --- a/tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts +++ b/tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts @@ -21,6 +21,7 @@ import { type ApplicationVersionComparisonResult, } from "../../../src/domain/javascript/javascriptApplicationVersionComparisonSchemas.js"; import { javascriptRuntimeReconciliationResultSchema } from "../../../src/domain/javascript/javascriptRuntimeReconciliationSchemas.js"; +import { electronPageInspectionSchema } from "../../../src/domain/javascript/electronObservation.js"; import { compareJavaScriptApplicationVersions } from "../../../src/domain/javascript/javascriptApplicationVersionComparison.js"; import { createJavaScriptApplicationGraph } from "../../../src/domain/javascript/javascriptApplicationGraph.js"; import { traceApplicationFeature } from "../../../src/domain/javascript/javascriptFeatureTrace.js"; @@ -443,7 +444,98 @@ describe("JavaScript application workflow error diagnostics", () => { }); }); +const RUNTIME_PATH_CASES = [ + { + name: "case-sensitive POSIX input root", + root: null, + path: "/applications/Example.app/Contents/Resources/app/renderer.js", + status: "unknown", + basis: "none", + }, + { + name: "case-sensitive POSIX mapping root", + root: "/runtime/App", + path: "/runtime/app/renderer.js", + status: "unknown", + basis: "none", + }, + { + name: "matching POSIX mapping root", + root: "/runtime/App", + path: "/runtime/App/renderer.js", + status: "matched", + basis: "operator-file-mapping", + }, + { + name: "case-insensitive Windows mapping root", + root: String.raw`C:\runtime\App`, + path: String.raw`c:\runtime\app\renderer.js`, + status: "matched", + basis: "operator-file-mapping", + }, +] as const; + describe("JavaScript runtime reconciliation local paths", () => { + it.each(RUNTIME_PATH_CASES)( + "uses $name on every host", + ({ root, path, status, basis }) => { + const base = JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE; + const original = base.runtime_observations[0]!; + const inspection = electronPageInspectionSchema.parse( + original.normalized_result, + ); + // Keep target and script coordinates consistent so an unrelated target + // cannot activate the static layer after its script mapping was rejected. + const runtime = createEvidence(undefined, original.provider, { + predicateType: original.predicate_type, + operation: original.operation, + parameters: { ...original.parameters, include_script_sources: false }, + result: { + ...inspection, + target: { ...inspection.target, file_path: path }, + scripts: { + ...inspection.scripts, + items: inspection.scripts.items.map((script) => ({ + ...script, + file_path: path, + source: { included: false, reason: "not-requested" }, + })), + }, + }, + confidence: original.confidence, + authority: original.authority, + }); + const reconciled = reconcileJavaScriptRuntimeEvidence({ + static_layers: base.static_layers.map((layer) => ({ + ...layer, + runtime_mappings: root === null ? [] : [{ kind: "file-root", root }], + })), + runtime_observations: [runtime], + }); + if (!reconciled.ok) throw reconciled.error; + const result = javascriptRuntimeReconciliationResultSchema.parse( + reconciled.value.normalized_result, + ); + expect( + result.reconciliations.find( + ({ entity_kind }) => entity_kind === "script", + ), + ).toMatchObject({ status, basis }); + expect( + result.static_load_states.find( + ({ kind }) => kind === "javascript-asset", + ), + ).toMatchObject( + status === "matched" + ? { status: "loaded", reason: "runtime-script-correspondence" } + : { status: "unknown", reason: "layer-outside-runtime-scope" }, + ); + expect(result.static_layers[0]?.runtime_mappings).toEqual( + root === null ? [] : [{ kind: "file-root", root, artifact_prefix: "" }], + ); + }, + ); + it("resolves relative file mapping roots before deriving result Evidence", () => { const absoluteRoot = resolve(process.cwd(), "runtime-cache-fixture"); const relativeRoot = relative(process.cwd(), absoluteRoot);