fix: bind identity and coverage to the producing rule (#1513)

* fix: bind identity and coverage to the producing rule

Logical paths, container families, examined bytes, path-segment conventions, destination collisions, file-id leases, Mach-O CPU selection, and firmware cleanup now follow the observation that owns each rule.

* fix: fail unmatched Mach-O slices and keep firmware roots independent

A fat target whose architecture lipo did not list now stops before slice tools run. Chained fixups still count unread LC_DYLD_INFO rebase bytes, and a firmware cleanup failure no longer blocks the next workspace.

* test: cover path identity and runtime families at the projection boundary

NFC joins and segment conventions are now asserted by the path resolver, the Apple projection, and a real APK inventory instead of helper unit tests.

* test: expect unread dyld rebase bytes in the Apple dispatch fixture

The legacy LC_DYLD_INFO fixture keeps a rebase range this reader does not decode, so the public CLI and MCP check now requires that partial coverage beside the bind opcodes.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-10-10 17:14:47 +08:00
1 parent 7a56fe6260
commit b4db35f092
48 files changed
+1251 -266

No files matched your search

@@ -2,6 +2,10 @@ import { posix, relative, win32 } from "node:path";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import {
logicalPathEscapesRoot,
normalizeJoinedLogicalPath,
} from "../artifactIdentity.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ParsedStaticLayer } from "./javascriptRuntimeReconciliationParsing.js";
@@ -174,7 +178,7 @@ const nodePaths = (node: ApplicationNode): string[] => {
paths.push(observation.evidence.location.value.path);
}
return paths
.map(normalizeArtifactPath)
.map(relativeLogicalPath)
.filter(
(path): path is string => path !== null && path !== "artifact-root",
);
@@ -221,7 +225,7 @@ const pathBelowRoot = (root: string, value: string): string | null => {
portableRemainder.startsWith("../")
)
return null;
return normalizeArtifactPath(portableRemainder);
return relativeLogicalPath(portableRemainder);
};
const absoluteFilePathStyle = (path: string): "posix" | "windows" | null => {
@@ -250,7 +254,7 @@ const pathBelowUrlPrefix = (prefix: string, value: string): string | null => {
)
return null;
try {
return normalizeArtifactPath(
return relativeLogicalPath(
decodeURIComponent(candidate.pathname.slice(base.pathname.length)),
);
} catch (cause: unknown) {
@@ -292,17 +296,13 @@ const pathWithinPrefix = (path: string, prefix: string): boolean =>
prefix === "" || path === prefix || path.startsWith(`${prefix}/`);
const safeArtifactPath = (prefix: string, suffix: string): string | null =>
normalizeArtifactPath(prefix === "" ? suffix : posix.join(prefix, suffix));
relativeLogicalPath(prefix === "" ? suffix : posix.join(prefix, suffix));
const normalizeArtifactPath = (value: string): string | null => {
const normalized = posix
.normalize(value.replaceAll("\\", "/"))
.replace(/^\.\//u, "");
const relativeLogicalPath = (value: string): string | null => {
const normalized = normalizeJoinedLogicalPath(value);
return normalized !== "" &&
normalized !== "." &&
normalized !== ".." &&
!normalized.startsWith("/") &&
!normalized.startsWith("../")
!logicalPathEscapesRoot(normalized)
? normalized
: null;
};