fix(artifacts): disclose unavailable content hashes in summaries (#6481)
This commit is contained in:
2 files changed
+91
-4
No files matched your search
@@ -258,7 +258,7 @@ export const projectArtifactInventoryView = (
|
||||
view.kind === "inventory" ||
|
||||
inventory.manifest.root_format === "godot-pck" ||
|
||||
inventory.manifest.root_format === "pyinstaller";
|
||||
const counts = includeHashCounts ? inventoryCounts(source) : undefined;
|
||||
const counts = inventoryCounts(source);
|
||||
const limitations = uniqueSorted([
|
||||
...parent.limitations,
|
||||
...inventory.limitations,
|
||||
@@ -280,8 +280,7 @@ export const projectArtifactInventoryView = (
|
||||
"Producer traversal coverage is unknown; missing matches do not prove source absence.",
|
||||
]
|
||||
: []),
|
||||
...(counts !== undefined &&
|
||||
(counts.hashes.unavailable > 0 || counts.hashes["not-hashed"] > 0)
|
||||
...(counts.hashes.unavailable > 0 || counts.hashes["not-hashed"] > 0
|
||||
? [
|
||||
"Content hashes are unknown for some recorded files; missing node IDs do not prove content absence.",
|
||||
]
|
||||
@@ -302,7 +301,6 @@ export const projectArtifactInventoryView = (
|
||||
| "unknowns"
|
||||
>;
|
||||
if (view.kind === "summary") {
|
||||
const counts = inventoryCounts(source);
|
||||
const examined =
|
||||
inventory.nodes.length +
|
||||
inventory.occurrences.length +
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { createPackageWithOptions } from "@electron/asar";
|
||||
import { expect } from "vitest";
|
||||
import { z } from "zod";
|
||||
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";
|
||||
|
||||
const summarySchema = z.object({
|
||||
unknowns: z.array(z.string()),
|
||||
summary: z.object({ source_complete: z.boolean() }),
|
||||
});
|
||||
|
||||
cliTest(
|
||||
"discloses unavailable ASAR content hashes in both summary routes and retains the parent",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-asar-summary-");
|
||||
const source = join(root, "source");
|
||||
await mkdir(join(source, "native"), { recursive: true });
|
||||
await writeFile(join(source, "main.js"), "console.log(1);\n");
|
||||
const bytes = "inert addon bytes\n";
|
||||
await writeFile(join(source, "native", "addon.node"), bytes);
|
||||
const archive = join(root, "app.asar");
|
||||
await createPackageWithOptions(source, archive, { unpack: "*.node" });
|
||||
const companion = join(`${archive}.unpacked`, "native", "addon.node");
|
||||
const caveat =
|
||||
"Content hashes are unknown for some recorded files; missing node IDs do not prove content absence.";
|
||||
for (const phase of ["present", "missing", "restored"]) {
|
||||
if (phase === "missing") await rm(companion);
|
||||
if (phase === "restored") await writeFile(companion, bytes);
|
||||
const bundle = join(root, `${phase}-evidence.json`);
|
||||
const direct = await cli.run({
|
||||
arguments: [
|
||||
"inspect-artifact",
|
||||
archive,
|
||||
"--detail",
|
||||
"summary",
|
||||
"--evidence_output",
|
||||
bundle,
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
expect(direct.exitCode).toBe(0);
|
||||
const compact = z
|
||||
.object({ summary: z.object({ normalized_result: summarySchema }) })
|
||||
.parse(direct.json);
|
||||
const retained = z
|
||||
.object({ records: z.array(z.unknown()) })
|
||||
.parse(JSON.parse(await readFile(bundle, "utf8")));
|
||||
const parent = retained.records
|
||||
.map(parseEvidence)
|
||||
.find((record) => record.operation === "inspect_artifact");
|
||||
expect(parent).toBeDefined();
|
||||
const request = join(root, "request.json");
|
||||
await writeFile(
|
||||
request,
|
||||
JSON.stringify({
|
||||
source: { kind: "inline", evidence: parent },
|
||||
view: { kind: "summary" },
|
||||
}),
|
||||
);
|
||||
const inline = await cli.run({
|
||||
arguments: ["inspect-analysis-view", request, "--json"],
|
||||
});
|
||||
expect(inline.exitCode).toBe(0);
|
||||
const summary = summarySchema.parse(
|
||||
parseEvidence(inline.json).normalized_result,
|
||||
);
|
||||
const inspection = artifactInspectionResultSchema.parse(
|
||||
parent!.normalized_result,
|
||||
);
|
||||
const occurrence = inspection.substeps
|
||||
.flatMap((step) => step.evidence.normalized_result.occurrences)
|
||||
.find((row) => row.logical_path === "native/addon.node");
|
||||
expect(occurrence).toMatchObject({
|
||||
hash_status: phase === "missing" ? "unavailable" : "verified",
|
||||
...(phase === "missing" ? { artifact_id: null } : {}),
|
||||
});
|
||||
for (const result of [compact.summary.normalized_result, summary]) {
|
||||
expect(result.summary.source_complete).toBe(true);
|
||||
expect(result.unknowns.includes(caveat), phase).toBe(
|
||||
phase === "missing",
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user