Preserve unread file and slice facts before Apple attribution. Report partial component coverage while keeping manifest count completeness independent. Directories and symlinks retain their existing representation. Consolidate occurrence identity interpretation and component schemas across Apple, Android and Harmony; remove superseded producers. Android/Harmony completed source replies and projection IDs remain byte-identical in controls. Apple's additive unavailable_components field changes its projection IDs. Adapt the unavailable-component proposal and real ZIP regressions from PR #4692 by @rodricksz4h5, adding directory, CLI, MCP and non-encrypted controls. Validation: 77 focused checks; check:fast; docs:check (5 README checks). 18 tiny completed source cost controls; no real-provider/target execution or heavy profiling. Published npm package and large-input costs unverified. Fixes #4688
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
|
import { expect } from "vitest";
|
|
|
|
import { appleApplicationProjectionResultSchema } from "../../../src/domain/apple/appleApplication.js";
|
|
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest(
|
|
"Apple CLI preserves unread paths and distinguishes readable coverage",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-apple-unread-cli-");
|
|
for (const encrypted of [false, true]) {
|
|
const path = join(root, `${encrypted ? "encrypted" : "readable"}.ipa`);
|
|
const unread = "Payload/Fixture.app/libentry.dylib";
|
|
const writer = new ZipWriter(new Uint8ArrayWriter());
|
|
await writer.add("Payload/Fixture.app/", undefined, { directory: true });
|
|
await writer.add(
|
|
"Payload/Fixture.app/Info.plist",
|
|
new TextReader("<plist/>"),
|
|
);
|
|
await writer.add(
|
|
unread,
|
|
new TextReader("harmless text"),
|
|
encrypted ? { password: "fixture-only" } : {},
|
|
);
|
|
await writeFile(path, await writer.close());
|
|
const inspected = await cli.run({
|
|
arguments: ["inspect-artifact", path, "--json"],
|
|
});
|
|
expect(inspected.exitCode, inspected.stderr).toBe(0);
|
|
const inspection = artifactInspectionResultSchema.parse(
|
|
parseEvidence(inspected.json).normalized_result,
|
|
);
|
|
const inventories = inspection.substeps.map(({ evidence }) => evidence);
|
|
const input = join(root, `${encrypted}.json`);
|
|
await writeFile(
|
|
input,
|
|
JSON.stringify({ inventory_evidence: inventories }),
|
|
);
|
|
const output = await cli.run({
|
|
arguments: ["project-apple-application-graph", input, "--json"],
|
|
});
|
|
expect(output.exitCode, output.stderr).toBe(0);
|
|
const evidence = parseEvidence(output.json);
|
|
const projection = appleApplicationProjectionResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
expect(projection.source_evidence_ids).toEqual(
|
|
inventories.map(({ evidence_id }) => evidence_id).sort(),
|
|
);
|
|
expect(projection.coverage).toEqual({
|
|
status: encrypted ? "partial" : "complete-within-inventory",
|
|
inventory_complete: true,
|
|
});
|
|
if (encrypted) {
|
|
const occurrence = inventories[0]?.normalized_result.occurrences.find(
|
|
({ logical_path }) => logical_path === unread,
|
|
);
|
|
expect(occurrence).toMatchObject({
|
|
artifact_id: null,
|
|
encrypted: true,
|
|
hash_status: "unavailable",
|
|
});
|
|
expect(projection.unavailable_components).toEqual([
|
|
{
|
|
path: unread,
|
|
artifact_id: null,
|
|
sha256: null,
|
|
format: occurrence?.artifact_format,
|
|
encrypted: true,
|
|
limitations: occurrence?.limitations,
|
|
},
|
|
]);
|
|
expect(projection.components.native_libraries).toEqual([]);
|
|
} else {
|
|
expect(projection.unavailable_components).toEqual([]);
|
|
expect(
|
|
projection.components.native_libraries.map(({ path }) => path),
|
|
).toEqual([unread]);
|
|
}
|
|
}
|
|
},
|
|
);
|