fix(artifacts): report unknown inventory traversal in inspection coverage (#5867)

fix(artifacts): keep unknown inventory traversal out of complete inspection coverage

Artifact inspection derived its coverage status only from always-zero
omission counts, so an inventory substep with unknown container-entry
traversal (for example, an omitted FIFO) yielded
complete-within-substeps. Analysis-view summaries also exposed the
inventory's typed source_coverage only for PCK and PyInstaller roots.

Derive the inspection status from the inventory substep's coverage
(unknown traversal is partial), and carry the producer's source_coverage
plus the traversal-unknown caveat in every artifact summary. Hash-count
fields keep their existing format gating.

Fixes #5837

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-21 04:59:09 -07:00
1 parent d124809950
commit ce1f4b1bba
4 files changed
+145 -14

No files matched your search

+3 -1
View File
@@ -355,7 +355,9 @@ page size or export retained Evidence if the view is too large.
Completed `inspect_artifact` and atomic `inventory_artifact` Evidence also
support summary and inventory views. A summary reports the manifest's expected
counts separately from available record counts, observed entry kinds, inventory
Evidence IDs and inspection coverage. An inventory view selects `nodes`,
Evidence IDs, the inventory producer's traversal `source_coverage` and
inspection coverage. Inspection coverage is `partial` when that traversal
coverage is unknown. An inventory view selects `nodes`,
`occurrences`, `edges`, or `integrity_contradictions`, with `offset` and `limit`
defaulting to 0 and 64. For occurrences, optional `logical_paths` matches exact
recorded strings: omission selects all available records; `[]` selects none.
@@ -245,17 +245,14 @@ export const projectArtifactInventoryView = (
inventory.occurrences.find((row) => row.logical_path === ".")
?.format_metadata ?? null;
const rootMetadata = source.rootMetadata;
const includeCoverage =
// Producer traversal coverage is the owner's fact for every format; missing
// coverage would read as unknown, so every view carries it inline.
const sourceCoverage = { source_coverage: inventory.coverage };
const includeHashCounts =
view.kind === "inventory" ||
inventory.manifest.root_format === "godot-pck" ||
inventory.manifest.root_format === "pyinstaller";
const sourceCoverage = includeCoverage
? { source_coverage: inventory.coverage }
: {};
const counts =
includeCoverage || view.kind === "summary"
? inventoryCounts(source)
: undefined;
const counts = includeHashCounts ? inventoryCounts(source) : undefined;
const limitations = uniqueSorted([
...parent.limitations,
...inventory.limitations,
@@ -272,13 +269,12 @@ export const projectArtifactInventoryView = (
limitations,
unknowns: [
"Inventory does not establish application runtime behavior or complete format-specific analysis.",
...(includeCoverage && inventory.coverage.status === "unknown"
...(inventory.coverage.status === "unknown"
? [
"Producer traversal coverage is unknown; missing matches do not prove source absence.",
]
: []),
...(includeCoverage &&
counts !== undefined &&
...(counts !== undefined &&
(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.",
@@ -325,7 +321,7 @@ export const projectArtifactInventoryView = (
? {}
: { metadata: counts.kinds.metadata }),
},
...(includeCoverage
...(includeHashCounts
? { available_hash_status_counts: counts.hashes }
: {}),
source_complete: inventory.complete,
+4 -1
View File
@@ -427,10 +427,13 @@ const branch = (
};
};
// A completed substep establishes only the coverage its producer examined, so
// unknown inventory traversal keeps the inspection partial, never complete.
const inspectionCoverage = (
_inventory: ArtifactInventoryResult,
inventory: ArtifactInventoryResult,
omissions: Readonly<Record<string, number>>,
): ArtifactInspectionResult["coverage"]["status"] => {
if (Object.values(omissions).some((count) => count > 0)) return "truncated";
if (inventory.coverage.status !== "complete") return "partial";
return "complete-within-substeps";
};
@@ -0,0 +1,130 @@
import { execFileSync } from "node:child_process";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { Ajv2020 } from "ajv/dist/2020.js";
import { describe, expect, it } from "vitest";
import { createArtifactBinarySession } from "../../../src/composition/binary.js";
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const TRAVERSAL_UNKNOWN =
"Producer traversal coverage is unknown; missing matches do not prove source absence.";
const writeBundle = async (withFifo: boolean): Promise<string> => {
const root = await createTestTempDirectory("rea-inspection-coverage-");
const bundle = join(root, "Fixture.app");
await mkdir(bundle);
await writeFile(
join(bundle, "Info.plist"),
"<plist><dict><key>CFBundleExecutable</key><string>main.js</string></dict></plist>",
);
await writeFile(join(bundle, "main.js"), "export const value = 1;\n");
if (withFifo) execFileSync("mkfifo", [join(bundle, "omitted.fifo")]);
return bundle;
};
const connect = async () => {
const session = await createArtifactBinarySession(process.env);
const server = createServer({ kind: "session", session });
const client = new Client({ name: "inspection-coverage", version: "1" });
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
await client.connect(clientTransport);
const definition = (await client.listTools()).tools.find(
(tool) => tool.name === "inspect_analysis_view",
);
expect(definition?.outputSchema).toBeDefined();
const outputSchema: Record<string, unknown> = definition!.outputSchema!;
const viewOutput = new Ajv2020({
strict: false,
validateFormats: false,
}).compile(outputSchema);
return { client, server, viewOutput };
};
describe.skipIf(process.platform === "win32")(
"inspect_artifact traversal coverage",
() => {
it.each([
{
withFifo: true,
status: "partial",
sourceCoverage: { scope: "container-entries", status: "unknown" },
},
{
withFifo: false,
status: "complete-within-substeps",
sourceCoverage: { scope: "container-entries", status: "complete" },
},
] as const)(
"derives inspection and summary coverage from inventory traversal (FIFO: $withFifo)",
async ({ withFifo, status, sourceCoverage }) => {
const path = await writeBundle(withFifo);
const { client, server, viewOutput } = await connect();
try {
const opened = await client.callTool({
name: "open_binary",
arguments: { path },
});
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
const inspected = await client.callTool({
name: "inspect_artifact",
arguments: {},
});
expect(inspected.isError, JSON.stringify(inspected)).not.toBe(true);
const parent = parseEvidence(inspected.structuredContent);
const inspection = artifactInspectionResultSchema.parse(
parent.normalized_result,
);
const inventory = inspection.substeps[0]!.evidence.normalized_result;
expect(inventory.coverage).toEqual(sourceCoverage);
expect(inspection.coverage.status).toBe(status);
const summaries = [
await client.callTool({
name: "inspect_analysis_view",
arguments: {
source: {
kind: "retained-evidence",
evidence_id: parent.evidence_id,
},
view: { kind: "summary" },
},
}),
await client.callTool({
name: "inspect_artifact",
arguments: { detail: "summary" },
}),
];
for (const reply of summaries) {
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
expect(
viewOutput(reply.structuredContent),
JSON.stringify(viewOutput.errors),
).toBe(true);
const view = analysisViewResultSchema.parse(
parseEvidence(reply.structuredContent).normalized_result,
);
if (view.kind !== "summary" || !("source_complete" in view.summary))
throw new Error("Expected an artifact inventory summary");
expect(view.summary.source_complete).toBe(true);
expect(view.summary.source_coverage).toEqual(sourceCoverage);
expect(view.summary.inspection?.coverage.status).toBe(status);
expect(view.unknowns.includes(TRAVERSAL_UNKNOWN)).toBe(withFifo);
}
} finally {
await client.close();
await server.close();
}
},
30_000,
);
},
);