mirror of
https://github.com/morluto/rea.git
synced 2026-10-28 02:15:44 +00:00
fix(javascript): preserve unavailable scope uncertainty in comparisons
Classify wholly unknown or unavailable node observations at the version-item owner and retain their limitations across matched and unmatched comparisons. Preserve positive mixed observations and genuine additions/removals. Validated source regressions and public CLI/stdout MCP workflows in the 103-test focused gate; typecheck and lint pass. Fixes #5534.
This commit is contained in:
1 parent
e52a71602d
commit
8d198d6ec7
4 files changed
+471
-22
No files matched your search
@@ -145,20 +145,28 @@ const matchedItem = (
|
||||
const relationships = compareRelationships(pair, context);
|
||||
const dimensions = changedDimensions(pair, relationships);
|
||||
const complete = graphsComplete(context);
|
||||
const ownEvidenceUncertain =
|
||||
ownPresenceUncertain(pair.left) || ownPresenceUncertain(pair.right);
|
||||
const observedChange =
|
||||
dimensions.includes("content") || dimensions.includes("properties");
|
||||
!ownEvidenceUncertain &&
|
||||
(dimensions.includes("content") || dimensions.includes("properties"));
|
||||
// Coordinates remain comparison evidence; relocation alone is not a
|
||||
// content, property, or relationship change.
|
||||
const completeChange =
|
||||
complete && dimensions.some((dimension) => dimension !== "location");
|
||||
!ownEvidenceUncertain &&
|
||||
complete &&
|
||||
dimensions.some((dimension) => dimension !== "location");
|
||||
const status: ApplicationVersionComparisonItem["status"] =
|
||||
observedChange || completeChange
|
||||
? "changed"
|
||||
: complete && !relationships.unresolved
|
||||
? "unchanged"
|
||||
: "unknown";
|
||||
const finalDimensions =
|
||||
status === "unknown" && !complete
|
||||
ownEvidenceUncertain
|
||||
? "unknown"
|
||||
: observedChange || completeChange
|
||||
? "changed"
|
||||
: complete && !relationships.unresolved
|
||||
? "unchanged"
|
||||
: "unknown";
|
||||
const finalDimensions = ownEvidenceUncertain
|
||||
? []
|
||||
: status === "unknown" && !complete
|
||||
? [...dimensions, "coverage" as const]
|
||||
: dimensions;
|
||||
const semantic: ComparisonItemSemantic = {
|
||||
@@ -170,6 +178,10 @@ const matchedItem = (
|
||||
dimensions: finalDimensions,
|
||||
evidence_links: itemEvidenceLinks(pair.left, pair.right, context),
|
||||
limitations: [
|
||||
...new Set([
|
||||
...ownAvailabilityLimitations(pair.left),
|
||||
...ownAvailabilityLimitations(pair.right),
|
||||
]),
|
||||
...matchLimitations(pair),
|
||||
...(status === "unknown" && !complete
|
||||
? [
|
||||
@@ -218,7 +230,20 @@ const leftOnlyItem = (
|
||||
candidate_right_node_ids: [firstCandidate, ...remainingCandidates],
|
||||
},
|
||||
dimensions: ["availability"],
|
||||
limitations: [AMBIGUOUS_MATCH_LIMITATION],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
AMBIGUOUS_MATCH_LIMITATION,
|
||||
],
|
||||
});
|
||||
if (ownPresenceUncertain(node))
|
||||
return itemWithId({
|
||||
...common,
|
||||
status: "unknown",
|
||||
left_node_id: node.node_id,
|
||||
right_node_id: null,
|
||||
match: unmatchedMatch(),
|
||||
dimensions: ["availability"],
|
||||
limitations: ownAvailabilityLimitations(node),
|
||||
});
|
||||
const absenceObserved = absenceClaimable(context.rightGraph.coverage);
|
||||
return itemWithId({
|
||||
@@ -230,7 +255,10 @@ const leftOnlyItem = (
|
||||
dimensions: absenceObserved
|
||||
? ["availability"]
|
||||
: ["availability", "coverage"],
|
||||
limitations: absenceObserved ? [] : [INCOMPLETE_GRAPH_LIMITATION],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
...(absenceObserved ? [] : [INCOMPLETE_GRAPH_LIMITATION]),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -258,7 +286,20 @@ const rightOnlyItem = (
|
||||
candidate_right_node_ids: [],
|
||||
},
|
||||
dimensions: ["availability"],
|
||||
limitations: [AMBIGUOUS_MATCH_LIMITATION],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
AMBIGUOUS_MATCH_LIMITATION,
|
||||
],
|
||||
});
|
||||
if (ownPresenceUncertain(node))
|
||||
return itemWithId({
|
||||
...common,
|
||||
status: "unknown",
|
||||
left_node_id: null,
|
||||
right_node_id: node.node_id,
|
||||
match: unmatchedMatch(),
|
||||
dimensions: ["availability"],
|
||||
limitations: ownAvailabilityLimitations(node),
|
||||
});
|
||||
const absenceObserved = absenceClaimable(context.leftGraph.coverage);
|
||||
return itemWithId({
|
||||
@@ -270,7 +311,10 @@ const rightOnlyItem = (
|
||||
dimensions: absenceObserved
|
||||
? ["availability"]
|
||||
: ["availability", "coverage"],
|
||||
limitations: absenceObserved ? [] : [INCOMPLETE_GRAPH_LIMITATION],
|
||||
limitations: [
|
||||
...ownAvailabilityLimitations(node),
|
||||
...(absenceObserved ? [] : [INCOMPLETE_GRAPH_LIMITATION]),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -331,6 +375,21 @@ const AMBIGUOUS_MATCH_LIMITATION =
|
||||
const INCOMPLETE_GRAPH_LIMITATION =
|
||||
"The opposite graph is incomplete, so an unmatched node cannot be classified as added or removed.";
|
||||
|
||||
const ownPresenceUncertain = (node: ApplicationNode): boolean =>
|
||||
node.observations.every(
|
||||
({ evidence }) =>
|
||||
evidence.state === "unknown" || evidence.state === "unavailable",
|
||||
);
|
||||
|
||||
const ownAvailabilityLimitations = (node: ApplicationNode): string[] => {
|
||||
const limitations = new Set<string>();
|
||||
for (const { evidence } of node.observations)
|
||||
if (evidence.state === "unknown" || evidence.state === "unavailable")
|
||||
for (const limitation of evidence.limitations)
|
||||
limitations.add(limitation);
|
||||
return [...limitations].sort(compareUnicodeCodePoints);
|
||||
};
|
||||
|
||||
const changedDimensions = (
|
||||
pair: ApplicationVersionNodePair,
|
||||
relationships: RelationshipComparison,
|
||||
|
||||
Reference in new issue
Block a user