integrate ganjihong engine refactor
This commit is contained in:
@@ -163,6 +163,13 @@ class TopologyDeltaRelation:
|
||||
source_value: Any
|
||||
result_values: tuple[Any, ...] = ()
|
||||
output_role: str | None = None
|
||||
derivation: str | None = None
|
||||
source_role: str | None = None
|
||||
result_role: str | None = None
|
||||
source_slot: str | None = None
|
||||
result_slot: str | None = None
|
||||
coverage: str = "complete"
|
||||
status: str = "proven"
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.event not in {"preserved", "modified", "generated", "deleted"}:
|
||||
@@ -173,6 +180,21 @@ class TopologyDeltaRelation:
|
||||
raise ValueError("deleted topology delta relations cannot have result values or an output role")
|
||||
if self.output_role is not None and (not isinstance(self.output_role, str) or not self.output_role):
|
||||
raise ValueError("topology delta output_role must be a non-empty string when provided")
|
||||
derivation = self.derivation
|
||||
if derivation is None:
|
||||
derivation = {
|
||||
"preserved": "continuation",
|
||||
"modified": "fragment" if len(self.result_values) > 1 else "continuation",
|
||||
"generated": "boundary",
|
||||
"deleted": "replacement",
|
||||
}[self.event]
|
||||
object.__setattr__(self, "derivation", derivation)
|
||||
if derivation not in {"continuation", "fragment", "merge", "intersection", "boundary", "replacement"}:
|
||||
raise ValueError(f"unsupported topology derivation {derivation!r}")
|
||||
if self.coverage not in {"complete", "partial", "none"}:
|
||||
raise ValueError(f"unsupported topology coverage {self.coverage!r}")
|
||||
if self.status not in {"proven", "unknown", "rejected"}:
|
||||
raise ValueError(f"unsupported topology relation status {self.status!r}")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -186,6 +208,37 @@ class TopologyDelta:
|
||||
|
||||
operation: str
|
||||
relations: tuple[TopologyDeltaRelation, ...] = ()
|
||||
# Boolean section edges are distinct from source continuations. INTERSECT
|
||||
# selectors may only rely on this exact builder evidence.
|
||||
section_values: tuple[Any, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TopologyLineage:
|
||||
"""An N:M semantic edge backed by an adapter history relation."""
|
||||
|
||||
source_record_ids: tuple[str, ...]
|
||||
result_record_ids: tuple[str, ...]
|
||||
derivation: str
|
||||
evidence: str
|
||||
coverage: str
|
||||
status: str
|
||||
operation: str
|
||||
output_role: str | None = None
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
result = {
|
||||
"source_record_ids": list(self.source_record_ids),
|
||||
"result_record_ids": list(self.result_record_ids),
|
||||
"derivation": self.derivation,
|
||||
"evidence": self.evidence,
|
||||
"coverage": self.coverage,
|
||||
"status": self.status,
|
||||
"operation": self.operation,
|
||||
}
|
||||
if self.output_role is not None:
|
||||
result["output_role"] = self.output_role
|
||||
return result
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -193,13 +246,29 @@ class SelectorResolution:
|
||||
selector: dict[str, Any]
|
||||
status: str
|
||||
record: TopologyRecord | None = None
|
||||
# ``records`` carries policy-authorized 1:N selector results. ``record``
|
||||
# remains the compatibility field for a unique selection and context use.
|
||||
records: tuple[TopologyRecord, ...] = ()
|
||||
candidates: tuple[dict[str, Any], ...] = ()
|
||||
diagnostic: RuntimeDiagnostic | None = None
|
||||
|
||||
@property
|
||||
def resolution_mode(self) -> str:
|
||||
if self.status == "resolved":
|
||||
if self.records:
|
||||
return "kernel_lineage"
|
||||
if self.selector.get("output_role"):
|
||||
return "operation_role"
|
||||
if self.selector.get("selector_intent"):
|
||||
return "kernel_lineage"
|
||||
return "geometry"
|
||||
return "unresolved"
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
output = {
|
||||
"selector": self.selector,
|
||||
"status": self.status,
|
||||
"resolution_mode": self.resolution_mode,
|
||||
"candidates": list(self.candidates),
|
||||
}
|
||||
if self.record is not None:
|
||||
@@ -211,8 +280,15 @@ class SelectorResolution:
|
||||
)
|
||||
if score is not None:
|
||||
output["score"] = score
|
||||
if self.records:
|
||||
output["records"] = [record.public_dict() for record in self.records]
|
||||
if self.diagnostic is not None:
|
||||
output["diagnostic"] = self.diagnostic.as_dict()
|
||||
if self.status == "resolved":
|
||||
output["evidence"] = {
|
||||
"source_records": [record.record_id for record in (self.records or ((self.record,) if self.record else ()))],
|
||||
"result_records": [record.record_id for record in (self.records or ((self.record,) if self.record else ()))],
|
||||
}
|
||||
return output
|
||||
|
||||
|
||||
@@ -224,6 +300,7 @@ class TopologyRegistry:
|
||||
self._by_feature: dict[str, list[TopologyRecord]] = {}
|
||||
self._active_body_id: str | None = None
|
||||
self._topology_deltas: list[dict[str, Any]] = []
|
||||
self._lineage: list[TopologyLineage] = []
|
||||
# #8 selector 持久性:old_record_id -> [new_record_id]。fillet/chamfer
|
||||
# 会把一条直线边拆分为若干段(中间直段 + 两端圆弧),旧边不再与任何
|
||||
# 新边几何等价;这里记录"位置轨迹延续"的直段后继,使后续 selector 的
|
||||
@@ -244,6 +321,10 @@ class TopologyRegistry:
|
||||
"""Return serializable evidence derived from exact adapter history."""
|
||||
return tuple(self._topology_deltas)
|
||||
|
||||
def lineage(self) -> tuple[TopologyLineage, ...]:
|
||||
"""Return kernel-backed N:M lineage without heuristic successors."""
|
||||
return tuple(self._lineage)
|
||||
|
||||
def register_context(self, feature_id: str, context: PlaneSpec | AxisSpec) -> TopologyRecord:
|
||||
kind = "plane" if isinstance(context, PlaneSpec) else "axis"
|
||||
record = TopologyRecord(
|
||||
@@ -378,10 +459,25 @@ class TopologyRegistry:
|
||||
if successor_id not in known:
|
||||
known.append(successor_id)
|
||||
if delta_evidence is not None:
|
||||
operation_lineage = [
|
||||
TopologyLineage(
|
||||
source_record_ids=tuple(item["source_record_ids"]),
|
||||
result_record_ids=tuple(item["result_record_ids"]),
|
||||
derivation=str(item["derivation"]),
|
||||
evidence="kernel_history",
|
||||
coverage=str(item["coverage"]),
|
||||
status=str(item["lineage_status"]),
|
||||
operation=topology_delta.operation,
|
||||
output_role=item.get("output_role"),
|
||||
)
|
||||
for item in delta_evidence
|
||||
]
|
||||
self._lineage.extend(operation_lineage)
|
||||
self._topology_deltas.append({
|
||||
"feature_id": feature_id,
|
||||
"operation": topology_delta.operation,
|
||||
"relations": delta_evidence,
|
||||
"lineage": [lineage.as_dict() for lineage in operation_lineage],
|
||||
})
|
||||
# #8 selector 持久性:被消费(拆分成段)的旧边记录演化后继,供后续
|
||||
# selector 的 stable_id 引用解析到 active body 内的新形态。多条演化
|
||||
@@ -471,7 +567,14 @@ class TopologyRegistry:
|
||||
"source_record_ids": [record.record_id for record in sources],
|
||||
"result_record_ids": [record.record_id for record in outputs],
|
||||
"proof": "kernel_history",
|
||||
"derivation": relation.derivation,
|
||||
"coverage": relation.coverage if len(outputs) == len(relation.result_values) else "partial",
|
||||
"lineage_status": relation.status,
|
||||
}
|
||||
for field_name in ("source_role", "result_role", "source_slot", "result_slot"):
|
||||
value = getattr(relation, field_name)
|
||||
if value is not None:
|
||||
item[field_name] = value
|
||||
if relation.output_role is not None:
|
||||
item["output_role"] = relation.output_role
|
||||
role_is_unique = len(relation.result_values) == 1 and len(outputs) == 1
|
||||
@@ -501,6 +604,24 @@ class TopologyRegistry:
|
||||
else:
|
||||
relation_links.append(None)
|
||||
evidence.append(item)
|
||||
if topology_delta.section_values:
|
||||
section_outputs = [
|
||||
record for record in current
|
||||
if record.kind == "edge"
|
||||
and any(cls._same_topology_value(record.value, value) for value in topology_delta.section_values)
|
||||
]
|
||||
evidence.append({
|
||||
"event": "generated",
|
||||
"kind": "edge",
|
||||
"source_record_ids": [],
|
||||
"result_record_ids": [record.record_id for record in section_outputs],
|
||||
"proof": "kernel_history",
|
||||
"derivation": "intersection",
|
||||
"coverage": "complete" if len(section_outputs) == len(topology_delta.section_values) else "partial",
|
||||
"lineage_status": "proven" if len(section_outputs) == len(topology_delta.section_values) else "unknown",
|
||||
"section_edge": True,
|
||||
"status": "recorded_section_edge",
|
||||
})
|
||||
predecessors = {
|
||||
result_id: next(iter(source_ids))
|
||||
for result_id, source_ids in candidate_sources.items()
|
||||
@@ -520,6 +641,8 @@ class TopologyRegistry:
|
||||
item["status"] = "non_unique_or_incomplete"
|
||||
else:
|
||||
item["status"] = "recorded_without_owner_transfer"
|
||||
if item["coverage"] != "complete" or relation.status != "proven":
|
||||
item["lineage_status"] = "unknown"
|
||||
return (
|
||||
predecessors,
|
||||
successors,
|
||||
@@ -529,6 +652,45 @@ class TopologyRegistry:
|
||||
evidence,
|
||||
)
|
||||
|
||||
def _intent_lineage_successors(
|
||||
self,
|
||||
source_record_id: str,
|
||||
*,
|
||||
allowed: set[str],
|
||||
active_body_id: str | None,
|
||||
) -> list[TopologyRecord]:
|
||||
"""Follow only complete, proven CDSL lineage edges to active records."""
|
||||
pending = [source_record_id]
|
||||
visited = {source_record_id}
|
||||
result_ids: set[str] = set()
|
||||
while pending:
|
||||
current = pending.pop()
|
||||
for edge in self._lineage:
|
||||
if (
|
||||
current not in edge.source_record_ids
|
||||
or edge.derivation not in allowed
|
||||
or edge.evidence != "kernel_history"
|
||||
or edge.coverage != "complete"
|
||||
or edge.status != "proven"
|
||||
):
|
||||
continue
|
||||
for record_id in edge.result_record_ids:
|
||||
if record_id not in visited:
|
||||
visited.add(record_id)
|
||||
pending.append(record_id)
|
||||
result_ids.add(record_id)
|
||||
active: list[TopologyRecord] = []
|
||||
for record in self._records:
|
||||
if record.record_id not in result_ids:
|
||||
continue
|
||||
if active_body_id is not None and not (
|
||||
record.body_id == active_body_id
|
||||
or (record.body_id is not None and record.body_id.startswith(f"{active_body_id}:"))
|
||||
):
|
||||
continue
|
||||
active.append(record)
|
||||
return active
|
||||
|
||||
@staticmethod
|
||||
def _numbers_equal(left: Any, right: Any, *, tolerance: float = 1e-6) -> bool:
|
||||
try:
|
||||
@@ -725,6 +887,8 @@ class TopologyRegistry:
|
||||
) -> SelectorResolution:
|
||||
kind = selector.get("kind")
|
||||
owner = selector.get("owner_feature_id")
|
||||
intent = selector.get("selector_intent")
|
||||
provenance_intent = isinstance(intent, dict) and intent.get("query_family") != "GEOMETRIC"
|
||||
candidates = [record for record in self._records if record.kind == kind]
|
||||
if active_body_id and kind in {"face", "edge", "vertex", "body"}:
|
||||
# #7 multi-body:记录 body_id 可能是 body:{feature}:{index}(多体
|
||||
@@ -895,6 +1059,49 @@ class TopologyRegistry:
|
||||
record.body_id == active_body_id
|
||||
or (record.body_id is not None and record.body_id.startswith(f"{active_body_id}:"))
|
||||
)
|
||||
if not is_active and provenance_intent:
|
||||
policy = intent.get("derivation_policy") or {}
|
||||
allowed = {
|
||||
value for value in policy.get("allowed") or ()
|
||||
if value in {"continuation", "fragment", "merge", "intersection", "boundary", "replacement"}
|
||||
}
|
||||
successors = self._intent_lineage_successors(
|
||||
record.record_id,
|
||||
allowed=allowed,
|
||||
active_body_id=active_body_id,
|
||||
)
|
||||
if len(successors) == 1:
|
||||
record = successors[0]
|
||||
is_active = True
|
||||
elif len(successors) > 1:
|
||||
if policy.get("multiplicity") == "all_fragments" and "fragment" in allowed:
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="resolved",
|
||||
records=tuple(successors),
|
||||
candidates=tuple({"score": 1.0, **candidate.public_dict()} for candidate in successors),
|
||||
)
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="ambiguous",
|
||||
candidates=tuple({"score": 1.0, **candidate.public_dict()} for candidate in successors),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code="selector_relation_non_unique",
|
||||
message="The proven topology lineage has more than one active result",
|
||||
detail={"stable_id": stable_id, "candidate_count": len(successors), "multiplicity": policy.get("multiplicity")},
|
||||
),
|
||||
)
|
||||
else:
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="not_found",
|
||||
candidates=(),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code="selector_kernel_history_missing",
|
||||
message="No complete proven lineage reaches an active topology record",
|
||||
detail={"stable_id": stable_id, "allowed": sorted(allowed)},
|
||||
),
|
||||
)
|
||||
if not is_active:
|
||||
successors = [
|
||||
candidate for candidate in stable_records
|
||||
@@ -974,6 +1181,24 @@ class TopologyRegistry:
|
||||
detail={"minimum_score": minimum_score},
|
||||
),
|
||||
)
|
||||
if provenance_intent:
|
||||
evidence = intent.get("evidence")
|
||||
code = "selector_geometry_only" if evidence == "geometry_hint" else "selector_kernel_history_missing"
|
||||
message = (
|
||||
"A FeatureScript provenance selector cannot resolve from geometry alone"
|
||||
if code == "selector_geometry_only"
|
||||
else "The FeatureScript selector has no stable active record or complete kernel history"
|
||||
)
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="not_found",
|
||||
candidates=(),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code=code,
|
||||
message=message,
|
||||
detail={"query_family": intent.get("query_family")},
|
||||
),
|
||||
)
|
||||
scored: list[tuple[float, TopologyRecord]] = []
|
||||
for candidate in candidates:
|
||||
# An owner-qualified context selector is deterministic when it has
|
||||
|
||||
Reference in New Issue
Block a user