integrate ganjihong engine refactor

This commit is contained in:
2026-09-09 18:41:56 +08:00
parent cf9cd0366a
commit 21a5185926
116 changed files with 3596 additions and 12259 deletions
+225
View File
@@ -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