Skip to content

feat(contracts): upgrade ModelContractReducer to v1.5.0 schema [OMN-648] - #148

Merged
jonahgabriel merged 6 commits into
mainfrom
jonah/omn-648-mvp-modelcontractreducer-v150-schema-upgrade
Dec 10, 2025
Merged

jonahgabriel merged 6 commits into
mainfrom
jonah/omn-648-mvp-modelcontractreducer-v150-schema-upgrade

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Dec 10, 2025 •

Copy link
Copy Markdown
Contributor

Summary

Upgrades ModelContractReducer and ModelFSMSubcontract to v1.5.0 schema to align with the NodeReducer v1.0 specification. This is a prerequisite for upgrading actual contracts (OMN-260) and validating them (OMN-257).

Changes

  • ModelContractReducer (src/omnibase_core/models/contracts/model_contract_reducer.py):

    • Add fingerprint field with regex validation (<semver>:<sha256_12>)
    • Rename state_transitions → state_machine with alias support for YAML flexibility
    • Add frozen=True for thread safety and immutability
    • Add populate_by_name=True for YAML alias flexibility
    • Update INTERFACE_VERSION to 1.5.0
  • ModelFSMSubcontract (src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py):

    • Add frozen=True for immutability after creation
    • Update INTERFACE_VERSION to 1.5.0
  • Tests (tests/unit/models/contracts/test_model_contract_reducer_v150.py):

    • 50 comprehensive tests covering fingerprint validation, state_machine alias, frozen behavior, and serialization

Test Plan

  • mypy --strict passes on both modified files (0 errors)
  • All 50 new tests pass
  • Existing reducer tests pass (11 tests in test_declarative_nodes.py)
  • FSM mixin tests pass (9 tests in test_mixin_fsm_execution.py)
  • Import smoke test passes
  • All pre-commit hooks pass

Linear Ticket

  • OMN-648: [MVP] ModelContractReducer v1.5.0 Schema Upgrade - Align with NodeReducer Spec

Dependencies

  • Blocked by: OMN-495 (NodeReducer v1.0 spec)
  • Blocks: OMN-260 (Upgrade contracts to v1.5.0), OMN-257 (Validate contracts)

Summary by CodeRabbit

  • New Features

    • Added fingerprint field for contract drift detection.
    • Introduced state_machine field with backward-compatible aliasing for state_transitions.
    • Contract models are now immutable (frozen) and interface version bumped to v1.5.0.
  • Behavior Changes

    • FSM loading/initialization now respects absence of a state_machine and adjusts initialization and logging accordingly.
    • Examples and docs updated to reference state_machine.
  • Tests

    • Added comprehensive v1.5.0 tests (fingerprint, aliasing, immutability, serialization, version checks).

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

- Add `fingerprint` field with regex validation (<semver>:<sha256_12>)
- Rename `state_transitions` to `state_machine` with alias support
- Add `frozen=True` to ModelContractReducer for thread safety
- Add `frozen=True` to ModelFSMSubcontract for immutability
- Add `populate_by_name=True` for YAML alias flexibility
- Update INTERFACE_VERSION to 1.5.0 for both models
- Add comprehensive test suite (50 tests) for v1.5.0 features

This is a prerequisite for OMN-260 (contract upgrades) and OMN-257 (validation).
@coderabbitai

coderabbitai Bot commented Dec 10, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Added contract fingerprinting and renamed FSM field to state_machine (alias state_transitions), bumped interface to v1.5.0, made contract/FSM models immutable (frozen, populate-by-name enabled), updated consumer code/examples to use state_machine, and added comprehensive v1.5.0 tests.

Changes

Cohort / File(s) Summary
Core Contract Model Updates
src/omnibase_core/models/contracts/model_contract_reducer.py
INTERFACE_VERSION → ModelSemVer(1,5,0); added public `fingerprint: str
FSM Subcontract Configuration
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
INTERFACE_VERSION → ModelSemVer(1,5,0); model_config adds frozen=True (immutability after creation) while retaining validate_assignment=True; validators documented to run before freeze; docstrings and schema version updated.
Tests (v1.5.0)
tests/unit/models/contracts/test_model_contract_reducer_v150.py
New comprehensive tests for fingerprint validation (valid/invalid), state_machine ↔ state_transitions aliasing and populate-by-name behavior, immutability/frozen semantics, hashability/serialization, and INTERFACE_VERSION checks.
Mixins & Generated Patterns
src/omnibase_core/mixins/mixin_fsm_execution.py, src/omnibase_core/mixins/mixin_metadata.yaml
Docstrings, examples and generated patterns updated to reference contract.state_machine instead of contract.state_transitions; no API signature changes.
Node Reducer Integration
src/omnibase_core/nodes/node_reducer.py
FSM loading/initialization now reads state_machine (guarded init when present); log messages updated to reference state_machine; initialization call guarded by non-None FSM subcontract.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Pay attention to fingerprint regex and validation edge cases in model_contract_reducer.py and tests.
  • Verify alias/populate-by-name interactions and round-trip serialization preserving fingerprint.
  • Confirm frozen model implications for hashing, assignment, and nested mutable structures (tests exercise these).
  • Ensure INTERFACE_VERSION consistency across ModelContractReducer and ModelFSMSubcontract.
  • Review node initialization guard in node_reducer.py to confirm no behavioral regressions.

Possibly related PRs

Poem

🐰 I nibbled schema leaves today,
A tiny hex-mark tucked away,
Two names for machines, one steady shell,
Fields sealed tight — immutability's spell,
Tests laugh as I hop along the way 🥕

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 'feat(contracts): upgrade ModelContractReducer to v1.5.0 schema [OMN-648]' is clear, specific, and directly aligned with the main changes in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jonah/omn-648-mvp-modelcontractreducer-v150-schema-upgrade

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

@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review - PR #148: ModelContractReducer v1.5.0 Schema Upgrade

Thank you for this well-structured PR! This is a solid implementation of the v1.5.0 schema upgrade. Below is my comprehensive review:


✅ Strengths

1. Excellent Test Coverage

  • 50 comprehensive tests covering all v1.5.0 changes
  • Well-organized into clear test classes by feature area
  • Good edge case coverage (uppercase hex, wrong lengths, special chars, etc.)
  • Tests verify both positive and negative validation scenarios

2. Backwards Compatibility

  • Smart use of alias="state_transitions" for YAML compatibility
  • populate_by_name=True allows both field name and alias - excellent!
  • This ensures existing YAML contracts won't break

3. Thread Safety & Immutability

  • frozen=True on both models is correct per project standards
  • Aligns with ONEX architecture principles for thread safety

4. Clear Documentation

  • PR description clearly explains all changes
  • Good inline comments (e.g., "Alias supports legacy YAML contracts")
  • Comprehensive test docstrings

⚠️ Issues & Recommendations

CRITICAL: validate_assignment Removed from ModelFSMSubcontract

Location: src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py:279

Issue: The PR removes validate_assignment=True from ModelFSMSubcontract.model_config:

model_config = ConfigDict(
    extra="ignore",
    frozen=True,
    use_enum_values=False,
-   validate_assignment=True,  # ❌ REMOVED
)

Why this matters:

  • validate_assignment=True enables runtime validation when fields are assigned
  • While frozen=True prevents mutation after creation, removing validate_assignment is inconsistent
  • ModelContractReducer KEEPS both frozen=True AND validate_assignment=True (line 474)

Recommendation:

model_config = ConfigDict(
    extra="ignore",
    frozen=True,
    use_enum_values=False,
    validate_assignment=True,  # ✅ Keep this for consistency
)

Rationale: Even with frozen=True, keeping validate_assignment=True is a defense-in-depth pattern. If someone uses __dict__ manipulation or future Pydantic changes, validation still applies.


MINOR: Fingerprint Pattern Documentation

Location: src/omnibase_core/models/contracts/model_contract_reducer.py:95

Current:

fingerprint: str | None = Field(
    default=None,
    description="Contract fingerprint in format <semver>:<sha256_12> for drift detection",
    pattern=r"^\d+\.\d+\.\d+:[a-f0-9]{12}$",
)

Observation:

  • The pattern enforces exactly 12 hex chars ({12})
  • But ModelContractFingerprint supports 8-64 chars (line 74-75 in model_contract_fingerprint.py)
  • This is intentional constraint for reducer contracts vs general fingerprint model - this is fine
  • However, the description says "sha256_12" which could be clearer

Recommendation (optional, low priority):

description="Contract fingerprint in format <semver>:<12-char-sha256-prefix> for drift detection",

MINOR: Test Naming Convention

Location: Multiple test files

Observation: Test class names use TestModelContractReducerFingerprint but method names use test_fingerprint_* - this creates some redundancy.

Example:

class TestModelContractReducerFingerprint:
    def test_fingerprint_valid_format_basic(self):  # "fingerprint" appears twice
        pass

Recommendation (optional, style preference):

class TestModelContractReducerFingerprint:
    def test_valid_format_basic(self):  # Context clear from class name
        pass

This is purely stylistic - current approach is also valid and explicit.


🔍 Security & Performance

✅ Security

  • Regex pattern for fingerprint is secure (no ReDoS vulnerability - simple pattern)
  • Frozen models prevent accidental mutation (good for security)
  • No injection risks in validation logic

✅ Performance

  • Regex validation is efficient for short strings
  • frozen=True enables Pydantic optimizations
  • Test timeout of 30s is appropriate

📋 Code Quality Checklist

Item Status Notes
Follows CLAUDE.md conventions ✅ Correct use of Poetry, ModelOnexError, etc.
Type hints complete ✅ All signatures properly typed
Error handling ✅ Uses ModelOnexError with proper error codes
Documentation ✅ Clear docstrings and comments
Test coverage ✅ 50 tests covering all changes
Backwards compatibility ✅ Alias pattern preserves existing contracts
Thread safety ✅ frozen=True applied correctly
Mypy strict compliance ⚠️ Needs verification (PR says passes)

🎯 Recommendations Summary

Must Fix:

  1. Restore validate_assignment=True in ModelFSMSubcontract.model_config for consistency with ModelContractReducer

Should Consider:

  1. Clarify fingerprint description to say "12-char-sha256-prefix" for precision
  2. Verify mypy passes on modified files (as claimed in PR description)

Nice to Have:

  1. Consider simplifying test method names (remove "fingerprint" redundancy in class TestModelContractReducerFingerprint)

✅ Overall Assessment

This is high-quality work that correctly implements the v1.5.0 schema requirements:

  • ✅ Fingerprint validation with proper regex
  • ✅ Backwards-compatible state_machine alias
  • ✅ Thread-safe frozen models
  • ✅ Comprehensive test coverage

With the validate_assignment fix, this PR is ready to merge.


📚 References

  • CLAUDE.md: Thread Safety section (confirms frozen=True requirement)
  • ModelContractFingerprint: Shows fingerprint format standards
  • Project conventions: Consistent with existing contract models

Great work on this foundational upgrade! 🚀

@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)
src/omnibase_core/models/contracts/model_contract_reducer.py (1)

199-205: state_machine alias handling and subcontract validation path look correct; minor wording nit

Using state_machine: ModelFSMSubcontract | None with alias="state_transitions" plus populate_by_name=True gives you the desired backwards compatibility for existing YAML while keeping the new field name in Python code. The updated checks in validate_node_specific_config and validate_subcontract_constraints correctly gate FSM validation on state_machine and accept either "state_machine" or "state_transitions" in incoming contract data, and _validate_fsm_subcontract now safely handles a None FSM.

One minor nit: the docstring comment above validate_subcontract_constraints still mentions “state_transitions subcontracts”, which could now be updated to say “state_machine (alias: state_transitions) subcontracts” for consistency with the new field name and error messages.

Based on learnings, this keeps the reducer contract purely declarative and does not mix in FSM transition logic, so it remains aligned with the ModelIntent-based FSM pattern in reducer nodes.

Also applies to: 330-336, 358-369, 400-402

tests/unit/models/contracts/test_model_contract_reducer_v150.py (2)

169-310: state_machine alias and populate_by_name behavior are well-covered; optional from_yaml check

The alias-focused tests (state_machine vs state_transitions) and the _create_minimal_fsm_subcontract helper do a good job verifying that both the field name and alias populate state_machine correctly and that FSM configuration is preserved.

If you want extra safety around the YAML boundary, consider adding a small test that runs a reducer contract with only state_transitions through ModelContractReducer.from_yaml(...) to assert that alias-based population also works via the YAML helper, not just model_validate. That would fully exercise the intended backwards-compatibility path.


311-407: Frozen behavior tests are thorough; hashability assertion may be brittle against library changes

The frozen-behavior tests around field mutation (including correlation_id and node_name) give solid coverage that the immutability contract is enforced at runtime.

The one potentially brittle spot is test_frozen_model_not_hashable_with_nested_mutable_objects, which asserts a TypeError on hash(contract) based on current library behavior and an implementation detail (nested mutable objects). Unless “must not be hashable” is an explicit part of your public contract, you might consider loosening or dropping that assertion so the test suite doesn’t start failing if the underlying model library later makes frozen models hashable without breaking immutability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6875257 and 595170f.

📒 Files selected for processing (3)
  • src/omnibase_core/models/contracts/model_contract_reducer.py (7 hunks)
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (2 hunks)
  • tests/unit/models/contracts/test_model_contract_reducer_v150.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for all Python package management and task execution - never use pip directly or execute python/pytest without poetry run prefix
Format all Python code with black and sort imports with isort - maintain consistent code formatting across the project

Files:

  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
  • tests/unit/models/contracts/test_model_contract_reducer_v150.py
  • src/omnibase_core/models/contracts/model_contract_reducer.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: Use structured error handling with ModelOnexError and EnumCoreErrorCode - never raise generic Exception
Use the @standard_error_handling decorator to eliminate 6+ lines of try/catch boilerplate in async operations
Use protocol-based dependency injection with container.get_service("ProtocolName") - never use concrete class names or isinstance checks with protocols
Type check all Python code with strict mypy (100% strict mode compliance required) - all functions must have type annotations and disallow Any return types
Use ModelEventEnvelope for inter-service event-driven communication - wrap event data in standardized envelope pattern
All source files must achieve 100% strict mypy compliance - no Any types allowed, all function parameters and return types must be explicitly annotated
Document all public APIs, classes, and functions with docstrings explaining purpose, parameters, return values, and usage examples
Never use generic Exception - always use ModelOnexError with specific EnumCoreErrorCode for error handling consistency
Use EnumNodeKind for high-level architectural routing (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR, RUNTIME_HOST) - use EnumNodeType for specific implementation discovery

Files:

  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
  • src/omnibase_core/models/contracts/model_contract_reducer.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use Pydantic 2.11+ models for all data structures with strict validation enabled

Files:

  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
  • src/omnibase_core/models/contracts/model_contract_reducer.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Write comprehensive test coverage with minimum 60% coverage requirement - organize tests in unit/ and integration/ directories with appropriate markers
Use appropriate pytest markers (@pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.performance) to categorize tests

Files:

  • tests/unit/models/contracts/test_model_contract_reducer_v150.py
src/omnibase_core/**/*reducer*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*reducer*.py: NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from omnibase_core.nodes (not deprecated 'Declarative' versions)
Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from `omnibase_core.nodes` (not deprecated 'Declarative' versions)
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
🧬 Code graph analysis (2)
tests/unit/models/contracts/test_model_contract_reducer_v150.py (3)
src/omnibase_core/models/contracts/model_contract_reducer.py (1)
  • to_yaml (480-495)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
  • ModelFSMSubcontract (35-279)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
src/omnibase_core/models/contracts/model_contract_reducer.py (2)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
  • ModelFSMSubcontract (35-279)
⏰ 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: Smoke Tests
  • GitHub Check: claude-review
🔇 Additional comments (5)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)

47-47: Interface version bump and frozen config align with v1.5.0 reducer usage

INTERFACE_VERSION set to 1.5.0 and model_config updated with frozen=True are consistent with the reducer contract’s new interface version and immutability semantics; they also match the new tests that assert FSM subcontract immutability and version. No changes requested.

Also applies to: 275-278

src/omnibase_core/models/contracts/model_contract_reducer.py (2)

77-77: Fingerprint field and interface version upgrade are coherent and well-scoped

The INTERFACE_VERSION bump to 1.5.0 and the new optional fingerprint field with the <semver>:<sha256_12> pattern are narrowly scoped and match the documented format and tests. The regex cleanly enforces the expected shape while allowing None for backwards compatibility. No changes requested.

Also applies to: 91-96


470-478: Model config flags (frozen + populate_by_name) match the intended contract semantics

The model_config updates (frozen=True for immutability and populate_by_name=True to accept both state_machine and its alias) line up with the new tests and with the goal of thread-safe, read-only contract instances that can still ingest legacy YAML. The other config values (extra="forbid", use_enum_values=False, validate_assignment, str_strip_whitespace, validate_default) remain consistent with strict validation expectations. No changes requested.

tests/unit/models/contracts/test_model_contract_reducer_v150.py (2)

42-167: Helper factories and fingerprint tests give strong coverage of the new field

get_minimal_reducer_contract_data / create_minimal_reducer_contract keep the tests DRY and focused, and the fingerprint test class exercises a wide range of valid and invalid patterns (including edge cases like empty strings, uppercase hex, wrong semver shape, and missing colon). This should make regressions in the regex or field optionality very visible. No changes requested.


409-531: Version, model_config, and serialization tests accurately pin the v1.5.0 contract surface

The remaining test classes (interface version checks, FSM subcontract version and frozen config, extra="forbid", populate_by_name, enum retention, and fingerprint presence in model_dump/to_yaml) neatly lock in the intended public surface for v1.5.0. Together they provide good guardrails against accidental interface or config drift. No changes requested.

…648]

- Enhance ModelContractReducer module and class docstrings with v1.5.0 details
- Document fingerprint field format (<semver>:<sha256_12>) and purpose
- Document state_machine alias mechanism for YAML flexibility
- Explain frozen=True thread safety and immutability guarantees
- Enhance ModelFSMSubcontract docstrings with immutability documentation
- Add Google-style docstrings to all model validators

Addresses PR #148 review feedback for clear documentation.
@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review: ModelContractReducer v1.5.0 Schema Upgrade

✅ Overall Assessment

This is a well-executed schema upgrade with excellent documentation, comprehensive testing, and careful attention to backwards compatibility. The changes align well with the omnibase_core architecture and follow project conventions.


🎯 Strengths

1. Comprehensive Test Coverage (50 tests)

  • ✅ Excellent coverage of fingerprint validation (17 edge cases)
  • ✅ Thorough alias mechanism testing (state_machine/state_transitions)
  • ✅ Complete frozen model behavior verification
  • ✅ Interface version validation
  • ✅ Serialization roundtrip testing

2. Documentation Quality

  • ✅ Clear, detailed docstrings explaining v1.5.0 features
  • ✅ Google-style docstrings for validators
  • ✅ Inline comments explaining frozen=True and populate_by_name=True
  • ✅ Excellent PR description with dependencies and test plan

3. Backwards Compatibility

  • ✅ Field aliasing (state_machine/state_transitions) using populate_by_name=True
  • ✅ Fingerprint is optional (defaults to None)
  • ✅ Preserves existing YAML contract compatibility

4. Type Safety

  • ✅ Strict typing maintained (no Any types)
  • ✅ Regex pattern validation for fingerprint
  • ✅ Proper field type annotations

5. Thread Safety

  • ✅ frozen=True on both ModelContractReducer and ModelFSMSubcontract
  • ✅ Clear documentation of immutability guarantees
  • ✅ Aligns with CLAUDE.md thread safety guidelines

🔍 Code Quality Observations

ModelContractReducer Changes:

✅ Fingerprint Field (lines 114-124)

fingerprint: str | None = Field(
    default=None,
    pattern=r"^\d+\.\d+\.\d+:[a-f0-9]{12}$",
    ...
)
  • Regex pattern is correct and well-documented
  • Optional field with sensible None default
  • Clear description of format and purpose

✅ State Machine Alias (lines 230-240)

state_machine: ModelFSMSubcontract | None = Field(
    default=None,
    alias="state_transitions",
    ...
)
  • Proper use of Pydantic alias mechanism
  • Combined with populate_by_name=True for flexibility
  • Type changed from object to ModelFSMSubcontract | None (good improvement!)

✅ Model Config (lines 512-520)

model_config = ConfigDict(
    extra="forbid",
    frozen=True,  # v1.5.0
    populate_by_name=True,  # v1.5.0
    ...
)
  • Comprehensive inline comments
  • All settings properly documented

✅ Validation Logic Updated (lines 365-367, 395-404)

  • Correctly checks both "state_machine" and "state_transitions" keys
  • Handles alias properly in validation
  • Updated error messages removed emojis (good - follows CLAUDE.md)

⚠️ Minor Observations (Not Blockers)

1. ModelFSMSubcontract Config Change

Location: model_fsm_subcontract.py:326-330

Observation:

model_config = ConfigDict(
    extra="ignore",  # Allow extra fields from YAML contracts
    frozen=True,  # v1.5.0
    use_enum_values=False,
    # validate_assignment=True,  # ← REMOVED
)

Question: The removal of validate_assignment=True is intentional but worth noting:

  • With frozen=True, assignment validation becomes less relevant (all assignments fail anyway)
  • However, ModelContractReducer still has validate_assignment=True (line 516)
  • Recommendation: Consider documenting why this differs between the two models, or align them for consistency

2. Test Coverage for validate_assignment

The tests verify that mutations raise ValidationError, but don't explicitly test the interaction between frozen=True and validate_assignment=True in ModelContractReducer.

Suggestion: Add a comment in the model_config explaining that validate_assignment=True is kept for consistency even though frozen=True makes it largely redundant.

3. Hash Limitation Acknowledged

Tests correctly document that frozen models with nested non-frozen objects aren't hashable (test_frozen_model_not_hashable_with_nested_mutable_objects). This is expected Pydantic behavior and well-documented in the tests. No action needed - just noting this is correctly handled.


🔒 Security Review

✅ No security concerns identified:

  • Fingerprint regex prevents injection (only allows semver + lowercase hex)
  • No unsafe deserialization
  • Frozen models prevent accidental state mutation
  • Input validation is comprehensive

📊 Performance Considerations

✅ No performance concerns:

  • Fingerprint validation is O(1) regex check
  • Frozen models may have slight performance benefit (immutability optimizations)
  • No new expensive operations introduced
  • Model creation overhead unchanged

🧪 Test Quality

Test Structure:

  • ✅ Well-organized into logical test classes
  • ✅ Descriptive test names following conventions
  • ✅ Proper use of pytest.mark.unit and pytest.mark.timeout
  • ✅ Helper functions (get_minimal_reducer_contract_data) reduce duplication
  • ✅ Comprehensive edge case coverage

Test Coverage Gaps: None identified - coverage is excellent


✅ Alignment with CLAUDE.md

Following project conventions:

  • ✅ Uses Poetry for dependency management (mentioned in PR)
  • ✅ ModelOnexError for structured errors
  • ✅ Strict mypy compliance (0 errors mentioned)
  • ✅ Thread safety documented (frozen=True)
  • ✅ No backwards-incompatible changes (v0.4.0 pre-release phase)
  • ✅ Comprehensive testing (50 new tests)
  • ✅ Removed emojis from error messages (line 400, 403, 409, 412)

📝 Recommendations

Optional Improvements (Not Blocking):

  1. Document validate_assignment difference:

    # In ModelContractReducer.model_config comment (line 516):
    validate_assignment=True,  # Validate on assignment (redundant with frozen but kept for explicitness)
  2. Consider adding migration notes:

    • If this schema upgrade affects existing YAML contracts, consider adding a migration guide similar to docs/guides/MIGRATING_TO_DECLARATIVE_NODES.md
    • However, since the PR description mentions this is a prerequisite for OMN-260 (contract upgrades), this may already be planned
  3. Fingerprint generation utility:

    • Future work: Consider adding a utility function to generate fingerprints from YAML contracts
    • This would make it easier to populate the fingerprint field correctly
    • Not needed for this PR, but worth tracking for OMN-260

🎯 Verdict

Status: ✅ APPROVED

This PR is production-ready with excellent quality across all dimensions:

  • ✅ Code quality: Excellent
  • ✅ Test coverage: Comprehensive (50 tests)
  • ✅ Documentation: Clear and thorough
  • ✅ Backwards compatibility: Maintained
  • ✅ Type safety: Strict mypy compliance
  • ✅ Thread safety: Properly implemented
  • ✅ Security: No concerns
  • ✅ Performance: No concerns

Minor suggestions above are optional improvements, not blockers.

Great work! This is a solid foundation for the contract upgrade and validation work in OMN-260 and OMN-257. 🚀


Review completed by Claude Code
Correlation ID: PR-148-review-2025-12-10

@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/models/contracts/model_contract_reducer.py (1)

512-520: Clarify the interaction between frozen=True and validate_assignment=True.

With frozen=True, any field assignment attempt raises a ValidationError immediately, making validate_assignment=True effectively redundant. The comment on line 516 acknowledges this ("triggers error if frozen"), but the combination may confuse future maintainers.

Consider removing validate_assignment=True since frozen=True already prevents all mutations, or updating the comment to explicitly note the redundancy is intentional for defensive purposes.

     model_config = ConfigDict(
         extra="forbid",  # Strict validation - reject unknown fields
         frozen=True,  # Thread safety and immutability - instances cannot be modified
         use_enum_values=False,  # Keep enum objects, don't convert to strings
-        validate_assignment=True,  # Validate on any assignment attempt (triggers error if frozen)
         str_strip_whitespace=True,  # Clean string inputs
         validate_default=True,  # Validate default values at model definition time
         populate_by_name=True,  # Allow both field name and alias for YAML flexibility
     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 595170f and 5e5dcce.

📒 Files selected for processing (2)
  • src/omnibase_core/models/contracts/model_contract_reducer.py (8 hunks)
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for all Python package management and task execution - never use pip directly or execute python/pytest without poetry run prefix
Format all Python code with black and sort imports with isort - maintain consistent code formatting across the project

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: Use structured error handling with ModelOnexError and EnumCoreErrorCode - never raise generic Exception
Use the @standard_error_handling decorator to eliminate 6+ lines of try/catch boilerplate in async operations
Use protocol-based dependency injection with container.get_service("ProtocolName") - never use concrete class names or isinstance checks with protocols
Type check all Python code with strict mypy (100% strict mode compliance required) - all functions must have type annotations and disallow Any return types
Use ModelEventEnvelope for inter-service event-driven communication - wrap event data in standardized envelope pattern
All source files must achieve 100% strict mypy compliance - no Any types allowed, all function parameters and return types must be explicitly annotated
Document all public APIs, classes, and functions with docstrings explaining purpose, parameters, return values, and usage examples
Never use generic Exception - always use ModelOnexError with specific EnumCoreErrorCode for error handling consistency
Use EnumNodeKind for high-level architectural routing (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR, RUNTIME_HOST) - use EnumNodeType for specific implementation discovery

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
src/omnibase_core/**/*reducer*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*reducer*.py: NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from omnibase_core.nodes (not deprecated 'Declarative' versions)
Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use Pydantic 2.11+ models for all data structures with strict validation enabled

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from `omnibase_core.nodes` (not deprecated 'Declarative' versions)
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from `omnibase_core.nodes` (not deprecated 'Declarative' versions)

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
🧬 Code graph analysis (2)
src/omnibase_core/models/contracts/model_contract_reducer.py (2)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
  • ModelFSMSubcontract (39-330)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
⏰ 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: claude-review
  • GitHub Check: Smoke Tests
🔇 Additional comments (6)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (2)

47-64: LGTM! Clean documentation of immutability semantics.

The docstring correctly explains that validators run during construction before the instance is frozen, and the INTERFACE_VERSION is properly bumped to 1.5.0 to align with the schema upgrade.


326-329: LGTM! Appropriate use of frozen=True for thread safety.

The frozen=True configuration correctly provides immutability guarantees after construction. Note that this removes the ability to use validate_assignment, but since frozen models reject all assignments anyway, this is the correct design choice for thread-safe immutable contracts.

src/omnibase_core/models/contracts/model_contract_reducer.py (4)

110-124: LGTM! Well-designed fingerprint field for drift detection.

The regex pattern ^\d+\.\d+\.\d+:[a-f0-9]{12}$ enforces a canonical format with lowercase hex characters. The optional nature (None default) appropriately supports both new contracts without fingerprints and versioned contracts with drift detection enabled.


227-240: LGTM! Good backward compatibility approach with field aliasing.

The rename from state_transitions to state_machine with alias="state_transitions" combined with populate_by_name=True in model_config enables YAML contracts to use either field name. This maintains backward compatibility while establishing the preferred v1.5.0 naming convention.


393-404: LGTM! Correct handling of both field name and alias in validation.

The check for both "state_machine" and "state_transitions" in contract_data correctly handles:

  1. Raw YAML input using the legacy state_transitions key
  2. model_dump() output which always uses the field name state_machine

This ensures subcontract constraint validation works regardless of the source.


435-437: LGTM! Correctly updated to use the new field name.

The _validate_fsm_subcontract method now reads from self.state_machine which aligns with the renamed field. The early return on None is appropriate.

…648]

Document why ModelFSMSubcontract omits validate_assignment while
ModelContractReducer includes it - both have identical behavior due
to frozen=True, but the explicit flag is kept for documentation clarity.
@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review - PR #148: ModelContractReducer v1.5.0 Schema Upgrade

Overview

This PR upgrades ModelContractReducer and ModelFSMSubcontract to v1.5.0 schema, adding fingerprint support, field aliasing, and immutability. The changes are well-structured and align with ONEX architecture patterns.


✅ Strengths

1. Excellent Test Coverage

  • 50 comprehensive tests covering all v1.5.0 features
  • Tests organized into logical categories: fingerprint validation, alias compatibility, frozen behavior, versioning
  • Edge cases well-covered (invalid formats, uppercase hex, special chars, etc.)
  • Good use of helper functions (get_minimal_reducer_contract_data, create_minimal_reducer_contract)

2. Backwards Compatibility

  • state_machine field with alias="state_transitions" maintains compatibility with legacy YAML contracts
  • populate_by_name=True ensures both field names work in YAML
  • Validation checks both field names: if "state_machine" not in contract_data and "state_transitions" not in contract_data

3. Thread Safety & Immutability

  • frozen=True on both ModelContractReducer and ModelFSMSubcontract
  • Well-documented rationale in docstrings and inline comments
  • Tests verify immutability behavior (test_mutation_raises_error_*)

4. Fingerprint Validation

  • Robust regex pattern: r"^\\d+\\.\\d+\\.\\d+:[a-f0-9]{12}$"
  • Clear format documentation: <semver>:<sha256_12>
  • Tests cover valid/invalid patterns thoroughly

5. Documentation Quality

  • Comprehensive docstrings with schema version notes
  • Clear explanations of why fields exist and how they work
  • Migration path documented in PR description

⚠️ Issues & Recommendations

1. Inconsistent validate_assignment Configuration (Minor)

Issue: ModelContractReducer has validate_assignment=True in model_config, but ModelFSMSubcontract omits it with a comment claiming it's redundant.

Location:

  • model_contract_reducer.py:516 - includes validate_assignment=True
  • model_fsm_subcontract.py:329 - comment says it's redundant with frozen=True

Analysis:

  • Both patterns work correctly (Pydantic enforces immutability with frozen=True regardless)
  • Comment in ModelFSMSubcontract is technically correct BUT creates inconsistency
  • 100+ other contract models in the codebase use validate_assignment=True with frozen=True

Recommendation:

  • Add validate_assignment=True to ModelFSMSubcontract.model_config for consistency with:
    • ModelContractReducer (this PR)
    • ModelContractCompute, ModelContractEffect, ModelContractOrchestrator
    • 100+ subcontract models
  • Update comment to match ModelContractReducer's approach: "Explicit for documentation - redundant with frozen=True but kept for clarity"

2. Potential Validation Redundancy (Very Minor)

Location: model_contract_reducer.py:429-504 (_validate_fsm_subcontract)

Observation:

  • _validate_fsm_subcontract duplicates some validation that ModelFSMSubcontract already performs via @model_validator:
    • Initial state existence (lines 440-453 vs FSM's validate_initial_state_exists)
    • State name validation (lines 456-468 vs FSM's validators)

Analysis:

  • Not a bug - defensive validation at contract level is acceptable
  • FSM validators run during FSM construction
  • Contract validators run during contract validation
  • Provides defense-in-depth

Recommendation:

  • No change required - double validation is safe and prevents issues if FSM validation changes
  • Consider adding a comment explaining this is intentional defense-in-depth

3. Missing Test: Simultaneous Field Name Usage (Minor)

Gap: Tests verify state_machine OR state_transitions work, but not what happens when BOTH are provided simultaneously in YAML.

Recommendation:

  • Add test: test_both_state_machine_and_state_transitions_provided
  • Expected behavior: Pydantic should use the field name (state_machine) and ignore the alias (state_transitions) when both present
  • Or it might raise a validation error - either way, behavior should be documented

4. Frozen Model Hashing Test Semantics (Documentation)

Location: test_model_contract_reducer_v150.py:379-406

Tests:

  • test_frozen_model_not_hashable_with_nested_mutable_objects
  • test_frozen_model_immutability_enforced_despite_hash_limitation

Observation:

  • Tests correctly document that frozen=True doesn't make models hashable when they contain nested mutable objects
  • This is expected Pydantic behavior

Recommendation:

  • Perfect as-is - these tests serve as important documentation
  • Consider adding a note in ModelContractReducer docstring about this limitation

🔍 Code Quality Assessment

Category Rating Notes
Architecture ⭐⭐⭐⭐⭐ Follows ONEX patterns perfectly
Type Safety ⭐⭐⭐⭐⭐ Strict typing, mypy compliant
Testing ⭐⭐⭐⭐⭐ 50 tests, excellent coverage
Documentation ⭐⭐⭐⭐⭐ Clear docstrings and comments
Backwards Compat ⭐⭐⭐⭐⭐ Alias support for legacy contracts
Thread Safety ⭐⭐⭐⭐⭐ Frozen models, well-documented
Consistency ⭐⭐⭐⭐ Minor inconsistency with validate_assignment

🎯 Recommendation: APPROVE with Minor Suggestions

This is excellent work that properly implements the v1.5.0 schema upgrade. The only recommendation is to add validate_assignment=True to ModelFSMSubcontract for consistency with the rest of the codebase.

Priority Actions:

  1. Optional: Add validate_assignment=True to ModelFSMSubcontract.model_config (consistency fix)
  2. Optional: Add test for simultaneous state_machine + state_transitions in YAML
  3. Optional: Add comment in _validate_fsm_subcontract explaining intentional defense-in-depth validation

None of these are blockers - the PR is ready to merge as-is if you prefer.


📊 Test Execution

The 50 new tests should integrate smoothly with the existing 12,000+ test suite. Expected impact:

  • CI runtime: Minimal (less than 5 seconds across 20 splits)
  • Coverage: Maintains 60%+ requirement
  • No conflicts with existing reducer tests

Reviewed by: Claude Sonnet 4.5
Review Date: 2025-12-10

@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 (2)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)

326-330: Consider removing the comparison to ModelContractReducer.

The note comparing to ModelContractReducer's configuration may confuse readers. The explanation that frozen=True already prevents modifications is sufficient without the comparison.

Apply this diff to simplify the note:

-    # Note: validate_assignment is intentionally omitted here because:
-    # 1. frozen=True already prevents all attribute modifications (raises ValidationError)
-    # 2. validate_assignment would be redundant - any assignment attempt fails immediately
-    # 3. This differs from ModelContractReducer which includes validate_assignment=True
-    #    for explicit documentation purposes, but the behavior is identical
+    # Note: validate_assignment is intentionally omitted because frozen=True
+    # already prevents all attribute modifications (raises ValidationError).
+    # Any assignment attempt fails immediately, making validate_assignment redundant.
src/omnibase_core/models/contracts/model_contract_reducer.py (1)

505-520: Consider removing validate_assignment=True as redundant.

While the comment acknowledges that validate_assignment=True is redundant with frozen=True, keeping it may confuse future maintainers. Since frozen models cannot be modified, validation on assignment is unnecessary.

If you prefer to keep it for documentation purposes, that's acceptable. Otherwise, apply this diff:

     model_config = ConfigDict(
         extra="forbid",  # Strict validation - reject unknown fields
         frozen=True,  # Thread safety and immutability - instances cannot be modified
         use_enum_values=False,  # Keep enum objects, don't convert to strings
-        validate_assignment=True,  # Explicit for documentation - redundant with frozen=True but kept for clarity
         str_strip_whitespace=True,  # Clean string inputs
         validate_default=True,  # Validate default values at model definition time
         populate_by_name=True,  # Allow both field name and alias for YAML flexibility
     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e5dcce and acdc200.

📒 Files selected for processing (2)
  • src/omnibase_core/models/contracts/model_contract_reducer.py (8 hunks)
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for all Python package management and task execution - never use pip directly or execute python/pytest without poetry run prefix
Format all Python code with black and sort imports with isort - maintain consistent code formatting across the project

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: Use structured error handling with ModelOnexError and EnumCoreErrorCode - never raise generic Exception
Use the @standard_error_handling decorator to eliminate 6+ lines of try/catch boilerplate in async operations
Use protocol-based dependency injection with container.get_service("ProtocolName") - never use concrete class names or isinstance checks with protocols
Type check all Python code with strict mypy (100% strict mode compliance required) - all functions must have type annotations and disallow Any return types
Use ModelEventEnvelope for inter-service event-driven communication - wrap event data in standardized envelope pattern
All source files must achieve 100% strict mypy compliance - no Any types allowed, all function parameters and return types must be explicitly annotated
Document all public APIs, classes, and functions with docstrings explaining purpose, parameters, return values, and usage examples
Never use generic Exception - always use ModelOnexError with specific EnumCoreErrorCode for error handling consistency
Use EnumNodeKind for high-level architectural routing (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR, RUNTIME_HOST) - use EnumNodeType for specific implementation discovery

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
src/omnibase_core/**/*reducer*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*reducer*.py: NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from omnibase_core.nodes (not deprecated 'Declarative' versions)
Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use Pydantic 2.11+ models for all data structures with strict validation enabled

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from `omnibase_core.nodes` (not deprecated 'Declarative' versions)

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
🧬 Code graph analysis (1)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
⏰ 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: Smoke Tests
  • GitHub Check: claude-review
🔇 Additional comments (9)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (3)

4-4: LGTM! Version bump to 1.5.0 is consistent.

The schema version and INTERFACE_VERSION are correctly updated to 1.5.0, aligning with the ModelContractReducer upgrade.

Also applies to: 64-64


47-59: LGTM! Immutability documentation is clear and accurate.

The documentation correctly explains that frozen=True provides thread safety by making instances immutable after creation, and that validators run during construction before freezing.


331-335: LGTM! Model configuration is correct for v1.5.0.

The frozen=True setting correctly implements immutability for thread safety, and the other configuration options are appropriate for FSM subcontracts.

src/omnibase_core/models/contracts/model_contract_reducer.py (6)

110-124: LGTM! Fingerprint field implementation is well-designed.

The fingerprint field correctly implements the <semver>:<sha256_12> format with proper regex validation and comprehensive documentation explaining its purpose for drift detection.


227-240: LGTM! Field aliasing is correctly implemented.

The state_machine field with alias="state_transitions" correctly supports backward compatibility with existing YAML contracts. Combined with populate_by_name=True in model_config, this allows both field names to be used when loading from YAML.


365-367: LGTM! FSM validation correctly uses state_machine field.

The validation methods correctly reference self.state_machine (the primary field name) rather than the alias. This is the proper approach since Pydantic internally uses the primary field name.

Also applies to: 435-437


76-91: LGTM! Comprehensive documentation for v1.5.0 features.

The documentation clearly explains the three key features added in v1.5.0: fingerprint for drift detection, state_machine with alias support, and frozen=True for immutability. The explanations are concise and informative.


512-520: LGTM! Model configuration correctly implements v1.5.0 requirements.

The ConfigDict properly combines frozen=True for immutability, populate_by_name=True for alias support, and other appropriate settings. The inline comments clearly explain each configuration option.


393-404: Clarify the validation logic for dual data sources.

The validation checks both state_machine and state_transitions to handle two scenarios: when original_contract_data is provided from YAML (where either name could appear due to populate_by_name=True), and when it's None and model_dump() is called (which returns only state_machine by default). The comment should clarify this distinction—specifically that model_dump() without by_alias=True returns the field name only, making the state_transitions check necessary only when validating YAML-loaded data. Consider updating the comment to: "Check both field name and alias to support YAML contracts that may use either name due to populate_by_name=True; when using model_dump(), only state_machine will be present."

- Add validate_assignment=True to ModelFSMSubcontract for consistency
- Add defense-in-depth docstring to _validate_fsm_subcontract method
- Add test for simultaneous state_machine/state_transitions field usage
- Add docstring note about frozen model hashing limitations
@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review for PR #148: ModelContractReducer v1.5.0 Schema Upgrade

Overall Assessment

This PR implements important foundational work for the NodeReducer v1.0 specification. The code quality is excellent with comprehensive tests (50 new tests), thorough documentation, and proper type safety. However, there is a critical backwards compatibility issue that must be addressed before merging.


🚨 Critical Issue: Breaking Change in Field Access

Problem

The PR renames state_transitions → state_machine with an alias, but existing code accesses this field directly as an attribute, which will break with Pydantic's alias mechanism.

Affected Code Locations:

  • src/omnibase_core/mixins/mixin_fsm_execution.py:76, 133, 179, 197, 218
  • src/omnibase_core/nodes/node_reducer.py:100, 142

Example from node_reducer.py:142:

if hasattr(self, "contract") and hasattr(self.contract, "state_transitions"):
    self.fsm_contract = self.contract.state_transitions  # ❌ Will fail!

Why This Breaks

In Pydantic v2 with alias="state_transitions":

  • The field name is state_machine (attribute access)
  • The alias is state_transitions (YAML/dict parsing only)
  • populate_by_name=True allows both names during parsing, but NOT for attribute access
  • Accessing .state_transitions as an attribute will raise AttributeError

Solution Options

Option 1: Add @Property for Backwards Compatibility (Recommended)

class ModelContractReducer(MixinNodeTypeValidator, ModelContractBase):
    state_machine: ModelFSMSubcontract | None = Field(
        default=None,
        alias="state_transitions",
        # ...
    )
    
    @property
    def state_transitions(self) -> ModelFSMSubcontract | None:
        """Backwards-compatible alias for state_machine field."""
        return self.state_machine

Option 2: Update All Call Sites
Update all 7 locations to use state_machine instead of state_transitions. Less desirable because it requires coordinated changes across multiple files.

Recommendation: Use Option 1 to maintain backwards compatibility during the transition period, then deprecate the property in a future version.


✅ Strengths

1. Excellent Test Coverage

  • 50 comprehensive tests covering all v1.5.0 features
  • Well-organized test classes by feature area
  • Edge cases thoroughly tested (invalid patterns, boundaries, special chars)

2. Documentation Quality

  • Clear docstrings explaining the v1.5.0 schema changes
  • Helpful examples in comments
  • Defense-in-Depth pattern well-documented (lines 445-459)

3. Type Safety

  • Proper type annotations throughout
  • Strict regex validation for fingerprint: r"^\d+\.\d+\.\d+:[a-f0-9]{12}$"
  • Correct use of str | None for optional fields

4. Thread Safety

  • frozen=True correctly implemented for immutability
  • Clear documentation about hashability limitations (lines 93-100)
  • Note about nested mutable objects is accurate

5. Validation Logic

  • Fingerprint pattern is well-designed (<semver>:<sha256_12>)
  • FSM validation preserved with defense-in-depth approach
  • Updated validation messages (removed emojis per CLAUDE.md guidelines ✅)

💡 Minor Suggestions

1. Fingerprint Validation Could Be More Explicit

Location: model_contract_reducer.py:133

Consider adding a field_validator for more detailed error messages:

@field_validator('fingerprint')
@classmethod
def validate_fingerprint_format(cls, v: str | None) -> str | None:
    if v is None:
        return v
    
    parts = v.split(':')
    if len(parts) != 2:
        raise ValueError("Fingerprint must be in format <semver>:<sha256_12>")
    
    semver, hash_part = parts
    if not re.match(r'^\d+\.\d+\.\d+$', semver):
        raise ValueError(f"Invalid semver format: {semver}")
    if not re.match(r'^[a-f0-9]{12}$', hash_part):
        raise ValueError(f"Invalid hash format (must be 12 lowercase hex chars): {hash_part}")
    
    return v

This would provide clearer error messages than Pydantic's generic pattern validation.

2. Consider Adding Fingerprint Generation Utility

Since the fingerprint format is well-defined, consider adding a utility function:

def generate_contract_fingerprint(contract: ModelContractReducer) -> str:
    """Generate fingerprint for contract drift detection."""
    import hashlib
    canonical = contract.model_dump_json(sort_keys=True, exclude={'fingerprint', 'correlation_id'})
    hash_full = hashlib.sha256(canonical.encode()).hexdigest()
    return f"{contract.INTERFACE_VERSION}:{hash_full[:12]}"

3. Test Could Verify Frozen Behavior More Thoroughly

Location: test_model_contract_reducer_v150.py

The frozen tests verify basic immutability, but could also test:

  • That ValidationError is raised (not AttributeError)
  • The specific error message mentions "frozen"
  • Nested model immutability behavior

Example:

def test_frozen_model_raises_validation_error_with_clear_message(self) -> None:
    contract = create_minimal_reducer_contract()
    with pytest.raises(ValidationError) as exc_info:
        contract.name = "modified"
    assert "frozen" in str(exc_info.value).lower()

4. ModelFSMSubcontract: Comment Redundancy

Location: model_fsm_subcontract.py:330

The comment # Explicit for documentation - redundant with frozen=True on validate_assignment=True is accurate. Consider removing validate_assignment=True entirely since it's truly redundant with frozen=True (frozen models cannot be assigned to anyway).


🔒 Security Considerations

✅ No security concerns identified:

  • Fingerprint validation prevents injection attacks (strict regex)
  • frozen=True prevents accidental mutation
  • No use of eval() or dynamic code execution
  • Proper error handling with ModelOnexError

📊 Performance Considerations

✅ Minimal performance impact:

  • frozen=True may slightly improve performance (Pydantic can optimize)
  • Regex validation on fingerprint is efficient (simple pattern)
  • No expensive operations in validators

Potential Concern: The _validate_fsm_subcontract() method (line 439) duplicates validation that ModelFSMSubcontract already performs. While this is intentional (defense-in-depth), it does add validation overhead. The documentation explains this trade-off well.


✅ Test Coverage Assessment

Based on the test file structure:

  • Fingerprint validation: 17 tests ✅ Excellent coverage
  • State machine alias: 15+ tests ✅ Comprehensive
  • Frozen behavior: 10+ tests ✅ Good coverage
  • Interface version: Tests present ✅

Missing Test: Interaction between populate_by_name=True and actual YAML parsing. Consider adding:

def test_yaml_loading_with_both_field_names(self) -> None:
    """Test that YAML can use either state_machine or state_transitions."""
    fsm = self._create_minimal_fsm_subcontract()
    
    # Test with state_machine field name
    yaml1 = yaml.dump({'state_machine': fsm.model_dump(), **base_fields})
    contract1 = ModelContractReducer.from_yaml(yaml1)
    assert contract1.state_machine is not None
    
    # Test with state_transitions alias
    yaml2 = yaml.dump({'state_transitions': fsm.model_dump(), **base_fields})
    contract2 = ModelContractReducer.from_yaml(yaml2)
    assert contract2.state_machine is not None  # Should work via alias

📋 Checklist Before Merge

  • CRITICAL: Fix backwards compatibility issue (add @property for state_transitions)
  • Verify all existing tests still pass after fix
  • Consider adding fingerprint generation utility
  • Optional: Add YAML parsing test for both field names
  • Optional: Enhanced fingerprint validation with field_validator

🎯 Recommendation

Status: ⚠️ Changes Required

This is high-quality work that significantly advances the NodeReducer specification. The implementation is clean, well-tested, and properly documented. However, the backwards compatibility issue must be resolved before merging to avoid breaking existing nodes.

Once the @property alias is added (5 lines of code), this PR will be ready to merge.

Estimated Fix Time: 10 minutes


Great work on the comprehensive test suite and documentation! The fingerprint mechanism is well-designed and will be valuable for contract drift detection. 🚀

@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 (2)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)

193-221: Inconsistent error message contains "list[Any]" artifact.

The error messages in validator methods contain list[Any] which appears to be a copy-paste artifact or typo. These should likely just say list or states list.

Examples at lines 208, 243, 260, 297, 311:

  • "Initial state '{self.initial_state}' not found in states list[Any]"
  • "Terminal state '{state_name}' not found in states list[Any]"

This is a pre-existing issue, not introduced by this PR, but worth noting for consistency.

Also applies to: 223-273, 275-324

src/omnibase_core/models/contracts/model_contract_reducer.py (1)

614-626: Coding guidelines: Generic Exception in from_yaml.

The from_yaml method at line 614 catches a generic Exception. Per coding guidelines, structured error handling with ModelOnexError and EnumCoreErrorCode should be used, and generic Exception should be avoided.

However, this is a catch-all for unexpected errors during YAML parsing/validation, and it does wrap the exception in ModelOnexError, so the re-raised error follows the guidelines. This is acceptable as a defensive catch-all after handling specific exceptions.

Consider being more specific about what exceptions might be caught here, or adding a comment explaining this is an intentional defensive catch-all:

         except yaml.YAMLError as e:
             raise ModelOnexError(
                 ...
             ) from e
-        except Exception as e:
+        # Defensive catch-all for unexpected errors during contract loading
+        except Exception as e:  # noqa: BLE001 - intentional broad exception handling
             raise ModelOnexError(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acdc200 and 5c037f3.

📒 Files selected for processing (3)
  • src/omnibase_core/models/contracts/model_contract_reducer.py (8 hunks)
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (6 hunks)
  • tests/unit/models/contracts/test_model_contract_reducer_v150.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use Poetry for all Python package management and task execution - never use pip directly or execute python/pytest without poetry run prefix
Format all Python code with black and sort imports with isort - maintain consistent code formatting across the project

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
  • tests/unit/models/contracts/test_model_contract_reducer_v150.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: Use structured error handling with ModelOnexError and EnumCoreErrorCode - never raise generic Exception
Use the @standard_error_handling decorator to eliminate 6+ lines of try/catch boilerplate in async operations
Use protocol-based dependency injection with container.get_service("ProtocolName") - never use concrete class names or isinstance checks with protocols
Type check all Python code with strict mypy (100% strict mode compliance required) - all functions must have type annotations and disallow Any return types
Use ModelEventEnvelope for inter-service event-driven communication - wrap event data in standardized envelope pattern
All source files must achieve 100% strict mypy compliance - no Any types allowed, all function parameters and return types must be explicitly annotated
Document all public APIs, classes, and functions with docstrings explaining purpose, parameters, return values, and usage examples
Never use generic Exception - always use ModelOnexError with specific EnumCoreErrorCode for error handling consistency
Use EnumNodeKind for high-level architectural routing (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR, RUNTIME_HOST) - use EnumNodeType for specific implementation discovery

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
src/omnibase_core/**/*reducer*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*reducer*.py: NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from omnibase_core.nodes (not deprecated 'Declarative' versions)
Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
src/omnibase_core/models/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use Pydantic 2.11+ models for all data structures with strict validation enabled

Files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Write comprehensive test coverage with minimum 60% coverage requirement - organize tests in unit/ and integration/ directories with appropriate markers
Use appropriate pytest markers (@pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.performance) to categorize tests

Files:

  • tests/unit/models/contracts/test_model_contract_reducer_v150.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : Implement FSM transitions using ModelIntent pattern in REDUCER nodes - keep state transition logic separate from side effects

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
  • src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py
📚 Learning: 2025-12-10T19:23:31.049Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T19:23:31.049Z
Learning: Applies to src/omnibase_core/**/*reducer*.py : NodeReducer and NodeOrchestrator are the primary implementations for v0.4.0+ - import from `omnibase_core.nodes` (not deprecated 'Declarative' versions)

Applied to files:

  • src/omnibase_core/models/contracts/model_contract_reducer.py
🧬 Code graph analysis (2)
src/omnibase_core/models/contracts/model_contract_reducer.py (3)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
  • ModelFSMSubcontract (39-331)
tests/unit/contracts/test_runtime_contracts.py (1)
  • contract_data (183-185)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
src/omnibase_core/models/primitives/model_semver.py (1)
  • ModelSemVer (24-143)
⏰ 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: Smoke Tests
  • GitHub Check: claude-review
🔇 Additional comments (18)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (3)

1-21: LGTM! Well-documented schema upgrade.

The module docstring clearly documents the v1.5.0 schema version, immutability guarantees via frozen=True, and thread-safety implications. Good addition of the pre-freeze construction semantics explanation.


64-64: LGTM! Interface version correctly updated.

The INTERFACE_VERSION is correctly set to ModelSemVer(major=1, minor=5, patch=0), aligning with the v1.5.0 schema upgrade.


326-331: LGTM! Configuration correctly enables immutability.

The frozen=True setting is correctly added for thread safety. The comment explaining that validate_assignment=True is redundant but kept for documentation clarity is helpful.

src/omnibase_core/models/contracts/model_contract_reducer.py (7)

1-15: LGTM! Clear v1.5.0 schema documentation.

The module docstring effectively summarizes the key v1.5.0 features: fingerprint for drift detection, field aliasing for YAML flexibility, and thread-safe immutability.


67-103: LGTM! Comprehensive class-level documentation.

Excellent documentation of the v1.5.0 schema features, including the important note about hashability limitations with nested mutable objects. This proactive documentation will help developers understand why hash() fails despite frozen=True.


120-134: LGTM! Well-designed fingerprint field.

The fingerprint field is well-implemented:

  • Optional with None default for backwards compatibility
  • Clear regex pattern r"^\d+\.\d+\.\d+:[a-f0-9]{12}$" matching the documented format
  • Descriptive field documentation with format example

The pattern correctly enforces lowercase hex characters and exactly 12 characters for the SHA256 prefix.


237-250: LGTM! State machine alias provides backwards compatibility.

The state_machine field with alias="state_transitions" combined with populate_by_name=True in the config correctly enables YAML contracts to use either field name. The documentation clearly explains the aliasing mechanism.


403-423: LGTM! Validation correctly checks both field name and alias.

The validation logic at lines 405-408 correctly checks for both "state_machine" and "state_transitions" keys when validating against the original contract data, ensuring the validation works regardless of which key the YAML contract uses.


439-464: LGTM! Defense-in-depth pattern well documented.

The detailed docstring explaining why the contract-level validation intentionally duplicates FSM-level validation is excellent. This documents the architectural decision and helps future maintainers understand the deliberate redundancy.

Based on learnings, this aligns with the pattern of keeping state transition logic separate from side effects in REDUCER nodes.


531-546: LGTM! Model configuration correctly implements v1.5.0 requirements.

The configuration correctly adds:

  • frozen=True for immutability and thread safety
  • populate_by_name=True for alias flexibility
  • Clear comments explaining each setting's purpose

The note about validate_assignment=True being redundant with frozen=True but kept for documentation clarity is helpful.

tests/unit/models/contracts/test_model_contract_reducer_v150.py (8)

1-21: LGTM! Comprehensive test documentation.

The module docstring clearly outlines what features are being tested and the requirements from v1.5.0. This makes it easy to understand the test coverage at a glance.


23-23: Use of Any type in test file.

Line 23 imports Any from typing. Per coding guidelines for src/omnibase_core/**/*.py, "No Any types allowed" with strict mypy compliance required. However, this file is in tests/ directory, not src/omnibase_core/, so the strict typing requirement may not apply here.

The Any usage in the helper functions (lines 42, 49, 61) is reasonable for test flexibility, allowing tests to pass arbitrary overrides without type constraints.


42-64: LGTM! Well-designed test helpers.

The helper functions get_minimal_reducer_contract_data and create_minimal_reducer_contract provide a clean way to create test fixtures with optional overrides, reducing boilerplate across tests.


66-167: LGTM! Thorough fingerprint validation tests.

Excellent coverage of fingerprint validation edge cases including:

  • Valid formats (basic, zero version, large version, all hex chars)
  • Invalid formats (missing colon, wrong semver, hex length issues, uppercase, non-hex chars, negative version, empty string, special chars, spaces)
  • Optional/default behavior (None allowed, defaults to None)

This provides good confidence in the regex pattern validation.


169-418: LGTM! Comprehensive alias compatibility tests.

The state machine alias tests thoroughly verify:

  • Direct field access via state_machine
  • Alias-based population via state_transitions (dict/YAML compatibility)
  • Both field name and alias working with populate_by_name=True
  • Rejection when both keys are provided simultaneously (extra='forbid' behavior)

The detailed docstring in test_both_state_machine_and_state_transitions_provided is particularly helpful for documenting the expected Pydantic behavior.


420-516: LGTM! Frozen model behavior tests.

Good coverage of immutability verification:

  • Config check for frozen=True
  • Mutation rejection for multiple fields
  • Correlation ID and node_name immutability
  • Important documentation of hashability limitations with nested mutable objects

The test at lines 488-500 correctly documents and tests the expected Pydantic limitation where frozen models with nested mutable objects cannot be hashed.


518-572: LGTM! Version tests validate v1.5.0 contract.

Tests correctly verify:

  • INTERFACE_VERSION is 1.5.0 for both ModelContractReducer and ModelFSMSubcontract
  • Version is proper ModelSemVer instance
  • Version is a class variable (not instance variable)
  • FSM subcontract also has frozen=True

574-640: LGTM! Configuration and serialization tests.

Good coverage of:

  • extra='forbid' rejecting unknown fields
  • populate_by_name=True configuration
  • use_enum_values=False preserving enum objects
  • Serialization including/preserving fingerprint in model_dump and to_yaml

These tests ensure the v1.5.0 configuration behaves as documented.

…es to state_machine [OMN-648]

Breaking Change: All code now uses `state_machine` field directly.
The `state_transitions` alias ONLY works for YAML/dict parsing
(Pydantic alias), NOT for attribute access. No backwards-compatible
property provided - no tech debt until v0.4.0.

Changes:
- Remove @Property state_transitions from ModelContractReducer
- Update node_reducer.py to use state_machine field
- Update mixin_fsm_execution.py docstring examples
- Update mixin_metadata.yaml code examples
@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review: ModelContractReducer v1.5.0 Schema Upgrade [OMN-648]

Summary

Excellent work on this schema upgrade! The implementation is production-ready with comprehensive test coverage (50 tests), clean architectural patterns, and thorough documentation. The changes align perfectly with omnibase_core conventions and NodeReducer v1.0 spec requirements.


✅ Strengths

1. Exceptional Test Coverage ⭐

  • 50 comprehensive tests covering all v1.5.0 features
  • Test categories well-organized: fingerprint validation, aliasing, immutability, serialization
  • Edge cases thoroughly tested (uppercase hex, wrong lengths, invalid patterns)
  • Helper functions (get_minimal_reducer_contract_data) promote DRY testing patterns

2. Excellent Documentation 📚

  • Docstrings clearly explain the purpose of frozen=True with thread-safety rationale
  • fingerprint field has detailed format documentation with examples
  • Defense-in-depth pattern in _validate_fsm_subcontract is well-documented (lines 447-467)
  • Schema version prominently documented in module-level docstrings

3. Backwards Compatibility 🔄

  • state_machine with alias="state_transitions" enables seamless migration
  • populate_by_name=True allows YAML contracts to use either field name
  • Existing contracts work without modification during transition period

4. Clean Architecture 🏗️

  • Changes follow omnibase_core patterns (Protocol-driven DI, strict typing)
  • frozen=True aligns with thread safety requirements from CLAUDE.md
  • Interface version bump to 1.5.0 follows semantic versioning
  • Validation logic in _validate_fsm_subcontract properly separated from FSM model

5. Consistent Updates Across Codebase

  • All references to state_transitions updated to state_machine in:
    • mixin_fsm_execution.py (6 references in docstrings/examples)
    • mixin_metadata.yaml (8 references in usage examples)
    • node_reducer.py (initialization and error messages)
  • No orphaned references left behind

💡 Minor Suggestions (Optional Improvements)

1. Fingerprint Validation Could Be More Explicit

Location: model_contract_reducer.py:133

The regex pattern r"^\d+\.\d+\.\d+:[a-f0-9]{12}$" is correct, but consider adding a field validator for better error messages:

@field_validator('fingerprint')
@classmethod
def validate_fingerprint_format(cls, v: str | None) -> str | None:
    """Validate fingerprint format with descriptive error messages."""
    if v is None:
        return v
    
    if ':' not in v:
        raise ValueError("Fingerprint must contain ':' separator (format: <semver>:<sha256_12>)")
    
    semver_part, hash_part = v.split(':', 1)
    if not re.match(r'^\d+\.\d+\.\d+$', semver_part):
        raise ValueError(f"Invalid semver format: {semver_part} (expected X.Y.Z)")
    
    if not re.match(r'^[a-f0-9]{12}$', hash_part):
        raise ValueError(f"Invalid hash format: {hash_part} (expected 12 lowercase hex chars)")
    
    return v

Why: Current pattern validation works but gives generic "pattern does not match" errors. Custom validator provides actionable feedback for developers.

Priority: Low (current implementation is functional)


2. Consider Type Narrowing in FSM Validation

Location: model_contract_reducer.py:441-467

The check if fsm is None: return at line 462 is redundant because line 405 already ensures self.state_machine is not None before calling this method. Consider using an assertion for clarity:

def _validate_fsm_subcontract(self) -> None:
    fsm = self.state_machine
    assert fsm is not None, "_validate_fsm_subcontract called with None FSM"
    # ... validation logic

Why: Makes the invariant explicit and helps mypy with type narrowing.

Priority: Low (current code is safe and mypy-compliant)


3. Test Missing: Serialization Round-Trip with FSM

Location: test_model_contract_reducer_v150.py

Tests cover serialization basics (lines 471-565) but don't verify round-trip with a full FSM subcontract. Consider adding:

def test_serialization_roundtrip_with_full_fsm(self) -> None:
    """Test that contract with FSM survives model_dump + reconstruction."""
    fsm = create_minimal_fsm_subcontract()
    original = create_minimal_reducer_contract(state_machine=fsm)
    
    # Serialize and reconstruct
    data = original.model_dump()
    reconstructed = ModelContractReducer(**data)
    
    # Verify FSM preserved
    assert reconstructed.state_machine is not None
    assert reconstructed.state_machine.state_machine_name == fsm.state_machine_name
    assert len(reconstructed.state_machine.states) == len(fsm.states)

Why: Ensures complex nested structures survive serialization (important for contract generation).

Priority: Low (current tests are comprehensive, this adds defense-in-depth)


4. Documentation: Clarify Fingerprint Generation

Location: model_contract_reducer.py:120-134

The docstring explains the format but not how to generate it. Consider adding a note:

fingerprint: str | None = Field(
    default=None,
    description=(
        "Contract fingerprint for drift detection. Format: <semver>:<sha256_12> "
        "where semver is the schema version (e.g., '1.5.0') and sha256_12 is the "
        "first 12 hex characters of the SHA256 hash of the canonical contract content. "
        "Example: '1.5.0:a1b2c3d4e5f6'. "
        "Generation: Use 'poetry run onex-cli contract fingerprint <contract.yaml>' "
        "or see docs/guides/CONTRACT_FINGERPRINTING.md for implementation details."
    ),
    pattern=r"^\d+\.\d+\.\d+:[a-f0-9]{12}$",
)

Why: Developers will need guidance on fingerprint generation workflow.

Priority: Low (can be addressed in OMN-260 when contracts are upgraded)


🔒 Security Considerations

✅ No security concerns identified

  • Regex pattern is anchored (^ and $) preventing ReDoS
  • frozen=True prevents mutation-based attacks on contract state
  • No user input directly flows into validation logic

🚀 Performance Considerations

✅ No performance concerns

  • Regex validation is O(1) with fixed-length input
  • frozen=True has negligible overhead (Pydantic optimization)
  • Validation logic runs once during construction

📊 Test Execution

Based on the PR description:

  • ✅ 50 new tests pass
  • ✅ Existing reducer tests pass (11 tests)
  • ✅ FSM mixin tests pass (9 tests)
  • ✅ mypy --strict passes (0 errors)
  • ✅ All pre-commit hooks pass

Coverage: Comprehensive across all v1.5.0 features


📋 Checklist Review

Per CLAUDE.md requirements:

  • ✅ Uses poetry run for all Python commands (test plan)
  • ✅ Strict typing enforced (no Any types introduced)
  • ✅ Follows ONEX architecture patterns (Protocol-driven DI, ModelOnexError)
  • ✅ Comprehensive test coverage (50 tests)
  • ✅ Documentation updated (docstrings, schema version)
  • ✅ No backwards compatibility hacks (uses proper aliasing)
  • ✅ Thread safety addressed (frozen=True)
  • ✅ Error handling uses ModelOnexError (line 418)
  • ✅ Interface version properly bumped (1.5.0)

🎯 Recommendation

APPROVE ✅

This PR is production-ready and can be merged. The suggested improvements above are optional enhancements that can be addressed in future PRs if desired.

Why Approve:

  1. Zero blocking issues - All requirements met
  2. Exceptional quality - Documentation, tests, and architecture are exemplary
  3. Clean migration path - Backwards compatibility ensures smooth transition
  4. Critical prerequisite - Blocks OMN-260 and OMN-257 (contract validation)

Post-Merge Actions:

  1. Consider the optional improvements in a follow-up PR (non-blocking)
  2. Proceed with OMN-260 (upgrade actual contracts)
  3. Implement OMN-257 (contract validation with fingerprint checking)

Reviewed by: Claude Sonnet 4.5 (omnibase_core CLAUDE.md compliant review)
Review Date: 2025-12-10

@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/nodes/node_reducer.py (1)

93-103: FSM initialization correctly targets state_machine; optional refinement for contract loading.

  • The example and __init__ now use contract.state_machine, which aligns with the v1.5.0 contract (with aliasing from state_transitions) and matches the mixin/docs.
  • The extra if self.fsm_contract is not None: guard before initialize_fsm_state is a good safety improvement with the new optional/aliased field.
  • One thing to consider: hasattr(self, "contract") (and then hasattr(self.contract, "state_machine")) will still swallow exceptions raised while loading the contract, treating a failing load as “no FSM present”. Long term, it may be cleaner to access self.contract directly and surface contract load errors explicitly, rather than hiding them behind the debug log. This is pre‑existing behavior but worth tightening when you next touch this path. Based on learnings, this still respects the super().__init__(container) constructor requirement.

Also applies to: 135-154

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c037f3 and de514cf.

📒 Files selected for processing (3)
  • src/omnibase_core/mixins/mixin_fsm_execution.py (5 hunks)
  • src/omnibase_core/mixins/mixin_metadata.yaml (5 hunks)
  • src/omnibase_core/nodes/node_reducer.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{py,pyc,pyi}

📄 CodeRabbit inference engine (CLAUDE.md)

Always use Poetry for Python package management and task execution - never use pip or python directly

Files:

  • src/omnibase_core/nodes/node_reducer.py
  • src/omnibase_core/mixins/mixin_fsm_execution.py
**/{node,nodes}/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/{node,nodes}/*.py: Use ModelONEXContainer (from omnibase_core.models.container.model_onex_container) for dependency injection in node constructors, never ModelContainer[T]
Always call super().init(container) when subclassing NodeCompute, NodeEffect, NodeReducer, or NodeOrchestrator

Files:

  • src/omnibase_core/nodes/node_reducer.py
src/omnibase_core/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/omnibase_core/**/*.py: Use ModelOnexError with EnumCoreErrorCode for all error handling, never generic Exception
Use @standard_error_handling decorator from omnibase_core.decorators.error_handling to eliminate try/catch boilerplate
Use protocol names (e.g., 'ProtocolEventBus') with container.get_service(), never concrete class names
Use EnumNodeKind for high-level architectural classification (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR) and EnumNodeType for specific implementation types
Maintain 100% strict mypy compliance - all functions must have type annotations and zero mypy errors
Do NOT share node instances across threads without explicit synchronization - most ONEX components are not thread-safe by default
Implement mixin system using available mixins (MixinDiscoveryResponder, MixinEventHandler, MixinNodeExecutor, etc.) for cross-cutting concerns
Use ModelEventEnvelope for inter-service communication and event-driven processing patterns
Python version requirement: 3.12+
Structure project following the omnibase_core directory layout with clear separation of concerns (constants, container, decorators, discovery, enums, errors, events, infrastructure, logging, mixins, models, nodes, primitives, types, utils, validation)

Files:

  • src/omnibase_core/nodes/node_reducer.py
  • src/omnibase_core/mixins/mixin_fsm_execution.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Import node classes from omnibase_core.nodes (NodeCompute, NodeEffect, NodeReducer, NodeOrchestrator), not from infrastructure submodules

Files:

  • src/omnibase_core/nodes/node_reducer.py
  • src/omnibase_core/mixins/mixin_fsm_execution.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (CLAUDE.md)

Format code with black and sort imports with isort before committing

Files:

  • src/omnibase_core/nodes/node_reducer.py
  • src/omnibase_core/mixins/mixin_fsm_execution.py
**/*.{yaml,yml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use declarative YAML contracts for node configuration in v0.4.0+ architecture

Files:

  • src/omnibase_core/mixins/mixin_metadata.yaml
🧠 Learnings (1)
📚 Learning: 2025-12-10T21:31:33.541Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T21:31:33.541Z
Learning: Applies to **/{node,nodes}/*.py : Always call super().__init__(container) when subclassing NodeCompute, NodeEffect, NodeReducer, or NodeOrchestrator

Applied to files:

  • src/omnibase_core/nodes/node_reducer.py
🧬 Code graph analysis (1)
src/omnibase_core/nodes/node_reducer.py (3)
src/omnibase_core/models/contracts/subcontracts/model_fsm_subcontract.py (1)
  • ModelFSMSubcontract (39-331)
src/omnibase_core/mixins/mixin_node_setup.py (1)
  • contract (61-64)
src/omnibase_core/mixins/mixin_fsm_execution.py (1)
  • initialize_fsm_state (184-205)
⏰ 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: claude-review
  • GitHub Check: Smoke Tests
🔇 Additional comments (2)
src/omnibase_core/mixins/mixin_fsm_execution.py (1)

75-87: Doc examples correctly migrated to contract.state_machine.

All updated examples now reference contract.state_machine, matching the new contract schema and the NodeReducer usage. No runtime behavior is affected, and the examples remain coherent and accurate.

Also applies to: 131-139, 177-181, 195-200, 217-221

src/omnibase_core/mixins/mixin_metadata.yaml (1)

2570-2584: FSM mixin metadata is consistent with the new state_machine field.

Generated Python patterns now use contract.state_machine, while YAML examples still declare state_transitions, which is appropriate given the aliasing on the contract model. This keeps code generation and docs aligned with v1.5.0 without breaking existing YAML.

Also applies to: 2587-2604, 2607-2619, 2637-2661, 2665-2682, 2687-2703

@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review: ModelContractReducer v1.5.0 Schema Upgrade

Overview

This PR upgrades ModelContractReducer and ModelFSMSubcontract to v1.5.0 schema with fingerprint support, field aliasing, and immutability. The changes are well-structured and thoroughly tested.


✅ Strengths

1. Excellent Test Coverage

  • 50 comprehensive tests in test_model_contract_reducer_v150.py
  • Tests cover all edge cases: fingerprint validation, alias behavior, immutability, serialization
  • Clear test names and documentation

2. Backwards Compatibility

  • The state_machine field with alias=state_transitions provides seamless backwards compatibility
  • populate_by_name=True enables both field names in YAML contracts
  • Migration path is clear and well-documented

3. Thread Safety & Immutability

  • frozen=True on both models prevents accidental mutation
  • Clear documentation about the limitations (nested mutable objects still not hashable)
  • Test test_frozen_model_not_hashable_with_nested_mutable_objects documents expected behavior

4. Documentation Quality

  • Comprehensive docstrings explaining the v1.5.0 changes
  • Clear examples in model validators
  • Defense-in-depth pattern explanation in _validate_fsm_subcontract is excellent

5. Consistent Updates

  • All references to state_transitions updated to state_machine across mixin_fsm_execution.py, mixin_metadata.yaml, and node_reducer.py
  • Interface versions correctly bumped to 1.5.0

🔍 Observations & Suggestions

1. Fingerprint Pattern Validation (Minor)
The fingerprint pattern is well-designed but could be clearer. The description mentions SHA256 hash but the pattern only captures 12 hex chars. Consider clarifying whether this is the first 12 chars of a full SHA256 hash (64 chars) or a truncated/shortened hash. This would help developers understand how to generate valid fingerprints.

2. Defense-in-Depth Documentation (Positive)
The explanation in _validate_fsm_subcontract about why validation is duplicated is excellent and should be used as a template for other contract validators.

3. FSM Contract Loading Logic (Minor)
The nested null check in NodeReducer.init is defensive programming and appropriate given the Optional typing. Recommendation: Keep as-is for safety.


🔒 Security Considerations

✅ No security concerns identified

  • Fingerprint validation uses a strict regex pattern (no injection risks)
  • Immutability prevents accidental state corruption
  • Thread safety improvements reduce race condition risks

🎯 Performance Considerations

✅ No performance regressions expected

  • frozen=True may provide slight performance improvements (Pydantic can optimize frozen models)
  • Alias resolution has minimal overhead
  • Fingerprint validation is a simple regex match

🧪 Test Coverage Assessment

Excellent coverage across all v1.5.0 changes:

  • Fingerprint validation: ✅ Comprehensive (14 tests)
  • State machine alias: ✅ Comprehensive (10 tests)
  • Frozen behavior: ✅ Comprehensive (9 tests)
  • Interface version: ✅ Adequate (3 tests)
  • Serialization: ✅ Comprehensive (14 tests)

Total: 50 tests for v1.5.0 changes + existing reducer tests passing


📋 Code Quality Checklist

  • ✅ Follows omnibase_core conventions (CLAUDE.md)
  • ✅ Strict typing maintained (no Any types)
  • ✅ Comprehensive docstrings
  • ✅ Consistent naming conventions
  • ✅ Error handling with ModelOnexError
  • ✅ Pre-commit hooks passing (mypy --strict, black, isort)
  • ✅ Thread safety considerations documented
  • ✅ Backwards compatibility preserved

🎯 Recommendations

Priority: Low (All optional improvements)

  1. Clarify fingerprint hash algorithm in field description (SHA256 full hash → 12 char truncation)
  2. Consider adding integration test showing end-to-end contract loading with fingerprint validation
  3. Document migration path for existing contracts (how to generate fingerprints for legacy contracts)

✅ Final Assessment

LGTM with minor suggestions ✨

This is a high-quality PR with:

  • Excellent test coverage (50 new tests)
  • Clear backwards compatibility strategy
  • Comprehensive documentation
  • No breaking changes
  • Thread safety improvements

The suggestions above are minor documentation enhancements and do not block merging.


📚 References

  • Linear Ticket: OMN-648
  • Blocks: OMN-260 (contract upgrades), OMN-257 (contract validation)
  • Architecture: Follows ONEX Four-Node Architecture patterns
  • Testing: Exceeds 60% coverage requirement

Recommendation: ✅ Approve and merge (after addressing optional suggestions if desired)


Reviewed using omnibase_core CLAUDE.md v0.4.0 guidelines

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