mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2de78b704e | ||
|
|
f410fbb1c1 | ||
|
|
516d1a9f62 | ||
|
|
7333602b85 | ||
|
|
e870ea43ac | ||
|
|
465e8b1e06 |
No files matched your search
@@ -972,6 +972,18 @@ finish without reading relationships, even when neither traversal mode is
|
||||
available. Discovery does not block that direct invocation or replace its typed
|
||||
partial/error outcomes.
|
||||
|
||||
`trace_native_values` includes `value_flow_support` for the provider routed to
|
||||
`analyze_function`. `supported` declares that the provider may supply value flow;
|
||||
it does not guarantee a model for the selected function. `unavailable` includes
|
||||
the provider's reason, and `unknown` means no declaration. The provider ID is
|
||||
preserved, or null if no analysis provider is routed. This declaration is separate
|
||||
from operation readiness and the availability of call-target resolution. When
|
||||
call-target resolution is missing, a direct trace may still return a local graph
|
||||
with call-resolution unknowns; even `max_depth: 0` attempts to resolve call sites.
|
||||
When support is unknown but the declared operation prerequisites are ready,
|
||||
availability can still be true; it does not promise that value flow is present.
|
||||
The trace's returned observations and unknowns remain authoritative for that call.
|
||||
|
||||
`trace_native_ui_action` reports authored-UI and native-seed modes separately.
|
||||
Apple dispatch metadata requires a Mach-O target; another provider may supply
|
||||
the symbol-only alternative on other formats. If neither mode is available,
|
||||
|
||||
@@ -317,6 +317,10 @@ export type CapabilityDescriptor = CapabilityAvailability & {
|
||||
readonly operation: Exclude<AnalysisOperation, "health">;
|
||||
readonly effects: CapabilityEffects;
|
||||
readonly limitations: readonly string[];
|
||||
/** Whether analyze_function can supply value flow for some procedures. */
|
||||
readonly nativeValueFlow?:
|
||||
| { readonly status: "supported" }
|
||||
| { readonly status: "unavailable"; readonly reason: string };
|
||||
};
|
||||
|
||||
/** Factory and capability declaration for an analysis implementation. */
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
toolAvailabilitySummaryInventory,
|
||||
TOOL_ANNOTATION_NAMES,
|
||||
TOOL_EFFECT_NAMES,
|
||||
type ProviderCapability,
|
||||
type ToolAvailabilityInventory,
|
||||
type ToolAvailabilitySummaryInventory,
|
||||
type ToolAvailability,
|
||||
@@ -71,7 +70,7 @@ const statusSchema = z.object({
|
||||
|
||||
type ToolAvailabilityReason = "available" | ToolUnavailabilityReason;
|
||||
|
||||
type ProviderDescriptor = ProviderCapability;
|
||||
type ProviderDescriptor = ReturnType<typeof expandProviderCapabilities>[number];
|
||||
type UnavailableDescriptor = Extract<ProviderDescriptor, { available: false }>;
|
||||
|
||||
/** Optional integrations whose verified hosts exclude some platforms. */
|
||||
@@ -310,6 +309,22 @@ export const buildCapabilityInventory = (
|
||||
const facts = {
|
||||
name: contract.name,
|
||||
surface: contract.kind,
|
||||
...(contract.name === "trace_native_values"
|
||||
? {
|
||||
value_flow_support: (() => {
|
||||
const source = descriptors.get("analyze_function");
|
||||
return source?.native_value_flow === undefined
|
||||
? {
|
||||
status: "unknown" as const,
|
||||
provider_id: source?.provider.id ?? null,
|
||||
}
|
||||
: {
|
||||
...source.native_value_flow,
|
||||
provider_id: source.provider.id,
|
||||
};
|
||||
})(),
|
||||
}
|
||||
: {}),
|
||||
...(availability.defaultModeAvailable === undefined
|
||||
? {}
|
||||
: { default_mode_available: availability.defaultModeAvailable }),
|
||||
@@ -377,6 +392,9 @@ export function compactToolAvailability(
|
||||
available: entry.available,
|
||||
reason: entry.reason,
|
||||
remediation: entry.remediation,
|
||||
...(entry.value_flow_support === undefined
|
||||
? {}
|
||||
: { value_flow_support: entry.value_flow_support }),
|
||||
...(entry.default_mode_available === undefined
|
||||
? {}
|
||||
: { default_mode_available: entry.default_mode_available }),
|
||||
@@ -933,13 +951,38 @@ const composedAvailability = (
|
||||
reason: "provider_missing",
|
||||
remediation: "No provider composition is declared for this operation.",
|
||||
};
|
||||
return "modes" in requirements
|
||||
? availabilityForModes(
|
||||
requirements.modes,
|
||||
requirements.defaultMode,
|
||||
descriptors,
|
||||
)
|
||||
: composedAvailabilityFor(requirements, descriptors).availability;
|
||||
const result =
|
||||
"modes" in requirements
|
||||
? availabilityForModes(
|
||||
requirements.modes,
|
||||
requirements.defaultMode,
|
||||
descriptors,
|
||||
)
|
||||
: composedAvailabilityFor(requirements, descriptors).availability;
|
||||
if (name !== "trace_native_values") return result;
|
||||
const source = descriptors.get("analyze_function");
|
||||
if (source?.available !== true) return result;
|
||||
if (source.native_value_flow?.status === "unavailable")
|
||||
return {
|
||||
reason:
|
||||
result.reason === "provider_unavailable" ||
|
||||
result.reason === "unsupported_host"
|
||||
? result.reason
|
||||
: "provider_missing",
|
||||
remediation: `${source.native_value_flow.reason} Select a provider that can report native value-flow observations; cross-function tracing also needs call-target resolution.${(result.reason === "provider_unavailable" || result.reason === "unsupported_host") && result.remediation !== null ? ` ${result.remediation}` : ""}`,
|
||||
};
|
||||
if (
|
||||
result.reason === "provider_missing" &&
|
||||
descriptors.get("resolve_native_call_targets") === undefined
|
||||
)
|
||||
return {
|
||||
...result,
|
||||
remediation:
|
||||
source.native_value_flow?.status === "supported"
|
||||
? "A direct trace may still return local value-flow observations with call-resolution unknowns. Configure a provider with resolve_native_call_targets for call resolution and cross-function tracing."
|
||||
: "Value-flow support is undeclared. A direct trace may return partial results or an unknown. Configure a provider with resolve_native_call_targets for call resolution and cross-function tracing.",
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
/** Evaluate source groups once so discovery and missing-operation facts agree. */
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
analysisProviderCandidate,
|
||||
analysisProviderCandidateSummary,
|
||||
expandProviderCapabilities,
|
||||
expandToolAvailability,
|
||||
sessionProviderFacts,
|
||||
type ProviderCapabilitySet,
|
||||
} from "../../contracts/toolOutputSchemaPrimitives.js";
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
import { NATIVE_MACOS_PROVIDER_IDENTITY } from "../../native/NativeMacosProviderIdentity.js";
|
||||
import {
|
||||
buildCapabilityInventory,
|
||||
compactToolAvailability,
|
||||
MULTI_MODE_REMEDIATION,
|
||||
} from "../CapabilityInventory.js";
|
||||
|
||||
@@ -74,7 +76,9 @@ type DescriptorOptions = {
|
||||
| {
|
||||
readonly available: false;
|
||||
readonly reason?: string;
|
||||
readonly availabilityCode?: "architecture_unsupported";
|
||||
readonly availabilityCode?:
|
||||
| "architecture_unsupported"
|
||||
| "unsupported_host";
|
||||
}
|
||||
);
|
||||
|
||||
@@ -850,3 +854,91 @@ it("groups readiness repairs exactly and drops one replaced by a runtime observa
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ detail: "summary", supported: true },
|
||||
{ detail: "complete", supported: true },
|
||||
{ detail: "summary", supported: false },
|
||||
{ detail: "complete", supported: false },
|
||||
] as const)(
|
||||
"retains routed value-flow ownership and runtime failures in $detail discovery when supported=$supported",
|
||||
async ({ detail, supported }) => {
|
||||
const source = {
|
||||
...descriptor(providerA, "analyze_function", []),
|
||||
nativeValueFlow: supported
|
||||
? { status: "supported" as const }
|
||||
: { status: "unavailable" as const, reason: "No value flow." },
|
||||
};
|
||||
const resolver = descriptor(providerB, "resolve_native_call_targets", []);
|
||||
const providerC = { id: "provider-c", name: "Provider C", version: "3" };
|
||||
const unselected = {
|
||||
...descriptor(providerC, "analyze_function", []),
|
||||
nativeValueFlow: { status: "unavailable" as const, reason: "Unselected" },
|
||||
};
|
||||
const status = await projectStatus(
|
||||
[source, resolver],
|
||||
[{ ...candidate(providerC, [unselected]), selected: false }],
|
||||
{
|
||||
target: dispatchTarget("elf"),
|
||||
detail,
|
||||
runtimeUnavailability: new Map([
|
||||
["resolve_native_call_targets", { reason: "Resolver failed." }],
|
||||
]),
|
||||
},
|
||||
);
|
||||
const projected = z
|
||||
.object({ capabilities: sessionProviderFacts.shape.capabilities })
|
||||
.parse(status);
|
||||
const route = expandProviderCapabilities(projected.capabilities);
|
||||
expect(
|
||||
route.find((item) => item.operation === "analyze_function"),
|
||||
).toMatchObject({
|
||||
provider: providerA,
|
||||
native_value_flow: { status: supported ? "supported" : "unavailable" },
|
||||
});
|
||||
const inventory = buildCapabilityInventory(status, {
|
||||
processCaptureEnabled: false,
|
||||
});
|
||||
const item = expandToolAvailability(
|
||||
compactToolAvailability(inventory, detail),
|
||||
).find((candidate) => candidate.name === "trace_native_values");
|
||||
expect(item).toMatchObject({
|
||||
available: false,
|
||||
reason: "provider_unavailable",
|
||||
remediation: expect.stringContaining("Resolver failed."),
|
||||
value_flow_support: {
|
||||
status: supported ? "supported" : "unavailable",
|
||||
provider_id: providerA.id,
|
||||
},
|
||||
});
|
||||
if (!supported) expect(item?.remediation).toContain("No value flow.");
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves an unsupported resolver host alongside unavailable value flow", async () => {
|
||||
const source = {
|
||||
...descriptor(providerA, "analyze_function", []),
|
||||
nativeValueFlow: {
|
||||
status: "unavailable" as const,
|
||||
reason: "No value flow.",
|
||||
},
|
||||
};
|
||||
const resolver = {
|
||||
...descriptor(providerB, "resolve_native_call_targets", [], {
|
||||
available: false,
|
||||
reason: "Resolver host is unsupported.",
|
||||
availabilityCode: "unsupported_host",
|
||||
}),
|
||||
remediation: "Use a supported resolver host.",
|
||||
};
|
||||
const status = await projectStatus([source, resolver], [], {
|
||||
target: dispatchTarget("elf"),
|
||||
detail: "summary",
|
||||
});
|
||||
const item = buildCapabilityInventory(status, {
|
||||
processCaptureEnabled: false,
|
||||
}).find((entry) => entry.name === "trace_native_values");
|
||||
expect(item).toMatchObject({ available: false, reason: "unsupported_host" });
|
||||
expect(item?.remediation).toContain("No value flow.");
|
||||
expect(item?.remediation).toContain("Use a supported resolver host.");
|
||||
});
|
||||
@@ -417,6 +417,7 @@ export const providerCapabilitySets = (
|
||||
const key = JSON.stringify([
|
||||
effects,
|
||||
limitations,
|
||||
descriptor.nativeValueFlow ?? null,
|
||||
status.available,
|
||||
status.reason,
|
||||
status.availability_code,
|
||||
@@ -429,6 +430,9 @@ export const providerCapabilitySets = (
|
||||
operations: [descriptor.operation],
|
||||
effects,
|
||||
limitations,
|
||||
...(descriptor.nativeValueFlow === undefined
|
||||
? {}
|
||||
: { native_value_flow: descriptor.nativeValueFlow }),
|
||||
...status,
|
||||
});
|
||||
else group.operations.push(descriptor.operation);
|
||||
|
||||
@@ -53,6 +53,29 @@ export const requireOutputSchema = <
|
||||
/** The readiness owner's repair instruction; never the failure description. */
|
||||
const providerRemediation = z.string().min(1);
|
||||
|
||||
const nativeValueFlowDeclaration = z.discriminatedUnion("status", [
|
||||
z.strictObject({ status: z.literal("supported") }),
|
||||
z.strictObject({
|
||||
status: z.literal("unavailable"),
|
||||
reason: z.string().min(1),
|
||||
}),
|
||||
]);
|
||||
const nativeValueFlowSupport = z.discriminatedUnion("status", [
|
||||
z.strictObject({
|
||||
status: z.literal("supported"),
|
||||
provider_id: z.string().min(1),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("unavailable"),
|
||||
provider_id: z.string().min(1),
|
||||
reason: z.string().min(1),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("unknown"),
|
||||
provider_id: z.string().min(1).nullable(),
|
||||
}),
|
||||
]);
|
||||
|
||||
const providerCapabilityFacts = {
|
||||
operation: z.string(),
|
||||
effects: z.object({
|
||||
@@ -65,6 +88,7 @@ const providerCapabilityFacts = {
|
||||
requires_root: z.boolean(),
|
||||
}),
|
||||
limitations: z.array(z.string()),
|
||||
native_value_flow: nativeValueFlowDeclaration.exactOptional(),
|
||||
};
|
||||
|
||||
export const providerCapability = z.discriminatedUnion("available", [
|
||||
@@ -100,6 +124,7 @@ const providerCapabilityGroupFacts = {
|
||||
operations: z.array(z.string().min(1)).min(1),
|
||||
effects: z.array(providerCapabilityFacts.effects.keyof()),
|
||||
limitations: z.array(z.string()),
|
||||
native_value_flow: nativeValueFlowDeclaration.exactOptional(),
|
||||
};
|
||||
|
||||
const providerCapabilityGroup = z.discriminatedUnion("available", [
|
||||
@@ -138,6 +163,9 @@ export const expandProviderCapabilities = (
|
||||
group.operations.map((operation) => ({
|
||||
...providerCapability.parse({
|
||||
operation,
|
||||
...(group.native_value_flow === undefined
|
||||
? {}
|
||||
: { native_value_flow: group.native_value_flow }),
|
||||
available: group.available,
|
||||
reason: group.reason,
|
||||
availability_code: group.availability_code,
|
||||
@@ -375,6 +403,11 @@ const toolAvailabilityFacts = {
|
||||
surface: z.string(),
|
||||
default_mode_available: z.boolean().optional(),
|
||||
modes: z.array(toolAvailabilityMode).optional(),
|
||||
value_flow_support: nativeValueFlowSupport
|
||||
.describe(
|
||||
"The named provider's declared ability to supply value flow for some functions, separate from runtime readiness. Supported does not guarantee a model for a particular function; unknown means no declaration.",
|
||||
)
|
||||
.exactOptional(),
|
||||
client_requirements: z.object({
|
||||
required: z.array(clientFeatureNameSchema),
|
||||
optional: z.array(clientFeatureNameSchema),
|
||||
@@ -440,6 +473,7 @@ const toolAvailabilityStatusGroupFacts = {
|
||||
names: z.array(z.string().min(1)).min(1),
|
||||
default_mode_available: toolAvailabilityFacts.default_mode_available,
|
||||
modes: toolAvailabilityFacts.modes,
|
||||
value_flow_support: toolAvailabilityFacts.value_flow_support,
|
||||
client_requirements: toolAvailabilityFacts.client_requirements.optional(),
|
||||
};
|
||||
|
||||
@@ -449,6 +483,7 @@ const toolAvailabilityGroupFacts = {
|
||||
default_mode_available:
|
||||
toolAvailabilityStatusGroupFacts.default_mode_available,
|
||||
modes: toolAvailabilityStatusGroupFacts.modes,
|
||||
value_flow_support: toolAvailabilityStatusGroupFacts.value_flow_support,
|
||||
client_requirements: toolAvailabilityStatusGroupFacts.client_requirements,
|
||||
effects: z.array(toolAvailabilityFacts.effects.keyof()),
|
||||
annotations: z.array(toolAvailabilityFacts.annotations.keyof()),
|
||||
|
||||
@@ -234,6 +234,9 @@ export const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
|
||||
return Object.freeze({
|
||||
provider: GHIDRA_PROVIDER_IDENTITY,
|
||||
operation,
|
||||
...(operation === "analyze_function"
|
||||
? { nativeValueFlow: { status: "supported" as const } }
|
||||
: {}),
|
||||
available: true,
|
||||
reason: null,
|
||||
// Project writes are private import machinery. Analysis reads are bound
|
||||
|
||||
@@ -29,6 +29,9 @@ export const KUNA_STRING_LIMITATION =
|
||||
export const KUNA_XREF_LIMITATION =
|
||||
"Kuna's static reference walk unifies forwarding aliases and omits references internal to that alias class. REA keeps only exact-address incoming edges; an empty result does not prove no reference exists.";
|
||||
|
||||
export const KUNA_VALUE_FLOW_UNAVAILABLE =
|
||||
"REA's Kuna provider does not report native def-use observations.";
|
||||
|
||||
/** Whether an operation is one Kuna answers. */
|
||||
export const isKunaOperation = (
|
||||
operation: AnalysisOperation,
|
||||
@@ -104,6 +107,14 @@ export const kunaCapabilities = (
|
||||
KUNA_OPERATIONS.map((operation) => ({
|
||||
provider: KUNA_PROVIDER_IDENTITY,
|
||||
operation,
|
||||
...(operation === "analyze_function"
|
||||
? {
|
||||
nativeValueFlow: {
|
||||
status: "unavailable" as const,
|
||||
reason: KUNA_VALUE_FLOW_UNAVAILABLE,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(availability.status === "available"
|
||||
? ({ available: true, reason: null } as const)
|
||||
: ({
|
||||
|
||||
@@ -86,6 +86,7 @@ import {
|
||||
isKunaOperation,
|
||||
KUNA_RELATIONSHIP_LIMITATION,
|
||||
KUNA_SINGLE_FUNCTION_LIMITATION,
|
||||
KUNA_VALUE_FLOW_UNAVAILABLE,
|
||||
kunaLimitations,
|
||||
type KunaOperation,
|
||||
} from "./KunaProviderCapabilities.js";
|
||||
@@ -332,10 +333,6 @@ const kunaVerificationLimitations = (
|
||||
/** Kuna names a procedure by its inventory entry or by an exact entry address. */
|
||||
const procedureSelectorSchema = z.string().min(1);
|
||||
|
||||
/** REA's Kuna integration does not supply a def-use model for any procedure. */
|
||||
export const KUNA_NATIVE_VALUE_FLOW_UNAVAILABLE =
|
||||
"REA's Kuna provider does not report native def-use observations.";
|
||||
|
||||
/**
|
||||
* One Kuna analysis session for one target.
|
||||
*
|
||||
@@ -1574,7 +1571,7 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
native_api: null,
|
||||
native_value_flow: {
|
||||
available: false,
|
||||
reason: KUNA_NATIVE_VALUE_FLOW_UNAVAILABLE,
|
||||
reason: KUNA_VALUE_FLOW_UNAVAILABLE,
|
||||
limitations: [],
|
||||
},
|
||||
facet_availability: {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, expect, it } from "vitest";
|
||||
import type { AnalysisProvider } from "../../../src/application/AnalysisProvider.js";
|
||||
import {
|
||||
valueTraceDiamond,
|
||||
valueTraceProvider,
|
||||
} from "../../../src/application/native/NativeValueTrace.fixture.js";
|
||||
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { advertisedMcpToolSchemas } from "../../fixtures/advertisedMcpTool.js";
|
||||
import { createMcpSchemaValidation } from "../../fixtures/mcpSchemaValidation.js";
|
||||
|
||||
const schemaValidation = createMcpSchemaValidation();
|
||||
afterAll(() => schemaValidation.clear());
|
||||
|
||||
it.each([
|
||||
{ profile: "compact", state: "supported" },
|
||||
{ profile: "full", state: "supported" },
|
||||
{ profile: "compact", state: "unknown" },
|
||||
{ profile: "full", state: "unknown" },
|
||||
{ profile: "compact", state: "unavailable" },
|
||||
{ profile: "full", state: "unavailable" },
|
||||
] as const)(
|
||||
"reports $state value flow without a resolver in $profile MCP",
|
||||
async ({ profile, state }) => {
|
||||
const target = join(
|
||||
await createTestTempDirectory("flow-capability-"),
|
||||
"fixture.com",
|
||||
);
|
||||
await writeFile(target, Buffer.from([0xb8, 0x00, 0x4c, 0xcd, 0x21]));
|
||||
const graph = valueTraceDiamond({ sessionSubject: true });
|
||||
const provider: AnalysisProvider = {
|
||||
identity: () => valueTraceProvider,
|
||||
capabilities: () => [
|
||||
{
|
||||
provider: valueTraceProvider,
|
||||
operation: "analyze_function",
|
||||
available: true,
|
||||
reason: null,
|
||||
...(state === "unknown"
|
||||
? {}
|
||||
: state === "supported"
|
||||
? { nativeValueFlow: { status: "supported" as const } }
|
||||
: {
|
||||
nativeValueFlow: {
|
||||
status: "unavailable" as const,
|
||||
reason: "Fixture has no value flow.",
|
||||
},
|
||||
}),
|
||||
effects: {
|
||||
mutatesArtifact: false,
|
||||
launchesProcess: false,
|
||||
mayShowUi: false,
|
||||
mayAccessNetwork: false,
|
||||
mayWriteFilesystem: false,
|
||||
changesPermissions: false,
|
||||
requiresRoot: false,
|
||||
},
|
||||
limitations: [],
|
||||
},
|
||||
],
|
||||
resolveAnalysisProfile: async () =>
|
||||
ok({
|
||||
profile: createAnalysisProfile(valueTraceProvider, { fixture: true }),
|
||||
}),
|
||||
createClient: () => ({
|
||||
execute: async (operation, parameters, options) => {
|
||||
const execution = await graph.port.execute(
|
||||
operation,
|
||||
parameters,
|
||||
options,
|
||||
);
|
||||
if (
|
||||
!execution.ok ||
|
||||
state !== "unavailable" ||
|
||||
operation !== "analyze_function"
|
||||
)
|
||||
return execution;
|
||||
const dossier = functionDossierSchema.parse(execution.value.result);
|
||||
return ok({
|
||||
...execution.value,
|
||||
result: {
|
||||
...dossier,
|
||||
native_value_flow: {
|
||||
available: false,
|
||||
reason: "Fixture has no value flow.",
|
||||
limitations: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
close: async () => ok(null),
|
||||
}),
|
||||
};
|
||||
const session = createTestBinarySession(provider);
|
||||
const { client } = await createApplicationMcpHarness({
|
||||
session,
|
||||
schemaValidator: schemaValidation,
|
||||
serverOptions: {
|
||||
environment: {
|
||||
...process.env,
|
||||
REA_MCP_INPUT_SCHEMA_PROFILE: profile,
|
||||
REA_MCP_OUTPUT_SCHEMA_PROFILE: profile,
|
||||
},
|
||||
},
|
||||
});
|
||||
const call = async (name: string, args: Record<string, unknown>) => {
|
||||
const schema = await advertisedMcpToolSchemas(
|
||||
client,
|
||||
name,
|
||||
schemaValidation.assertions,
|
||||
);
|
||||
const reply = await client.callTool(
|
||||
{ name, arguments: args },
|
||||
{ toolDefinition: schema.toolDefinition },
|
||||
);
|
||||
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
|
||||
expect(
|
||||
schema.ajv.validate(schema.outputSchema, reply.structuredContent),
|
||||
JSON.stringify(schema.ajv.errors),
|
||||
).toBe(true);
|
||||
return reply.structuredContent;
|
||||
};
|
||||
await call("open_binary", { path: target, format: "dos-com" });
|
||||
const status = (await call("binary_session", { detail: "summary" })) as {
|
||||
result: {
|
||||
tool_availability: {
|
||||
groups: Array<{
|
||||
names: string[];
|
||||
available: boolean;
|
||||
value_flow_support?: { status: string; provider_id: string };
|
||||
remediation: string | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
const traceStatus = status.result.tool_availability.groups.find((g) =>
|
||||
g.names.includes("trace_native_values"),
|
||||
);
|
||||
expect(traceStatus).toMatchObject({
|
||||
available: false,
|
||||
value_flow_support: { status: state, provider_id: "fixture" },
|
||||
remediation: expect.any(String),
|
||||
});
|
||||
if (state === "unavailable")
|
||||
expect(traceStatus?.remediation).toContain("Fixture has no value flow.");
|
||||
else
|
||||
expect(traceStatus?.remediation).toContain("resolve_native_call_targets");
|
||||
const trace = parseEvidence(
|
||||
await call("trace_native_values", { procedure: "0x1000", max_depth: 0 }),
|
||||
);
|
||||
const value = trace.normalized_result as {
|
||||
total_nodes: number;
|
||||
unknowns: Array<{ reason: string }>;
|
||||
};
|
||||
if (state === "unavailable") {
|
||||
expect(value.total_nodes).toBe(0);
|
||||
expect(value.unknowns).toContainEqual(
|
||||
expect.objectContaining({ reason: "Fixture has no value flow." }),
|
||||
);
|
||||
} else {
|
||||
expect(value.total_nodes).toBeGreaterThan(0);
|
||||
expect(
|
||||
value.unknowns.some((unknown) =>
|
||||
unknown.reason.includes("resolve_native_call_targets"),
|
||||
),
|
||||
).toBe(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -20,7 +20,7 @@ import { OwnedFileHandle } from "../../../../src/filesystem/OwnedFileHandle.js";
|
||||
import { AnalysisProviderRegistry } from "../../../../src/application/binary/AnalysisProviderRegistry.js";
|
||||
import { composeBinarySession } from "../../../../src/application/binary/BinarySessionComposition.js";
|
||||
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
|
||||
import { KUNA_NATIVE_VALUE_FLOW_UNAVAILABLE } from "../../../../src/kuna/KunaSessionClient.js";
|
||||
import { KUNA_VALUE_FLOW_UNAVAILABLE } from "../../../../src/kuna/KunaProviderCapabilities.js";
|
||||
import { KunaProvider } from "../../../../src/kuna/KunaProvider.js";
|
||||
import { KUNA_FUNCTION_TEXT_BYTES } from "../../../../src/kuna/KunaLimits.js";
|
||||
import { KunaProjectCache } from "../../../../src/kuna/KunaProjectCache.js";
|
||||
@@ -512,7 +512,7 @@ it("builds a dossier with text and call relationships, leaving unsupported facet
|
||||
assembly: ["main: ; 0x1000", " 00001000: c3 RET"],
|
||||
native_value_flow: {
|
||||
available: false,
|
||||
reason: KUNA_NATIVE_VALUE_FLOW_UNAVAILABLE,
|
||||
reason: KUNA_VALUE_FLOW_UNAVAILABLE,
|
||||
},
|
||||
facet_availability: {
|
||||
pseudocode: { available: true },
|
||||
|
||||
Reference in new issue
Block a user