fix(kuna): give actionable lookup recovery (#8656)

* fix(kuna): give actionable lookup recovery

* fix(kuna): keep lookup advice operation neutral

* style(kuna): apply repository formatter
This commit is contained in:
morluto authored and GitHub committed 2026-11-03 09:50:10 +08:00
1 parent 6259ac41a4
commit d496d803a4
2 files changed
+67 -2

No files matched your search

+18 -1
View File
@@ -1057,6 +1057,9 @@ export class KunaSessionClient implements AnalysisClient {
err(
new AnalysisInputError(operation, {
userMessage: kunaDataEntryMessage(entry, index.data.get(entry)!),
conformsToInputContract: true,
remediationAction:
"Inspect the data address with address_name or xrefs, or select a procedure entry from list_procedures. Do not repeat the unchanged call.",
}),
);
const selected = (entry: KunaInventoryFunction) => {
@@ -1076,6 +1079,9 @@ export class KunaSessionClient implements AnalysisClient {
return err(
new AnalysisInputError(operation, {
userMessage: `${address} is not a procedure entry Kuna found; list_procedures shows the entries it found.`,
conformsToInputContract: true,
remediationAction:
"Inspect list_procedures and select a discovered entry if it answers your question. An unlisted address is not proof of a function; REA cannot use it as a Kuna procedure in this session. Do not repeat the unchanged call.",
}),
);
return refused(entry) ? dataRefusal(entry) : selected(entry);
@@ -1088,9 +1094,20 @@ export class KunaSessionClient implements AnalysisClient {
return dataRefusal(named[0]);
return err(
new AnalysisInputError(operation, {
conformsToInputContract: true,
remediationAction:
matches.length === 0 && named.length === 0
? field === "procedure"
? "Use search_procedures or list_procedures to inspect Kuna's discovered entries, then select a matching entry. Do not repeat the unchanged call."
: "Inspect the selected target for the intended address and use that address explicitly. Do not repeat the unchanged call."
: matches.length === 0
? "Inspect the data addresses with address_name or xrefs, or select a procedure entry from list_procedures. Do not repeat the unchanged call."
: "Select the intended entry by its exact address. Do not repeat the unchanged call.",
userMessage:
matches.length === 0 && named.length === 0
? `Kuna found no procedure named ${selector}; search_procedures finds similar names.`
? field === "procedure"
? `Kuna found no procedure named ${selector}; search_procedures finds similar names.`
: `Kuna found no inventory entry named ${selector}.`
: matches.length === 0
? `${selector} names ${String(named.length)} Kuna entries that lie in Mach-O data regions (${named.map(({ address }) => address).join(", ")}); they are data, not procedures.`
: `${selector} names ${String(matches.length)} Kuna entries (${matches.map(({ address }) => address).join(", ")}); select one by address.`,
@@ -201,7 +201,7 @@ test("traces a procedure found only by its alias through the composed feature tr
test("omits a data entry an alias names while keeping the code entry that shares it", async ({
mcp,
}) => {
const { search, observe } = await aliasSession(
const { client, search, observe } = await aliasSession(
mcp,
"rea-kuna-alias-macho-",
{
@@ -234,4 +234,52 @@ test("omits a data entry an alias names while keeping the code entry that shares
),
);
expect(await search({ pattern: "global_count" })).toEqual([]);
const { error } = parseMcpToolError(
await client.callTool({
name: "procedure_address",
arguments: { procedure: "global_count" },
}),
);
expect(error.recovery).toBeUndefined();
expect(error.remediation.action).toContain("address_name");
});
test("reports lookup failures with recovery for the selected target, not the input schema", async ({
mcp,
}) => {
const { client } = await aliasSession(mcp, "rea-kuna-lookup-recovery-", {
functions: VERSIONED,
});
for (const [procedure, guidance] of [
["0x1335", "list_procedures"],
["absent_name", "search_procedures"],
["audit_transform", "exact address"],
]) {
const response = await client.callTool({
name: "procedure_address",
arguments: { procedure },
});
const { error } = parseMcpToolError(response);
expect(error.code).toBe("invalid_request");
expect(error.recovery).toBeUndefined();
expect(error.remediation.action).toContain(guidance);
expect(error.remediation.action).not.toContain("input schema");
}
const assemblyError = parseMcpToolError(
await client.callTool({
name: "procedure_assembly",
arguments: { procedure: "0x1335" },
}),
).error;
expect(assemblyError.remediation.action).toContain("Kuna procedure");
expect(assemblyError.remediation.action).not.toContain("decompile");
const missingAddress = parseMcpToolError(
await client.callTool({
name: "xrefs",
arguments: { address: "absent_name" },
}),
).error;
expect(missingAddress.recovery).toBeUndefined();
expect(missingAddress.message).toContain("no inventory entry named");
expect(missingAddress.remediation.action).toContain("address");
});