refactor(cdsl_engine): sink atomic runtime capability flags into schema contracts

Phase 4 of the decoupling refactor (behavior-preserving):
- profile_schema.json: every operation contract now carries
  runtime_capability {body_mutating, requires_active_body,
  replayable, requires_selector, open_profile_ok} (single source of truth)
- operation_contracts.py: validates and forwards the flags
- capabilities.py: the five data-classification frozensets are now
  derived from the schema at import time; dispatch-logic sets
  (_HOLE_ATOMICS, _PATTERN_ATOMICS, extent constants) stay in code
- test_profile_schema.py: completeness + structural invariants test

Equivalence proven by flag counts (27/13/25/6/2) matching the previous
hand-written sets and by the unchanged test-baseline failure set.
This commit is contained in:
2026-09-09 13:41:38 +08:00
parent 5ffb106f36
commit beac59fc8b
4 changed files with 101 additions and 66 deletions
+33 -31
View File
@@ -19,31 +19,8 @@ from .runtime_types import (
from .operation_contracts import materialized_feature_contracts
_SELECTOR_REQUIRED = frozenset({"extrude_add_blind_with_hole", "extrude_from_face", "loft_add_with_cap_face", "fillet", "chamfer", "shell"})
_SKETCH_ATOM_PREFIXES = ("extrude_", "revolve_", "sweep_")
# 开放轮廓(closed=false / role=open)只有"刀具截面补槽口边闭合后作切除"的
# 物理意义:仅 extrude 直切类原子支持;add/回转对开放轮廓会造出无意义的封块。
_OPEN_PROFILE_ATOMICS = frozenset({"extrude_cut_blind", "extrude_cut_through"})
_PRIMARY_ATOMICS = frozenset({
"extrude_add_blind", "extrude_add_blind_with_hole", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_from_face", "extrude_surface",
"extrude_cut_through",
"revolve_add", "revolve_cut", "revolve_surface", "hole_blind", "hole_countersink",
"hole_counterbore", "sphere_add", "box_add", "cylinder_add",
})
_HOLE_ATOMICS = frozenset({"hole_blind", "hole_countersink", "hole_counterbore", "hole_wizard"})
_ACTIVE_BODY_REQUIRED = frozenset({
"extrude_cut_blind", "extrude_cut_two_sided", "extrude_cut_through",
"loft_add_with_cap_face", "revolve_cut", *_HOLE_ATOMICS, "fillet", "chamfer", "shell",
# thread_cut 是 cut 型特征:必须在已有主体(宿主)上做布尔差,不能凭空
# 造实体;无宿主时按 active_body 前置阻止而非让 executor 在 None 上崩溃。
"thread_cut",
})
_BODY_MUTATING_ATOMICS = frozenset({
"extrude_add_blind", "extrude_add_blind_with_hole", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_from_face",
"extrude_cut_through", "loft_add", "loft_add_with_cap_face", "sweep_add",
"revolve_add", "revolve_cut", "sphere_add", "box_add", "cylinder_add",
"thread_add", "thread_cut", "bend_add", "gear_add", "rack_add", *_HOLE_ATOMICS, "fillet", "chamfer", "shell",
})
# ``ExecutionSession.body_members`` only contains independently selectable
# body outputs. A normal additive/cut/dress-up feature replaces the active
# aggregate, while ``result_mode: new_body`` and ``keep_tools`` are the two
@@ -51,14 +28,6 @@ _BODY_MUTATING_ATOMICS = frozenset({
# members can additionally expose a proven COPY instance; replayed/fused
# patterns remain ineligible because no exact instance ownership exists.
_PATTERN_ATOMICS = frozenset({"pattern_linear", "pattern_mirror", "pattern_circular"})
# A pattern may replay a previous pattern as well as a direct body mutation.
# Context-only features have no geometry definition to instance. thread_add,
# thread_cut, bend_add, gear_add and rack_add are excluded: pattern
# translation does not yet move their parametric axis/frame, so a replayed
# instance would silently re-run at the original location.
_REPLAYABLE_ATOMICS = (
_BODY_MUTATING_ATOMICS - frozenset({"thread_add", "thread_cut", "bend_add", "gear_add", "rack_add"})
) | frozenset({"pattern_linear", "pattern_mirror", "pattern_circular"})
_SUPPORTED_EXTENTS = frozenset({
"blind", "mid_plane", "through_all", "through_all_both", "through_all_and_blind",
"up_to_surface", "up_to_vertex", "offset_from_surface", "through_next", "up_to_body",
@@ -71,6 +40,39 @@ _EXTENT_TARGET_KINDS = {
}
def _runtime_capabilities() -> dict[str, dict[str, bool]]:
"""Return the per-atomic runtime capability flags from the schema registry.
``profile_schema.json.operation_contracts[*].runtime_capability`` is the
single source of truth for these classifications. Adding an atomic
operation updates one JSON contract instead of editing code-level sets.
"""
path = Path(__file__).with_name("profile_schema.json")
contracts = materialized_feature_contracts(json.loads(path.read_text(encoding="utf-8")))
return {atomic_id: contract["runtime_capability"] for atomic_id, contract in contracts.items()}
_RUNTIME_CAPABILITIES = _runtime_capabilities()
_SELECTOR_REQUIRED = frozenset(a for a, c in _RUNTIME_CAPABILITIES.items() if c["requires_selector"])
# 开放轮廓(closed=false / role=open)只有"刀具截面补槽口边闭合后作切除"的
# 物理意义:仅 extrude 直切类原子支持;add/回转对开放轮廓会造出无意义的封块。
_OPEN_PROFILE_ATOMICS = frozenset(a for a, c in _RUNTIME_CAPABILITIES.items() if c["open_profile_ok"])
_PRIMARY_ATOMICS = frozenset({
"extrude_add_blind", "extrude_add_blind_with_hole", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_from_face", "extrude_surface",
"extrude_cut_through",
"revolve_add", "revolve_cut", "revolve_surface", "hole_blind", "hole_countersink",
"hole_counterbore", "sphere_add", "box_add", "cylinder_add",
})
_ACTIVE_BODY_REQUIRED = frozenset(a for a, c in _RUNTIME_CAPABILITIES.items() if c["requires_active_body"])
_BODY_MUTATING_ATOMICS = frozenset(a for a, c in _RUNTIME_CAPABILITIES.items() if c["body_mutating"])
# A pattern may replay a previous pattern as well as a direct body mutation.
# Context-only features have no geometry definition to instance. thread_add,
# thread_cut, bend_add, gear_add and rack_add are excluded: pattern
# translation does not yet move their parametric axis/frame, so a replayed
# instance would silently re-run at the original location.
_REPLAYABLE_ATOMICS = frozenset(a for a, c in _RUNTIME_CAPABILITIES.items() if c["replayable"])
def _mappings(value: Any):
"""Yield nested feature mappings for capability-only contract checks."""
if isinstance(value, dict):