mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(kuna): say why Kuna cannot answer instead of a generic message (#4113)
Kuna capability gaps such as "no code at this address" or "no function node at this address" were built without a user message, so MCP and the CLI showed only "This analysis is unavailable for the current target. Choose another analysis or target." and kept the reason in details. One Kuna helper now presents the reason as the message for every such gap, and the unsupported-operation reason names call relationships and xrefs, which Kuna now answers. Refs #1957 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import { join } from "node:path";
|
||||
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisCancelledError,
|
||||
AnalysisResourceConstraintError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
} from "./KunaProjectFiles.js";
|
||||
import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
|
||||
import { KUNA_FUNCTION_TEXT_BYTES } from "./KunaLimits.js";
|
||||
import { kunaUnavailable } from "./KunaUnavailable.js";
|
||||
|
||||
/** The exact producing range for one facet, including a failed C block. */
|
||||
export interface KunaProjectFileSpan {
|
||||
@@ -134,8 +134,7 @@ export class KunaProjectReader {
|
||||
const span = index.value.code.get(address);
|
||||
if (span === undefined)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
kunaUnavailable(
|
||||
operation,
|
||||
`Kuna's export lists no code at ${address}; import slots and data entries have no listing.`,
|
||||
),
|
||||
|
||||
@@ -8,10 +8,7 @@ import type {
|
||||
ProviderAvailability,
|
||||
ProviderTargetSupport,
|
||||
} from "../application/AnalysisProvider.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
createAnalysisProfile,
|
||||
type AnalysisProfileCommitment,
|
||||
@@ -33,6 +30,7 @@ import {
|
||||
import { KUNA_COMMAND_REMEDIATION, KUNA_PROVIDER_ID } from "./KunaRelease.js";
|
||||
import { KunaRunSlots } from "./KunaRunSlots.js";
|
||||
import { KunaSessionClient } from "./KunaSessionClient.js";
|
||||
import { kunaUnavailable } from "./KunaUnavailable.js";
|
||||
|
||||
/** Architectures REA verified Kuna on: x86-64 and AArch64 ELF and Mach-O, plus ARM Thumb. */
|
||||
const VERIFIED_ARCHITECTURES: ReadonlySet<string> = new Set([
|
||||
@@ -169,14 +167,7 @@ export class KunaProvider implements AnalysisProviderCandidate {
|
||||
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
||||
});
|
||||
if (!installation.ok)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
"open_binary",
|
||||
installation.error.reason,
|
||||
{ userMessage: installation.error.reason },
|
||||
),
|
||||
);
|
||||
return err(kunaUnavailable("open_binary", installation.error.reason));
|
||||
const { value } = installation;
|
||||
return ok({
|
||||
profile: createAnalysisProfile(
|
||||
@@ -218,13 +209,7 @@ export class KunaProvider implements AnalysisProviderCandidate {
|
||||
if (!configuration.ok)
|
||||
return {
|
||||
execute: async (operation) =>
|
||||
err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
operation,
|
||||
configuration.error.reason,
|
||||
),
|
||||
),
|
||||
err(kunaUnavailable(operation, configuration.error.reason)),
|
||||
close: async () => ok(null),
|
||||
};
|
||||
return new KunaSessionClient(target, profile, {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisResourceConstraintError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
} from "./KunaReferenceFiles.js";
|
||||
import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
|
||||
import type { KunaPendingQuery, KunaQueryWork } from "./KunaQueryWork.js";
|
||||
import { kunaUnavailable } from "./KunaUnavailable.js";
|
||||
|
||||
/** Charge raw JSON bytes conservatively; retained projections are smaller. */
|
||||
const XREF_CACHE_BYTES = 16 * 1024 * 1024;
|
||||
@@ -158,8 +158,7 @@ export class KunaReferenceQueries {
|
||||
const node = graph.nodes.get(address);
|
||||
if (node === undefined)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
kunaUnavailable(
|
||||
operation,
|
||||
`Kuna's graph has no function node at ${address}; its relationships are unknown.`,
|
||||
),
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
KunaProjectReader,
|
||||
type KunaProjectFileSpan,
|
||||
} from "./KunaProjectReader.js";
|
||||
import { kunaUnavailable } from "./KunaUnavailable.js";
|
||||
|
||||
/** How often a request waiting on the project export reports that it still waits. */
|
||||
const WAIT_PROGRESS_INTERVAL_MS = 10_000;
|
||||
@@ -278,13 +279,7 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
options: ExecutionOptions = {},
|
||||
): Promise<Result<AnalysisExecution, AnalysisError>> {
|
||||
if (this.#closing)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
operation,
|
||||
"This Kuna session is closed.",
|
||||
),
|
||||
);
|
||||
return err(kunaUnavailable(operation, "This Kuna session is closed."));
|
||||
return this.#own(
|
||||
this.#execute(operation, parameters, {
|
||||
...options,
|
||||
@@ -312,8 +307,7 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
return err(new AnalysisCancelledError(operation));
|
||||
if (operation !== "health" && !isKunaOperation(operation))
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
kunaUnavailable(
|
||||
operation,
|
||||
"Kuna answers procedure and string queries, pseudocode, assembly and references; this operation needs another provider.",
|
||||
),
|
||||
@@ -1229,14 +1223,7 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
this.#installation ??= inspectKunaInstallation(this.#environment).then(
|
||||
(inspected): Result<KunaInstallation, AnalysisError> => {
|
||||
if (!inspected.ok)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
KUNA_PROVIDER_ID,
|
||||
"health",
|
||||
inspected.error.reason,
|
||||
{ userMessage: inspected.error.reason },
|
||||
),
|
||||
);
|
||||
return err(kunaUnavailable("health", inspected.error.reason));
|
||||
const committed = this.#profile?.parameters["kuna_sha256"];
|
||||
if (committed !== undefined && committed !== inspected.value.sha256)
|
||||
return err(
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore.js";
|
||||
import type { AnalysisErrorOptions } from "../domain/analysisErrorBase.js";
|
||||
import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
|
||||
|
||||
/**
|
||||
* A Kuna capability gap whose reason is what the caller needs to read. Without
|
||||
* a user message the projection shows only "choose another analysis or
|
||||
* target", which hides facts such as "no code at this address".
|
||||
*/
|
||||
export const kunaUnavailable = (
|
||||
operation: string,
|
||||
reason: string,
|
||||
options: AnalysisErrorOptions = {},
|
||||
): AnalysisCapabilityUnavailableError =>
|
||||
new AnalysisCapabilityUnavailableError(KUNA_PROVIDER_ID, operation, reason, {
|
||||
...options,
|
||||
userMessage: reason,
|
||||
});
|
||||
@@ -80,3 +80,41 @@ it("reports a graph beyond REA's buffer once per session instead of rebuilding i
|
||||
expect(await client.close()).toEqual({ ok: true, value: null });
|
||||
}
|
||||
});
|
||||
|
||||
it("says why Kuna cannot answer instead of a generic unavailable message", async () => {
|
||||
const { open } = await session("rea-kuna-unavailable-reasons-");
|
||||
const client = open();
|
||||
try {
|
||||
const cases = [
|
||||
{
|
||||
operation: "procedure_assembly",
|
||||
parameters: { procedure: "0x2000" },
|
||||
message:
|
||||
"Kuna's export lists no code at 0x2000; import slots and data entries have no listing.",
|
||||
},
|
||||
{
|
||||
operation: "procedure_callers",
|
||||
parameters: { procedure: "0x1004" },
|
||||
message:
|
||||
"Kuna's graph has no function node at 0x1004; its relationships are unknown.",
|
||||
},
|
||||
{
|
||||
operation: "list_segments",
|
||||
parameters: {},
|
||||
message:
|
||||
"Kuna answers procedure and string queries, pseudocode, assembly and references; this operation needs another provider.",
|
||||
},
|
||||
] as const;
|
||||
for (const { operation, parameters, message } of cases) {
|
||||
const result = await client.execute(operation, parameters);
|
||||
if (result.ok) throw new Error(`${operation} unexpectedly succeeded`);
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: "capability_unavailable",
|
||||
message,
|
||||
retryable: false,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
expect(await client.close()).toEqual({ ok: true, value: null });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user