Files
rea/src/domain/functionReferenceComparison.ts
T
morlutoandHenglie 92dadf9334 fix(domain): preserve partial reference comparison facts
Own endpoint identity and per-edge kind knowledge in one parsed reference
representation. Preserve exact same-artifact coordinates and cross-artifact
name uncertainty, including generated and nonunique procedure labels.

Use exact multiplicity-preserving sparse compatibility flow instead of
positional/greedy pairing or a quadratic adjacency matrix. Share the native
coordinate parser with call paths and remove the superseded projections.

Validate public MCP comparison/reconstruction outcomes, independent tiny
permutation oracles, dense matching and advertised JSON Schemas.

Fixes #4210
Fixes #5131

Co-authored-by: Henglie <ebhenglie@gmail.com>
2026-10-19 07:45:34 +08:00

247 lines
7.4 KiB
TypeScript

import { canonicalJson } from "./comparisonSemantics.js";
import type { FunctionSnapshot } from "./functionDossierEvidence.js";
import { isAutoName, sorted } from "./functionComparisonNormalization.js";
import {
referenceFactsCompatible,
type ReferenceCompatibilityRow,
} from "./functionReferenceCompatibility.js";
import { nativeAddressInputSchema } from "./native/nativeAddress.js";
type Reference = FunctionSnapshot["dossier"]["outgoing_references"][number];
type KnownKind = Omit<
Extract<Reference["kind"], { available: true }>,
"available" | "provenance"
>;
type Endpoint =
| { readonly kind: "address"; readonly address: string }
| {
readonly kind: "name";
readonly name: string;
readonly ambiguous: boolean;
}
| {
readonly kind: "unknown";
readonly reason: "unparsed_address" | "missing_name" | "generated_name";
readonly reported_address: string;
readonly reported_name: string | null;
};
interface ReferenceFact {
readonly direction: "in" | "out";
readonly source: Endpoint;
readonly target: Endpoint;
readonly kind: KnownKind | null;
}
type ReferenceComparison = {
readonly leftCount: number;
readonly rightCount: number;
readonly limitations: readonly string[];
} & (
| { readonly status: "unknown" }
| {
readonly status: "changed" | "unchanged";
readonly left: readonly ReferenceFact[];
readonly right: readonly ReferenceFact[];
}
);
/** Names observed at different procedure entries do not uniquely identify an overload. */
const ambiguousProcedureNames = (
snapshots: readonly FunctionSnapshot[],
): Set<string> => {
const ambiguous = new Set<string>();
for (const snapshot of snapshots) {
const entries = new Map<string, string>();
const observe = (procedure: Reference["source_procedure"]): void => {
if (procedure === null) return;
const address = nativeAddressInputSchema.safeParse(procedure.address);
if (!address.success) {
ambiguous.add(procedure.name);
return;
}
const previous = entries.get(procedure.name);
if (previous !== undefined && previous !== address.data)
ambiguous.add(procedure.name);
entries.set(procedure.name, address.data);
};
observe(snapshot.dossier.procedure);
for (const edge of snapshot.dossier.incoming_references) {
observe(edge.source_procedure);
observe(edge.target_procedure);
}
for (const edge of snapshot.dossier.outgoing_references) {
observe(edge.source_procedure);
observe(edge.target_procedure);
}
}
return ambiguous;
};
const endpoint = (
procedure: Reference["source_procedure"],
address: string,
sameArtifact: boolean,
ambiguousNames: ReadonlySet<string>,
): Endpoint => {
if (sameArtifact) {
const parsed = nativeAddressInputSchema.safeParse(address);
if (parsed.success) return { kind: "address", address: parsed.data };
return {
kind: "unknown",
reason: "unparsed_address",
reported_address: address,
reported_name: procedure?.name ?? null,
};
}
if (
procedure === null ||
procedure.name.length === 0 ||
isAutoName(procedure.name)
)
return {
kind: "unknown",
reason:
procedure !== null && isAutoName(procedure.name)
? "generated_name"
: "missing_name",
reported_address: address,
reported_name: procedure?.name ?? null,
};
return {
kind: "name",
name: procedure.name,
ambiguous: ambiguousNames.has(procedure.name),
};
};
const project = (
snapshot: FunctionSnapshot,
sameArtifact: boolean,
ambiguousNames: ReadonlySet<string>,
): ReferenceFact[] => {
const facts: ReferenceFact[] = [];
const collect = (
references: readonly Reference[],
direction: "in" | "out",
): void => {
for (const edge of references) {
let kind: KnownKind | null = null;
if (edge.kind.available) {
const {
available: _available,
provenance: _provenance,
...known
} = edge.kind;
kind = known;
}
facts.push({
direction,
source: endpoint(
edge.source_procedure,
edge.source_address,
sameArtifact,
ambiguousNames,
),
target: endpoint(
edge.target_procedure,
edge.target_address,
sameArtifact,
ambiguousNames,
),
kind,
});
}
};
collect(snapshot.dossier.incoming_references, "in");
collect(snapshot.dossier.outgoing_references, "out");
return facts;
};
/** Ambiguity blocks equality, but a known label can still contradict another label. */
const endpointKey = (value: Endpoint): string | null => {
if (value.kind === "unknown") return null;
return canonicalJson(
value.kind === "address"
? { address: value.address }
: { name: value.name },
);
};
const constraints = (
facts: readonly ReferenceFact[],
): ReferenceCompatibilityRow[] =>
facts.map((fact) => ({
direction: fact.direction,
source: endpointKey(fact.source),
target: endpointKey(fact.target),
kind: fact.kind === null ? null : canonicalJson(fact.kind),
}));
/** Compare parsed endpoint and kind facts without replacing partial knowledge with absence. */
export const compareFunctionReferences = (
left: FunctionSnapshot,
right: FunctionSnapshot,
): ReferenceComparison => {
const sameArtifact =
left.subject.digest.sha256 === right.subject.digest.sha256;
const ambiguousNames = sameArtifact
? new Set<string>()
: ambiguousProcedureNames([left, right]);
const leftFacts = project(left, sameArtifact, ambiguousNames);
const rightFacts = project(right, sameArtifact, ambiguousNames);
let unknownIdentity = false;
let ambiguousIdentity = false;
let unknownKinds = false;
for (const facts of [leftFacts, rightFacts]) {
for (const fact of facts) {
unknownKinds ||= fact.kind === null;
for (const value of [fact.source, fact.target]) {
unknownIdentity ||= value.kind === "unknown";
ambiguousIdentity ||= value.kind === "name" && value.ambiguous;
}
}
}
const limitations: string[] = [];
if (unknownIdentity)
limitations.push(
sameArtifact
? "Reference endpoint coordinates could not be parsed, so their identity remains unknown."
: "Unnamed reference endpoints and generated procedure names have no stable identity across different artifacts.",
);
if (ambiguousIdentity)
limitations.push(
"A reference name does not uniquely identify an observed procedure entry, so matching names do not establish equal endpoints.",
);
if (unknownKinds)
limitations.push(
"At least one provider did not expose reference kinds, so compatible endpoints do not prove equal edge semantics.",
);
const context = {
leftCount: leftFacts.length,
rightCount: rightFacts.length,
limitations,
};
if (
!referenceFactsCompatible(constraints(leftFacts), constraints(rightFacts))
)
return {
...context,
status: "changed",
left: sorted(leftFacts),
right: sorted(rightFacts),
limitations: [
"Known reference facts cannot be paired without contradicting an endpoint or reference kind.",
...limitations,
],
};
if (unknownIdentity || ambiguousIdentity || unknownKinds)
return { ...context, status: "unknown" };
return {
...context,
status: "unchanged",
left: sorted(leftFacts),
right: sorted(rightFacts),
};
};