mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
Match retained native inventories through shared CLI/MCP workflows with conservative symbol/byte correspondence, unresolved structural candidates, complete Evidence provenance and bounded paged reads. Closes #5343.
312 lines
9.6 KiB
TypeScript
312 lines
9.6 KiB
TypeScript
import { z } from "zod";
|
|
import {
|
|
nativeFunctionMatchingSchema,
|
|
nativeFunctionMatchingContextSchema,
|
|
nativeFunctionMatchSchema,
|
|
nativeFunctionMatchMemberSchema,
|
|
nativeMatchStateSchema,
|
|
type NativeFunctionMatching,
|
|
} from "../native/functionMatching.js";
|
|
import { isImmutableJsonSnapshot } from "../immutableJson.js";
|
|
import { parseAnalysisViewResult } from "./analysisViewParent.js";
|
|
import { analysisViewInputError } from "./analysisViewErrors.js";
|
|
import {
|
|
boundedViewMessages,
|
|
completeWithinViewCoverage,
|
|
pageViewCoverage,
|
|
} from "./analysisViewCoverage.js";
|
|
import { err, ok, type Result } from "../result.js";
|
|
import type { AnalysisError } from "../analysisErrorBase.js";
|
|
import type {
|
|
AnalysisViewParent,
|
|
AnalysisViewRequest,
|
|
UnsignedAnalysisView,
|
|
} from "./analysisView.js";
|
|
import type { JsonValue } from "../jsonValue.js";
|
|
|
|
const matchRow = nativeFunctionMatchSchema.extend({
|
|
left_count: z.number().int().nonnegative(),
|
|
right_count: z.number().int().nonnegative(),
|
|
});
|
|
const functionRow = nativeFunctionMatchMemberSchema.extend({
|
|
match_id: nativeFunctionMatchSchema.shape.match_id,
|
|
state: nativeMatchStateSchema,
|
|
side: z.enum(["left", "right"]),
|
|
});
|
|
const page = {
|
|
kind: z.literal("function-matches"),
|
|
states: z.array(nativeMatchStateSchema).optional(),
|
|
match_ids: z.array(z.string()).optional(),
|
|
function_ids: z.array(z.string()).optional(),
|
|
offset: z.number().int().nonnegative().default(0),
|
|
limit: z.number().int().positive().default(64),
|
|
};
|
|
export const nativeFunctionMatchingViewRequestSchema = z.discriminatedUnion(
|
|
"collection",
|
|
[
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("matches"),
|
|
fields: z.array(matchRow.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("functions"),
|
|
sides: z.array(z.enum(["left", "right"])).optional(),
|
|
names: z.array(z.string()).optional(),
|
|
fields: z.array(functionRow.keyof()).optional(),
|
|
}),
|
|
],
|
|
);
|
|
export const nativeFunctionMatchingViewItemsSchema = z.union([
|
|
z.array(
|
|
matchRow.partial().required({
|
|
match_id: true,
|
|
state: true,
|
|
left_count: true,
|
|
right_count: true,
|
|
}),
|
|
),
|
|
z.array(
|
|
functionRow.partial().required({
|
|
function_id: true,
|
|
match_id: true,
|
|
state: true,
|
|
side: true,
|
|
inventory_evidence_id: true,
|
|
source_index: true,
|
|
address: true,
|
|
evidence_links: true,
|
|
}),
|
|
),
|
|
]);
|
|
export const nativeFunctionMatchingViewContextSchema =
|
|
nativeFunctionMatchingContextSchema;
|
|
interface FunctionRow {
|
|
readonly member: NativeFunctionMatching["matches"][number]["left"][number];
|
|
readonly match_id: string;
|
|
readonly state: NativeFunctionMatching["matches"][number]["state"];
|
|
readonly side: "left" | "right";
|
|
}
|
|
interface Checked {
|
|
readonly report: NativeFunctionMatching;
|
|
functions?: readonly FunctionRow[];
|
|
recent?: { readonly key: string; readonly rows: readonly number[] };
|
|
}
|
|
const checked = new WeakMap<object, Checked>();
|
|
|
|
type Request = z.output<typeof nativeFunctionMatchingViewRequestSchema>;
|
|
const checkedReport = (
|
|
parent: AnalysisViewParent,
|
|
): Result<Checked, AnalysisError> => {
|
|
const source = parent.normalizedResult;
|
|
const reusable =
|
|
typeof source === "object" &&
|
|
source !== null &&
|
|
parent.sealedResult === true &&
|
|
isImmutableJsonSnapshot(source);
|
|
const cached = reusable ? checked.get(source) : undefined;
|
|
if (cached !== undefined) return ok(cached);
|
|
const parsed = parseAnalysisViewResult(nativeFunctionMatchingSchema, parent);
|
|
if (!parsed.ok) return parsed;
|
|
const entry: Checked = {
|
|
report: reusable ? (source as NativeFunctionMatching) : parsed.value,
|
|
};
|
|
if (reusable) checked.set(source, entry);
|
|
return ok(entry);
|
|
};
|
|
const functionRows = (entry: Checked): readonly FunctionRow[] => {
|
|
if (entry.functions !== undefined) return entry.functions;
|
|
const functions: FunctionRow[] = [];
|
|
for (const match of entry.report.matches)
|
|
for (const side of ["left", "right"] as const)
|
|
for (const member of match[side])
|
|
functions.push({
|
|
member,
|
|
match_id: match.match_id,
|
|
state: match.state,
|
|
side,
|
|
});
|
|
entry.functions = functions;
|
|
return functions;
|
|
};
|
|
const sharedContext = (
|
|
parent: AnalysisViewParent,
|
|
report: NativeFunctionMatching,
|
|
) => ({
|
|
parent_evidence_id: parent.evidenceId,
|
|
parent_operation: "match_native_functions" as const,
|
|
parent_digest: parent.evidenceId.slice(3),
|
|
artifact: null,
|
|
limitations: boundedViewMessages(
|
|
[...parent.limitations, ...report.limitations],
|
|
parent.evidenceId,
|
|
"limitations",
|
|
),
|
|
unknowns: [
|
|
"Unpaired observations and page exhaustion do not establish binary-wide function absence or behavioral equivalence.",
|
|
],
|
|
});
|
|
const selected = (values: readonly string[] | undefined) =>
|
|
values === undefined ? undefined : new Set(values);
|
|
const filterRows = (entry: Checked, view: Request): readonly number[] => {
|
|
const key = JSON.stringify([
|
|
view.collection,
|
|
view.states,
|
|
view.match_ids,
|
|
view.function_ids,
|
|
view.collection === "functions" ? view.sides : null,
|
|
view.collection === "functions" ? view.names : null,
|
|
]);
|
|
if (entry.recent?.key === key) return entry.recent.rows;
|
|
const states = selected(view.states),
|
|
ids = selected(view.match_ids),
|
|
functions = selected(view.function_ids),
|
|
sides = selected(view.collection === "functions" ? view.sides : undefined),
|
|
names = selected(view.collection === "functions" ? view.names : undefined);
|
|
const matchesIdentity = (row: { state: string; match_id: string }): boolean =>
|
|
(states === undefined || states.has(row.state)) &&
|
|
(ids === undefined || ids.has(row.match_id));
|
|
const rows: number[] = [];
|
|
if (view.collection === "matches")
|
|
entry.report.matches.forEach((row, index) => {
|
|
if (
|
|
matchesIdentity(row) &&
|
|
(functions === undefined ||
|
|
[...row.left, ...row.right].some((member) =>
|
|
functions.has(member.function_id),
|
|
))
|
|
)
|
|
rows.push(index);
|
|
});
|
|
else
|
|
functionRows(entry).forEach((row, index) => {
|
|
if (
|
|
matchesIdentity(row) &&
|
|
(functions === undefined || functions.has(row.member.function_id)) &&
|
|
(sides === undefined || sides.has(row.side)) &&
|
|
(names === undefined || names.has(row.member.name))
|
|
)
|
|
rows.push(index);
|
|
});
|
|
// One recent selector and at most one row index per source occurrence.
|
|
const count = entry.report.left.functions + entry.report.right.functions;
|
|
if (key.length <= Math.max(256, count * 8)) entry.recent = { key, rows };
|
|
return rows;
|
|
};
|
|
const selectedItems = (
|
|
entry: Checked,
|
|
view: Request,
|
|
rows: readonly number[],
|
|
) => {
|
|
const indices = rows.slice(view.offset, view.offset + view.limit);
|
|
const defaults =
|
|
view.collection === "matches"
|
|
? [
|
|
"match_id",
|
|
"state",
|
|
"basis",
|
|
"reason",
|
|
"left_count",
|
|
"right_count",
|
|
"compare_functions_input",
|
|
]
|
|
: [
|
|
"function_id",
|
|
"match_id",
|
|
"state",
|
|
"side",
|
|
"inventory_evidence_id",
|
|
"source_index",
|
|
"address",
|
|
"name",
|
|
"body_sha256",
|
|
"structural_candidate_sha256",
|
|
"body_bytes",
|
|
"dossier_evidence_id",
|
|
"evidence_links",
|
|
];
|
|
const fields = new Set<string>(view.fields ?? defaults);
|
|
const columns = (row: Readonly<Record<string, JsonValue>>) =>
|
|
Object.fromEntries([...fields].map((field) => [field, row[field]!]));
|
|
if (view.collection === "matches")
|
|
return indices.map((index) => {
|
|
const row = entry.report.matches[index]!,
|
|
left_count = row.left.length,
|
|
right_count = row.right.length;
|
|
return {
|
|
...columns({ ...row, left_count, right_count }),
|
|
match_id: row.match_id,
|
|
state: row.state,
|
|
left_count,
|
|
right_count,
|
|
};
|
|
});
|
|
const functions = functionRows(entry);
|
|
return indices.map((index) => {
|
|
const row = functions[index]!,
|
|
member = row.member;
|
|
return {
|
|
...columns({
|
|
...member,
|
|
match_id: row.match_id,
|
|
state: row.state,
|
|
side: row.side,
|
|
}),
|
|
function_id: member.function_id,
|
|
match_id: row.match_id,
|
|
state: row.state,
|
|
side: row.side,
|
|
inventory_evidence_id: member.inventory_evidence_id,
|
|
source_index: member.source_index,
|
|
address: member.address,
|
|
evidence_links: member.evidence_links,
|
|
};
|
|
});
|
|
};
|
|
|
|
/** Query immutable matching facts without copying full alias groups into selected pages. */
|
|
export const projectNativeFunctionMatchingView = (
|
|
parent: AnalysisViewParent,
|
|
view: AnalysisViewRequest,
|
|
): Result<UnsignedAnalysisView, AnalysisError> => {
|
|
if (view.kind !== "summary" && view.kind !== "function-matches")
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["view"],
|
|
reason: "invalid_value",
|
|
message:
|
|
"Native matching supports summary and function-matches pages",
|
|
},
|
|
]),
|
|
);
|
|
const parsed = checkedReport(parent);
|
|
if (!parsed.ok) return parsed;
|
|
const entry = parsed.value;
|
|
const {
|
|
matches: _matches,
|
|
limitations: _limitations,
|
|
...context
|
|
} = entry.report;
|
|
const shared = sharedContext(parent, entry.report);
|
|
if (view.kind === "summary")
|
|
return ok({
|
|
kind: "summary",
|
|
view,
|
|
...shared,
|
|
summary: context,
|
|
coverage: completeWithinViewCoverage(1, 1),
|
|
});
|
|
const rows = filterRows(entry, view),
|
|
items = selectedItems(entry, view, rows);
|
|
return ok({
|
|
kind: "function-matches",
|
|
view,
|
|
...shared,
|
|
...context,
|
|
items,
|
|
coverage: pageViewCoverage(view.offset, items.length, rows.length),
|
|
});
|
|
};
|