fix(artifacts): describe ZIP non-entry coverage accurately (#2059)

* fix(artifacts): describe ZIP non-entry coverage accurately

* fix(process): report no remaining time after a provider deadline fires

Node timers can fire while performance.now() is still a fraction of a
millisecond short of the monotonic deadline. ProviderStartupDeadline then
reported interruption "timeout" and an aborted signal while remainingMs()
returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0
(Hopper and Ghidra startup) could run past an elapsed deadline, and the
real-timer test "aborts a bounded wait when its real monotonic deadline
expires" failed intermittently in CI (expected 1 to be 0).

Once the deadline's own timer has classified a timeout, report zero
remaining time. A deterministic regression test fires the timer with the
monotonic clock 0.4 ms short of the deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: format comparison and IDA sources

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-14 03:37:35 +08:00
1 parent 2320ac23ee
commit 3cb9d61ec1
5 files changed
+103 -9

No files matched your search

+2 -2
View File
@@ -13,7 +13,7 @@ import type {
} from "../../domain/artifactGraph.js";
import {
isZipPackageFormat,
ZIP_NON_ENTRY_TAIL_LIMITATION,
ZIP_NON_ENTRY_LIMITATION,
} from "../../domain/zipPackageFormat.js";
import type { StableRegularFileDescriptor } from "../../filesystem/RegularFile.js";
@@ -56,7 +56,7 @@ export const inventoryCoverage = (
coverage: { scope: "container-entries", status: traversal.status },
limitations: [
...(isZipPackageFormat(reader.format)
? [ZIP_NON_ENTRY_TAIL_LIMITATION]
? [ZIP_NON_ENTRY_LIMITATION]
: []),
...traversal.limitations,
],
+2 -2
View File
@@ -20,7 +20,7 @@ import { parseEvidence, type Evidence } from "./evidence.js";
import { artifactInspectionResultSchema } from "./artifactInspection.js";
import {
isZipPackageFormat,
ZIP_NON_ENTRY_TAIL_LIMITATION,
isZipInventoryScopeLimitation,
} from "./zipPackageFormat.js";
/** Complete or partially assembled inventory used by comparison workflows. */
@@ -279,7 +279,7 @@ const legacyInventoryCoverage = (
status: limitations.some(
(limitation) =>
limitation !== "Artifact has no child container entries." &&
limitation !== ZIP_NON_ENTRY_TAIL_LIMITATION &&
!isZipInventoryScopeLimitation(limitation) &&
!(
contradictionCount > 0 &&
limitation ===
+9 -3
View File
@@ -52,6 +52,12 @@ export const zipPackageFormatForPath = (
return undefined;
};
/** Inventory cannot see bytes that are not central-directory entries. */
export const ZIP_NON_ENTRY_TAIL_LIMITATION =
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.";
/** Inventory lists entries, not all bytes surrounding the central directory. */
export const ZIP_NON_ENTRY_LIMITATION =
"ZIP inventory lists central-directory entries only. Non-entry bytes, including an APK Signing Block before the central directory or a ZIP comment after it, are not occurrences.";
/** Recognize the same inventory scope in current and previously retained Evidence. */
export const isZipInventoryScopeLimitation = (limitation: string): boolean =>
limitation === ZIP_NON_ENTRY_LIMITATION ||
limitation ===
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.";
@@ -2,6 +2,7 @@ import { chmod, mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
import { fragmentInventoryEvidence } from "../../fixtures/artifactEvidence.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
@@ -553,3 +554,90 @@ describe("artifact comparison completeness", () => {
);
});
});
describe("ZIP non-entry coverage", () => {
it("retains pre-directory APK block changes and compares historical scope descriptions", async () => {
const root = await createTestTempDirectory("rea-apk-block-compare-");
const writer = new ZipWriter(new Uint8ArrayWriter());
await writer.add("classes.dex", new TextReader("unchanged entry"));
const zip = Buffer.from(await writer.close());
const eocd = zip.length - 22;
expect(zip.readUInt32LE(eocd)).toBe(0x06054b50);
const directory = zip.readUInt32LE(eocd + 16);
// APK Signing Block framing with one opaque padding pair, not a verified signature.
// https://source.android.com/docs/security/features/apksigning/v2#apk-signing-block
const block = Buffer.alloc(48);
block.writeBigUInt64LE(40n, 0);
block.writeBigUInt64LE(8n, 8);
block.writeUInt32LE(0x42726577, 16);
block.writeBigUInt64LE(40n, 24);
block.write("APK Sig Block 42", 32, "ascii");
const paths = [join(root, "before.apk"), join(root, "after.apk")];
for (const [index, path] of paths.entries()) {
block.writeUInt32LE(index, 20);
const apk = Buffer.concat([
zip.subarray(0, directory),
block,
zip.subarray(directory),
]);
apk.writeUInt32LE(directory + block.length, eocd + block.length + 16);
await writeFile(path, apk);
}
const left = await observe(paths[0]!);
const right = await observe(paths[1]!);
const inventory = artifactInventoryResultSchema.parse(
left.normalized_result,
);
expect(inventory.limitations).toContain(
"ZIP inventory lists central-directory entries only. Non-entry bytes, including an APK Signing Block before the central directory or a ZIP comment after it, are not occurrences.",
);
expect(
inventory.occurrences.map(({ logical_path }) => logical_path).sort(),
).toEqual([".", "classes.dex"]);
expect(compareArtifacts(left, right)).toMatchObject({
status: "changed",
summary: { changed: 1, unchanged: 1, unknown: 0 },
changes: [
expect.objectContaining({ logical_path: ".", dimensions: ["content"] }),
],
});
const {
coverage: _coverage,
coverage_status: _coverageStatus,
...legacyInventory
} = inventory;
const withLimitation = (limitation: string) =>
createEvidence(
{
path: paths[0]!,
sha256: inventory.manifest.root_sha256,
format: "apk",
},
PROVIDER,
{
operation: "inventory_artifact",
parameters: {},
result: jsonValueSchema.parse({
...legacyInventory,
limitations: [limitation],
}),
confidence: "observed",
authority: "shipped-artifact",
},
);
const historical = withLimitation(
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.",
);
expect(compareArtifacts(historical, left)).toMatchObject({
status: "unchanged",
summary: { unchanged: 2, unknown: 0 },
changes: [],
});
expect(
compareArtifacts(
withLimitation("ZIP entry bytes could not be read."),
left,
).status,
).toBe("unknown");
});
});
@@ -20,7 +20,7 @@ import { artifactInventoryResultSchema } from "../../../../src/domain/artifactGr
import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js";
import {
ZIP_PACKAGE_FORMATS,
ZIP_NON_ENTRY_TAIL_LIMITATION,
ZIP_NON_ENTRY_LIMITATION,
} from "../../../../src/domain/zipPackageFormat.js";
const { runProviderAnalysis } = createDirectAnalysis({});
@@ -264,7 +264,7 @@ describe("HarmonyOS inventory coverage", () => {
artifact_kind: "container",
artifact_format: format,
});
expect(inventory.limitations).toContain(ZIP_NON_ENTRY_TAIL_LIMITATION);
expect(inventory.limitations).toContain(ZIP_NON_ENTRY_LIMITATION);
},
);