Skip to content

feat(replay): add configuration override injection for A/B testing (OMN-1205) - #331

Merged
jonahgabriel merged 7 commits into
mainfrom
jonah/omn-1205-demo-configuration-override-injection
Jan 5, 2026
Merged

jonahgabriel merged 7 commits into
mainfrom
jonah/omn-1205-demo-configuration-override-injection

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jan 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add ServiceConfigOverrideInjector to inject configuration overrides during replay, enabling A/B testing of different models, parameters, or endpoints while keeping inputs identical.

Key Features:

  • ModelConfigOverride and ModelConfigOverrideSet for override specifications
  • ModelReplayInput[T] to bundle inputs with optional overrides
  • Three-phase API: validate() → preview() → apply()
  • Environment overlay support (without mutating os.environ)
  • Nested dot-path traversal (e.g., llm.openai.temperature)
  • Thread-safe, stateless service with deep-copy immutability

Non-negotiable invariants enforced:

  • Original inputs are NEVER modified
  • Unspecified fields retain original values
  • No cross-contamination between concurrent replays

Structural Fixes (from OMN-1116):

  • Moved RecorderEffect, InjectorTime, InjectorRNG from pipeline/replay/ to services/replay/
  • These are passive observers/injectors, NOT nodes - they don't belong in node-governed directories
  • Added architectural exemption to naming validation hook

Test Plan

  • 85 unit tests covering all core behaviors
  • mypy strict mode passes (0 errors)
  • All pre-commit and pre-push hooks pass
  • Imports verified working from new locations
  • Backwards compatibility maintained via re-exports

Files Changed

New Files (OMN-1205):

  • src/omnibase_core/enums/replay/enum_override_injection_point.py
  • src/omnibase_core/models/replay/model_config_override*.py (5 files)
  • src/omnibase_core/models/replay/model_replay_input.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • src/omnibase_core/services/replay/sentinel_missing.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • tests/unit/models/replay/test_model_config_overrides.py
  • tests/unit/models/replay/test_model_replay_input.py

Relocated Files (OMN-1116 fix):

  • pipeline/replay/recorder_effect.py → services/replay/recorder_effect.py
  • pipeline/replay/injector_time.py → services/replay/injector_time.py
  • pipeline/replay/injector_rng.py → services/replay/injector_rng.py

Modified Files:

  • scripts/validation/validate_naming.py (architectural exemption)
  • */__init__.py files for exports

Summary by CodeRabbit

  • New Features

    • Add configuration-override tooling for replay: validate overrides, produce dry-run previews, and apply safe, non‑mutating patches.
    • Add a replay input model to bundle input data with optional overrides and immutable update helpers.
    • Introduce a fixed set of override injection points and a sentinel to distinguish missing config paths.
  • Refactor

    • Consolidate replay components under a dedicated services.replay surface for clearer imports.
  • Tests

    • Extensive unit tests covering validation, preview, apply, concurrency, and serialization.

✏️ Tip: You can customize this high-level summary in your review settings.

…MN-1205)

Add ServiceConfigOverrideInjector to inject configuration overrides during
replay, enabling A/B testing of different models, parameters, or endpoints
while keeping inputs identical.

Key features:
- ModelConfigOverride and ModelConfigOverrideSet for override specifications
- ModelReplayInput[T] to bundle inputs with optional overrides
- Three-phase API: validate() -> preview() -> apply()
- Environment overlay support (without mutating os.environ)
- Nested dot-path traversal (e.g., "llm.openai.temperature")
- Thread-safe, stateless service with deep-copy immutability

Non-negotiable invariants enforced:
- Original inputs are NEVER modified
- Unspecified fields retain original values
- No cross-contamination between concurrent replays

85 unit tests covering all core behaviors.
Move RecorderEffect, InjectorTime, and InjectorRNG from pipeline/replay/
to services/replay/ to fix structural misplacement.

These utilities are passive observers and injection mechanisms - NOT nodes.
Nodes are addressable graph vertices with contracts and dispatch semantics.
Recorders and injectors are lifecycle-bound utilities for side-effect capture.

The pipeline/ directory is governed by Node naming rules, which correctly
flagged RecorderEffect as a violation. The fix is relocation, not renaming.

Moved files:
- pipeline/replay/recorder_effect.py → services/replay/recorder_effect.py
- pipeline/replay/injector_time.py → services/replay/injector_time.py
- pipeline/replay/injector_rng.py → services/replay/injector_rng.py

Backwards compatibility maintained via re-exports from pipeline/replay/__init__.py.
Add RecorderEffect, InjectorTime, InjectorRNG, and ReplaySession to the
naming convention exemptions for services/replay/.

These classes are deterministic replay utilities (OMN-1116), NOT nodes.
The heuristic incorrectly flagged "RecorderEffect" because it contains
"effect" - but RecorderEffect RECORDS effects, it doesn't PERFORM them
as a node would.

Nodes are addressable graph vertices with contracts and dispatch semantics.
Recorders and injectors are lifecycle-bound utilities for side-effect capture.
@coderabbitai

coderabbitai Bot commented Jan 4, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Reorganizes replay surface: moves recorder/injector types into a new services/replay package; adds EnumOverrideInjectionPoint and several immutable Pydantic models for config overrides; introduces ServiceConfigOverrideInjector and MISSING sentinel; updates pipeline re-exports and tests to the new service paths. (49 words)

Changes

Cohort / File(s) Summary
Validation & Naming Rules
scripts/validation/validate_naming.py
NAMING_PATTERNS updated: new services/replay/ entries (RecorderEffect, InjectorTime, InjectorRNG); pipeline/replay/ reduced to ExecutorReplay and SessionReplay.
Replay Enums
src/omnibase_core/enums/replay/__init__.py, .../enum_override_injection_point.py
Added EnumOverrideInjectionPoint (HANDLER_CONFIG, ENVIRONMENT, CONTEXT) and re-exported it in package __init__.
Config Override Models
src/omnibase_core/models/replay/* (model_config_override.py, model_config_override_field_preview.py, model_config_override_preview.py, model_config_override_result.py, model_config_override_set.py, model_config_override_validation.py, __init__.py)
Introduced immutable Pydantic models for override spec, field preview, aggregated preview, result, set (with grouping/with_override), and validation report; all re-exported in package __init__.
Replay Input Model
src/omnibase_core/models/replay/model_replay_input.py
Added generic ModelReplayInput[T_Data] bundling input data with optional ModelConfigOverrideSet, immutability, has_overrides, and with_overrides helper; exported.
Service Package & Sentinel
src/omnibase_core/services/replay/__init__.py, src/omnibase_core/services/replay/sentinel_missing.py
New services/replay package and MISSING sentinel singleton to represent non-existent config paths; package __all__ set to [].
Config Override Service
src/omnibase_core/services/replay/service_config_override_injector.py
Added ServiceConfigOverrideInjector (stateless/thread-safe) with validate, preview, apply, apply_environment_overlay; includes path syntax checks, traversal helpers, deep-copy semantics, and returns the new model types.
Replay Component Re-exports
src/omnibase_core/pipeline/__init__.py, src/omnibase_core/pipeline/replay/__init__.py, src/omnibase_core/pipeline/replay/executor_replay.py, src/omnibase_core/pipeline/replay/session_replay.py
Re-exported InjectorRNG, InjectorTime, RecorderEffect from services.replay; updated internal imports to new module paths.
Service Modules (docs only changes)
src/omnibase_core/services/replay/injector_rng.py, .../injector_time.py, .../recorder_effect.py
Docstring/examples updated to reference services.replay import paths; no behavioral changes.
Protocol Docstrings
src/omnibase_core/protocols/replay/*
Example imports in protocol docstrings updated to services.replay modules.
Tests
tests/unit/models/replay/*, tests/unit/services/replay/*, tests/unit/pipeline/replay/*, tests/unit/services/replay/__init__.py
Added comprehensive tests for models and ServiceConfigOverrideInjector (validate/preview/apply, concurrency, Pydantic support); updated pipeline replay tests to import new service paths; added test package initializer.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Injector as ServiceConfigOverrideInjector
    participant Config as ConfigStore
    participant Models as Validation/Preview/Result

    rect rgb(235,245,255)
    note right of Injector: validate(overrides, config, injection_point)
    Caller->>Injector: validate(overrides, config, injection_point)
    Injector->>Injector: validate path syntax & depth
    Injector->>Config: read values (or MISSING)
    Injector->>Injector: type checks, accumulate violations/warnings
    Injector-->>Caller: ModelConfigOverrideValidation
    end

    rect rgb(245,255,240)
    note right of Injector: preview(overrides, config, injection_point)
    Caller->>Injector: preview(overrides, config, injection_point)
    Injector->>Config: read original values
    Injector->>Injector: build field previews, detect missing/type mismatches
    Injector-->>Caller: ModelConfigOverridePreview
    end

    rect rgb(255,250,230)
    note right of Injector: apply(overrides, config, injection_point)
    Caller->>Injector: apply(overrides, config, injection_point)
    Injector->>Injector: deep_copy(config)
    Injector->>Config: operate on copy, set values/create intermediates
    Injector->>Models: assemble ModelConfigOverrideResult
    Injector-->>Caller: ModelConfigOverrideResult
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hop through services, new paths to find,
MISSING sentinels guard what’s left behind.
Previews whisper changes, then apply with care,
Deep copies keep originals safe and fair.
A tidy replay garden — hop, sniff, and share!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding configuration override injection for A/B testing, and includes the relevant issue identifier (OMN-1205). It is concise, specific, and accurately reflects the primary functionality being introduced in this comprehensive PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Combine replay infrastructure exemptions from both branches:
- services/replay/: RecorderEffect, InjectorTime, InjectorRNG (relocated)
- pipeline/replay/: ExecutorReplay, SessionReplay (remaining in pipeline)
@claude

claude Bot commented Jan 4, 2026

Copy link
Copy Markdown

Code Review - PR #331: Configuration Override Injection for A/B Testing

Executive Summary

This PR introduces a comprehensive configuration override system for replay-based A/B testing. The implementation is well-architected, thoroughly tested, and follows ONEX conventions. I have a few minor suggestions but overall this is production-ready code.


✅ Strengths

1. Excellent Architecture & Design

  • Three-phase API (validate() → preview() → apply()) provides excellent UX and safety
  • Immutability guarantees: Original configs never modified, deep-copy semantics enforced
  • MISSING sentinel pattern cleanly distinguishes "path not found" from "value is None"
  • Thread-safe by design: Stateless service with pure functions
  • Proper separation of concerns: Injection points (HANDLER_CONFIG, ENVIRONMENT, CONTEXT) are well-defined

2. Robust Error Handling

  • Path syntax validation with MAX_PATH_DEPTH protection against DoS
  • Regex validation for identifiers (^[a-zA-Z_][a-zA-Z0-9_]*$)
  • Type mismatch detection with graduated severity (violations/warnings/suggestions)
  • Numeric type coercion handled gracefully (int ↔ float)

3. Comprehensive Testing

  • 85 unit tests covering core behaviors (excellent coverage)
  • Test categories well-organized: Validate, Preview, Apply, Environment, PathTraversal, Pydantic, Concurrency
  • Thread-safety verified with concurrent access tests
  • Edge cases covered (frozen Pydantic models, list indexing, deep nesting)

4. Code Quality

  • Excellent documentation: Docstrings follow Google style, comprehensive module headers
  • Type hints throughout: Proper use of tuple[T, ...] immutable collections
  • Follows ONEX conventions: Protocol-driven, error handling patterns, naming conventions
  • Clean code: Well-factored methods, clear variable names, logical flow

5. Structural Improvements (OMN-1116)

  • Correct relocation of RecorderEffect, InjectorTime, InjectorRNG from pipeline/ to services/
  • Architectural rationale is sound: These are passive observers/injectors, not nodes
  • Backwards compatibility maintained via re-exports

🔍 Minor Issues & Suggestions

1. Potential Issue: Frozen Pydantic Models

Location: service_config_override_injector.py:314-329

The code attempts setattr() on Pydantic models, but frozen models will raise ValidationError:

# Line 322-328
try:
    setattr(current, final_part, value)
    return (True, "")
except (AttributeError, TypeError, ValueError) as e:
    return (False, f"Cannot set field '{final_part}' on Pydantic model: {e}")

Issue: Frozen Pydantic models raise pydantic.ValidationError, not in the caught exceptions.

Recommendation:

from pydantic import ValidationError

except (AttributeError, TypeError, ValueError, ValidationError) as e:
    # catch-all-ok: boundary handler for all Pydantic/type errors

Or use PYDANTIC_MODEL_ERRORS from omnibase_core.errors.exception_groups per CLAUDE.md conventions:

from omnibase_core.errors.exception_groups import PYDANTIC_MODEL_ERRORS

except (AttributeError, TypeError, ValueError, *PYDANTIC_MODEL_ERRORS) as e:

2. Documentation: from_attributes=True Rationale

Location: model_config_override_validation.py:115-117

The comment explains from_attributes=True for pytest-xdist compatibility. This is correct per CLAUDE.md, but consider referencing the docs:

# from_attributes=True allows Pydantic to accept objects with matching
# attributes even when class identity differs (e.g., in pytest-xdist
# parallel execution where model classes are imported in separate workers).
# See CLAUDE.md section "Pydantic from_attributes=True for Value Objects".

Recommendation: Add a link to the exact section for future maintainers.

3. Environment Variable Naming Convention

Location: service_config_override_injector.py:679-680

env_name = parts[-1].upper()  # Use last segment, uppercase by convention

Issue: This could cause collisions if multiple nested paths have the same final segment:

  • llm.openai.timeout → TIMEOUT
  • database.timeout → TIMEOUT (collision!)

Recommendation: Consider using the full path with dots → underscores:

env_name = "_".join(parts).upper()  # "LLM_OPENAI_TIMEOUT"

Or document the collision risk explicitly in the docstring.

4. Type Annotation: Generic Bound

Location: model_replay_input.py:20

T_Data = TypeVar("T_Data")

Minor: Consider bounding to BaseModel | dict[str, Any] if that's the intended use:

T_Data = TypeVar("T_Data", bound=BaseModel | dict[str, Any])

This provides better type safety and IDE autocomplete.

5. Markdown Preview Formatting

Location: model_config_override_field_preview.py:82-97

The _format_value() method handles None, str, but complex types (dicts, lists) will use default repr():

return f"`{value}`"  # Could be ugly for large dicts

Recommendation: Add truncation or JSON formatting for complex types:

import json

def _format_value(self, value: Any) -> str:
    if value is None:
        return "`null`"
    if isinstance(value, str):
        return f'"`{value}`"'
    if isinstance(value, (dict, list)):
        json_str = json.dumps(value)
        if len(json_str) > 50:
            return f"`{json_str[:47]}...`"
        return f"`{json_str}`"
    return f"`{value}`"

🔒 Security Considerations

✅ Good Security Practices

  1. DoS protection: MAX_PATH_DEPTH = 20 prevents deeply nested path attacks
  2. No os.environ mutation: Environment overlay is returned as dict, caller responsible
  3. Input validation: Path syntax validated with regex before traversal
  4. Deep copy immutability: Original configs never exposed to mutation

⚠️ Potential Risk: Arbitrary Path Creation

Location: service_config_override_injector.py:426-434

The code allows creating new paths in configs:

if current_value is MISSING:
    if override.injection_point == EnumOverrideInjectionPoint.ENVIRONMENT:
        suggestions.append(f"Path '{override.path}' will be created as new environment variable")
    else:
        warnings.append(f"Path '{override.path}' does not exist in config (will be created)")

Risk: In non-strict mode, an attacker could inject arbitrary config keys if overrides are user-controlled.

Recommendation:

  • Document that overrides should be admin/CI-controlled, not user input
  • Consider adding an allow_new_paths: bool = False parameter to enforce stricter validation
  • Add a security note to the module docstring

📊 Performance Considerations

✅ Efficient Design

  • Stateless service: No locking overhead
  • Deep copy only on apply(): Validate/preview don't copy
  • Short-circuit validation: Invalid paths skip type checks

Minor Optimization Opportunity

Location: service_config_override_injector.py:200-246

_get_value_at_path() and _set_value_at_path() both split the path string:

parts = path.split(".")  # Called twice for apply()

Recommendation: For high-throughput scenarios, consider caching split paths:

@lru_cache(maxsize=128)
def _split_path(path: str) -> tuple[str, ...]:
    return tuple(path.split("."))

This is a micro-optimization and probably not needed unless profiling shows it's a bottleneck.


📚 Documentation Quality

✅ Excellent

  • Comprehensive module docstrings with examples
  • Thread-safety guarantees documented
  • Architecture section explains design decisions
  • Related OMN tickets referenced (OMN-1205, OMN-1116)

Minor Improvements

  1. Add usage example to README/docs: This is a major feature, deserves a guide
  2. Link to ONEX terminology: Reference deterministic replay in docs/standards/onex_terminology.md
  3. Migration guide: If there were previous override mechanisms, document migration

🧪 Test Coverage

✅ Comprehensive

  • 85 tests across 3 test files
  • Edge cases well-covered (frozen models, lists, concurrent access)
  • Fixtures well-organized and reusable

Suggestion: Add Integration Test

Current tests are all unit tests. Consider adding an integration test:

def test_end_to_end_replay_with_overrides():
    # Create replay context with config overrides
    # Execute a real handler with patched config
    # Verify handler used overridden values

This would validate the full pipeline integration.


✅ Verdict: APPROVE with Minor Suggestions

This PR is high-quality, well-tested, and production-ready. The minor suggestions above are non-blocking improvements for future iterations.

Recommended Actions:

  1. ✅ Merge after CI passes (assuming all checks green)
  2. 📝 Consider frozen Pydantic exception handling (Issue Complete SPI Migration and Core Stabilization: LogLevel Standardization + ONEX Architecture #1)
  3. 📚 Add user-facing documentation/guide
  4. 🔒 Document security assumptions (overrides should be trusted)

Great work on this feature! The three-phase API and immutability guarantees are excellent design choices. 🚀

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/omnibase_core/services/replay/injector_rng.py (1)

1-10: Rename file to follow naming convention: injector_rng.py → service_injector_rng.py

Per the naming convention enforcer, all files in src/omnibase_core/services/ must use the service_ prefix. The file should be renamed from injector_rng.py to service_injector_rng.py. Ensure all imports throughout the codebase are updated to reflect this change.

Note: injector_time.py, recorder_effect.py, and sentinel_missing.py in the same directory also violate this convention and should be similarly renamed to service_injector_time.py, service_recorder_effect.py, and service_sentinel_missing.py respectively.

🧹 Nitpick comments (2)
src/omnibase_core/models/replay/model_config_override.py (1)

1-54: Consider adding __all__ export for explicit public API.

The module does not define __all__. While not strictly required if the model is re-exported from __init__.py, adding __all__ = ["ModelConfigOverride"] would make the public API explicit and help with tooling/IDE support.

🔎 Suggested addition
 from omnibase_core.enums.replay.enum_override_injection_point import (
     EnumOverrideInjectionPoint,
 )
+
+__all__ = ["ModelConfigOverride"]
 
 
 class ModelConfigOverride(BaseModel):
src/omnibase_core/services/replay/service_config_override_injector.py (1)

127-131: Unused class attribute PATH_SYNTAX_PATTERN.

The compiled regex pattern is defined but never used in the validation logic. The _is_valid_path_syntax method performs manual validation instead, which provides better error messages and flexibility for numeric indices.

Consider removing this unused attribute to avoid confusion.

🔎 Proposed fix
-    # Valid path segment pattern: starts with letter or underscore,
-    # followed by letters, digits, or underscores
-    PATH_SYNTAX_PATTERN: re.Pattern[str] = re.compile(
-        r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*|\.[0-9]+)*$"
-    )
-
     # Maximum path depth to prevent DoS via deeply nested paths
     MAX_PATH_DEPTH: int = 20
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d93260 and 9334793.

📒 Files selected for processing (31)
  • scripts/validation/validate_naming.py
  • src/omnibase_core/enums/replay/__init__.py
  • src/omnibase_core/enums/replay/enum_override_injection_point.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_result.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override_validation.py
  • src/omnibase_core/models/replay/model_replay_input.py
  • src/omnibase_core/pipeline/__init__.py
  • src/omnibase_core/pipeline/replay/__init__.py
  • src/omnibase_core/pipeline/replay/executor_replay.py
  • src/omnibase_core/pipeline/replay/session_replay.py
  • src/omnibase_core/protocols/replay/protocol_effect_recorder.py
  • src/omnibase_core/protocols/replay/protocol_rng_service.py
  • src/omnibase_core/protocols/replay/protocol_time_service.py
  • src/omnibase_core/services/replay/__init__.py
  • src/omnibase_core/services/replay/injector_rng.py
  • src/omnibase_core/services/replay/injector_time.py
  • src/omnibase_core/services/replay/recorder_effect.py
  • src/omnibase_core/services/replay/sentinel_missing.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • tests/unit/models/replay/test_model_config_overrides.py
  • tests/unit/models/replay/test_model_replay_input.py
  • tests/unit/pipeline/replay/test_injector_rng.py
  • tests/unit/pipeline/replay/test_injector_time.py
  • tests/unit/pipeline/replay/test_recorder_effect.py
  • tests/unit/services/replay/__init__.py
  • tests/unit/services/replay/test_service_config_override_injector.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for Python package management and task execution (poetry run pytest, poetry run mypy, poetry add, poetry install). Never use pip directly or python -m for execution.
Implement ONEX Four-Node Architecture following the unidirectional workflow: EFFECT → COMPUTE → REDUCER → ORCHESTRATOR. Each node must inherit from appropriate base class (NodeEffect, NodeCompute, NodeReducer, NodeOrchestrator).
Always call super().init(container) in node constructors, where container is of type ModelONEXContainer. This eliminates boilerplate initialization code.
Use ModelONEXContainer for dependency injection in node constructors, never use ModelContainer[T]. Resolve services using protocol names only (e.g., container.get_service('ProtocolEventBus')), never by concrete class names.
Use ModelOnexError with EnumCoreErrorCode for all exception handling. Never raise generic Exception. Use the @standard_error_handling decorator to eliminate try/catch boilerplate.
Use PEP 604 union syntax (str | None) instead of Optional[str] and Union types. This is enforced by ruff UP007.
Prefix exception tuples with standardized markers (# fallback-ok:, # catch-all-ok:, # cleanup-resilience-ok:, # boundary-ok:, # init-errors-ok:, # tool-resilience-ok:) to document intent of broad exception handlers.
Order exception tuples alphabetically by exception class name (AttributeError, TypeError, ValidationError, ValueError).
Use centralized exception groups from omnibase_core.errors.exception_groups (PYDANTIC_MODEL_ERRORS, VALIDATION_ERRORS, FILE_IO_ERRORS, etc.) instead of manual exception tuple construction.
Never share node instances across threads. Use thread-local instances or separate instances per thread. Node instances (NodeCompute, NodeEffect) are not thread-safe by default.
Use duck typing with protocols - never use isinstance() checks. Rely on protocol interfaces for type checking and service resolution.
Load runtime contracts using FileRegistry from omnibase_core.runti...

Files:

  • src/omnibase_core/models/replay/model_config_override_validation.py
  • tests/unit/pipeline/replay/test_injector_rng.py
  • src/omnibase_core/protocols/replay/protocol_rng_service.py
  • src/omnibase_core/services/replay/__init__.py
  • src/omnibase_core/services/replay/recorder_effect.py
  • src/omnibase_core/pipeline/__init__.py
  • src/omnibase_core/services/replay/sentinel_missing.py
  • src/omnibase_core/enums/replay/enum_override_injection_point.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/services/replay/injector_time.py
  • tests/unit/models/replay/test_model_replay_input.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/enums/replay/__init__.py
  • src/omnibase_core/pipeline/replay/session_replay.py
  • src/omnibase_core/pipeline/replay/executor_replay.py
  • src/omnibase_core/models/replay/model_replay_input.py
  • tests/unit/models/replay/test_model_config_overrides.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/protocols/replay/protocol_effect_recorder.py
  • tests/unit/services/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • scripts/validation/validate_naming.py
  • src/omnibase_core/protocols/replay/protocol_time_service.py
  • src/omnibase_core/models/replay/model_config_override.py
  • tests/unit/pipeline/replay/test_recorder_effect.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_result.py
  • tests/unit/pipeline/replay/test_injector_time.py
  • src/omnibase_core/pipeline/replay/__init__.py
  • src/omnibase_core/services/replay/injector_rng.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: All Python files in src/omnibase_core/ must follow directory-specific naming prefixes enforced by checker_naming_convention.py. Examples: cli_, container_, decorator_, enum_, error_, exception_, factory_, model_, node_, mixin_, protocol_, service_, util_, validator_, checker_, resolver_, etc.
Maintain 100% mypy strict mode compliance and ensure pyright passes in CI. Type annotations must be complete and correct.

Files:

  • src/omnibase_core/models/replay/model_config_override_validation.py
  • src/omnibase_core/protocols/replay/protocol_rng_service.py
  • src/omnibase_core/services/replay/__init__.py
  • src/omnibase_core/services/replay/recorder_effect.py
  • src/omnibase_core/pipeline/__init__.py
  • src/omnibase_core/services/replay/sentinel_missing.py
  • src/omnibase_core/enums/replay/enum_override_injection_point.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/services/replay/injector_time.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/enums/replay/__init__.py
  • src/omnibase_core/pipeline/replay/session_replay.py
  • src/omnibase_core/pipeline/replay/executor_replay.py
  • src/omnibase_core/models/replay/model_replay_input.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/protocols/replay/protocol_effect_recorder.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/protocols/replay/protocol_time_service.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_result.py
  • src/omnibase_core/pipeline/replay/__init__.py
  • src/omnibase_core/services/replay/injector_rng.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/models/**/*.py: Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.
Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.

Files:

  • src/omnibase_core/models/replay/model_config_override_validation.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_replay_input.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_result.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test with: poetry run pytest tests/ for all tests, poetry run pytest tests/unit/ for unit tests only, poetry run pytest tests/ -n 0 -xvs for debug mode. Use markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.smoke, @pytest.mark.performance.

Files:

  • tests/unit/pipeline/replay/test_injector_rng.py
  • tests/unit/models/replay/test_model_replay_input.py
  • tests/unit/models/replay/test_model_config_overrides.py
  • tests/unit/services/replay/__init__.py
  • tests/unit/pipeline/replay/test_recorder_effect.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • tests/unit/pipeline/replay/test_injector_time.py
🧠 Learnings (2)
📚 Learning: 2026-01-04T19:09:33.269Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.269Z
Learning: Applies to src/omnibase_core/models/**/*.py : Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.

Applied to files:

  • src/omnibase_core/models/replay/model_config_override_validation.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_replay_input.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_result.py
📚 Learning: 2026-01-04T19:09:33.269Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.269Z
Learning: Applies to src/omnibase_core/models/**/*.py : Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.

Applied to files:

  • src/omnibase_core/models/replay/model_config_override_validation.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_replay_input.py
  • tests/unit/models/replay/test_model_config_overrides.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_result.py
🧬 Code graph analysis (14)
src/omnibase_core/models/replay/model_config_override_validation.py (1)
src/omnibase_core/protocols/validation/protocol_constraint_validation_result.py (1)
  • warnings (85-87)
tests/unit/pipeline/replay/test_injector_rng.py (1)
src/omnibase_core/services/replay/injector_rng.py (1)
  • InjectorRNG (64-185)
src/omnibase_core/pipeline/__init__.py (1)
src/omnibase_core/services/replay/injector_rng.py (1)
  • InjectorRNG (64-185)
src/omnibase_core/models/replay/model_config_override_field_preview.py (1)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override_preview.py (2)
src/omnibase_core/models/replay/model_config_override_field_preview.py (2)
  • ModelConfigOverrideFieldPreview (20-98)
  • to_markdown_row (68-80)
src/omnibase_core/services/replay/service_config_override_injector.py (1)
  • preview (475-563)
src/omnibase_core/models/replay/model_replay_input.py (1)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (21-50)
tests/unit/models/replay/test_model_config_overrides.py (3)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (20-54)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override_set.py (3)
  • ModelConfigOverrideSet (21-50)
  • by_injection_point (39-46)
  • with_override (48-50)
src/omnibase_core/models/replay/__init__.py (8)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (20-54)
src/omnibase_core/models/replay/model_config_override_field_preview.py (1)
  • ModelConfigOverrideFieldPreview (20-98)
src/omnibase_core/models/replay/model_config_override_preview.py (1)
  • ModelConfigOverridePreview (18-98)
src/omnibase_core/models/replay/model_config_override_result.py (1)
  • ModelConfigOverrideResult (16-43)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (21-50)
src/omnibase_core/models/replay/model_effect_record.py (1)
  • ModelEffectRecord (75-158)
src/omnibase_core/models/replay/model_replay_context.py (1)
  • ModelReplayContext (87-272)
src/omnibase_core/models/replay/model_replay_input.py (1)
  • ModelReplayInput (24-80)
src/omnibase_core/models/replay/model_config_override_set.py (2)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (20-54)
src/omnibase_core/models/replay/model_config_override.py (1)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
tests/unit/pipeline/replay/test_recorder_effect.py (4)
src/omnibase_core/services/replay/recorder_effect.py (1)
  • RecorderEffect (103-483)
src/omnibase_core/services/replay/injector_time.py (1)
  • InjectorTime (62-172)
src/omnibase_core/enums/replay/enum_recorder_mode.py (1)
  • EnumRecorderMode (45-67)
src/omnibase_core/models/replay/model_effect_record.py (1)
  • ModelEffectRecord (75-158)
tests/unit/services/replay/test_service_config_override_injector.py (6)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (20-54)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (21-50)
src/omnibase_core/services/replay/service_config_override_injector.py (3)
  • preview (475-563)
  • apply (565-638)
  • apply_environment_overlay (640-692)
src/omnibase_core/models/replay/model_config_override_preview.py (1)
  • to_markdown (68-98)
src/omnibase_core/services/replay/sentinel_missing.py (1)
  • _Missing (15-41)
tests/unit/pipeline/replay/test_injector_time.py (1)
src/omnibase_core/services/replay/injector_time.py (1)
  • InjectorTime (62-172)
src/omnibase_core/pipeline/replay/__init__.py (3)
src/omnibase_core/services/replay/injector_rng.py (1)
  • InjectorRNG (64-185)
src/omnibase_core/services/replay/injector_time.py (1)
  • InjectorTime (62-172)
src/omnibase_core/services/replay/recorder_effect.py (1)
  • RecorderEffect (103-483)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Tests (Split 12/20)
  • GitHub Check: claude-review
🔇 Additional comments (50)
src/omnibase_core/services/replay/injector_time.py (1)

18-18: LGTM - Documentation correctly updated to reflect new module location.

The import path in the usage example has been properly updated to match the relocated module structure.

src/omnibase_core/protocols/replay/protocol_time_service.py (1)

23-23: LGTM - Example import path updated correctly.

The documentation example now imports from the new services.replay location, consistent with the structural reorganization.

tests/unit/services/replay/__init__.py (1)

1-15: LGTM - Standard test namespace initialization.

The file follows best practices with proper SPDX headers, descriptive docstring, and explicitly typed empty __all__ declaration for the test namespace.

src/omnibase_core/services/replay/__init__.py (1)

1-29: LGTM - Package initializer correctly implements OMN-1071 policy.

The module documentation clearly states that services are not exported at the package level, with helpful examples showing the correct import patterns. The empty __all__ correctly enforces this policy.

scripts/validation/validate_naming.py (1)

214-235: LGTM - Architectural exemptions correctly updated for replay reorganization.

The exemptions are now properly split between services/replay/ (for injectors and recorders) and pipeline/replay/ (for executors and sessions), with clear rationale explaining why these utilities don't follow standard node naming conventions.

src/omnibase_core/services/replay/injector_rng.py (2)

18-18: Verify the docstring example import path aligns with actual module location.

Line 18 shows the import as from omnibase_core.services.replay.injector_rng import InjectorRNG. Confirm:

  1. This matches the actual module location (appears to be correct based on file path)
  2. The example is consistent with other documentation in the codebase
  3. If re-exports exist in omnibase_core.pipeline.replay for backward compatibility, consider documenting both import paths

64-110: Module-level protocol compliance check is good practice.

The pattern at lines 188–189 (_rng_check: ProtocolRNGService = InjectorRNG(seed=0)) provides compile-time verification that InjectorRNG correctly implements ProtocolRNGService. This is excellent defensive programming.

src/omnibase_core/protocols/replay/protocol_effect_recorder.py (1)

24-24: Import path update is correct and properly supported.

The docstring example update importing RecorderEffect from omnibase_core.services.replay.recorder_effect is valid. Both the new path and the legacy omnibase_core.pipeline.replay path work correctly—RecorderEffect is defined in services/replay and re-exported in pipeline/replay for backward compatibility. The example usage will execute without issue.

src/omnibase_core/protocols/replay/protocol_rng_service.py (1)

93-93: Import path update is correct and properly re-exported.

The new import path omnibase_core.services.replay.injector_rng exists and properly exposes InjectorRNG. Backward compatibility re-exports are in place in omnibase_core.pipeline.replay.__init__.py, making the class accessible via both the direct import (recommended per OMN-1071 policy) and the re-export path. All consumers of the example code can import successfully.

src/omnibase_core/services/replay/recorder_effect.py (1)

25-25: LGTM! Documentation examples updated correctly.

The import paths in the docstring examples have been properly updated to reflect the new module location. This ensures users follow the correct import pattern.

Also applies to: 124-124

tests/unit/pipeline/replay/test_injector_rng.py (1)

338-338: LGTM! Test updated to verify new import path.

The test correctly validates that InjectorRNG can be imported from the new services.replay module location.

src/omnibase_core/pipeline/replay/executor_replay.py (1)

95-97: LGTM! Imports updated to reflect architectural reorganization.

The three replay infrastructure components (InjectorRNG, InjectorTime, RecorderEffect) are now correctly imported from their new canonical location in services.replay. This aligns with the architectural decision to treat these as passive utility services rather than pipeline nodes.

tests/unit/pipeline/replay/test_recorder_effect.py (1)

34-34: LGTM! Test imports comprehensively updated.

All test fixtures and test cases have been updated to import RecorderEffect and InjectorTime from the new services.replay module location. The updates are consistent throughout the file and maintain test functionality.

Also applies to: 46-46, 74-74, 85-85, 120-120, 390-390, 453-455, 490-492, 532-532

src/omnibase_core/enums/replay/__init__.py (1)

6-10: LGTM! New enum properly integrated.

The EnumOverrideInjectionPoint enum is correctly added to the module exports with updated documentation. This supports the new configuration override injection feature for A/B testing (OMN-1205).

Also applies to: 15-17, 22-22

tests/unit/pipeline/replay/test_injector_time.py (1)

29-29: LGTM! Import paths consistently updated.

All imports of InjectorTime have been correctly migrated from omnibase_core.pipeline.replay.injector_time to omnibase_core.services.replay.injector_time, aligning with the structural relocation of replay components to the services module.

Also applies to: 35-35, 49-49, 128-128, 159-159, 183-183, 238-238, 248-248, 258-258

src/omnibase_core/pipeline/__init__.py (1)

94-94: LGTM! Import path updated while preserving public API.

The InjectorRNG import source has been correctly updated to the new services.replay location. The re-export in __all__ maintains backwards compatibility for consumers importing from omnibase_core.pipeline.

src/omnibase_core/pipeline/replay/session_replay.py (1)

66-68: LGTM! Import paths consistently migrated.

All imports for InjectorRNG, InjectorTime, and RecorderEffect have been updated to the new omnibase_core.services.replay module locations, both in TYPE_CHECKING blocks and in the deferred factory functions. The deferred import pattern is correctly maintained to avoid circular dependencies.

Also applies to: 134-134, 141-141, 148-148

src/omnibase_core/services/replay/sentinel_missing.py (1)

15-46: LGTM! Well-designed sentinel for distinguishing missing paths from None.

The singleton pattern is correctly implemented. While the __new__ method has a theoretical race condition, this is mitigated by creating the singleton at module load time (line 45), which is protected by Python's import lock. The __bool__ returning False is a sensible choice for intuitive truthiness checks.

tests/unit/models/replay/test_model_config_overrides.py (2)

80-519: LGTM! Comprehensive test coverage for config override models.

Excellent test structure covering:

  • Creation scenarios (defaults, explicit values, complex/None values)
  • Immutability verification via ValidationError on frozen models
  • Field validation (path min_length, required fields, extra fields forbidden)
  • Serialization roundtrips (dict and JSON)
  • ModelConfigOverrideSet grouping (by_injection_point) and immutable updates (with_override)

All test classes correctly use @pytest.mark.unit marker as per guidelines.


26-29: Fix incorrect import path: use singular form or package-level import.

The test imports from omnibase_core.models.replay.model_config_overrides (plural), but no such module exists. All actual files use singular form (model_config_override.py, model_config_override_set.py). The __init__.py re-exports these models, so either import directly from the package or use the singular file path:

  • from omnibase_core.models.replay import ModelConfigOverride (preferred via init.py), or
  • from omnibase_core.models.replay.model_config_override import ModelConfigOverride (direct from singular file)

Also applies to: 35-35

⛔ Skipped due to learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.269Z
Learning: Applies to src/omnibase_core/models/**/*.py : Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.269Z
Learning: Applies to src/omnibase_core/**/*.py : All Python files in src/omnibase_core/ must follow directory-specific naming prefixes enforced by checker_naming_convention.py. Examples: cli_*, container_*, decorator_*, enum_*, error_*, exception_*, factory_*, model_*, node_*, mixin_*, protocol_*, service_*, util_*, validator_*, checker_*, resolver_*, etc.
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.269Z
Learning: Applies to src/omnibase_core/models/**/*.py : Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)

16-55: LGTM! Well-designed enum with proper documentation.

The EnumOverrideInjectionPoint follows best practices:

  • Inherits from str, Enum for seamless JSON serialization
  • Lowercase string values align with Python/JSON conventions
  • Comprehensive docstring with thread safety notes and usage examples
  • Correct __all__ export
src/omnibase_core/models/replay/model_config_override.py (1)

40-54: LGTM! Model follows Pydantic best practices and coding guidelines.

The ModelConfigOverride is correctly configured:

  • ConfigDict(frozen=True, extra="forbid", from_attributes=True) — immutable, strict, and compatible with pytest-xdist parallel execution (per learnings)
  • path field with min_length=1 prevents empty path errors
  • injection_point defaults to HANDLER_CONFIG for the common case
src/omnibase_core/models/replay/model_config_override_validation.py (1)

75-137: LGTM! Well-designed immutable validation result model.

The model correctly follows the violations/warnings/suggestions pattern consistent with other validation components in the codebase. Proper use of ConfigDict(frozen=True, extra="forbid", from_attributes=True) ensures immutability and pytest-xdist compatibility. Using tuple types for collections enforces immutability at the field level.

tests/unit/models/replay/test_model_replay_input.py (2)

27-33: Good use of TYPE_CHECKING for type hints.

Using TYPE_CHECKING guard with lazy imports inside fixtures avoids circular import issues while maintaining type safety. This is a clean pattern for test files.


85-401: Comprehensive test coverage for ModelReplayInput.

The tests thoroughly cover model creation, immutability enforcement, validation, the has_overrides property edge cases (None, empty, populated), immutable update via with_overrides, generic type preservation, and serialization roundtrips. Good coverage of the model's invariants.

src/omnibase_core/models/replay/model_replay_input.py (1)

24-81: LGTM! Well-implemented generic replay input model.

The generic ModelReplayInput[T_Data] correctly uses Pydantic v2's pattern for generic models. The has_overrides property properly handles both None and empty override sets. The with_overrides method correctly implements the immutable update pattern by preserving the input_id and data while replacing overrides.

src/omnibase_core/pipeline/replay/__init__.py (1)

54-66: Good backwards-compatible re-export structure.

The re-exports from omnibase_core.services.replay maintain backwards compatibility while the documentation clearly indicates the new canonical import paths. This allows existing code to continue working while guiding users to the preferred imports.

src/omnibase_core/models/replay/model_config_override_set.py (3)

31-31: LGTM on model configuration!

The ConfigDict properly includes from_attributes=True for pytest-xdist parallel execution compatibility, along with frozen=True for immutability and extra="forbid" for strict validation. Based on learnings, this follows the project guidelines.


38-46: LGTM!

The by_injection_point property correctly creates a new dict with new lists on each call, preserving the model's immutability guarantees. The grouping logic using setdefault is concise and correct.


48-50: LGTM!

The with_override method correctly implements the immutable update pattern, returning a new instance with the additional override appended.

tests/unit/services/replay/test_service_config_override_injector.py (9)

98-124: Test Pydantic models are appropriately configured for test scope.

These test models are local fixtures and not shared across pytest-xdist workers as value objects, so the absence of from_attributes=True is acceptable here. The frozen model (FrozenPydanticConfig) correctly tests the injector's behavior with immutable Pydantic models.


131-238: LGTM!

The TestValidate class provides good coverage for validation scenarios including path existence, type checking, strict mode behavior, and path syntax validation. The assertions verify both positive and negative cases correctly.


245-332: LGTM!

The TestPreview class adequately tests the preview functionality including before/after state tracking, new path identification, markdown table generation, and type mismatch detection.


339-437: LGTM on critical immutability tests!

The TestApply class properly verifies the key invariants:

  • Original configuration is never modified (verified with deep copy comparison)
  • Patched config is a new object with deep-copied nested structures
  • Unspecified fields are preserved
  • Path creation is tracked correctly

These tests are essential for ensuring the injector's safety guarantees.


444-534: LGTM!

The TestEnvironmentOverlay class correctly verifies that:

  • os.environ is never mutated
  • Only ENVIRONMENT injection point overrides are included
  • Values are properly converted to strings (including None → "", bool → "true"/"false")

541-619: LGTM!

The TestPathTraversal class provides comprehensive coverage for nested path handling including deeply nested dicts, list index access, out-of-bounds error handling, and automatic intermediate dict creation.


626-709: LGTM!

The TestPydanticModelConfig class adequately tests Pydantic model support including mutable models, frozen model error handling, path validation, and nested model traversal.


716-831: LGTM on thread-safety tests!

The TestConcurrentAccess class properly verifies the service's thread-safety guarantees:

  • Concurrent apply() calls don't cause race conditions
  • Original config remains unchanged across all concurrent operations
  • Single injector instance can be safely reused across threads
  • Each result has independent patched config

The use of ThreadPoolExecutor with proper synchronization for result collection is appropriate.


838-945: LGTM!

The TestEdgeCases class covers important boundary conditions:

  • Empty override set handling
  • Setting values to None
  • MISSING sentinel singleton behavior
  • Injection point filtering
  • Path depth limit enforcement (DoS prevention)

Good coverage for robustness.

src/omnibase_core/models/replay/__init__.py (2)

46-62: LGTM!

The new model imports are properly organized and follow the existing pattern. All seven new override-related models and ModelReplayInput are correctly imported from their respective modules.


64-74: LGTM!

The __all__ list is properly updated with all new exports in alphabetical order, maintaining consistency with the module's public API surface.

src/omnibase_core/services/replay/service_config_override_injector.py (9)

104-144: LGTM on class design!

The service is correctly designed as stateless (only strict_mode configuration), making it inherently thread-safe. The class attributes PATH_SYNTAX_PATTERN and MAX_PATH_DEPTH are appropriate constants for validation.


200-246: LGTM!

The _get_value_at_path method correctly traverses nested structures (dict, Pydantic model, list/tuple) and returns the MISSING sentinel when paths don't exist. The fallback to getattr for other object types provides reasonable extensibility.


248-344: LGTM!

The _set_value_at_path method correctly handles in-place modification with proper error handling:

  • Creates intermediate dicts when needed
  • Handles Pydantic models with appropriate exception catching
  • Returns informative error messages for various failure cases
  • Exception tuple (AttributeError, TypeError, ValueError) is alphabetically ordered per coding guidelines

346-364: LGTM!

The _deep_copy_config method correctly uses Pydantic's model_copy(deep=True) for BaseModel instances and copy.deepcopy for other types, ensuring proper deep copying semantics for both cases.


366-473: LGTM!

The validate method provides comprehensive validation:

  • Path syntax validation
  • Path existence checking (warning vs. violation based on injection point)
  • Type compatibility checking with numeric conversion allowance
  • Strict mode support for failing on warnings

The logic correctly distinguishes between violations (hard errors) and warnings (soft issues).


475-563: LGTM!

The preview method correctly generates a preview of configuration changes:

  • Skips invalid paths gracefully
  • Tracks paths that don't exist
  • Identifies type mismatches (excluding numeric conversions)
  • Creates detailed field previews with before/after values

565-638: LGTM on the critical apply method!

The implementation correctly maintains the key invariant that the original config is never modified:

  • Deep copies the config before any modification
  • Tracks which paths were newly created
  • Collects errors without throwing exceptions
  • Returns a comprehensive result object

This is the core functionality for safe A/B testing configuration injection.


640-692: LGTM!

The apply_environment_overlay method correctly creates an environment overlay dict:

  • Only includes ENVIRONMENT injection point overrides
  • Uses the last path segment as the env var name (uppercased by convention)
  • Properly converts values to strings (None → "", bool → "true"/"false")
  • Never mutates os.environ

695-695: LGTM!

The __all__ export correctly exposes ServiceConfigOverrideInjector and MISSING as the public API of this module.

Comment thread src/omnibase_core/models/replay/model_config_override_preview.py
Comment thread src/omnibase_core/models/replay/model_config_override_result.py
- Update test imports from model_config_overrides to model_config_override
  and model_config_override_set (file was renamed/split in prior commit)
- Add __all__ exports to all replay model files for consistency
- Remove unused PATH_SYNTAX_PATTERN from ServiceConfigOverrideInjector
- Fix _Missing import in test_service_config_override_injector.py

Fixes CI failures in Test Suite (Splits 16/20 and 19/20).
Addresses PR review nitpicks about missing exports.
@claude

claude Bot commented Jan 4, 2026

Copy link
Copy Markdown

Code Review: Configuration Override Injection for A/B Testing (OMN-1205)

Summary

This is an excellent PR that adds sophisticated configuration override capabilities to the replay infrastructure. The implementation is well-architected, thoroughly tested, and follows ONEX best practices. The code demonstrates strong software engineering principles with a focus on immutability, thread-safety, and clear API design.

Strengths 🌟

1. Exemplary Architecture & Design

  • Three-phase API (validate() → preview() → apply()) provides excellent UX for interactive configuration
  • Non-negotiable immutability: Original configs NEVER modified - deep-copy pattern consistently enforced
  • Thread-safe by design: Stateless service with pure functions, explicitly tested for concurrent access
  • Sentinel pattern: Clean distinction between "path not found" vs "value is None" using MISSING singleton
  • CLOSED vocabulary: EnumOverrideInjectionPoint prevents unbounded injection points - good security practice

2. Code Quality Excellence

  • 688-line service implementation with comprehensive docstrings and examples
  • Type annotations throughout - mypy strict mode compliance
  • Error handling: Graceful degradation with detailed error messages
  • Pydantic integration: Proper use of model_copy(deep=True) for immutable updates
  • Security: MAX_PATH_DEPTH limit (20) prevents DoS via deeply nested paths
  • Following CLAUDE.md: Uses Poetry, PEP 604 union syntax, proper exception handling markers

3. Test Coverage Outstanding

  • 85 unit tests across 3 test files (34 + 26 + 25)
  • Thread-safety tests: Concurrent access with 20 threads + ThreadPoolExecutor
  • Edge cases: Empty sets, None values, out-of-bounds indices, frozen Pydantic models
  • Immutability verification: Explicit tests that original configs remain unchanged
  • Path traversal: Deep nesting, list indices, intermediate dict creation
  • All tests follow pytest conventions with proper fixtures and markers

4. Documentation Quality

  • Rich docstrings with usage examples, thread-safety notes, architecture explanations
  • Inline comments explaining design decisions (e.g., why MISSING sentinel is needed)
  • Preview markdown generation: User-friendly diff tables for A/B testing scenarios
  • PR description: Clear summary of features, invariants, and structural fixes

5. Structural Refactoring (OMN-1116)

  • Correct relocation: RecorderEffect, InjectorTime, InjectorRNG moved from pipeline/ to services/
  • Architectural clarity: These are passive observers, NOT nodes - location now reflects semantics
  • Backwards compatibility: Re-exports maintained via pipeline/replay/__init__.py
  • Validation exemptions: Properly documented architectural reasons in validate_naming.py

Issues & Recommendations 🔍

1. Property Returns Mutable Dict (⚠️ Minor Bug)

Location: model_config_override_set.py:40-48

The by_injection_point property returns a mutable dict, breaking the immutability guarantee of the frozen model. Callers can mutate the returned dict/lists, violating thread-safety.

Fix: Return immutable types using Mapping[..., tuple[...]] or use types.MappingProxyType for read-only view.


2. Frozen Pydantic Model Handling (⚠️ API Design)

Location: service_config_override_injector.py:354-357, test line 654-669

Frozen Pydantic models fail to apply overrides because model_copy(deep=True) creates a frozen copy that cannot be mutated.

Recommendation: Document this limitation more prominently in the apply() method docstring, or consider enhancing to support frozen models via model_validate() with merged dict.


3. Environment Variable Naming Logic (❓ Clarification Needed)

Location: service_config_override_injector.py:671-673

Code uses only the last segment of the path for environment variable names. Path "api.openai.key" becomes env var "KEY" (loses context).

Suggestion: Either use full path with dot→underscore ("API_OPENAI_KEY") or document the rationale if last-segment-only is intentional.


4. Missing Test: Injection Point Filtering in Preview (Minor)

Coverage gap: preview() method supports injection_point parameter but no test validates filtering (tested for validate() and apply() but not preview()).


5. Code Style: Exception Tuple Ordering (Nitpick)

CLAUDE.md requires alphabetical ordering of exception tuples. Current code appears correct, but verify all exception handlers follow this pattern.


Performance Considerations ⚡

  1. Deep copy overhead: copy.deepcopy() can be expensive for large configs - consider documenting performance characteristics
  2. Regex compilation: re.match() at line 184 called per-segment - consider pre-compiling as class constant

Security Assessment ✅

  • DoS protection: MAX_PATH_DEPTH=20 prevents deeply nested path attacks
  • No os.environ mutation: Environment overlay pattern is safe
  • No injection vulnerabilities: Path validation prevents malicious paths
  • Thread-safety: Concurrent access tests pass - no race conditions

Compliance with ONEX Standards ✅

  • ✅ Poetry usage: All commands use poetry run
  • ✅ Type checking: Mypy strict mode passes (0 errors)
  • ✅ PEP 604 unions: Uses str | None syntax
  • ✅ Pydantic best practices: frozen=True, from_attributes=True, ConfigDict
  • ✅ File naming: All files follow service_*, model_*, enum_* conventions
  • ✅ Test coverage: 85 tests exceeds 60% requirement
  • ✅ Documentation: Comprehensive docstrings with examples

Final Verdict ✅

APPROVE with minor suggestions

This PR is production-ready and demonstrates high-quality software engineering. The issues identified are minor and can be addressed in follow-up commits or noted for future work. The test coverage is exceptional and the design is well-thought-out.

Recommended actions:

  1. Critical: Fix by_injection_point mutability issue
  2. Important: Clarify environment variable naming rationale
  3. Nice-to-have: Add preview filtering test, document frozen model limitation

Great work on OMN-1205! 🚀

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/unit/services/replay/test_service_config_override_injector.py (2)

98-124: Consider adding from_attributes=True to test Pydantic models for pytest-xdist compatibility.

The test Pydantic models (SamplePydanticConfig, FrozenPydanticConfig, NestedPydanticConfig) don't include from_attributes=True in their ConfigDict. While these are test fixtures rather than production models, adding this setting would prevent potential class identity issues during parallel test execution with pytest-xdist.

🔎 Suggested update
 class SamplePydanticConfig(BaseModel):
     """Sample Pydantic model for testing override application."""
 
-    model_config = ConfigDict(extra="forbid")
+    model_config = ConfigDict(extra="forbid", from_attributes=True)
 
     timeout: int = 30
     debug: bool = False
     name: str = "default"


 class FrozenPydanticConfig(BaseModel):
     """Frozen Pydantic model for testing immutability."""
 
-    model_config = ConfigDict(frozen=True, extra="forbid")
+    model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)
 
     timeout: int = 30
     debug: bool = False


 class NestedPydanticConfig(BaseModel):
     """Nested Pydantic model for testing deep paths."""
 
-    model_config = ConfigDict(extra="forbid")
+    model_config = ConfigDict(extra="forbid", from_attributes=True)
 
     inner: SamplePydanticConfig = SamplePydanticConfig()
     level: int = 1

Based on learnings, this prevents class identity mismatches across pytest-xdist worker processes.


448-477: Tighten environment variable name assertions.

Lines 476-477 check for "TEST_VAR" in overlay or "test_var" in overlay, which is imprecise. Based on the service implementation (lines 670-673), the environment overlay uses parts[-1].upper(), so "test_var" should always become "TEST_VAR", not "test_var".

🔎 More precise assertions
         # Overlay should contain the environment variables
-        assert "TEST_VAR" in overlay or "test_var" in overlay
+        assert "TEST_VAR" in overlay
         assert "KEY" in overlay  # Last segment, uppercased
src/omnibase_core/services/replay/service_config_override_injector.py (1)

315-322: Document exception tuple intent with standardized marker.

The exception handler at line 318 catches multiple exception types. Per coding guidelines, broad exception handlers should be prefixed with standardized markers like # fallback-ok:, # boundary-ok:, etc., to document the intent.

🔎 Add exception tuple marker
             try:
                 setattr(current, final_part, value)
                 return (True, "")
+            # boundary-ok: Pydantic field assignment validation
             except (AttributeError, TypeError, ValueError) as e:
                 return (
                     False,
                     f"Cannot set field '{final_part}' on Pydantic model: {e}",
                 )

As per coding guidelines, this documents the boundary validation intent of the exception handler.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9334793 and 5d3cd55.

📒 Files selected for processing (9)
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_result.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • tests/unit/models/replay/test_model_config_overrides.py
  • tests/unit/models/replay/test_model_replay_input.py
  • tests/unit/services/replay/test_service_config_override_injector.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/models/replay/test_model_config_overrides.py
  • src/omnibase_core/models/replay/model_config_override_result.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for Python package management and task execution (poetry run pytest, poetry run mypy, poetry add, poetry install). Never use pip directly or python -m for execution.
Implement ONEX Four-Node Architecture following the unidirectional workflow: EFFECT → COMPUTE → REDUCER → ORCHESTRATOR. Each node must inherit from appropriate base class (NodeEffect, NodeCompute, NodeReducer, NodeOrchestrator).
Always call super().init(container) in node constructors, where container is of type ModelONEXContainer. This eliminates boilerplate initialization code.
Use ModelONEXContainer for dependency injection in node constructors, never use ModelContainer[T]. Resolve services using protocol names only (e.g., container.get_service('ProtocolEventBus')), never by concrete class names.
Use ModelOnexError with EnumCoreErrorCode for all exception handling. Never raise generic Exception. Use the @standard_error_handling decorator to eliminate try/catch boilerplate.
Use PEP 604 union syntax (str | None) instead of Optional[str] and Union types. This is enforced by ruff UP007.
Prefix exception tuples with standardized markers (# fallback-ok:, # catch-all-ok:, # cleanup-resilience-ok:, # boundary-ok:, # init-errors-ok:, # tool-resilience-ok:) to document intent of broad exception handlers.
Order exception tuples alphabetically by exception class name (AttributeError, TypeError, ValidationError, ValueError).
Use centralized exception groups from omnibase_core.errors.exception_groups (PYDANTIC_MODEL_ERRORS, VALIDATION_ERRORS, FILE_IO_ERRORS, etc.) instead of manual exception tuple construction.
Never share node instances across threads. Use thread-local instances or separate instances per thread. Node instances (NodeCompute, NodeEffect) are not thread-safe by default.
Use duck typing with protocols - never use isinstance() checks. Rely on protocol interfaces for type checking and service resolution.
Load runtime contracts using FileRegistry from omnibase_core.runti...

Files:

  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • tests/unit/models/replay/test_model_replay_input.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: All Python files in src/omnibase_core/ must follow directory-specific naming prefixes enforced by checker_naming_convention.py. Examples: cli_, container_, decorator_, enum_, error_, exception_, factory_, model_, node_, mixin_, protocol_, service_, util_, validator_, checker_, resolver_, etc.
Maintain 100% mypy strict mode compliance and ensure pyright passes in CI. Type annotations must be complete and correct.

Files:

  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/models/**/*.py: Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.
Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.

Files:

  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test with: poetry run pytest tests/ for all tests, poetry run pytest tests/unit/ for unit tests only, poetry run pytest tests/ -n 0 -xvs for debug mode. Use markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.smoke, @pytest.mark.performance.

Files:

  • tests/unit/models/replay/test_model_replay_input.py
  • tests/unit/services/replay/test_service_config_override_injector.py
🧠 Learnings (2)
📚 Learning: 2026-01-04T19:09:33.280Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.280Z
Learning: Applies to src/omnibase_core/models/**/*.py : Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.

Applied to files:

  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override.py
📚 Learning: 2026-01-04T19:09:33.280Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.280Z
Learning: Applies to src/omnibase_core/models/**/*.py : Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.

Applied to files:

  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override.py
🧬 Code graph analysis (7)
src/omnibase_core/models/replay/model_config_override_set.py (2)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (22-56)
src/omnibase_core/services/replay/service_config_override_injector.py (6)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override_field_preview.py (1)
  • ModelConfigOverrideFieldPreview (22-100)
src/omnibase_core/models/replay/model_config_override_preview.py (1)
  • ModelConfigOverridePreview (20-100)
src/omnibase_core/models/replay/model_config_override_result.py (1)
  • ModelConfigOverrideResult (18-45)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (23-52)
src/omnibase_core/models/replay/model_config_override_validation.py (1)
  • ModelConfigOverrideValidation (75-134)
tests/unit/models/replay/test_model_replay_input.py (3)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (22-56)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (23-52)
src/omnibase_core/models/replay/model_replay_input.py (3)
  • ModelReplayInput (24-80)
  • has_overrides (65-70)
  • with_overrides (72-80)
src/omnibase_core/models/replay/model_config_override_preview.py (2)
src/omnibase_core/models/replay/model_config_override_field_preview.py (2)
  • ModelConfigOverrideFieldPreview (22-100)
  • to_markdown_row (70-82)
src/omnibase_core/services/replay/service_config_override_injector.py (1)
  • preview (468-556)
src/omnibase_core/models/replay/model_config_override_field_preview.py (3)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/core/model_extension_data.py (1)
  • value_type (89-105)
src/omnibase_core/protocols/event_bus/protocol_event_message.py (1)
  • value (33-35)
tests/unit/services/replay/test_service_config_override_injector.py (5)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (22-56)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (23-52)
src/omnibase_core/services/replay/service_config_override_injector.py (5)
  • ServiceConfigOverrideInjector (104-685)
  • validate (359-466)
  • preview (468-556)
  • apply (558-631)
  • apply_environment_overlay (633-685)
src/omnibase_core/services/replay/sentinel_missing.py (1)
  • _Missing (15-41)
src/omnibase_core/models/replay/model_config_override.py (1)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Tests (Split 1/20)
  • GitHub Check: Tests (Split 9/20)
  • GitHub Check: Tests (Split 8/20)
  • GitHub Check: Tests (Split 12/20)
  • GitHub Check: Tests (Split 16/20)
  • GitHub Check: Tests (Split 15/20)
  • GitHub Check: Tests (Split 14/20)
  • GitHub Check: Tests (Split 19/20)
  • GitHub Check: Tests (Split 10/20)
  • GitHub Check: Tests (Split 7/20)
  • GitHub Check: Tests (Split 11/20)
  • GitHub Check: Tests (Split 13/20)
  • GitHub Check: Tests (Split 17/20)
  • GitHub Check: Tests (Split 18/20)
  • GitHub Check: Tests (Split 5/20)
  • GitHub Check: Tests (Split 20/20)
  • GitHub Check: Tests (Split 2/20)
  • GitHub Check: Tests (Split 6/20)
  • GitHub Check: Tests (Split 3/20)
  • GitHub Check: Tests (Split 4/20)
  • GitHub Check: claude-review
🔇 Additional comments (21)
tests/unit/models/replay/test_model_replay_input.py (9)

19-29: Excellent use of TYPE_CHECKING for forward references.

The conditional import pattern correctly avoids circular dependencies while maintaining type hints for IDEs and type checkers.


32-77: Well-structured fixtures provide good test isolation.

The fixture hierarchy (sample_data → simple_override → override_set → replay_input variants) creates reusable test data with clear dependencies. The lazy imports within fixtures ensure models are loaded only when needed.


79-138: Comprehensive creation tests validate all construction paths.

Tests cover:

  • Data-only creation (default overrides)
  • Auto-generated UUIDs with uniqueness
  • Explicit UUID assignment
  • Override inclusion

This ensures the model's factory defaults and explicit parameters work correctly.


140-168: Immutability tests properly validate frozen model behavior.

The ValidationError assertions confirm that Pydantic's frozen=True prevents field reassignment on all fields (data, input_id, config_overrides), which is critical for the thread-safety guarantee documented in the PR.


170-196: Validation tests enforce required fields and extra field rejection.

The tests correctly verify:

  • data is required (line 179)
  • Extra fields trigger ValidationError with clear messages (lines 183-195)

This aligns with the extra="forbid" ConfigDict setting in the model.


198-225: has_overrides property logic thoroughly tested.

All three branches are covered:

  • None overrides → False
  • Empty override set → False
  • Non-empty overrides → True

This validates the short-circuit logic in the property implementation.


227-282: with_overrides immutable update pattern properly validated.

Tests confirm:

  • New instance returned (not mutation)
  • input_id preserved across updates
  • Original data preserved
  • Existing overrides replaced (not merged)

This validates the immutable update contract documented in ModelReplayInput.


284-326: Generic type preservation validated across diverse data types.

Tests exercise the Generic[T_Data] with dict, list, string, and nested structures, confirming that Pydantic preserves the original data types without coercion.


328-392: Serialization roundtrips validate persistence contract.

Tests confirm:

  • model_dump/model_validate roundtrip preserves all fields
  • JSON serialization via model_dump_json/model_validate_json works correctly
  • UUID serializes as string (line 386-392), which is the expected JSON representation

This ensures ModelReplayInput can be safely persisted and restored.

src/omnibase_core/models/replay/model_config_override_field_preview.py (3)

19-19: Proper module export added.

The __all__ export ensures explicit public API surface, addressing the past review comment. This aligns with package conventions.


22-68: Well-designed immutable preview model.

The model correctly:

  • Uses ConfigDict(frozen=True, extra="forbid", from_attributes=True) per guidelines
  • Provides clear field descriptions
  • Documents thread-safety guarantees
  • Includes helpful docstring example

The field defaults (value_type="unknown", path_exists=True) are sensible for typical use cases.

Based on learnings and coding guidelines.


70-100: Clean markdown rendering with appropriate value formatting.

The to_markdown_row() and _format_value() methods produce readable table rows. The value formatter correctly:

  • Renders None as `null`
  • Wraps strings in quotes: `"value"`
  • Backtick-wraps other types

Note: The docstring mentions "MISSING sentinel" for old_value (line 32), but _format_value handles it as None. This is correct—the service layer (service_config_override_injector.py lines 520-524) converts MISSING to None for display.

src/omnibase_core/models/replay/model_config_override_set.py (3)

20-20: Explicit module export present.

The __all__ declaration correctly exposes the public API.


23-38: Immutable collection model properly configured.

The model follows all guidelines:

  • ConfigDict(frozen=True, extra="forbid", from_attributes=True) for pytest-xdist compatibility
  • Uses tuple for the overrides collection, maintaining immutability
  • Default factory prevents shared mutable defaults

Based on learnings and coding guidelines.


40-52: Efficient grouping and immutable update patterns.

The by_injection_point property computes a fresh dict on each access, which is acceptable since:

  • The model is frozen (no internal caching possible)
  • The returned dict is not shared state
  • Typical usage involves a single call for batch processing

The with_override method correctly implements the immutable update pattern using tuple unpacking (*self.overrides, override).

src/omnibase_core/models/replay/model_config_override.py (2)

19-19: Module export properly declared.


22-56: Well-designed single override specification.

The model implementation is excellent:

  • ConfigDict(frozen=True, extra="forbid", from_attributes=True) per guidelines
  • path field enforces min_length=1 to prevent empty paths
  • value uses Any type, which is correct for configuration overrides that can be any JSON-serializable value
  • injection_point defaults to HANDLER_CONFIG, the most common use case
  • Clear documentation with example usage

Based on learnings and coding guidelines.

src/omnibase_core/models/replay/model_config_override_preview.py (3)

17-17: Module export added per previous review.

The __all__ declaration addresses the past review comment, ensuring consistent public API exposure.


20-68: Comprehensive aggregate preview model.

The model design is excellent:

  • ConfigDict(frozen=True, extra="forbid", from_attributes=True) per guidelines
  • All collections use tuple to maintain immutability
  • Clear separation of concerns: field previews, missing paths, type mismatches
  • Rich docstring with example showing expected usage

Based on learnings and coding guidelines.


70-100: Clean markdown diff generation with conditional sections.

The to_markdown() method produces a well-structured diff report:

  • Standard markdown table with headers
  • Conditional rendering of "New Paths" and "Type Mismatches" sections (only when populated)
  • Each section is clearly labeled and formatted

This aligns with the preview/validation/apply workflow documented in the PR objectives.

src/omnibase_core/services/replay/service_config_override_injector.py (1)

104-685: Excellent implementation with strong immutability and thread-safety guarantees.

The service implementation demonstrates several strengths:

  • Thread safety: Correctly implemented through stateless design and deep-copy semantics. The apply() method's deep copy on line 589 ensures the original config is never mutated.
  • Path traversal: Comprehensive handling of dicts, Pydantic models, and lists with proper bounds checking and the MISSING sentinel pattern.
  • Error handling: Good coverage of edge cases including frozen Pydantic models, out-of-bounds indices, and invalid path syntax.
  • DoS prevention: MAX_PATH_DEPTH limit (line 127) protects against deeply nested path attacks.
  • Environment overlay safety: Correctly avoids mutating os.environ and returns an isolated dict.

The implementation aligns well with the PR objectives for A/B testing support while maintaining the stated invariants.

Merged OMN-1202 (execution corpus) with OMN-1205 (config overrides).
Combined all replay model imports and exports from both branches.
MAJOR fixes:
- Add robust error handling with None input validation
- Return immutable MappingProxyType from apply_environment_overlay
- Add frozen Pydantic model detection and dict conversion

MINOR fixes:
- Add from_attributes=True rationale comments to all models
- Add complete docstrings to ModelConfigOverrideSet methods
- Document environment variable naming derivation logic
- Add test_preview_filters_by_injection_point test

NITPICK fixes:
- Add from_attributes=True to test Pydantic models
- Tighten environment variable assertions in tests
- Add exception handling with alphabetical ordering and markers
- Add test_env_overlay_is_immutable test
- Add test_is_frozen_pydantic_model_detection test

OMN-1205
@claude

claude Bot commented Jan 5, 2026

Copy link
Copy Markdown

PR Review: Configuration Override Injection for A/B Testing (OMN-1205)

Overall Assessment

Verdict: ✅ APPROVE with minor suggestions

This is a well-architected, thoroughly tested feature that adds configuration override injection capabilities to the replay infrastructure. The implementation demonstrates excellent code quality, comprehensive testing, and careful attention to thread safety and immutability guarantees.


Code Quality ✅

Strengths

  1. Excellent Documentation: Every class, method, and field has clear docstrings with examples. The architectural documentation is particularly strong.

  2. Immutability by Design: Proper use of frozen=True on all models, deep copying in apply(), and MappingProxyType for environment overlays ensures thread safety.

  3. Error Handling: Follows CLAUDE.md conventions with standardized exception markers (# boundary-ok:, # catch-all-ok:) and alphabetically ordered exception tuples.

  4. Type Safety: Strong typing throughout with proper use of generics (ModelReplayInput[T]), Any only where necessary, and PEP 604 union syntax.

  5. Consistent Patterns: Follows project conventions (violations/warnings/suggestions pattern, from_attributes=True for value objects, proper file naming).


Architecture & Design ✅

Well-Designed Features

  1. Three-Phase API (validate() → preview() → apply()): Excellent separation of concerns allowing users to verify changes before applying.

  2. Sentinel Pattern: Using MISSING sentinel to distinguish "path not found" vs "value is None" is the correct approach.

  3. Frozen Model Detection: The _is_frozen_pydantic_model() check with automatic dict conversion (lines 139-181, 447-460) is clever and handles an important edge case.

  4. Environment Overlay Design: Returning immutable MappingProxyType prevents accidental mutation of os.environ - this is a security best practice.

  5. Injection Point Enum: Closed vocabulary with HANDLER_CONFIG, ENVIRONMENT, CONTEXT provides clear boundaries.


Security Considerations ✅

Good Security Practices

  1. Path Depth Limits: MAX_PATH_DEPTH = 20 prevents DoS attacks via deeply nested paths (line 128).

  2. Path Syntax Validation: Regex validation prevents injection attacks through malformed paths (lines 183-237).

  3. No os.environ Mutation: Environment overlay pattern avoids global state pollution.

  4. Deep Copy Enforcement: Original configs are NEVER modified - all operations work on copies.

No Critical Security Issues Found


Testing ✅

Test Coverage

  • 85 unit tests for the override injector service
  • 468 lines of model override tests
  • 392 lines of replay input tests
  • 1,083 lines total for injector tests

Test Quality

  1. Comprehensive Categories: Validation, preview, apply, environment overlay, path traversal, Pydantic models, concurrency.

  2. Thread Safety Testing: Concurrent access tests verify thread-safety claims.

  3. Fixtures: Well-organized fixtures for different scenarios (dict configs, Pydantic configs, frozen models).

  4. Edge Cases: Tests for None inputs, empty paths, frozen models, type mismatches.

Test Coverage Appears Excellent

The test file structure shows thorough coverage of all major code paths.


Potential Issues & Suggestions

MINOR: Performance Consideration

Location: service_config_override_injector.py:423-460

The _deep_copy_config() method always uses copy.deepcopy() for frozen Pydantic models via model_dump(). For large configs, this double conversion (model→dict→deepcopy) could be expensive.

Suggestion: Consider adding a comment about potential performance implications for large frozen models, or benchmark if this becomes a bottleneck.

# Note: Frozen models require dict conversion for mutability.
# For large configs (>1000 fields), this may impact performance.
return copy.deepcopy(config.model_dump())

MINOR: Type Mismatch Warning Logic

Location: service_config_override_injector.py:561-570

The numeric type conversion exemption (int ↔ float) is good, but the warning for other type mismatches might be too permissive. Consider whether str → int conversions should be violations rather than warnings.

Current Behavior:

# Type mismatch str → int produces WARNING (not VIOLATION)
override = ModelConfigOverride(path="timeout", value="30")  # str
config = {"timeout": 30}  # int → WARNING

Suggestion: Document the rationale for warnings vs violations for type mismatches, or add a strict_type_checking flag.


NITPICK: Exception Tuple Ordering

Location: service_config_override_injector.py:294, 371, 395, 416

Exception tuples follow alphabetical ordering per CLAUDE.md, but the ordering could be more consistent:

# Line 294 - Good
except (AttributeError, IndexError, KeyError, TypeError) as e:

# Line 395 - Good  
except (AttributeError, TypeError, ValueError) as e:

All exception tuples appear correctly ordered. ✅


NITPICK: Environment Variable Naming Documentation

Location: service_config_override_injector.py:798-823

The environment variable naming derivation is excellently documented with examples and rationale. This is a model for good documentation.

Minor Enhancement: Consider adding a note about potential collisions:

# Potential collision example:
# "config.db.timeout" → TIMEOUT
# "api.timeout" → TIMEOUT (COLLISION!)

This is already implicitly handled (last write wins), but documenting it would prevent confusion.


Best Practices Adherence ✅

CLAUDE.md Compliance

Requirement Status Evidence
Poetry usage ✅ Pre-commit hooks passed
Type annotations (PEP 604) ✅ str | None syntax throughout
Error handling markers ✅ # boundary-ok:, # catch-all-ok: present
Exception ordering ✅ Alphabetical tuples
from_attributes=True ✅ All models + rationale comments
File naming ✅ service_*, model_*, enum_* prefixes
Immutability ✅ frozen=True on all models
Thread safety docs ✅ Documented on all classes/methods

Structural Improvements (OMN-1116) ✅

Good Refactoring

The relocation of RecorderEffect, InjectorTime, InjectorRNG from pipeline/replay/ to services/replay/ is architecturally correct:

  • Rationale: These are passive observers/injectors, NOT nodes
  • Backwards Compatibility: Re-exports maintain compatibility
  • Validation Update: Proper exemptions added to naming validation

This demonstrates good architectural discipline.


Performance Considerations

Strengths

  1. Lazy Evaluation: by_injection_point property only groups when called
  2. Path Depth Limits: Prevents pathological cases
  3. Stateless Service: No shared mutable state to lock

Potential Concerns

  1. Deep Copy Overhead: Every apply() call does a deep copy - acceptable for replay use case
  2. Path Traversal: O(n) for n-segment paths, but MAX_PATH_DEPTH=20 bounds this

Verdict: Performance is appropriate for the replay/testing use case.


Documentation Quality ✅

Outstanding Documentation

  1. Module Docstrings: Comprehensive with architecture diagrams and examples
  2. Class Docstrings: Clear purpose, thread safety notes, examples
  3. Method Docstrings: Args, returns, examples, thread safety
  4. Inline Comments: Rationale for non-obvious decisions

Example of Excellent Documentation (apply_environment_overlay):

  • 68 lines of documentation
  • Environment variable naming derivation explained
  • Examples with different scenarios
  • Thread safety guarantees
  • Immutability guarantees

Recommendations

For Immediate Action

None - The PR is ready to merge as-is.

For Future Consideration

  1. Performance Profiling: Benchmark _deep_copy_config() on large frozen models if this becomes a bottleneck in production.

  2. Type Checking Strictness: Consider adding strict_type_checking: bool flag to make type mismatch behavior configurable.

  3. Environment Variable Collision Detection: Add optional collision detection for environment overlays (e.g., warning when multiple paths map to same env var name).

  4. Integration Tests: While unit tests are comprehensive, integration tests with actual replay execution would validate the end-to-end workflow.


Final Notes

This PR demonstrates exemplary engineering practices:

  • ✅ Comprehensive testing (85+ tests)
  • ✅ Excellent documentation
  • ✅ Thread-safe, immutable design
  • ✅ Security-conscious (path validation, no global mutation)
  • ✅ Follows all CLAUDE.md conventions
  • ✅ Backwards compatible (re-exports)

The latest commit (3ca288d) addressed all prior review feedback, including:

  • Robust error handling with None input validation
  • Immutable MappingProxyType return from apply_environment_overlay
  • Frozen Pydantic model detection and dict conversion
  • Complete docstrings and rationale comments

Great work! This is production-ready code. 🚀


Approval

✅ APPROVED - Ready to merge.

Reviewed by: Claude Code (Sonnet 4.5)
Review Date: 2026-01-05
Commits Reviewed: 40512aa through 3ca288d (7 commits)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/omnibase_core/services/replay/service_config_override_injector.py (1)

867-884: Consider warning users about potential environment variable name collisions.

The current logic uses parts[-1].upper() for environment variable naming, which means:

  • "config.timeout" → "TIMEOUT"
  • "other.timeout" → "TIMEOUT" (collision!)

While this is documented in the method docstring, silent overwrites could cause subtle bugs. Consider adding a warning log when collisions occur:

🔎 Proposed collision detection
         for override in overrides.overrides:
             if override.injection_point != EnumOverrideInjectionPoint.ENVIRONMENT:
                 continue

             # Environment variable names are the path (or last segment)
             # Path can be the full env var name or nested for grouping
             parts = override.path.split(".")
             env_name = parts[-1].upper()  # Use last segment, uppercase by convention

+            if env_name in env_overlay:
+                logger.warning(
+                    "Environment variable collision: '%s' from path '%s' "
+                    "overwrites previous value from another path",
+                    env_name,
+                    override.path,
+                )
+
             # Convert value to string
             if override.value is None:
                 value_str = ""
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3cd55 and 3ca288d.

📒 Files selected for processing (9)
  • scripts/validation/validate_naming.py
  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_result.py
  • src/omnibase_core/models/replay/model_config_override_set.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • tests/unit/services/replay/test_service_config_override_injector.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/validation/validate_naming.py
  • src/omnibase_core/models/replay/model_config_override_result.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for Python package management and task execution (poetry run pytest, poetry run mypy, poetry add, poetry install). Never use pip directly or python -m for execution.
Implement ONEX Four-Node Architecture following the unidirectional workflow: EFFECT → COMPUTE → REDUCER → ORCHESTRATOR. Each node must inherit from appropriate base class (NodeEffect, NodeCompute, NodeReducer, NodeOrchestrator).
Always call super().init(container) in node constructors, where container is of type ModelONEXContainer. This eliminates boilerplate initialization code.
Use ModelONEXContainer for dependency injection in node constructors, never use ModelContainer[T]. Resolve services using protocol names only (e.g., container.get_service('ProtocolEventBus')), never by concrete class names.
Use ModelOnexError with EnumCoreErrorCode for all exception handling. Never raise generic Exception. Use the @standard_error_handling decorator to eliminate try/catch boilerplate.
Use PEP 604 union syntax (str | None) instead of Optional[str] and Union types. This is enforced by ruff UP007.
Prefix exception tuples with standardized markers (# fallback-ok:, # catch-all-ok:, # cleanup-resilience-ok:, # boundary-ok:, # init-errors-ok:, # tool-resilience-ok:) to document intent of broad exception handlers.
Order exception tuples alphabetically by exception class name (AttributeError, TypeError, ValidationError, ValueError).
Use centralized exception groups from omnibase_core.errors.exception_groups (PYDANTIC_MODEL_ERRORS, VALIDATION_ERRORS, FILE_IO_ERRORS, etc.) instead of manual exception tuple construction.
Never share node instances across threads. Use thread-local instances or separate instances per thread. Node instances (NodeCompute, NodeEffect) are not thread-safe by default.
Use duck typing with protocols - never use isinstance() checks. Rely on protocol interfaces for type checking and service resolution.
Load runtime contracts using FileRegistry from omnibase_core.runti...

Files:

  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • tests/unit/services/replay/test_service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_set.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: All Python files in src/omnibase_core/ must follow directory-specific naming prefixes enforced by checker_naming_convention.py. Examples: cli_, container_, decorator_, enum_, error_, exception_, factory_, model_, node_, mixin_, protocol_, service_, util_, validator_, checker_, resolver_, etc.
Maintain 100% mypy strict mode compliance and ensure pyright passes in CI. Type annotations must be complete and correct.

Files:

  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/services/replay/service_config_override_injector.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_set.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/models/**/*.py: Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.
Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.

Files:

  • src/omnibase_core/models/replay/__init__.py
  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_set.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test with: poetry run pytest tests/ for all tests, poetry run pytest tests/unit/ for unit tests only, poetry run pytest tests/ -n 0 -xvs for debug mode. Use markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.smoke, @pytest.mark.performance.

Files:

  • tests/unit/services/replay/test_service_config_override_injector.py
🧠 Learnings (2)
📚 Learning: 2026-01-04T19:09:33.280Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.280Z
Learning: Applies to src/omnibase_core/models/**/*.py : Canonical Pydantic base class for omnibase_core is BaseModel from pydantic. Use ConfigDict for configuration. Ensure model_config is properly set.

Applied to files:

  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_set.py
📚 Learning: 2026-01-04T19:09:33.280Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T19:09:33.280Z
Learning: Applies to src/omnibase_core/models/**/*.py : Add ConfigDict(from_attributes=True) to Pydantic models used as value objects for pytest-xdist parallel execution to prevent class identity issues across worker processes.

Applied to files:

  • src/omnibase_core/models/replay/model_config_override.py
  • src/omnibase_core/models/replay/model_config_override_field_preview.py
  • src/omnibase_core/models/replay/model_config_override_preview.py
  • src/omnibase_core/models/replay/model_config_override_set.py
🧬 Code graph analysis (7)
src/omnibase_core/models/replay/__init__.py (7)
src/omnibase_core/models/manifest/model_execution_manifest.py (1)
  • ModelExecutionManifest (37-327)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (22-60)
src/omnibase_core/models/replay/model_config_override_field_preview.py (1)
  • ModelConfigOverrideFieldPreview (22-104)
src/omnibase_core/models/replay/model_config_override_preview.py (1)
  • ModelConfigOverridePreview (20-104)
src/omnibase_core/models/replay/model_config_override_result.py (1)
  • ModelConfigOverrideResult (18-49)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (23-98)
src/omnibase_core/models/replay/model_replay_input.py (1)
  • ModelReplayInput (24-80)
src/omnibase_core/models/replay/model_config_override.py (1)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/services/replay/service_config_override_injector.py (6)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override_field_preview.py (1)
  • ModelConfigOverrideFieldPreview (22-104)
src/omnibase_core/models/replay/model_config_override_preview.py (1)
  • ModelConfigOverridePreview (20-104)
src/omnibase_core/models/replay/model_config_override_result.py (1)
  • ModelConfigOverrideResult (18-49)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (23-98)
src/omnibase_core/models/replay/model_config_override_validation.py (1)
  • ModelConfigOverrideValidation (75-134)
src/omnibase_core/models/replay/model_config_override_field_preview.py (2)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/core/model_extension_data.py (1)
  • value_type (89-105)
tests/unit/services/replay/test_service_config_override_injector.py (4)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (22-60)
src/omnibase_core/models/replay/model_config_override_set.py (1)
  • ModelConfigOverrideSet (23-98)
src/omnibase_core/services/replay/service_config_override_injector.py (4)
  • preview (588-686)
  • apply (688-783)
  • apply_environment_overlay (785-886)
  • _is_frozen_pydantic_model (140-182)
src/omnibase_core/models/replay/model_config_override_preview.py (1)
  • to_markdown (74-104)
src/omnibase_core/models/replay/model_config_override_preview.py (2)
src/omnibase_core/models/replay/model_config_override_field_preview.py (2)
  • ModelConfigOverrideFieldPreview (22-104)
  • to_markdown_row (74-86)
src/omnibase_core/services/replay/service_config_override_injector.py (1)
  • preview (588-686)
src/omnibase_core/models/replay/model_config_override_set.py (2)
src/omnibase_core/enums/replay/enum_override_injection_point.py (1)
  • EnumOverrideInjectionPoint (16-52)
src/omnibase_core/models/replay/model_config_override.py (1)
  • ModelConfigOverride (22-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Tests (Split 6/20)
  • GitHub Check: Tests (Split 2/20)
  • GitHub Check: Tests (Split 4/20)
  • GitHub Check: Tests (Split 8/20)
  • GitHub Check: Tests (Split 20/20)
  • GitHub Check: Tests (Split 16/20)
  • GitHub Check: Tests (Split 15/20)
  • GitHub Check: Tests (Split 12/20)
  • GitHub Check: Tests (Split 11/20)
  • GitHub Check: Tests (Split 14/20)
  • GitHub Check: Tests (Split 19/20)
  • GitHub Check: Tests (Split 18/20)
  • GitHub Check: Tests (Split 5/20)
  • GitHub Check: Tests (Split 13/20)
  • GitHub Check: Tests (Split 3/20)
  • GitHub Check: Tests (Split 10/20)
  • GitHub Check: Tests (Split 9/20)
  • GitHub Check: Tests (Split 17/20)
  • GitHub Check: Tests (Split 7/20)
  • GitHub Check: Tests (Split 1/20)
  • GitHub Check: claude-review
🔇 Additional comments (20)
src/omnibase_core/models/replay/model_config_override.py (1)

22-60: Excellent immutable model design with clear documentation.

The ModelConfigOverride model follows all best practices:

  • Frozen Pydantic model ensures thread-safety
  • from_attributes=True documented for pytest-xdist compatibility
  • Clear field descriptions and validation (min_length=1 on path)
  • Comprehensive docstring with usage example

The design correctly enforces immutability and provides a solid foundation for the override injection system.

Based on learnings and coding guidelines.

src/omnibase_core/models/replay/model_config_override_set.py (2)

44-74: Well-designed grouping property for efficient batch processing.

The by_injection_point property efficiently groups overrides by injection point, enabling batch application. The implementation correctly uses setdefault() for clean aggregation.

Note: The property returns a mutable dict, which is acceptable here since it's a fresh computation each time and not exposing internal state.


76-98: Perfect immutable update pattern implementation.

The with_override method correctly implements the immutable update pattern:

  • Creates a new instance rather than modifying the existing one
  • Uses tuple unpacking to append the new override
  • Consistent with frozen=True configuration

This ensures thread-safety and prevents accidental mutations.

tests/unit/services/replay/test_service_config_override_injector.py (4)

98-139: Excellent fixture design with documented pytest-xdist considerations.

The Pydantic model fixtures (SamplePydanticConfig, FrozenPydanticConfig, NestedPydanticConfig) correctly document the rationale for from_attributes=True in the context of parallel test execution. This demonstrates awareness of pytest-xdist's worker isolation challenges.

The fixture organization is clean and provides good test data coverage.

Based on coding guidelines for pytest-xdist compatibility.


602-644: Robust immutability verification for environment overlays.

The test correctly verifies that apply_environment_overlay returns an immutable MappingProxyType:

  • Confirms the return type
  • Tests that mutation raises TypeError
  • Verifies that dict() can create a mutable copy for subprocess use

This is excellent defensive testing that ensures callers cannot accidentally mutate the overlay.


860-908: Comprehensive thread-safety verification with realistic concurrency.

The concurrent apply test uses 20 parallel operations with a ThreadPoolExecutor to verify:

  • No race conditions occur
  • Original config remains unchanged
  • Each operation produces independent results

This provides strong evidence of thread-safety. The test design is realistic and thorough.


830-849: Testing implementation detail is justified here.

The test directly accesses _is_frozen_pydantic_model, a private method. While normally testing implementation details should be avoided, this behavior is critical for correctness (frozen models must be detected and converted to dicts).

This is an acceptable exception given the importance of the frozen model detection logic.

src/omnibase_core/models/replay/model_config_override_preview.py (1)

74-104: Clean markdown generation with smart conditional rendering.

The to_markdown() method produces well-structured output:

  • Clear table format with headers
  • Delegates row formatting to ModelConfigOverrideFieldPreview.to_markdown_row()
  • Conditionally renders "New Paths" and "Type Mismatches" sections only when needed

This provides users with a clear, actionable preview before applying overrides.

src/omnibase_core/models/replay/__init__.py (1)

68-111: Well-organized package exports with clear categorization.

The module exports are properly structured:

  • New configuration override models grouped together (lines 93-99)
  • Clear separation between model categories (configuration, corpus, replay)
  • All imports match their corresponding modules
  • __all__ list is complete and organized

The documentation additions (lines 9-14, 58-60) clearly indicate the new functionality added in OMN-1205.

src/omnibase_core/models/replay/model_config_override_field_preview.py (1)

1-104: Well-structured Pydantic model following project conventions.

The model correctly implements:

  • ConfigDict(frozen=True, extra="forbid", from_attributes=True) per coding guidelines for value objects
  • Clear docstrings with usage examples
  • __all__ export (addressing previous feedback)
  • Thread-safety through immutability

The _format_value helper appropriately handles None, strings (with quotes), and other types for markdown display. Based on learnings, from_attributes=True is correctly applied for pytest-xdist compatibility.

src/omnibase_core/services/replay/service_config_override_injector.py (10)

1-138: Well-documented module with clear architectural design.

The module docstring provides comprehensive documentation including:

  • Thread-safety guarantees
  • Three injection points architecture
  • Complete usage examples
  • Related ticket references

The class setup with MAX_PATH_DEPTH = 20 appropriately guards against DoS via deeply nested paths.


140-182: Correct frozen model detection logic.

The method properly handles both dict-style and ConfigDict (Mapping) configurations. The type checks are ordered correctly with dict before Mapping.


184-237: Path validation is thorough and secure.

The method correctly:

  • Guards against empty/whitespace paths
  • Enforces depth limit (DoS protection)
  • Validates identifier format with proper regex
  • Allows numeric segments for array indexing

The ASCII-only identifier restriction (^[a-zA-Z_][a-zA-Z0-9_]*$) is appropriate for configuration paths.


239-303: Robust path traversal with proper sentinel usage.

The method correctly:

  • Returns MISSING sentinel to distinguish "path not found" from "value is None"
  • Handles dict, Pydantic models, and list/tuple with index access
  • Uses alphabetically ordered exception tuple with # boundary-ok: marker

305-421: Path setting logic handles edge cases well.

The method appropriately:

  • Documents the in-place modification warning
  • Creates intermediate dictionaries for nested paths
  • Provides clear error messages for frozen Pydantic models
  • Handles list index bounds checking
  • Uses alphabetically ordered exception tuples with proper markers

423-460: Deep copy strategy correctly handles frozen models.

The method properly:

  • Detects frozen Pydantic models and converts to dict (required for mutability)
  • Uses model_copy(deep=True) for mutable Pydantic models (more efficient)
  • Falls back to copy.deepcopy for other types

The copy.deepcopy(config.model_dump()) is correct since model_dump() produces a shallow dict.


462-586: Comprehensive validation with clear severity levels.

The method correctly implements the three-tier validation pattern (violations/warnings/suggestions):

  • Violations: Invalid path syntax
  • Warnings: Non-existent paths, type mismatches
  • Suggestions: New environment variables, setting to None

The strict_mode integration and numeric type conversion allowance are well-designed.


588-686: Preview generation is pure and comprehensive.

The method correctly:

  • Handles None inputs gracefully
  • Filters by injection point when specified
  • Tracks both paths_not_found and type_mismatches for user review
  • Uses None for old_value display when path doesn't exist (appropriate for UI)

688-783: Apply method maintains immutability invariant.

The method correctly:

  • Deep copies config before any modification (thread-safety guarantee)
  • Returns structured result with success/failure, applied count, created paths, and errors
  • Handles None inputs with proper error responses
  • Never mutates the original config

888-889: Appropriate public API exports.

The __all__ correctly exports the service class and MISSING sentinel for external use.

@jonahgabriel
jonahgabriel merged commit 51ec56c into main Jan 5, 2026
31 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-1205-demo-configuration-override-injection branch April 4, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant