fix(native)!: preserve value-flow absence semantics (#8785)

* fix(native)!: preserve value-flow absence semantics

* test(native): compare value-flow reasons with their owners

Stop freezing the unrecorded and Kuna unavailable sentences. Assert the
procedure-scoped unknown and that an explicit provider reason is preserved.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-11-05 02:32:15 +08:00
1 parent dd4b83c220
commit 1ff5a655bc
8 files changed
+81 -8

No files matched your search

+4 -2
View File
@@ -466,8 +466,10 @@ resources it owns stops the trace: the result is that provider's own
`cleanup_incomplete` failure with its resources and any bound cleanup retry, and the work
admitted before the stop is retained as partial Evidence with a located unknown
at the stopping read. A successful seed without a native def-use
model still returns its procedure and an explicit coverage unknown. A new trace
can retry failed selectors.
model still returns its procedure and an explicit coverage unknown. An unrecorded
facet stays unknown; when the provider explicitly reports unavailability, the trace
retains its reason. REA's Kuna provider explicitly reports that it does not supply
native def-use observations. A new trace can retry failed selectors.
Paging and traversal completeness are separate facts. `page_truncated` means the
page omits retained nodes, and the edges they source, before `offset` or after
@@ -13,6 +13,9 @@ export const workflowAnalysisProfile = (
createAnalysisProfile(REA_WORKFLOW_PROVIDER, {
upstream_analysis_profile: upstream,
source_limitations: "provider-operation-caveats-v1",
...(operation === "trace_native_values"
? { value_flow_absence: "recorded-versus-unavailable-v1" }
: {}),
...(operation === "binary_overview"
? { workflow_revision: 5 }
: operation === "batch_decompile"
@@ -1357,3 +1357,15 @@ it("revises the trace_feature workflow profile so earlier results are not replay
"other workflows keep their profile",
).toEqual(workflowAnalysisProfile(profile));
});
it("binds native value-trace absence semantics without changing other workflows", () => {
const profile = ANALYSIS_SNAPSHOT_PROFILE;
const valueTrace = workflowAnalysisProfile(profile, "trace_native_values");
expect(valueTrace.parameters.value_flow_absence).toBe(
"recorded-versus-unavailable-v1",
);
expect(valueTrace.digest).not.toBe(workflowAnalysisProfile(profile).digest);
expect(workflowAnalysisProfile(profile, "trace_call_path")).toEqual(
workflowAnalysisProfile(profile),
);
});
@@ -1,6 +1,9 @@
import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js";
import { describe, expect, it } from "vitest";
import { traceNativeValues as traceNativeValuesCore } from "./NativeValueTrace.js";
import {
traceNativeValues as traceNativeValuesCore,
UNRECORDED_NATIVE_VALUE_FLOW,
} from "./NativeValueTrace.js";
import { executeComposedWorkflow } from "../WorkflowEvidence.js";
import {
createAnalysisExecution,
@@ -190,12 +193,47 @@ describe("native value trace seed admission", () => {
expect.objectContaining({ address: base.procedure.address }),
],
unknowns: [
expect.objectContaining({
reason: "Provider has no native def-use model",
}),
{
procedure: base.procedure.address,
address: null,
reason: UNRECORDED_NATIVE_VALUE_FLOW,
},
],
});
});
it("preserves an explicit unavailable reason for that procedure", async () => {
const base = functionDossierSchema.parse(ghidraFunctionDossier());
const providerReason = "selected procedure has no p-code model";
const result = await traceNativeValues(
{
execute: async () =>
ok(
createAnalysisExecution(
{
...base,
native_value_flow: {
available: false,
reason: providerReason,
limitations: [],
},
},
provider,
{ subject },
),
),
},
{ procedure: base.procedure.address },
);
if (!result.ok) throw result.error;
expect(nativeValueTraceSchema.parse(result.value).unknowns).toEqual([
{
procedure: base.procedure.address,
address: null,
reason: providerReason,
},
]);
});
});
describe("native value parameter-use node membership", () => {
+5 -1
View File
@@ -28,6 +28,10 @@ import { createValueTraceBudget } from "./NativeValueTraceBudget.js";
export const CLEANUP_STOPPED_VALUE_TRACE =
"Trace stopped because the provider did not confirm cleanup of resources it owns";
/** Fallback when a dossier has no native value-flow facet for this procedure. */
export const UNRECORDED_NATIVE_VALUE_FLOW =
"Native def-use was not recorded for this procedure.";
/** Compose bounded high-p-code def-use graphs across exactly referenced call destinations. */
export const traceNativeValues = async (
analysis: AnalysisOperationPort,
@@ -181,7 +185,7 @@ export const traceNativeValues = async (
unknowns.push({
procedure: address,
address: null,
reason: flow?.reason ?? "Provider has no native def-use model",
reason: flow?.reason ?? UNRECORDED_NATIVE_VALUE_FLOW,
});
continue;
}
+1
View File
@@ -281,6 +281,7 @@ export class KunaProvider implements AnalysisProviderCandidate {
termination: "any",
xrefs: false,
},
value_flow: "explicitly-unavailable-v1",
cache_policy: "live",
},
),
+9 -1
View File
@@ -298,6 +298,10 @@ 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.
*
@@ -1523,7 +1527,11 @@ export class KunaSessionClient implements AnalysisClient {
referenced_names: [],
basic_blocks: [],
native_api: null,
native_value_flow: null,
native_value_flow: {
available: false,
reason: KUNA_NATIVE_VALUE_FLOW_UNAVAILABLE,
limitations: [],
},
facet_availability: {
pseudocode:
text === null
@@ -20,6 +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 { KunaProvider } from "../../../../src/kuna/KunaProvider.js";
import { KUNA_FUNCTION_TEXT_BYTES } from "../../../../src/kuna/KunaLimits.js";
import { KunaProjectCache } from "../../../../src/kuna/KunaProjectCache.js";
@@ -509,6 +510,10 @@ it("builds a dossier with text and call relationships, leaving unsupported facet
},
pseudocode: "int main(void)\n{\n return 0;\n}",
assembly: ["main: ; 0x1000", " 00001000: c3 RET"],
native_value_flow: {
available: false,
reason: KUNA_NATIVE_VALUE_FLOW_UNAVAILABLE,
},
facet_availability: {
pseudocode: { available: true },
assembly: { available: true },