Files
rea/src/application/BinaryOverview.test.ts
T
Kaoru0822andClaude Opus 5.5 d2518a19f8 feat(native): label binary overview counts with their inventory scope (#8338)
* feat(native): label binary overview counts with their inventory scope

`binary_overview` described its counts as "exhaustive", and its result was
a pair of bare scalars. An agent could read them as a discovery of every
function and string in the file, or as comparable across providers. They
are exact totals of the rows each provider's inventories return.

The shared `projectBinaryOverview` now adds one provider-neutral
`inventory_coverage` record, so Ghidra's direct summary and the
listing fallback carry the same record:
`{ scope: "provider_inventory", discovery_coverage: "unknown" }`.
The record reuses the `discovery_coverage` vocabulary that
`list_procedures({detail:"summary"})` already reports, now owned by one
domain schema. The tool description states the scope and that counts are
not comparable across providers. Counts, segment omission semantics and
limitations are unchanged.

Fixes #8287

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): label overviews in the workflow so older snapshots stay valid

Analysis snapshots replay the projected inventory summary. A snapshot saved
before inventory_coverage existed would then fail the overview output schema.
The record describes what overview counts mean, whoever produced them, so
the binary_overview workflow now adds it on both its provider-summary and
listing paths. Provider values and projectBinaryOverview are unchanged, and
the composed overview's linked atomic record stays as the provider
reported it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(native): refresh composed overviews from before inventory_coverage

Bump the binary_overview workflow revision to 5, so snapshot and CLI replay
recompute a revision-4 composed overview from its atomic record. Replay
would otherwise return the cached overview without its count labels. The
revision-4 case joins the existing refresh scenario. The atomic Evidence
is not rewritten.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-31 11:12:52 -07:00

170 lines
4.7 KiB
TypeScript

import { expect, it } from "vitest";
import { EnhancedTools } from "./EnhancedTools.js";
import { createAnalysisExecution } from "./AnalysisProvider.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { err, ok } from "../domain/result.js";
it("uses the provider-owned overview without requesting complete inventories", async () => {
const calls: string[] = [];
const result = {
document: "selected",
procedure_count: 12,
string_count: 34,
};
const tools = new EnhancedTools({
execute: async (operation) => {
calls.push(operation);
return ok(
createAnalysisExecution(result, {
id: "fixture",
name: "Fixture",
version: "1",
}),
);
},
});
// A provider or a snapshot saved before the record existed omits it.
expect(await tools.execute("binary_overview", {})).toEqual(
ok({
...result,
inventory_coverage: {
scope: "provider_inventory",
discovery_coverage: "unknown",
},
}),
);
expect(calls).toEqual(["read_binary_inventory_summary"]);
});
it.each([
new AnalysisOutputError("binary_overview", "Malformed aggregate"),
new AnalysisCancelledError("binary_overview"),
new AnalysisCapabilityUnavailableError(
"fixture",
"binary_overview",
"Cleanup pending",
{
cleanup: {
reason: "Owned process is still closing",
resources: ["session"],
},
},
),
])(
"preserves an aggregate failure rather than silently repeating analysis: %s",
async (failure) => {
const calls: string[] = [];
const tools = new EnhancedTools({
execute: async (operation) => {
calls.push(operation);
return err(failure);
},
});
expect(await tools.execute("binary_overview", {})).toEqual(err(failure));
expect(calls).toEqual(["read_binary_inventory_summary"]);
},
);
const fallbackOverview = (procedures: JsonValue, strings: JsonValue) =>
new EnhancedTools({
execute: async (operation) => {
if (
operation === "read_binary_inventory_summary" ||
operation === "list_segments"
)
return err(
new AnalysisCapabilityUnavailableError(
"fixture",
operation,
"Not supported",
),
);
const result =
operation === "current_document"
? "selected"
: operation === "list_procedures"
? procedures
: strings;
return ok(
createAnalysisExecution(result, {
id: "fixture",
name: "Fixture",
version: "1",
}),
);
},
});
it.each(["list_procedures", "list_strings"] as const)(
"rejects malformed successful %s inventories in the public overview workflow",
async (operation) => {
for (const malformed of [
{ error: "inventory unavailable" },
{ "0x1000": "obsolete map" },
{ [operation === "list_procedures" ? "procedures" : "strings"]: [] },
JSON.parse('{"__proto__":"observed"}'),
[null],
[{ address: "0x1000" }],
[{ address: "0x1000", value: 1 }],
]) {
const tools = fallbackOverview(
operation === "list_procedures" ? malformed : [],
operation === "list_strings" ? malformed : [],
);
const result = await tools.execute("binary_overview", {});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBeInstanceOf(AnalysisOutputError);
if (result.error instanceof AnalysisOutputError)
expect(result.error.operation).toBe(operation);
}
}
},
);
it("counts canonical fallback inventories including typed and display-only strings", async () => {
const tools = fallbackOverview(
[{ address: "0x1000", value: "entry" }],
[
{ address: "0x2000", value: "display-only" },
{
address: "0x2000",
value: "typed",
provider_value: "source display",
string: {
encoding: "utf-8",
termination: "present_or_not_required",
byte_length: 5,
},
},
],
);
expect(await tools.execute("binary_overview", {})).toEqual(
ok({
document: "selected",
procedure_count: 1,
string_count: 2,
inventory_coverage: {
scope: "provider_inventory",
discovery_coverage: "unknown",
},
}),
);
expect(await fallbackOverview([], []).execute("binary_overview", {})).toEqual(
ok({
document: "selected",
procedure_count: 0,
string_count: 0,
inventory_coverage: {
scope: "provider_inventory",
discovery_coverage: "unknown",
},
}),
);
});