Coverage for openhcs/core/pipeline/step_attribute_stripper.py: 61.9%
30 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""
2Step attribute stripper for OpenHCS.
4This module provides the StepAttributeStripper class, which is responsible for
5stripping all attributes from Step instances after planning, ensuring that steps
6are attribute-less shells that operate solely via the processing context.
8Doctrinal Clauses:
9- Clause 12 — Absolute Clean Execution
10- Clause 66 — Immutability After Construction
11- Clause 88 — No Inferred Capabilities
12- Clause 245 — Plan Context Supremacy
13- Clause 503 — Cognitive Load Transfer
14"""
16import logging
17from typing import Any, Dict, List
19logger = logging.getLogger(__name__)
21# Error messages for doctrinal violations
22ERROR_ATTRIBUTE_DELETION_FAILED = (
23 "Clause 66 Violation: Failed to delete attribute '{0}' from step '{1}'. "
24 "All attributes must be stripped after planning."
25)
27ERROR_RESERVED_ATTRIBUTE = (
28 "Clause 245 Violation: Step '{0}' has reserved attribute '{1}' that cannot be deleted. "
29 "This indicates a design flaw in the step implementation."
30)
33class StepAttributeStripper:
34 """
35 Planner that strips all attributes from Step instances after planning.
37 This planner ensures that steps are attribute-less shells that operate
38 solely via the processing context, with no mutable state or pre-declared
39 fields accessible during execution.
41 Key principles:
42 1. All attributes must be stripped from steps after planning
43 2. Steps must operate solely via the processing context
44 3. No mutable state or pre-declared fields can be accessed during execution
45 4. Execution must be based solely on the plan context
46 """
48 @staticmethod
49 def strip_step_attributes(steps: List[Any], step_plans: Dict[str, Dict[str, Any]]) -> None:
50 """
51 Strip all attributes from Step instances after planning.
53 Args:
54 steps: List of Step instances
55 step_plans: Dictionary mapping step UIDs to step plans
57 Raises:
58 ValueError: If attribute deletion fails
59 RuntimeError: If a step has reserved attributes that cannot be deleted
60 """
61 if not steps: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 logger.warning("No steps provided to StepAttributeStripper")
63 return
65 # Process each step
66 for step in steps:
67 # Get step identifier for error messages
68 step_id = getattr(step, "step_id", str(id(step)))
69 step_name = getattr(step, "name", f"Step {step_id}")
71 # Get all attributes
72 attributes = set(vars(step).keys())
74 # Log attributes being stripped
75 logger.debug(f"Stripping {len(attributes)} attributes from step '{step_name}': {attributes}")
77 # Delete all attributes
78 for attr in list(attributes):
79 try:
80 delattr(step, attr)
81 except (AttributeError, TypeError) as e:
82 # Check if this is a reserved attribute that cannot be deleted
83 if hasattr(type(step), attr) and not hasattr(type(step), "__slots__"):
84 # This is likely a class attribute or method, not an instance attribute
85 logger.debug(f"Skipping class attribute/method '{attr}' on step '{step_name}'")
86 continue
88 # If deletion failed for other reasons, raise an error
89 if hasattr(type(step), "__slots__") and attr in getattr(type(step), "__slots__", []):
90 raise RuntimeError(ERROR_RESERVED_ATTRIBUTE.format(step_name, attr)) from e
91 else:
92 raise ValueError(ERROR_ATTRIBUTE_DELETION_FAILED.format(attr, step_name)) from e
94 # Verify that all attributes have been stripped
95 remaining_attrs = set(vars(step).keys())
96 if remaining_attrs: 96 ↛ 97line 96 didn't jump to line 97 because the condition on line 96 was never true
97 raise ValueError(
98 f"Clause 66 Violation: Step '{step_name}' still has attributes after stripping: {remaining_attrs}. "
99 f"All attributes must be stripped after planning."
100 )
102 logger.info(f"Successfully stripped all attributes from step '{step_name}'")