mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
c7f6b8bad1
Require the native node owner's content digest instead of borrowing an observation artifact digest, which can identify the containing ASAR. Keep unavailable nodes and their path/provenance/partial facts in the trace, without a false actionable target or native Evidence link. Remove the old fallback and allocate the query index only for eligible native targets. Verify real public CLI/stdio MCP workflows, known/missing companion inputs and shipped Joplin controls. Preserve reproducible scripts, source pins and compact correctness/resource/token records without archiving applications. Fixes #3926. Part of #4027. Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
|
import type { Evidence } from "../evidence.js";
|
|
import type {
|
|
ApplicationEdge,
|
|
ApplicationNode,
|
|
} from "./javascriptApplicationGraphSchemas.js";
|
|
import type { ApplicationFeatureTraceResult } from "./javascriptFeatureTraceSchemas.js";
|
|
|
|
type NativeHandoff = ApplicationFeatureTraceResult["native_handoffs"][number];
|
|
|
|
/** Build provider-neutral addon targets and link exact-subject native Evidence. */
|
|
export const buildJavaScriptNativeHandoffs = (
|
|
nodes: readonly ApplicationNode[],
|
|
edges: readonly ApplicationEdge[],
|
|
nativeEvidence: readonly Evidence[],
|
|
): NativeHandoff[] => {
|
|
let nodeById: Map<string, ApplicationNode> | undefined;
|
|
return nodes
|
|
.filter(({ kind }) => kind === "native-addon")
|
|
.flatMap((node) => {
|
|
// Observations may describe only a containing archive. A native target
|
|
// requires the node owner's exact byte identity, independently of its
|
|
// path, availability labels or the artifact supporting an observation.
|
|
if (node.identity.strategy !== "content-digest") return [];
|
|
const digest = node.identity.sha256;
|
|
const linked = nativeEvidence.filter(
|
|
({ subject }) => subject?.digest.sha256 === digest,
|
|
);
|
|
nodeById ??= new Map(
|
|
nodes.map((candidate) => [candidate.node_id, candidate]),
|
|
);
|
|
const exportNodes = nativeExportNodes(node, edges, nodeById);
|
|
return [
|
|
{
|
|
native_node_id: node.node_id,
|
|
artifact_sha256: digest,
|
|
artifact_path: artifactPath(node),
|
|
export_node_ids: exportNodes.map(({ node_id: id }) => id),
|
|
requested_exports: requestedExports(exportNodes),
|
|
status:
|
|
linked.length === 0
|
|
? ("requires-provider-analysis" as const)
|
|
: ("evidence-linked" as const),
|
|
providers: uniqueProviders(linked),
|
|
evidence_ids: linked
|
|
.map(({ evidence_id: id }) => id)
|
|
.sort(compareUnicodeCodePoints),
|
|
recommended_tools: [
|
|
"open_binary" as const,
|
|
"binary_overview" as const,
|
|
"search_procedures" as const,
|
|
"analyze_function" as const,
|
|
"xrefs" as const,
|
|
],
|
|
},
|
|
];
|
|
})
|
|
.sort((left, right) =>
|
|
compareUnicodeCodePoints(left.native_node_id, right.native_node_id),
|
|
);
|
|
};
|
|
|
|
const artifactPath = (node: ApplicationNode): string | null => {
|
|
for (const { evidence } of node.observations)
|
|
if (
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "artifact-path"
|
|
)
|
|
return evidence.location.value.path;
|
|
return null;
|
|
};
|
|
|
|
const nativeExportNodes = (
|
|
addon: ApplicationNode,
|
|
edges: readonly ApplicationEdge[],
|
|
nodeById: ReadonlyMap<string, ApplicationNode>,
|
|
): ApplicationNode[] => {
|
|
const ids = edges.flatMap((edge) => {
|
|
if (
|
|
edge.source_node_id === addon.node_id &&
|
|
["contains", "exposes"].includes(edge.relation)
|
|
)
|
|
return [edge.target_node_id];
|
|
if (
|
|
edge.target_node_id === addon.node_id &&
|
|
["imports", "loads"].includes(edge.relation)
|
|
)
|
|
return [edge.source_node_id];
|
|
return [];
|
|
});
|
|
return [...new Set(ids)]
|
|
.map((id) => nodeById.get(id))
|
|
.filter((node): node is ApplicationNode => node?.kind === "native-export")
|
|
.sort((left, right) =>
|
|
compareUnicodeCodePoints(left.node_id, right.node_id),
|
|
);
|
|
};
|
|
|
|
const requestedExports = (nodes: readonly ApplicationNode[]): string[] => {
|
|
const values = nodes.flatMap((node) =>
|
|
node.observations.flatMap((observation) => [
|
|
// Display labels can summarize several names or namespace access.
|
|
// Only legacy observations without structured member facts need them.
|
|
...(observation.label === null ||
|
|
Array.isArray(observation.properties.requested_members)
|
|
? []
|
|
: [observation.label]),
|
|
...stringArray(observation.properties.requested_members),
|
|
...stringArray(observation.properties.members),
|
|
]),
|
|
);
|
|
return [...new Set(values)].sort(compareUnicodeCodePoints);
|
|
};
|
|
|
|
const stringArray = (value: unknown): string[] =>
|
|
Array.isArray(value)
|
|
? value.filter((item): item is string => typeof item === "string")
|
|
: [];
|
|
|
|
const uniqueProviders = (
|
|
evidence: readonly Evidence[],
|
|
): NativeHandoff["providers"] =>
|
|
[
|
|
...new Map(
|
|
evidence.map(({ provider }) => [
|
|
`${provider.id}\0${provider.name}\0${provider.version ?? ""}`,
|
|
provider,
|
|
]),
|
|
).values(),
|
|
].sort((left, right) =>
|
|
compareUnicodeCodePoints(
|
|
`${left.id}\0${left.name}\0${left.version ?? ""}`,
|
|
`${right.id}\0${right.name}\0${right.version ?? ""}`,
|
|
),
|
|
);
|