mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 13:56:06 +00:00
fix!: preserve feature traces when owner lookup is unavailable (#8368)
* fix!: preserve feature traces when owner lookup is unavailable * test: strengthen feature trace cancellation assertions * fix: preserve feature trace provider readiness errors * test: verify all feature trace reference sources * test: verify IDA guidance at its owning boundary * style: format feature trace regression
This commit is contained in:
1 parent
f72638f6b1
commit
6e9ee97a69
16 files changed
+440
-37
No files matched your search
@@ -776,6 +776,23 @@ describe("trace_feature requirements", () => {
|
||||
reason: null,
|
||||
}));
|
||||
|
||||
it("remains available when owner lookup is unavailable", () => {
|
||||
expect(
|
||||
entry(
|
||||
"trace_feature",
|
||||
status({
|
||||
open: true,
|
||||
kind: "executable",
|
||||
capabilities: capabilities([
|
||||
"search_strings",
|
||||
"search_procedures",
|
||||
"xrefs",
|
||||
]),
|
||||
}),
|
||||
),
|
||||
).toMatchObject({ available: true, reason: "available" });
|
||||
});
|
||||
|
||||
it("needs the search operations the workflow actually calls", () => {
|
||||
expect(
|
||||
entry(
|
||||
|
||||
@@ -215,12 +215,7 @@ const ENHANCED_REQUIREMENTS: ReadonlyMap<string, ProviderComposition> = new Map(
|
||||
],
|
||||
analyze_function: ["analyze_function"],
|
||||
inspect_native_api: ["analyze_function"],
|
||||
trace_feature: [
|
||||
"search_strings",
|
||||
"search_procedures",
|
||||
"xrefs",
|
||||
"resolve_containing_procedure",
|
||||
],
|
||||
trace_feature: ["search_strings", "search_procedures", "xrefs"],
|
||||
} satisfies Readonly<Record<EnhancedContractName, ProviderComposition>>),
|
||||
);
|
||||
|
||||
|
||||
@@ -4,12 +4,16 @@ import type {
|
||||
AnalysisExecution,
|
||||
AnalysisOperation,
|
||||
} from "./AnalysisProvider.js";
|
||||
import { AnalysisOutputError } from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import {
|
||||
parseContainingProcedureResolution,
|
||||
type ContainingProcedureResolution,
|
||||
type TraceContainingProcedure,
|
||||
} from "../domain/native/nativeObservation.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import type { EnhancedResult, TraceReference } from "./EnhancedToolTypes.js";
|
||||
@@ -70,7 +74,7 @@ export const traceLiteralFeature = async (
|
||||
search_mode: "literal",
|
||||
matches: searched.value.matches,
|
||||
references: traced.value.references,
|
||||
truncated: traced.value.residual.length > 0,
|
||||
truncated: traced.value.truncated,
|
||||
residual_unknowns: traced.value.residual,
|
||||
});
|
||||
};
|
||||
@@ -121,14 +125,18 @@ const traceReferences = async (
|
||||
observe: ObserveCall,
|
||||
request: ReferenceTraceRequest,
|
||||
): Promise<
|
||||
Result<{ references: TraceReference[]; residual: string[] }, AnalysisError>
|
||||
Result<
|
||||
{ references: TraceReference[]; residual: string[]; truncated: boolean },
|
||||
AnalysisError
|
||||
>
|
||||
> => {
|
||||
const references: TraceReference[] = [];
|
||||
const residual: string[] = [];
|
||||
let truncated = false;
|
||||
// References identify targets by address, independently of search alternatives.
|
||||
const tracedTargets = new Set<string>();
|
||||
// A source address is resolved once however many matches reference it.
|
||||
const containing = new Map<string, ContainingProcedureResolution>();
|
||||
const containing = new Map<string, TraceContainingProcedure>();
|
||||
for (const match of request.matches) {
|
||||
if (tracedTargets.has(match.address)) continue;
|
||||
tracedTargets.add(match.address);
|
||||
@@ -140,10 +148,12 @@ const traceReferences = async (
|
||||
if (!observed.ok) return observed;
|
||||
// Only an explicit producer report of truncation is a coverage gap; a
|
||||
// provider that reports nothing keeps its previous, unqualified result.
|
||||
if (observed.value.referencesTruncated === true)
|
||||
if (observed.value.referencesTruncated === true) {
|
||||
truncated = true;
|
||||
residual.push(
|
||||
`References to ${match.address} were truncated by the provider; additional references may exist.`,
|
||||
);
|
||||
}
|
||||
const sources = observed.value.result;
|
||||
if (!Array.isArray(sources))
|
||||
return err(
|
||||
@@ -167,10 +177,34 @@ const traceReferences = async (
|
||||
{ address: source },
|
||||
request.signal,
|
||||
);
|
||||
if (!resolved.ok) return resolved;
|
||||
const parsed = parseContainingProcedureResolution(resolved.value);
|
||||
if (!parsed.ok) return parsed;
|
||||
containingProcedure = parsed.value;
|
||||
if (!resolved.ok) {
|
||||
if (
|
||||
!(resolved.error instanceof AnalysisCapabilityUnavailableError) ||
|
||||
resolved.error.operation !== "resolve_containing_procedure" ||
|
||||
resolved.error.cleanupIncomplete ||
|
||||
resolved.error.retryable
|
||||
)
|
||||
return resolved;
|
||||
if (request.signal?.aborted === true)
|
||||
return err(
|
||||
new AnalysisCancelledError("trace_feature", {
|
||||
cause: request.signal.reason,
|
||||
}),
|
||||
);
|
||||
containingProcedure = {
|
||||
query_address: source,
|
||||
status: "unavailable",
|
||||
provider_id: resolved.error.providerId,
|
||||
reason: resolved.error.reason,
|
||||
};
|
||||
residual.push(
|
||||
`Containing procedure for ${source} is unavailable: ${resolved.error.reason}`,
|
||||
);
|
||||
} else {
|
||||
const parsed = parseContainingProcedureResolution(resolved.value);
|
||||
if (!parsed.ok) return parsed;
|
||||
containingProcedure = parsed.value;
|
||||
}
|
||||
containing.set(source, containingProcedure);
|
||||
}
|
||||
references.push({
|
||||
@@ -180,5 +214,5 @@ const traceReferences = async (
|
||||
});
|
||||
}
|
||||
}
|
||||
return ok({ references, residual });
|
||||
return ok({ references, residual, truncated });
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import type { z } from "zod";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import type { ContainingProcedureResolution } from "../domain/native/nativeObservation.js";
|
||||
import type { TraceContainingProcedure } from "../domain/native/nativeObservation.js";
|
||||
import { err, type Result } from "../domain/result.js";
|
||||
import {
|
||||
enhancedInputSchemas,
|
||||
@@ -14,7 +14,7 @@ export type EnhancedResult = Promise<Result<JsonValue, AnalysisError>>;
|
||||
export type TraceReference = {
|
||||
readonly target_address: string;
|
||||
readonly source_address: string;
|
||||
readonly containing_procedure: ContainingProcedureResolution;
|
||||
readonly containing_procedure: TraceContainingProcedure;
|
||||
};
|
||||
|
||||
/** One enhanced call whose input was parsed at its owning adapter boundary. */
|
||||
|
||||
@@ -17,7 +17,9 @@ export const workflowAnalysisProfile = (
|
||||
? { workflow_revision: 5 }
|
||||
: operation === "batch_decompile"
|
||||
? { workflow_revision: 4 }
|
||||
: operation === "get_call_graph" || operation === "trace_feature"
|
||||
? { workflow_revision: 3 }
|
||||
: { workflow_revision: 1 }),
|
||||
: operation === "trace_feature"
|
||||
? { workflow_revision: 4 }
|
||||
: operation === "get_call_graph"
|
||||
? { workflow_revision: 3 }
|
||||
: { workflow_revision: 1 }),
|
||||
});
|
||||
@@ -1248,6 +1248,11 @@ describe("executions that report truncated references", () => {
|
||||
it("revises the trace_feature workflow profile so earlier results are not replayed", () => {
|
||||
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
||||
|
||||
expect(
|
||||
workflowAnalysisProfile(profile, "trace_feature").parameters
|
||||
.workflow_revision,
|
||||
).toBe(4);
|
||||
|
||||
expect(workflowAnalysisProfile(profile, "trace_feature")).not.toEqual(
|
||||
workflowAnalysisProfile(profile),
|
||||
);
|
||||
|
||||
@@ -80,7 +80,7 @@ export const ENHANCED_TOOL_CONTRACTS = [
|
||||
...FUNCTION_WORKFLOW_TOOL_CONTRACTS,
|
||||
enhanced(
|
||||
"trace_feature",
|
||||
"Trace the complete literal query, preserving spaces, through every matching string and procedure, their xrefs, and truthful containing-procedure resolution. Returns observations and residual unknowns without inferring reference kinds.",
|
||||
"Trace the complete literal query, preserving spaces, through every matching string and procedure and their xrefs. Preserve an explicit unavailable owner for a non-retryable containment capability refusal. Returns observations and residual unknowns without inferring reference kinds; truncated only reports an observed reference cap.",
|
||||
enhancedInputSchemas.trace_feature,
|
||||
),
|
||||
enhanced(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
nativeAddressListSchema,
|
||||
nativeObservationSourceSchemas,
|
||||
containingProcedureResolutionSchema,
|
||||
traceContainingProcedureSchema,
|
||||
} from "../domain/native/nativeObservation.js";
|
||||
import { overviewInventoryCoverageSchema } from "../domain/native/inventoryCoverage.js";
|
||||
import {
|
||||
@@ -261,7 +261,7 @@ const literalTraceOutput = evidenceResultOf(
|
||||
z.object({
|
||||
target_address: z.string(),
|
||||
source_address: z.string(),
|
||||
containing_procedure: containingProcedureResolutionSchema,
|
||||
containing_procedure: traceContainingProcedureSchema,
|
||||
}),
|
||||
),
|
||||
truncated: z.boolean(),
|
||||
|
||||
@@ -27,6 +27,20 @@ export type ContainingProcedureResolution = z.infer<
|
||||
typeof containingProcedureResolutionSchema
|
||||
>;
|
||||
|
||||
/** A trace can retain a reference when its provider cannot resolve ownership. */
|
||||
export const traceContainingProcedureSchema = z.union([
|
||||
containingProcedureResolutionSchema,
|
||||
z.strictObject({
|
||||
query_address: z.string(),
|
||||
status: z.literal("unavailable"),
|
||||
provider_id: z.string(),
|
||||
reason: z.string(),
|
||||
}),
|
||||
]);
|
||||
export type TraceContainingProcedure = z.infer<
|
||||
typeof traceContainingProcedureSchema
|
||||
>;
|
||||
|
||||
/** Validate provider procedure facts before a workflow interprets absence. */
|
||||
export const parseContainingProcedureResolution = (
|
||||
value: JsonValue,
|
||||
|
||||
Reference in new issue
Block a user