Reference guide for constraint propagation rules in typed holes refactoring including dependency tracking and solution space narrowing. Use as reference when propagating constraints between resolved holes.
Complete guide to constraint propagation in typed holes refactoring.
When a hole is resolved, its resolution creates constraints that propagate to dependent holes. This narrows the solution space and discovers new requirements.
Pattern: Concrete types flow to consumers
Rule:
IF: Interface hole resolved with type T
THEN: All consumers must handle type T
Example 1: Async Interface
# Resolution of R3_abstraction_layers
class NodeInterface:
async def run(self, context: Context) -> Result
# Propagates to:
R4_parallel_execution:
MUST handle async/await
MUST use asyncio for concurrency
R5_error_handling:
MUST catch async exceptions
MUST use try/except with async context
R7_testing:
MUST use pytest-asyncio
MUST test async behavior
Example 2: Generic Types
# Resolution of R2_module_boundaries
class Repository(Protocol[T]):
def get(self, id: str) -> T
def save(self, entity: T) -> None
# Propagates to:
R4_implementations:
MUST provide type parameter
MUST implement both methods with consistent T
R7_testing:
MUST test with concrete types
MUST verify type safety
Pattern: Resource usage flows to dependent operations
Rule:
IF: Implementation uses N resources
THEN: Dependent operations share/compete for N resources
Example 1: Concurrency Limits
# Resolution of R4_parallelization
parallel_executor = {
"max_concurrent": 3,
"strategy": "asyncio.gather"
}
# Propagates to:
R8_rate_limiting:
rate_limit = provider_limit / 3
R9_memory_budget:
memory_per_operation * 3 < total_memory
R10_connection_pool:
pool_size >= 3
R7_testing:
MUST test with max_concurrent operations
MUST verify no race conditions
Example 2: Cache Size
# Resolution of R5_caching_strategy
cache = {
"max_size": 1000,
"eviction": "LRU",
"ttl": 3600
}
# Propagates to:
R9_memory_budget:
cache_memory = 1000 * avg_entry_size
total_memory >= cache_memory + operation_memory
R4_key_design:
MUST design keys for good hit rate
MUST handle cache invalidation
R7_testing:
MUST test cache hit/miss scenarios
MUST test eviction behavior
Pattern: Test needs flow upstream
Rule:
IF: Validation requires N test cases
THEN: Test data and metrics must support N cases
Example 1: Optimization Validation
# Resolution of R8_optimization_testing
validation = {
"required_examples": 50,
"statistical_significance": 0.05,
"comparison_metric": "accuracy"
}
# Propagates to:
R6_test_data:
MUST collect >= 50 labeled examples
MUST ensure diversity in examples
R7_metrics:
MUST support batch evaluation
MUST compute statistical significance
R9_infrastructure:
MUST support running 50+ test cases
MUST store results for comparison
Example 2: Performance Testing
# Resolution of R9_performance_validation
perf_tests = {
"required_scenarios": 20,
"iterations_per_scenario": 100,
"metrics": ["latency_p50", "latency_p99", "throughput"]
}
# Propagates to:
R7_test_infrastructure:
MUST support benchmark harness
MUST collect timing data
R6_test_data:
MUST have 20 representative scenarios
R8_monitoring:
MUST track same metrics in production
Pattern: Chosen dependencies constrain implementations
Rule:
IF: Dependency D chosen with version V
THEN: Implementation must be compatible with D@V
Example 1: Framework Choice
# Resolution of R2_testing_framework
testing = {
"framework": "pytest",
"version": "7.4.0",
"plugins": ["pytest-asyncio", "pytest-cov"]
}
# Propagates to:
R7_test_writing:
MUST use pytest fixtures
MUST use pytest-asyncio for async tests
R8_ci_config:
MUST install pytest 7.4.0
MUST run pytest command
R4_async_implementation:
CAN use asyncio (pytest-asyncio supports it)
Example 2: Library Version
# Resolution of R3_http_client
http = {
"library": "httpx",
"version": "0.24.0",
"features": ["async", "http2"]
}
# Propagates to:
R4_api_implementations:
MUST use httpx.AsyncClient
CAN use HTTP/2 features
R5_error_handling:
MUST handle httpx exceptions
MUST handle httpx.TimeoutException
R6_testing:
MUST mock httpx (not requests)
CAN use httpx.MockTransport
Pattern: Architecture rules flow to all modules
Rule:
IF: Architecture defines layer L with rules R
THEN: All modules in L must follow R
Example 1: Layered Architecture
# Resolution of R1_target_architecture
architecture = {
"layers": ["api", "domain", "data"],
"rules": {
"api": {
"can_import": ["domain"],
"cannot_import": ["data"]
},
"domain": {
"can_import": [],
"cannot_import": ["api", "data"]
},
"data": {
"can_import": ["domain"],
"cannot_import": ["api"]
}
}
}
# Propagates to:
R2_all_modules:
MUST respect layer import rules
R4_new_modules:
MUST be assigned to a layer
MUST follow that layer's rules
R7_testing:
MUST test layer violation detection
test_no_layer_violations()
Example 2: Dependency Direction
# Resolution of R1_dependency_flow
flow = {
"direction": "top-down",
"rule": "Higher layers depend on lower, never reverse"
}
# Propagates to:
R4_all_implementations:
abstractions in lower layers
implementations in higher layers
R5_interfaces:
defined in lower layers
consumed by higher layers
Sometimes resolving a hole discovers NEW constraints:
Example: Discovering Resource Limits
# Resolution of R4_parallelization
# Attempts max_concurrent=10
# During testing, discovers:
- Provider rate limit = 30 req/min
- Memory per operation = 500MB
- Total memory = 4GB
# This DISCOVERS new constraints:
R4_updated:
max_concurrent <= min(
30 / requests_per_operation, # Rate limit
4000 / 500, # Memory limit (=8)
10 # Desired
) = 8
# And PROPAGATES:
R8_rate_limiting:
MUST implement backoff for rate limits
R9_memory_management:
MUST monitor memory usage
MUST fail fast if memory exceeded
If constraints contradict, stop and resolve:
Example: Conflicting Requirements
# R4_performance resolved
constraint_1 = "latency < 100ms"
# R5_security resolved
constraint_2 = "encrypt all data (adds 150ms overhead)"
# CONFLICT DETECTED:
# Cannot satisfy both constraints simultaneously
# Resolution options:
1. Relax performance: latency < 200ms
2. Optimize encryption: use hardware acceleration
3. Parallelize: encrypt async, don't block main path
4. Compromise: encrypt at rest, not in transit
# Document decision in REFACTOR_IR.md
def propagate_constraints(resolved_hole, resolution):
"""Propagate constraints from resolved hole to dependents"""
# 1. Extract constraints from resolution
new_constraints = extract_constraints(resolution)
# 2. Find dependent holes
dependents = get_dependent_holes(resolved_hole)
# 3. For each dependent
for dependent in dependents:
# Apply each propagation rule
for constraint in new_constraints:
rule = match_propagation_rule(constraint, dependent)
if rule:
# Add constraint to dependent
dependent.constraints.add(
apply_rule(rule, constraint)
)
# Narrow solution space
dependent.solution_space = filter(
dependent.solution_space,
lambda s: satisfies(s, constraint)
)
# Check if dependent is now over-constrained
if is_unsatisfiable(dependent):
raise ContradictionError(dependent, new_constraints)
# Update dependent documentation
update_refactor_ir(dependent)
# 4. Check for newly resolvable holes
newly_ready = find_ready_holes()
return newly_ready
# Resolve R1: target_architecture
resolution_R1 = {
"layers": ["api", "domain", "infrastructure"],
"principle": "dependency inversion"
}
# Propagates to R2: module_boundaries
constraint_R2 = "domain must not depend on infrastructure"
# Resolve R2: module_boundaries
resolution_R2 = {
"domain": ["core", "services", "interfaces"],
"infrastructure": ["repositories", "external_apis"]
}
# Propagates to R3: abstraction_layers
constraint_R3 = "domain defines repository interfaces"
# Resolve R3: abstraction_layers
resolution_R3 = {
"RepositoryProtocol": "defined in domain/interfaces"
}
# Propagates to R4: implementations
constraint_R4 = "infrastructure implements RepositoryProtocol"
# Resolve R4: implementations
resolution_R4 = {
"PostgresRepository": "implements RepositoryProtocol in infrastructure"
}
# Propagates to R7: testing
constraint_R7 = "test domain with mock repositories"
# Full chain:
R1 → R2 → R3 → R4 → R7
After propagation, validate all constraints:
def validate_all_constraints():
"""Check that all constraints are satisfiable"""
for hole in all_holes:
# Check local constraint satisfaction
if not are_satisfiable(hole.constraints):
raise ContradictionError(hole)
# Check global constraint interaction
for other_hole in all_holes:
if conflicts(hole.constraints, other_hole.constraints):
raise GlobalContradictionError(hole, other_hole)
return True
Constraint propagation is the heart of typed holes refactoring:
This ensures that each hole resolution is informed by all previous decisions, maintaining global consistency throughout the refactoring process.