fix(artifacts): describe observed inventory scope accurately

This commit is contained in:
morluto
2026-10-24 15:12:57 +08:00
parent 0c49878b9a
commit 77be87db99
5 changed files with 59 additions and 11 deletions
+8 -5
View File
@@ -68,11 +68,14 @@ byte correspondence or decode the map's original source paths.
Artifact inventories declare `coverage.scope` (`root` or `container-entries`)
and `coverage.status` (`complete` or `unknown`) independently of diagnostic
wording. Comparison requires matching scopes, complete graph members and complete
traversal coverage before concluding equality. A fully observed root without
container children can be `unchanged`; its scope notice stays in `limitations`
without creating a gap. Content and executable metadata changes still produce
root deltas. Missing graph members yield `truncated`; unknown traversal coverage
or unavailable occurrence hashes retain uncertainty. Integrity mismatches remain
traversal coverage before concluding equality. Fully observed roots can be
`unchanged` within root scope; their scope notice stays in `limitations` without
creating a gap. Root-scoped equality establishes equality of the observed root
bytes. Child-container presence and unexamined format details remain unresolved,
including when the root format is `unknown`. Content and executable metadata
changes still produce root deltas. Missing graph members yield `truncated`;
unknown traversal coverage or unavailable occurrence hashes retain uncertainty.
Integrity mismatches remain
contradictions. Static artifact equality does not establish unchanged runtime
behavior.
Source-map relation deltas include the content identity of the mapped source,
+3 -1
View File
@@ -106,6 +106,8 @@ export const inventoryCoverage = (
};
return {
coverage: { scope: "root", status: "complete" },
limitations: ["Artifact has no child container entries."],
limitations: [
"Inventory covers root bytes only; child container entries were not examined.",
],
};
};
@@ -174,7 +174,7 @@ cliTest(
edge_count: 0,
});
expect(inventory.limitations).toEqual([
"Artifact has no child container entries.",
"Inventory covers root bytes only; child container entries were not examined.",
]);
const equal = await compare(left, right);
expect(equal.normalized_result).toMatchObject({
@@ -182,8 +182,8 @@ cliTest(
summary: { unchanged: 1, unknown: 0 },
changes: [],
limitations: [
"Left: Artifact has no child container entries.",
"Right: Artifact has no child container entries.",
"Left: Inventory covers root bytes only; child container entries were not examined.",
"Right: Inventory covers root bytes only; child container entries were not examined.",
],
});
const behavior = await call("find_changed_behavior", {
@@ -36,7 +36,9 @@ it("compares identical complete plist inventories across CLI and MCP and creates
throw new Error("CLI inspection did not retain inventory Evidence");
expect(cliInventory.normalized_result).toMatchObject({
coverage: { scope: "root", status: "complete" },
limitations: ["Artifact has no child container entries."],
limitations: [
"Inventory covers root bytes only; child container entries were not examined.",
],
});
const { call } = await connectLocalToolsMcp();
@@ -53,7 +55,9 @@ it("compares identical complete plist inventories across CLI and MCP and creates
throw new Error("MCP inspection did not retain inventory Evidence");
expect(mcpInventory.normalized_result).toMatchObject({
coverage: { scope: "root", status: "complete" },
limitations: ["Artifact has no child container entries."],
limitations: [
"Inventory covers root bytes only; child container entries were not examined.",
],
});
const compared = await call("compare_artifacts", {
@@ -20,6 +20,11 @@ import { asarHeaderBytes } from "../../fixtures/asarHeader.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { readWithoutFifoWriter } from "../../fixtures/fifoInput.js";
import { DEFAULT_ASAR_MAX_HEADER_BYTES } from "../../../src/config/asarHeaderAdmission.js";
import {
CPU,
arArchive,
fatImage,
} from "../../../src/artifacts/apple/MachoImage.fixture.js";
it.skipIf(process.platform === "win32")(
"rejects a FIFO artifact root without waiting for a writer",
@@ -81,6 +86,40 @@ it("keeps unavailable container traversal distinct from observed root bytes", as
});
});
it("retains root identity without asserting absent children for an unclassified FAT archive", async () => {
const root = await createTestTempDirectory("rea-fat-archive-root-scope-");
const path = join(root, "libfat.a");
const archive = arArchive();
const bytes = fatImage([
{ cpu: CPU.x86_64, bytes: archive },
{ cpu: CPU.arm64, bytes: archive },
]);
await writeFile(path, bytes);
const inventory = await inventoryArtifact(path);
const sha256 = createHash("sha256").update(bytes).digest("hex");
expect(inventory.manifest).toMatchObject({
root_format: "unknown",
root_sha256: sha256,
node_count: 1,
occurrence_count: 1,
edge_count: 0,
});
expect(inventory.nodes).toEqual([
expect.objectContaining({ format: "unknown", sha256, size: bytes.length }),
]);
expect(inventory.occurrences).toEqual([
expect.objectContaining({
artifact_format: "unknown",
hash_status: "verified",
}),
]);
expect(inventory.coverage).toEqual({ scope: "root", status: "complete" });
expect(inventory.limitations).toEqual([
"Inventory covers root bytes only; child container entries were not examined.",
]);
});
it("admits a FAT root when a valid slice header starts at the prefix boundary", async () => {
const root = await createTestTempDirectory("rea-fat-root-header-boundary-");
const path = join(root, "universal");