fix(javascript): bind native handoffs to canonical byte identity

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>
This commit is contained in:
Ysopwnable
2026-10-16 01:08:49 +08:00
committed by GitHub
parent 633e4ff434
commit c7f6b8bad1
7 changed files with 1820 additions and 13 deletions
@@ -14,15 +14,21 @@ export const buildJavaScriptNativeHandoffs = (
edges: readonly ApplicationEdge[],
nativeEvidence: readonly Evidence[],
): NativeHandoff[] => {
const nodeById = new Map(nodes.map((node) => [node.node_id, node]));
let nodeById: Map<string, ApplicationNode> | undefined;
return nodes
.filter(({ kind }) => kind === "native-addon")
.flatMap((node) => {
const digest = artifactDigest(node);
if (digest === null) return [];
// 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 [
{
@@ -54,16 +60,6 @@ export const buildJavaScriptNativeHandoffs = (
);
};
const artifactDigest = (node: ApplicationNode): string | null => {
if (node.identity.strategy === "content-digest") return node.identity.sha256;
const observation = node.observations.find(
({ evidence }) => evidence.artifact.available,
);
return observation?.evidence.artifact.available === true
? observation.evidence.artifact.sha256
: null;
};
const artifactPath = (node: ApplicationNode): string | null => {
for (const { evidence } of node.observations)
if (