mirror of
https://github.com/morluto/rea.git
synced 2026-10-28 02:15:44 +00:00
fix(javascript): separate endpoint syntax correspondence from target identity
This commit is contained in:
1 parent
6c307e9c86
commit
f6b525d3af
6 files changed
+451
-28
No files matched your search
@@ -7,6 +7,7 @@ import type {
|
||||
ApplicationNode,
|
||||
} from "./javascriptApplicationGraphSchemas.js";
|
||||
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
|
||||
import { applicationEndpointCorrespondence } from "./javascriptApplicationEndpointCorrespondence.js";
|
||||
import type { ApplicationVersionComparisonItem } from "./javascriptApplicationVersionComparisonSchemas.js";
|
||||
import type {
|
||||
ApplicationVersionMatchingProjection,
|
||||
@@ -29,6 +30,8 @@ interface ItemContext {
|
||||
readonly pairByRight: ReadonlyMap<string, string>;
|
||||
readonly leftRelationships: RelationshipIndex;
|
||||
readonly rightRelationships: RelationshipIndex;
|
||||
readonly leftUnresolvedEndpointKinds: ReadonlySet<string | null>;
|
||||
readonly rightUnresolvedEndpointKinds: ReadonlySet<string | null>;
|
||||
}
|
||||
|
||||
type ItemContextInput = Omit<
|
||||
@@ -39,6 +42,8 @@ type ItemContextInput = Omit<
|
||||
| "pairByRight"
|
||||
| "leftRelationships"
|
||||
| "rightRelationships"
|
||||
| "leftUnresolvedEndpointKinds"
|
||||
| "rightUnresolvedEndpointKinds"
|
||||
> & {
|
||||
readonly leftNativeEvidence: readonly Evidence[];
|
||||
readonly rightNativeEvidence: readonly Evidence[];
|
||||
@@ -106,6 +111,8 @@ export const classifyJavaScriptApplicationVersions = (
|
||||
pairByRight,
|
||||
leftRelationships: indexRelationships(context.leftGraph),
|
||||
rightRelationships: indexRelationships(context.rightGraph),
|
||||
leftUnresolvedEndpointKinds: unresolvedEndpointKinds(context.leftGraph),
|
||||
rightUnresolvedEndpointKinds: unresolvedEndpointKinds(context.rightGraph),
|
||||
};
|
||||
const leftItems = matching.unmatchedLeft.map((node) => {
|
||||
const candidates = matching.candidatesForLeft.get(node.node_id) ?? [];
|
||||
@@ -147,6 +154,8 @@ const matchedItem = (
|
||||
const complete = graphsComplete(context);
|
||||
const ownEvidenceUncertain =
|
||||
ownPresenceUncertain(pair.left) || ownPresenceUncertain(pair.right);
|
||||
const endpointTargetUncertain =
|
||||
unresolvedEndpointTarget(pair.left) || unresolvedEndpointTarget(pair.right);
|
||||
const observedChange =
|
||||
!ownEvidenceUncertain &&
|
||||
(dimensions.includes("content") || dimensions.includes("properties"));
|
||||
@@ -161,7 +170,7 @@ const matchedItem = (
|
||||
? "unknown"
|
||||
: observedChange || completeChange
|
||||
? "changed"
|
||||
: complete && !relationships.unresolved
|
||||
: complete && !relationships.unresolved && !endpointTargetUncertain
|
||||
? "unchanged"
|
||||
: "unknown";
|
||||
const finalDimensions = ownEvidenceUncertain
|
||||
@@ -179,8 +188,8 @@ const matchedItem = (
|
||||
evidence_links: itemEvidenceLinks(pair.left, pair.right, context),
|
||||
limitations: [
|
||||
...new Set([
|
||||
...ownAvailabilityLimitations(pair.left),
|
||||
...ownAvailabilityLimitations(pair.right),
|
||||
...ownComparisonLimitations(pair.left),
|
||||
...ownComparisonLimitations(pair.right),
|
||||
]),
|
||||
...matchLimitations(pair),
|
||||
...(status === "unknown" && !complete
|
||||
@@ -231,11 +240,11 @@ const leftOnlyItem = (
|
||||
},
|
||||
dimensions: ["availability"],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
...ownComparisonLimitations(node),
|
||||
AMBIGUOUS_MATCH_LIMITATION,
|
||||
],
|
||||
});
|
||||
if (ownPresenceUncertain(node))
|
||||
if (ownPresenceUncertain(node) || unresolvedEndpointTarget(node))
|
||||
return itemWithId({
|
||||
...common,
|
||||
status: "unknown",
|
||||
@@ -243,21 +252,31 @@ const leftOnlyItem = (
|
||||
right_node_id: null,
|
||||
match: unmatchedMatch(),
|
||||
dimensions: ["availability"],
|
||||
limitations: ownAvailabilityLimitations(node),
|
||||
limitations: ownComparisonLimitations(node),
|
||||
});
|
||||
const absenceObserved = absenceClaimable(context.rightGraph.coverage);
|
||||
const endpointGap = oppositeEndpointUncertain(
|
||||
node,
|
||||
context.rightUnresolvedEndpointKinds,
|
||||
);
|
||||
const absenceObserved =
|
||||
!endpointGap && absenceClaimable(context.rightGraph.coverage);
|
||||
return itemWithId({
|
||||
...common,
|
||||
status: absenceObserved ? "removed" : "unknown",
|
||||
left_node_id: node.node_id,
|
||||
right_node_id: null,
|
||||
match: unmatchedMatch(),
|
||||
dimensions: absenceObserved
|
||||
? ["availability"]
|
||||
: ["availability", "coverage"],
|
||||
dimensions:
|
||||
absenceObserved || endpointGap
|
||||
? ["availability"]
|
||||
: ["availability", "coverage"],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
...(absenceObserved ? [] : [INCOMPLETE_GRAPH_LIMITATION]),
|
||||
...ownComparisonLimitations(node),
|
||||
...(endpointGap
|
||||
? [ENDPOINT_ABSENCE_LIMITATION]
|
||||
: absenceObserved
|
||||
? []
|
||||
: [INCOMPLETE_GRAPH_LIMITATION]),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -287,11 +306,11 @@ const rightOnlyItem = (
|
||||
},
|
||||
dimensions: ["availability"],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
...ownComparisonLimitations(node),
|
||||
AMBIGUOUS_MATCH_LIMITATION,
|
||||
],
|
||||
});
|
||||
if (ownPresenceUncertain(node))
|
||||
if (ownPresenceUncertain(node) || unresolvedEndpointTarget(node))
|
||||
return itemWithId({
|
||||
...common,
|
||||
status: "unknown",
|
||||
@@ -299,21 +318,31 @@ const rightOnlyItem = (
|
||||
right_node_id: node.node_id,
|
||||
match: unmatchedMatch(),
|
||||
dimensions: ["availability"],
|
||||
limitations: ownAvailabilityLimitations(node),
|
||||
limitations: ownComparisonLimitations(node),
|
||||
});
|
||||
const absenceObserved = absenceClaimable(context.leftGraph.coverage);
|
||||
const endpointGap = oppositeEndpointUncertain(
|
||||
node,
|
||||
context.leftUnresolvedEndpointKinds,
|
||||
);
|
||||
const absenceObserved =
|
||||
!endpointGap && absenceClaimable(context.leftGraph.coverage);
|
||||
return itemWithId({
|
||||
...common,
|
||||
status: absenceObserved ? "added" : "unknown",
|
||||
left_node_id: null,
|
||||
right_node_id: node.node_id,
|
||||
match: unmatchedMatch(),
|
||||
dimensions: absenceObserved
|
||||
? ["availability"]
|
||||
: ["availability", "coverage"],
|
||||
dimensions:
|
||||
absenceObserved || endpointGap
|
||||
? ["availability"]
|
||||
: ["availability", "coverage"],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
...(absenceObserved ? [] : [INCOMPLETE_GRAPH_LIMITATION]),
|
||||
...ownComparisonLimitations(node),
|
||||
...(endpointGap
|
||||
? [ENDPOINT_ABSENCE_LIMITATION]
|
||||
: absenceObserved
|
||||
? []
|
||||
: [INCOMPLETE_GRAPH_LIMITATION]),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -374,6 +403,35 @@ const AMBIGUOUS_MATCH_LIMITATION =
|
||||
"Multiple nodes share the same permitted match key; no cross-version pair was selected.";
|
||||
const INCOMPLETE_GRAPH_LIMITATION =
|
||||
"The opposite graph is incomplete, so an unmatched node cannot be classified as added or removed.";
|
||||
const ENDPOINT_TARGET_LIMITATION =
|
||||
"Endpoint target identity remains unresolved; any matched source site aligns retained syntax, not an established runtime destination.";
|
||||
const ENDPOINT_ABSENCE_LIMITATION =
|
||||
"An unresolved endpoint of the same kind on the opposite side prevents proving this endpoint target absent.";
|
||||
|
||||
const unresolvedEndpointTarget = (node: ApplicationNode): boolean =>
|
||||
node.kind === "endpoint" &&
|
||||
applicationEndpointCorrespondence(node).targetResolution !== "exact";
|
||||
|
||||
const unresolvedEndpointKinds = (
|
||||
graph: JavaScriptApplicationGraph,
|
||||
): ReadonlySet<string | null> => {
|
||||
const kinds = new Set<string | null>();
|
||||
for (const node of graph.nodes) {
|
||||
if (node.kind !== "endpoint") continue;
|
||||
const identity = applicationEndpointCorrespondence(node);
|
||||
if (identity.targetResolution !== "exact") kinds.add(identity.endpointKind);
|
||||
}
|
||||
return kinds;
|
||||
};
|
||||
|
||||
const oppositeEndpointUncertain = (
|
||||
node: ApplicationNode,
|
||||
kinds: ReadonlySet<string | null>,
|
||||
): boolean => {
|
||||
if (node.kind !== "endpoint" || kinds.size === 0) return false;
|
||||
const { endpointKind } = applicationEndpointCorrespondence(node);
|
||||
return endpointKind === null || kinds.has(null) || kinds.has(endpointKind);
|
||||
};
|
||||
|
||||
const ownPresenceUncertain = (node: ApplicationNode): boolean =>
|
||||
node.observations.every(
|
||||
@@ -381,10 +439,16 @@ const ownPresenceUncertain = (node: ApplicationNode): boolean =>
|
||||
evidence.state === "unknown" || evidence.state === "unavailable",
|
||||
);
|
||||
|
||||
const ownAvailabilityLimitations = (node: ApplicationNode): string[] => {
|
||||
const ownComparisonLimitations = (node: ApplicationNode): string[] => {
|
||||
const limitations = new Set<string>();
|
||||
const endpointTargetUncertain = unresolvedEndpointTarget(node);
|
||||
if (endpointTargetUncertain) limitations.add(ENDPOINT_TARGET_LIMITATION);
|
||||
for (const { evidence } of node.observations)
|
||||
if (evidence.state === "unknown" || evidence.state === "unavailable")
|
||||
if (
|
||||
endpointTargetUncertain ||
|
||||
evidence.state === "unknown" ||
|
||||
evidence.state === "unavailable"
|
||||
)
|
||||
for (const limitation of evidence.limitations)
|
||||
limitations.add(limitation);
|
||||
return [...limitations].sort(compareUnicodeCodePoints);
|
||||
|
||||
Reference in new issue
Block a user