Files
rea/src/application/native/NativeFunctionMatchingService.ts
T
morluto 6b7aa124ed fix(native): own matching source validation and admission
Separate source corruption and selected-role errors from unexpected derivation failures. Normalize coordinates before body membership and conflict checks, reuse parsed requests, and account for outer observation arrays before validation copies.
2026-10-23 07:27:15 +08:00

192 lines
7.3 KiB
TypeScript

import { NativeMatchingAdmission } from "./NativeMatchingAdmission.js";
import { loadNativeMatchingBundleSources } from "./NativeMatchingBundleSources.js";
import { RegularFileCleanupFailure } from "../../filesystem/OwnedRegularFile.js";
import { matchFunctionInventories } from "../../domain/native/matchFunctionInventories.js";
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
import { setImmediate } from "node:timers/promises";
import {
matchNativeFunctionsInputSchema,
type MatchNativeFunctionsInput,
} from "../../contracts/native/functionMatchingContracts.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import { readFunctionInventory } from "../../domain/native/functionInventory.js";
import { nativeFunctionMatchingSchema } from "../../domain/native/functionMatching.js";
import { AnalysisError } from "../../domain/analysisErrorBase.js";
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
import { err, ok, type Result } from "../../domain/result.js";
import type { EvidenceLookup } from "../EvidenceInputResolver.js";
import { resolveNativeMatchingSources } from "./NativeMatchingSources.js";
import type { ExecutionOptions } from "../AnalysisProvider.js";
import { FUNCTION_COMPARISON_PROVIDER } from "../../domain/comparisonEvidenceIdentity.js";
const OPERATION = "match_native_functions";
export interface NativeFunctionMatchingResult {
readonly evidence: Evidence;
readonly sources: readonly Evidence[];
readonly detail: MatchNativeFunctionsInput["detail"];
}
/** Pair existing observations through one source/analysis admission. */
export const matchNativeFunctions = async (
rawInput: unknown,
options?: ExecutionOptions,
lookup?: EvidenceLookup,
): Promise<Result<NativeFunctionMatchingResult, AnalysisError>> => {
const admission = new NativeMatchingAdmission(OPERATION, options?.signal);
try {
await setImmediate();
await admission.checkpoint();
await admission.admit(rawInput);
const parsed = matchNativeFunctionsInputSchema.safeParse(rawInput);
if (!parsed.success)
return err(
analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput),
);
return ok(await matchWithAdmission(parsed.data, admission, lookup));
} catch (cause: unknown) {
return matchingFailure(cause, options);
}
};
const matchWithAdmission = async (
input: MatchNativeFunctionsInput,
admission: NativeMatchingAdmission,
lookup: EvidenceLookup | undefined,
): Promise<NativeFunctionMatchingResult> => {
const checkpoint = admission.checkpoint;
const {
left,
right,
leftObservations,
rightObservations,
directLinks,
sources,
} = await resolveNativeMatchingSources(input, admission, lookup);
const leftFunctions = await readFunctionInventory(
left,
leftObservations,
checkpoint,
);
const rightFunctions = await readFunctionInventory(
right,
rightObservations,
checkpoint,
);
const compatible =
left.subject?.architecture != null &&
left.subject.architecture === right.subject?.architecture;
const { matches, summary } = await matchFunctionInventories(
leftFunctions,
rightFunctions,
{ identicalBytesComparable: compatible, checkpoint },
);
const limitations = [
...new Set([
...left.limitations.map((x) => `Left: ${x}`),
...right.limitations.map((x) => `Right: ${x}`),
"Coverage is restricted to the supplied provider-observed inventories; missing/truncated inventory facts never prove absence from either binary.",
"Only unique exact symbols and bytes are paired. Compatible observed CFG/call/string fingerprints propose unresolved candidates, never identity or equivalence. Missing structural coverage stays unknown.",
"Name or byte correspondence is not behavioral equivalence; use analyze_function for selected pairs, then compare_functions. Existing dossier pairs include directly usable retained-reference inputs.",
...(!compatible
? [
"Identical-byte matching unavailable: input architectures are missing or differ.",
]
: []),
]),
];
const report = nativeFunctionMatchingSchema.parse({
left: {
evidence_id: left.evidence_id,
sha256: left.subject!.digest.sha256,
functions: leftFunctions.length,
},
right: {
evidence_id: right.evidence_id,
sha256: right.subject!.digest.sha256,
functions: rightFunctions.length,
},
scope: "provider-observed-inputs",
source_coverage: {
status:
summary.only_left + summary.only_right + summary.unresolved > 0
? "partial"
: "complete-within-inputs",
left_inventory_completeness: "unknown",
right_inventory_completeness: "unknown",
left_byte_fingerprints: leftFunctions.filter(
(x) => x.fingerprint !== null,
).length,
left_structural_candidates: leftFunctions.filter(
(x) => x.member.structural_candidate_sha256 !== null,
).length,
right_structural_candidates: rightFunctions.filter(
(x) => x.member.structural_candidate_sha256 !== null,
).length,
right_byte_fingerprints: rightFunctions.filter(
(x) => x.fingerprint !== null,
).length,
},
summary,
matches,
limitations,
});
await checkpoint();
const evidence = createEvidence(undefined, FUNCTION_COMPARISON_PROVIDER, {
operation: OPERATION,
predicateType: "rea.native-function-matching",
confidence: "derived",
authority: "analyst-inference",
parameters: {
left_evidence_id: left.evidence_id,
right_evidence_id: right.evidence_id,
left_observation_ids: leftObservations.map((x) => x.evidence_id),
right_observation_ids: rightObservations.map((x) => x.evidence_id),
algorithm: "exact-symbol-body-bytes-and-structural-candidates-v1",
},
result: report,
limitations,
evidenceLinks: directLinks,
});
await checkpoint();
return { evidence, sources, detail: input.detail };
};
/** Preserve cleanup owners and expected failures; unexpected implementation failures escape. */
const matchingFailure = (
cause: unknown,
options: ExecutionOptions | undefined,
): Result<never, AnalysisError> => {
if (cause instanceof RegularFileCleanupFailure) throw cause;
if (options?.signal?.aborted)
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
if (cause instanceof AnalysisError) return err(cause);
throw cause;
};
/** Load selected portable records; do not retain unrelated records across bundles. */
export const matchNativeFunctionsFromBundles = async (
input: MatchNativeFunctionsInput,
paths: readonly string[],
resources: ArtifactResourceScope,
options?: ExecutionOptions,
): Promise<Result<NativeFunctionMatchingResult, AnalysisError>> => {
try {
const admission = new NativeMatchingAdmission(OPERATION, options?.signal);
await admission.admit(input);
const loaded = await loadNativeMatchingBundleSources({
input,
paths,
resources,
admission,
});
await setImmediate();
await admission.checkpoint();
return ok(await matchWithAdmission(loaded.input, admission, loaded.lookup));
} catch (cause: unknown) {
return matchingFailure(cause, options);
}
};