Skip to content

refactor(registry): load handler routing from contract.yaml [OMN-1316] - #153

Merged
jonahgabriel merged 8 commits into
mainfrom
jonah/omn-1316-load-subcontract-from-contractyaml-instead-of-programmatic
Jan 15, 2026
Merged

jonahgabriel merged 8 commits into
mainfrom
jonah/omn-1316-load-subcontract-from-contractyaml-instead-of-programmatic

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jan 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Extract handler routing loader to shared utility (runtime/contract_loaders/handler_routing_loader.py)
  • Update registry to dynamically load handlers from contract.yaml using Handler Plugin Loader pattern
  • Eliminate dual source of truth between contract.yaml and hardcoded Python imports

Changes

File Change
runtime/contract_loaders/__init__.py New package exports
runtime/contract_loaders/handler_routing_loader.py New shared utility for loading handler routing
nodes/node_registration_orchestrator/node.py Uses shared utility via thin wrapper
nodes/node_registration_orchestrator/registry/...py Contract-driven handler loading
tests/unit/runtime/contract_loaders/ 35 new tests

Approach

Per ticket discussion:

  1. Handler Plugin Loader pattern - Aligns with platform direction (contract-driven, standardized loader)
  2. Keep DI/constructor params - Contract describes what is wired, not a second DI container
  3. Shared utility - load_handler_routing_subcontract() extracted and made public

Test plan

  • All 99 related tests pass
  • New test suite covers happy path and error cases
  • Type checking passes (mypy)
  • Existing routing tests still pass

Closes OMN-1316

Summary by CodeRabbit

  • New Features

    • Declarative handler routing via contract.yaml with public loader utilities and contract-driven handler discovery.
  • Refactor

    • Registration now uses dynamic, contract-driven handler loading with namespace allowlisting and fail-fast validation.
  • Chores

    • Centralized contract loading, enforced file-size/security checks, improved error messages and warnings for special-case handlers.
  • Tests

    • Extensive unit and integration tests covering loader behavior, edge cases, routing rules, and file-size enforcement.
  • Documentation

    • Updated docstrings/comments and minor validation exemption metadata updates.

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

Replace programmatic handler construction with contract-driven loading
using the Handler Plugin Loader pattern. This eliminates the dual source
of truth between contract.yaml and hardcoded Python imports.

Changes:
- Extract handler routing loader to shared utility at
  runtime/contract_loaders/handler_routing_loader.py
- Update node.py to use shared utility via thin wrapper
- Refactor registry to dynamically load handlers from contract.yaml
- Add comprehensive test suite (35 new tests)

The registry now:
- Loads handler class paths from contract.yaml handler_routing section
- Uses importlib for dynamic class loading
- Keeps DI/constructor params for dependency injection
- Validates protocol compliance before registration
@linear

linear Bot commented Jan 15, 2026

Copy link
Copy Markdown

OMN-1316

@coderabbitai

coderabbitai Bot commented Jan 15, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Centralizes handler-routing contract parsing into a new runtime contract loader; node orchestrator delegates subcontract loading to it; registry now dynamically imports, instantiates, and validates handlers from contract.yaml using an allowlist and dependency map, replacing hard-coded handler wiring.

Changes

Cohort / File(s) Summary
Contract Loader Package
src/omnibase_infra/runtime/contract_loaders/__init__.py, src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
New package/module that parses and validates contract.yaml, enforces a 10MB file-size limit, validates routing strategies, converts CamelCase→kebab-case (convert_class_to_handler_key), and exposes load_handler_routing_subcontract and load_handler_class_info_from_contract.
Node Orchestrator Refactor
src/omnibase_infra/nodes/node_registration_orchestrator/node.py
Removed local YAML parsing and helper conversion; _create_handler_routing_subcontract() now delegates to shared load_handler_routing_subcontract() (same signature, simplified implementation).
Registry Dynamic Loading
src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
Adds ALLOWED_NAMESPACES and _load_handler_class(); uses load_handler_class_info_from_contract to drive dynamic imports, applies namespace allowlist, maps handler constructor dependencies, instantiates handlers, validates conformance to ProtocolMessageHandler, and replaces prior static handler wiring (including heartbeat handling paths and richer error contexts).
Tests: Contract Loader
tests/unit/runtime/contract_loaders/__init__.py, tests/unit/runtime/contract_loaders/conftest.py, tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
New test package with extensive fixtures and unit/integration tests covering conversion, valid/invalid contracts, edge cases, file-size enforcement, routing strategy validation, and integration expectations.
Minor docs/comment changes
src/omnibase_infra/runtime/handler_contract_source.py, src/omnibase_infra/validation/validation_exemptions.yaml
Updated TODO/task id and expanded comment in handler_contract_source; revised exemption reasoning and ticket id in validation_exemptions.yaml.

Sequence Diagram(s)

sequenceDiagram
    participant Registry as RegistryOrchestrator
    participant SharedLoader as ContractLoader
    participant ContractFile as "contract.yaml"
    participant Importer as DynamicImporter
    participant HandlerClass as HandlerClass
    participant HandlerInst as HandlerInstance

    Registry->>SharedLoader: load_handler_class_info_from_contract(path)
    SharedLoader->>ContractFile: read & parse handler_routing
    SharedLoader-->>Registry: return list of {handler_module, handler_class, routing_key}

    loop for each handler entry
        Registry->>Importer: import module.class (namespace allowlist)
        Importer-->>Registry: class object or error
        Registry->>HandlerClass: instantiate(class, dependencies...)
        HandlerClass-->>HandlerInst: created
        Registry->>Registry: validate protocol & register handler
    end

    Registry-->>Registry: registration complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰
I nibble through YAML rows so keen,
Map names to keys, keep everything clean.
Handlers spring from contract decree,
Namespaces hold them safe and free.
A hop, a load, and systems agree!


🧹 Recent nitpick comments
src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (1)

429-451: Consider edge case: handlers with no dependencies.

The check if not deps: (line 432) would raise an error for handlers that legitimately have no constructor arguments. While all current handlers require at least projection_reader, this could be a future pitfall.

Consider distinguishing between "handler not in map" (missing entry) vs "handler has empty dependencies" (legitimate). This could be achieved by using a sentinel value or checking handler_class_name not in handler_dependencies instead.

♻️ Proposed fix
-            deps = handler_dependencies.get(handler_class_name, {})
-            if not deps:
+            if handler_class_name not in handler_dependencies:
                 ctx = ModelInfraErrorContext.with_correlation(
                     transport_type=EnumInfraTransportType.RUNTIME,
                     operation="create_registry",
                     target_name="RegistryInfraNodeRegistrationOrchestrator",
                 )
                 raise ProtocolConfigurationError(
                     f"No dependency configuration found for handler '{handler_class_name}'. "
                     ...
                 )
+            deps = handler_dependencies[handler_class_name]

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 55ae48e and 7fae801.

📒 Files selected for processing (2)
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Never use Any type - use object for generic payloads in function parameters and return types
Use X | None (PEP 604) instead of Optional[X] for nullable types
All services must use ModelONEXContainer for dependency injection via __init__(self, container: ModelONEXContainer)
Use @allow_any decorator with documented reason as exemption mechanism for Any type violations
Use JsonType from omnibase_core.types as the canonical type alias for JSON-compatible values
Use ModelEventEnvelope[object] for generic dispatcher interfaces and object for generic payloads
Infrastructure error handling must use OnexError base class and never expose passwords, API keys, PII, or connection strings with credentials in error messages
Use InfraConnectionError, InfraTimeoutError, InfraAuthenticationError, InfraUnavailableError for transport failures with proper ModelInfraErrorContext
Correlation IDs must be propagated from incoming requests, auto-generated with uuid4() if missing, and included in all error contexts
External service adapters must implement MixinAsyncCircuitBreaker with appropriate threshold and reset_timeout configuration
Protocol resolution must use duck typing via protocols, never use isinstance checks

Files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
**/registry_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Registry files must follow naming convention: node-specific registries use registry_infra_<node_name>.py → RegistryInfra<NodeName>, standalone registries use registry_<purpose>.py → Registry<Purpose>

Files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
**/handler_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Handlers must NOT have direct event bus access - only orchestrators may have bus parameters and publish events

Files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
🧠 Learnings (33)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contracts/ : Organize contract subcomponents into separate files (contract_actions.yaml, contract_models.yaml, contract_validation.yaml, etc.) and reference them from main contract.yaml
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/nodes/**/*.py : Use NodeEffect, NodeCompute, NodeReducer, and NodeOrchestrator base classes with declarative YAML contracts; import from omnibase_core.nodes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/**/*.py : Use `omnibase_infra` handlers for OmniIntelligence queries via HttpRestAdapter envelope pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/registry_*.py : Registry files must follow naming convention: node-specific registries use `registry_infra_<node_name>.py` → `RegistryInfra<NodeName>`, standalone registries use `registry_<purpose>.py` → `Registry<Purpose>`

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/reducer/**/*.py : FSM behavior must be defined in YAML contracts, not as multiple Python reducer classes; use one NodeReducer class that loads different FSM contracts

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contract.yaml : The main contract.yaml file serves as the source of truth for node interfaces and should reference subcontracts using $ref patterns

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contracts/ : Organize contract subcomponents into separate files (contract_actions.yaml, contract_models.yaml, contract_validation.yaml, etc.) and reference them from main contract.yaml

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contract.yaml : Use shared schema references with project root paths in contract definitions (e.g., schemas/onex_field_model.schema.yaml, schemas/semver_model.schema.yaml)

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/*.py : Never use `Any` type - use `object` for generic payloads in function parameters and return types

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/protocols/protocol_*.py : Avoid using Any, dict, or primitive types in protocol signatures; use the strongest typing possible with Pydantic models

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-15T13:12:08.552Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.552Z
Learning: Applies to **/*.{py,pyi} : Prefer interface/protocol-based type annotations over concrete types; use duck typing and avoid isinstance checks

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-24T16:33:32.747Z
Learnt from: CR
Repo: OmniNode-ai/omninode_bridge PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T16:33:32.747Z
Learning: Applies to **/{models,protocols}/{model_*,protocol_*}.py : Avoid using Any, dict, or primitive types in model and protocol definitions; use strongest typing possible

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/protocols/protocol_*.py : Use Protocol from typing module for all interface definitions; never use ABC (Abstract Base Classes) for service interfaces

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-06T17:57:00.689Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_spi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T17:57:00.689Z
Learning: Applies to src/omnibase_spi/protocols/handlers/**/*.py : Protocol naming convention: Handler protocols must follow `Protocol{Type}Handler` pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/*.py : Correlation IDs must be propagated from incoming requests, auto-generated with `uuid4()` if missing, and included in all error contexts

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-03T16:55:49.755Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T16:55:49.755Z
Learning: Applies to agents/**/*.py : Use correlation_id UUID for end-to-end traceability across all agent routing, manifest injection, and execution events

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-03T16:55:49.755Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T16:55:49.755Z
Learning: Applies to agents/**/*.py : Implement agent observability using three-layer traceability with correlation_id tracking through agent_routing_decisions, agent_manifest_injections, and agent_execution_logs tables

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-29T17:13:38.776Z
Learnt from: CR
Repo: OmniNode-ai/omniarchon PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T17:13:38.776Z
Learning: Applies to {services/**/*.py,scripts/**/*.py} : Use correlation IDs for distributed logging across services. Implement log tracing with `python3 scripts/view_pipeline_logs.py --correlation-id X`.

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/*.py : Use `InfraConnectionError`, `InfraTimeoutError`, `InfraAuthenticationError`, `InfraUnavailableError` for transport failures with proper `ModelInfraErrorContext`

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/models/error_codes.py : All ONEX node error handling must use auto-generated error codes defined in `models/error_codes.py` from contract definitions

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/protocols/protocol_*.py : Use Protocol for tool interfaces and plugin APIs based on method shape (structural typing), not Pydantic models with inheritance

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/effect/**/*.py : Use handler envelopes from `omnibase_infra` for all I/O operations (HTTP, database, Kafka) instead of custom clients

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/model_*.py : Pydantic workaround for `Any` type must include `# NOTE:` comment documenting the reason when technically required

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-28T18:58:53.781Z
Learnt from: CR
Repo: OmniNode-ai/omninode_bridge PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-28T18:58:53.781Z
Learning: Applies to **/*.py : Use proper union type definitions and discriminated unions where appropriate

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-07T17:50:13.678Z
Learnt from: CR
Repo: OmniNode-ai/omniintelligence PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T17:50:13.678Z
Learning: Applies to **/contracts/*.yaml : Validate contracts using the contract linter tool: python -m omniintelligence.tools.contract_linter for YAML contract definitions

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-24T16:32:55.606Z
Learnt from: CR
Repo: OmniNode-ai/omninode_bridge PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T16:32:55.606Z
Learning: Applies to **/*contract*.yaml : All ONEX nodes must have validated YAML contracts following the contract-driven development pattern with input_state and output_state schema definitions

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/models/model_contract_*.py : Models must be auto-generated from contract.yaml files, never hand-written

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
🧬 Code graph analysis (2)
src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (4)
src/omnibase_infra/enums/enum_infra_transport_type.py (1)
  • EnumInfraTransportType (32-60)
src/omnibase_infra/models/errors/model_infra_error_context.py (2)
  • ModelInfraErrorContext (17-96)
  • with_correlation (71-96)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (1)
  • load_handler_class_info_from_contract (384-455)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (5)
src/omnibase_infra/enums/enum_infra_transport_type.py (1)
  • EnumInfraTransportType (32-60)
src/omnibase_infra/models/errors/model_infra_error_context.py (2)
  • ModelInfraErrorContext (17-96)
  • with_correlation (71-96)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/models/routing/model_routing_entry.py (1)
  • ModelRoutingEntry (14-49)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
🔇 Additional comments (13)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (6)

1-79: LGTM!

The module docstring is comprehensive with clear usage examples, and the imports are appropriate. The MAX_CONTRACT_FILE_SIZE_BYTES constant is well-documented with security rationale and error code.


82-122: LGTM!

The file size check correctly uses FILESYSTEM transport type and with_correlation() for error context. The early return on FileNotFoundError allows the caller to provide more specific error context.


167-241: LGTM!

The validation flow is well-structured with security-first ordering (size check before YAML parse). Error messages are actionable with error codes, and YAML error sanitization prevents content leakage.


244-279: LGTM!

The VALID_ROUTING_STRATEGIES correctly contains only the implemented strategy. The convert_class_to_handler_key function handles CamelCase to kebab-case conversion including edge cases like consecutive uppercase letters (e.g., "HTTP").


281-381: LGTM!

The function follows the Handler Plugin Loader pattern correctly. Invalid routing strategies are handled gracefully with warnings and fallback to the default. The comment about validation order (lines 361-363) clarifies the design decision.


384-464: LGTM!

The load_handler_class_info_from_contract function correctly reuses the shared YAML loading helper and returns appropriately typed data. The __all__ exports match the documented public API.

src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (7)

29-61: Excellent documentation of the design trade-off.

The detailed explanation of why explicit dependency mapping is preferred over auto-discovery (type safety, testability, security, auditability) is valuable for maintainability. The maintenance requirement section clearly explains what developers need to do when adding handlers.


103-110: LGTM!

The ALLOWED_NAMESPACES tuple correctly restricts dynamic imports to trusted packages, preventing arbitrary code execution via malicious contract.yaml configurations. The error code documentation is helpful.


113-154: LGTM!

The duck typing approach for protocol validation aligns with the coding guidelines (no isinstance checks). All required ProtocolMessageHandler members are verified with clear return semantics.


156-234: LGTM!

The namespace allowlist validation (lines 176-190) is a critical security control. The return type type[object] correctly avoids implicit Any. All error paths include correlation IDs via with_correlation() and use appropriate error codes.


369-405: Well-designed handler dependency map with clear documentation.

The explicit dependency map with detailed comments (lines 369-388) explains the design trade-off clearly. The maintenance requirement is well-documented, and the fail-fast error message (lines 438-451) provides actionable guidance for adding new handlers.


453-478: Clear documentation of dependency filtering rationale.

The inline comments thoroughly explain the filtering logic: projection_reader is always passed for fail-fast handler validation, while optional dependencies (projector, consul_handler) are only passed when provided to avoid overriding handler defaults.


480-523: LGTM!

The handler instantiation, protocol validation, and registration flow is comprehensive. Error handling wraps TypeError with actionable context, and the registry is properly frozen for thread safety.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

PR Review: Contract-Driven Handler Loading [OMN-1316]

Summary

This PR successfully refactors handler registry loading to use contract-driven plugin loading, eliminating the dual source of truth between contract.yaml and hardcoded Python imports. The implementation is well-structured with comprehensive test coverage.


✅ Strengths

Architecture & Design

  • Excellent separation of concerns: Extracted load_handler_routing_subcontract() to a shared utility that can be reused across orchestrators
  • Contract-first approach: Aligns perfectly with ONEX declarative patterns - handlers are now discovered from YAML
  • Proper layering: Registry uses contract loader for routing config, then loads handler classes dynamically
  • Clear abstraction boundaries: node.py delegates to shared utility, registry handles DI and instantiation

Code Quality

  • Strong error handling: Comprehensive ProtocolConfigurationError coverage with proper context
  • Security-conscious: Uses yaml.safe_load(), sanitizes error messages, validates protocol compliance
  • Well-documented: Excellent docstrings explaining contract structure, transformation logic, and design decisions
  • Type safety: No Any types detected (verified via grep) - uses proper type annotations throughout

Testing

  • Impressive test coverage: 35 new tests (611 lines) covering happy path, error cases, and edge cases
  • Good test organization: Clear test class grouping (conversion, happy path, errors, edge cases)
  • Realistic fixtures: conftest.py provides reusable YAML fixtures for various scenarios

🔍 Issues & Concerns

1. CRITICAL: Security - Arbitrary Code Execution via Dynamic Import

Location: registry_infra_node_registration_orchestrator.py:126

module = importlib.import_module(module_path)

Risk: The registry loads handler modules directly from contract.yaml without namespace validation. A malicious or compromised contract could import arbitrary modules.

Attack Vector:

handler:
  name: "MaliciousHandler"
  module: "os"  # Or any module with side effects on import

Recommendation:
According to CLAUDE.md Handler Plugin Loader Security section, you should:

  1. Enable namespace allowlisting (mentioned but not implemented here)
  2. Add allowed_namespaces parameter to registry factory
  3. Validate module paths before import:
ALLOWED_NAMESPACES = [
    "omnibase_infra.handlers.",
    "omnibase_infra.nodes.",
]

def _load_handler_class(class_name: str, module_path: str, allowed_namespaces: list[str]) -> type:
    if not any(module_path.startswith(ns) for ns in allowed_namespaces):
        raise ProtocolConfigurationError(
            f"Handler module {module_path} not in allowed namespaces"
        )
    # ... rest of implementation

Severity: High - This is production code that loads from configuration files


2. Code Duplication: YAML Loading Logic

Locations:

  • handler_routing_loader.py:139-193 (loads contract for routing)
  • registry_infra_node_registration_orchestrator.py:182-227 (loads contract for handler list)

Issue: Both functions parse contract.yaml with identical error handling patterns. This violates DRY and makes maintenance harder.

Impact:

  • Changes to YAML parsing need updates in 2 places
  • Inconsistent error messages possible
  • Larger code footprint

Recommendation: Extract common YAML loading logic:

def _load_contract_yaml(contract_path: Path) -> dict:
    """Load and validate contract.yaml with standardized error handling."""
    # Common loading + error handling logic
    pass

Then both functions use this shared helper.


3. Brittle: Hardcoded Dependency Map

Location: registry_infra_node_registration_orchestrator.py:385-401

handler_dependencies: dict[str, dict[str, object]] = {
    "HandlerNodeIntrospected": {
        "projection_reader": projection_reader,
        "projector": projector,
        "consul_handler": consul_handler,
    },
    # ... 3 more handlers
}

Issues:

  1. Out-of-band configuration: Dependencies are in Python, not contract.yaml (creates new dual source of truth)
  2. Fragile: Adding a new handler requires code changes to registry
  3. No validation: If contract.yaml references a handler not in this map, you get a runtime error
  4. Inconsistent with PR goals: You eliminated one dual source (handler imports) but created another (dependencies)

Better Approach (from CLAUDE.md):
The contract already has handler_dependencies section (lines 367-374)! Consider using that:

handler_routing:
  handlers:
    - event_model: { name: "ModelNodeIntrospectionEvent" }
      handler: 
        name: "HandlerNodeIntrospected"
        dependencies:
          - projection_reader
          - projector
          - consul_handler

Then construct the dependency map from contract data at runtime.


4. Inconsistent Transport Type in Error Context

Location: handler_routing_loader.py:144

ctx = ModelInfraErrorContext(
    transport_type=EnumInfraTransportType.DATABASE,  # ← Wrong\!
    operation="load_handler_routing_contract",
)

Issue: Loading a YAML file is not a database operation. This should be RUNTIME (as used in the registry file:188).

Impact: Misleading error categorization, incorrect monitoring/alerting

Fix: Change all instances in handler_routing_loader.py to EnumInfraTransportType.RUNTIME


5. Missing Protocol Validation for Loaded Handlers

Location: Registry validates handlers AFTER instantiation (line 464), but the _load_handler_class function doesn't check if the class implements the protocol.

Risk: You could load a class that's not a handler, waste resources instantiating it, then fail.

Recommendation: Add early validation in _load_handler_class:

handler_class: type = getattr(module, class_name)

# Quick check: class should have handle method
if not callable(getattr(handler_class, "handle", None)):
    raise ProtocolConfigurationError(
        f"{class_name} does not appear to be a handler (missing handle method)"
    )

return handler_class

6. Ambiguous Error Message

Location: registry_infra_node_registration_orchestrator.py:433

raise ProtocolConfigurationError(
    f"No dependency configuration found for handler {handler_class_name}. "
    "Update handler_dependencies map with required constructor arguments.",
)

Issue: This error tells developers to "update handler_dependencies map" but doesn't say WHERE that map is (it's in the same file, but not obvious to someone debugging at 2am).

Recommendation: Include file location:

f"No dependency configuration found for handler {handler_class_name}. "
f"Update handler_dependencies map in {__file__} with required constructor arguments."

7. Test Coverage Gap: Dynamic Import Failures

Observation: Tests cover ModuleNotFoundError and ImportError paths (good!), but don't test:

  1. Module exists but has side effects on import (logs, network calls)
  2. Handler class exists but __init__ raises unexpected exception types
  3. Thread safety of importlib.import_module (does the registry handle concurrent calls?)

Recommendation: Add integration tests that:

  • Mock importlib.import_module to simulate various failure modes
  • Verify that import errors don't corrupt the registry (partial registration state)

8. Performance: Repeated Contract Parsing

Issue: If you call create_registry() multiple times (e.g., in tests), each call re-parses contract.yaml.

Impact: Minor - YAML parsing is fast, but this violates single-responsibility (registry shouldn't own contract loading)

Suggestion: Consider memoizing _load_handler_routing_from_contract or injecting parsed config


🎨 Style & Convention

Minor Issues

  1. Inconsistent comment style:

    • handler_routing_loader.py:97: Uses # inline comments
    • Registry file uses block comments
    • Recommendation: Pick one style for consistency
  2. Magic string (registry_infra_node_registration_orchestrator.py:442):

    if v is not None or k == "projection_reader"  # ← magic string

    Recommendation: Extract constant REQUIRED_DEPENDENCY = "projection_reader"

  3. Long function (create_registry is 127 lines):
    Could extract "load and instantiate handler" logic to a helper function


📋 Testing Notes

What's Tested Well

  • ✅ Class name to handler key conversion (all edge cases)
  • ✅ Contract loading errors (missing file, invalid YAML, empty file)
  • ✅ Malformed contracts (missing sections, incomplete entries)
  • ✅ Happy path (valid contract produces correct routing)

What Could Use More Tests

  • ⚠️ Registry factory with various dependency configurations
  • ⚠️ Handler instantiation failures (TypeError, ValueError)
  • ⚠️ Protocol validation failures (missing methods)
  • ⚠️ Concurrent registry creation (thread safety)
  • ⚠️ Integration test: end-to-end handler loading from real contract

🔐 Security Checklist (Per CLAUDE.md)

Control Status Notes
YAML safe loading ✅ Uses yaml.safe_load()
File size limits ❌ No 10MB limit enforced
Protocol validation ✅ Validates 5 ProtocolHandler methods
Namespace allowlisting ❌ Not implemented (CRITICAL)
Error containment ✅ Exceptions don't crash system
Correlation tracking ⚠️ Not present in contract loader

Action Required: Implement namespace allowlisting before production deployment.


🎯 Recommendations Priority

Priority Issue Effort
P0 (Blocker) Security: Add namespace allowlisting Medium
P1 (High) Fix: Wrong transport type in errors Low
P1 (High) Fix: Extract common YAML loading logic Medium
P2 (Medium) Refactor: Move dependency config to contract High
P2 (Medium) Test: Add integration tests for registry Medium
P3 (Low) Style: Extract magic strings to constants Low

✨ Overall Assessment

Score: 7.5/10

Verdict: Approve with required security fixes before merge.

This is a solid refactoring that achieves the core goal (eliminate dual source of truth for handler routing). The code is well-structured, properly tested, and follows ONEX patterns. However, the namespace allowlisting security gap is a blocker for production use according to CLAUDE.md's Handler Plugin Loader Security guidelines.

The hardcoded dependency map is a design concern (creates a new dual source of truth), but it's acceptable as a transitional step - the contract already has the structure to support dependency declaration.

Recommended next steps:

  1. Before merge: Add namespace allowlisting parameter to _load_handler_class
  2. Follow-up PR: Extract dependency configuration to contract.yaml
  3. Follow-up PR: Add file size limits and correlation ID tracking per CLAUDE.md security checklist

Great work on the comprehensive tests and clear documentation! 🚀

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py`:
- Around line 163-249: Replace the duplicated YAML parsing in
_load_handler_routing_from_contract by reusing the shared loader: import
load_handler_routing_subcontract from handler_routing_loader.py and call it to
obtain handler keys, then either (preferred) extend
load_handler_routing_subcontract to also return the handler "module" field so
this registry can get fully qualified module paths, or (alternative) call the
shared loader for names and load the raw contract.yaml only to extract module
fields (keeping all YAML parsing centralized). Update
_load_handler_routing_from_contract to remove its own yaml.safe_load logic and
instead map the shared loader's output (plus module info if you extended the
shared loader) to the expected list of {"handler_class": ..., "handler_module":
...}.

In `@src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py`:
- Around line 143-147: Replace EnumInfraTransportType.DATABASE with
EnumInfraTransportType.FILESYSTEM in every ModelInfraErrorContext created inside
the load_handler_routing_contract flow; specifically update the transport_type
argument for the ModelInfraErrorContext instances tied to
operation="load_handler_routing_contract" (the ones around the contract_path,
parsing, validation, and any filesystem read error handlers) so the error
context correctly reflects local filesystem transport. Ensure all four
ModelInfraErrorContext calls in this function use
EnumInfraTransportType.FILESYSTEM.
🧹 Nitpick comments (5)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (2)

234-239: Consider validating routing_strategy value before constructing the model.

The ModelRoutingSubcontract.routing_strategy field is typed as Literal["payload_type_match"], so Pydantic will raise a validation error if an invalid value is passed. However, the current code passes the raw string from YAML directly. If the contract contains an unsupported strategy (e.g., "round_robin"), the Pydantic ValidationError may be less informative than a custom ProtocolConfigurationError with context.

This is optional—Pydantic validation will still catch invalid values, but a custom error would provide better context.


139-141: Consider using FileRegistry for contract loading consistency.

Based on learnings, FileRegistry from omnibase_core is the canonical pattern for loading YAML contracts, providing structured error codes like FILE_NOT_FOUND, CONFIGURATION_PARSE_ERROR, etc. The current implementation provides equivalent functionality but diverges from the established pattern.

This is optional since the current error handling is robust and the learning applies primarily to omnibase_core files.

src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (2)

385-401: Handler dependencies map requires code changes for new handlers.

The handler_dependencies map hardcodes constructor arguments for each handler. Adding a new handler to contract.yaml requires updating this map, which partially undermines the "contract-driven" goal.

Consider documenting this coupling in the docstring or exploring ways to make dependency wiring more declarative (e.g., defining dependencies in the contract or using inspection). This is optional given the current architecture.


442-446: Filter logic clarification for optional dependencies.

The condition if v is not None or k == "projection_reader" ensures projection_reader is always passed even if None. However, projection_reader is a required parameter to create_registry (not Optional), so it should never be None at this point. The defensive check is harmless but the comment could clarify this invariant.

tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (1)

132-139: Weak assertion for underscore handling test.

The assertion assert "_" in result or "-" in result doesn't definitively verify the expected behavior. If the function's behavior with underscores is implementation-dependent, consider either:

  1. Pinning down the expected behavior and testing for it explicitly
  2. Documenting why the behavior is intentionally undefined
♻️ Suggested improvement
     def test_underscore_preserved(self) -> None:
         """Test that underscores are preserved (not converted)."""
         from omnibase_infra.runtime.contract_loaders import convert_class_to_handler_key
 
-        # Underscores are not typical in class names but should be preserved
-        result = convert_class_to_handler_key("My_Handler")
-        assert "_" in result or "-" in result  # Implementation dependent
+        # Underscores are not typical in class names; verify actual behavior
+        result = convert_class_to_handler_key("My_Handler")
+        # Pin down expected behavior - adjust based on actual implementation
+        assert result == "my_-handler" or result == "my-_handler", (
+            f"Unexpected underscore handling: {result}"
+        )
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 49c7581 and 0b918f0.

📒 Files selected for processing (7)
  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/__init__.py
  • tests/unit/runtime/contract_loaders/conftest.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Never use Any type - use object for generic payloads in function parameters and return types
Use X | None (PEP 604) instead of Optional[X] for nullable types
All services must use ModelONEXContainer for dependency injection via __init__(self, container: ModelONEXContainer)
Use @allow_any decorator with documented reason as exemption mechanism for Any type violations
Use JsonType from omnibase_core.types as the canonical type alias for JSON-compatible values
Use ModelEventEnvelope[object] for generic dispatcher interfaces and object for generic payloads
Infrastructure error handling must use OnexError base class and never expose passwords, API keys, PII, or connection strings with credentials in error messages
Use InfraConnectionError, InfraTimeoutError, InfraAuthenticationError, InfraUnavailableError for transport failures with proper ModelInfraErrorContext
Correlation IDs must be propagated from incoming requests, auto-generated with uuid4() if missing, and included in all error contexts
External service adapters must implement MixinAsyncCircuitBreaker with appropriate threshold and reset_timeout configuration
Protocol resolution must use duck typing via protocols, never use isinstance checks

Files:

  • tests/unit/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/conftest.py
  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
**/node.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/node.py: Node files must be named node.py with class name Node<Name><Type> and must be declarative with no custom logic
All nodes must extend base classes from omnibase_core.nodes (NodeOrchestrator, NodeEffect, NodeCompute, NodeReducer) with container injection and no custom logic in node.py

Files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
**/handler_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Handlers must NOT have direct event bus access - only orchestrators may have bus parameters and publish events

Files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
**/registry_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Registry files must follow naming convention: node-specific registries use registry_infra_<node_name>.py → RegistryInfra<NodeName>, standalone registries use registry_<purpose>.py → Registry<Purpose>

Files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
🧠 Learnings (18)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/models/model_contract_*.py : All ONEX node auto-generated Pydantic models must be organized in a `models/` directory with files for state.py, model_contract_actions.py, model_contract_models.py, model_contract_validation.py, model_contract_cli.py (optional), model_contract_capabilities.py (optional), and error_codes.py, generated from the corresponding contract definitions

Applied to files:

  • tests/unit/runtime/contract_loaders/__init__.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/node_tests/**/*.py : All ONEX node tests must be organized in a `node_tests/` directory using scenario-driven testing patterns with fixture-injected tests

Applied to files:

  • tests/unit/runtime/contract_loaders/__init__.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • tests/unit/runtime/contract_loaders/conftest.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-30T21:55:10.298Z
Learnt from: CR
Repo: OmniNode-ai/omninode_bridge PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:55:10.298Z
Learning: Applies to src/omninode_bridge/nodes/**/contract.yaml : All contract YAML files for ONEX v2.0 nodes MUST define subcontract references, input/output models, and FSM configurations. Use YAML 1.2 syntax.

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/nodes/**/*.py : Use NodeEffect, NodeCompute, NodeReducer, and NodeOrchestrator base classes with declarative YAML contracts; import from omnibase_core.nodes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/reducer/**/*.py : FSM behavior must be defined in YAML contracts, not as multiple Python reducer classes; use one NodeReducer class that loads different FSM contracts

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/conftest.py
  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node subcontracts must be organized in a `contracts/` subdirectory within the versioned implementation directory with separate files for contract_actions.yaml, contract_models.yaml, contract_validation.yaml, contract_cli.yaml (optional), and contract_capabilities.yaml (optional)

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contract.yaml : All ONEX node contract definitions must follow the linked document architecture pattern with contract.yaml linking to node_config.yaml and deployment_config.yaml as associated documents

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/nodes/node_orchestrator.py : Implement NodeOrchestrator with ModelAction Pattern for lease-based single-writer semantics and workflow-driven actions

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/node.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/conftest.py
  • src/omnibase_infra/runtime/contract_loaders/__init__.py
📚 Learning: 2025-11-24T16:33:51.604Z
Learnt from: CR
Repo: OmniNode-ai/omninode_bridge PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-24T16:33:51.604Z
Learning: Applies to tests/**/conftest.py : Test fixtures must be defined in `conftest.py` and should provide reusable sample data, UUIDs, semantic versions, and model data

Applied to files:

  • tests/unit/runtime/contract_loaders/conftest.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/**/*.py : Use `omnibase_infra` handlers for OmniIntelligence queries via HttpRestAdapter envelope pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/registry_*.py : Registry files must follow naming convention: node-specific registries use `registry_infra_<node_name>.py` → `RegistryInfra<NodeName>`, standalone registries use `registry_<purpose>.py` → `Registry<Purpose>`

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
🧬 Code graph analysis (4)
src/omnibase_infra/nodes/node_registration_orchestrator/node.py (2)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (1)
  • load_handler_routing_subcontract (103-239)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (5)
src/omnibase_infra/enums/enum_infra_transport_type.py (1)
  • EnumInfraTransportType (32-60)
src/omnibase_infra/models/errors/model_infra_error_context.py (1)
  • ModelInfraErrorContext (17-96)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/models/routing/model_routing_entry.py (1)
  • ModelRoutingEntry (14-49)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
src/omnibase_infra/runtime/contract_loaders/__init__.py (1)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (2)
  • convert_class_to_handler_key (77-100)
  • load_handler_routing_subcontract (103-239)
src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (8)
src/omnibase_infra/models/errors/model_infra_error_context.py (1)
  • ModelInfraErrorContext (17-96)
src/omnibase_infra/enums/enum_infra_transport_type.py (1)
  • EnumInfraTransportType (32-60)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/runtime/projector_plugin_loader.py (1)
  • contract (123-125)
src/omnibase_infra/runtime/projector_shell.py (1)
  • contract (229-231)
tests/integration/projectors/conftest.py (2)
  • contract (287-330)
  • projector (201-230)
tests/integration/registration/e2e/conftest.py (1)
  • projection_reader (363-378)
tests/unit/handlers/test_service_discovery_protocol_compliance.py (1)
  • consul_handler (79-89)
🔇 Additional comments (17)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (1)

77-100: LGTM! Well-documented CamelCase to kebab-case conversion.

The regex-based conversion handles edge cases like consecutive uppercase letters (MyHTTPHandler → my-http-handler) correctly. The docstring examples are helpful.

tests/unit/runtime/contract_loaders/__init__.py (1)

1-12: LGTM!

Clean test package initializer with appropriate documentation.

src/omnibase_infra/runtime/contract_loaders/__init__.py (1)

1-36: LGTM!

Clean package initialization with appropriate re-exports and well-documented usage examples.

src/omnibase_infra/nodes/node_registration_orchestrator/node.py (2)

70-88: LGTM! Clean refactor to shared utility.

The thin wrapper pattern preserves the existing function signature while delegating to the shared loader. This maintains backward compatibility and follows the DRY principle effectively.


61-62: Good import organization.

The imports from the new shared contract loader module are correctly added, enabling the delegation pattern.

src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (2)

112-160: Well-implemented dynamic handler class loading.

Good separation of ModuleNotFoundError and ImportError for clearer error messages. The use of EnumInfraTransportType.RUNTIME is semantically correct for runtime module loading.


403-484: Good dynamic handler loading implementation with proper validation.

The implementation correctly:

  • Handles the heartbeat handler special case based on projector availability
  • Validates protocol compliance via duck typing (per ONEX conventions)
  • Provides detailed error messages for instantiation failures
  • Logs handler registration at DEBUG level

The fail-fast pattern at lines 363-377 combined with the skip logic at lines 410-420 provides clear production vs. testing behavior.

tests/unit/runtime/contract_loaders/conftest.py (4)

1-14: LGTM!

Clean header, appropriate imports, and good module documentation referencing the ticket number for traceability.


20-127: LGTM!

Excellent coverage of test scenarios with well-documented YAML constants. The samples appropriately cover valid configurations, edge cases (empty handlers, incomplete entries), and error conditions (invalid YAML, missing sections). Based on learnings, the handler contract structure with routing_strategy, event models, and handler declarations aligns with expected contract patterns.


135-238: LGTM!

Well-structured fixtures with clear docstrings, proper use of tmp_path for test isolation, and explicit return type annotations. The nonexistent_contract_path fixture correctly returns a path that won't exist without creating the parent directory.


245-267: LGTM!

Comprehensive __all__ exports with clear categorization. Note that pytest auto-discovers fixtures from conftest.py, so including them in __all__ is primarily for documentation purposes, which is fine.

tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (6)

1-34: LGTM!

Excellent module documentation with clear test categorization, usage examples, and ticket reference. The imports are minimal and appropriate, using relative imports for conftest constants.


146-258: LGTM!

Comprehensive happy path tests covering valid contracts, minimal configurations, default values, and handler key conversion. The tests properly verify the ModelRoutingSubcontract structure and routing key mappings.


265-373: LGTM!

Thorough error handling tests that verify appropriate ProtocolConfigurationError exceptions are raised with informative messages. Good verification of error context for debugging purposes.


380-544: LGTM!

Excellent edge case coverage including graceful handling of incomplete entries, default values, and both absolute and relative path support. Good use of caplog to verify warning logs for skipped entries and monkeypatch.chdir for relative path testing.


551-599: LGTM!

Solid integration tests that verify the loader works with the real orchestrator contract. The handler key validation assertions (lines 586-598) effectively enforce naming conventions: kebab-case, lowercase, and hyphen-prefixed. The flexible assertion >= 4 appropriately accommodates future handler additions.


605-611: LGTM!

The __all__ exports document the test classes, though pytest auto-discovers test classes regardless. This is consistent with the pattern used in conftest.py.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py Outdated
…ading [OMN-1316]

Address PR #153 review feedback:

Security (CRITICAL):
- Add ALLOWED_NAMESPACES constant for trusted module namespaces
- Validate module paths before dynamic import in _load_handler_class
- Reject untrusted namespaces with NAMESPACE_NOT_ALLOWED error code

Code Quality (MAJOR):
- Extract load_handler_class_info_from_contract() to shared loader
- Remove duplicate _load_handler_routing_from_contract from registry
- Change transport type from DATABASE to FILESYSTEM for file operations
- Improve error messages to be actionable with fix guidance

Validation (NITPICK):
- Add VALID_ROUTING_STRATEGIES constant with validation
- Clarify filter logic comments for optional dependencies
- Strengthen weak assertion in underscore handling test
@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

PR Review: Handler Routing Loader Refactoring [OMN-1316]

Summary

This PR successfully extracts handler routing loading logic into a shared utility, implementing the Handler Plugin Loader pattern. The refactoring eliminates code duplication and establishes contract-driven handler loading as a first-class pattern. Overall, this is high-quality work with excellent test coverage (625 lines of tests for 406 lines of production code).


✅ Strengths

Architecture & Design

  • Eliminates dual source of truth: Contract.yaml is now the authoritative source for handler routing
  • Proper separation of concerns: Loader utility is independent and reusable across orchestrators
  • Follows ONEX patterns: Aligns with declarative node architecture from CLAUDE.md
  • Security-first approach: Namespace allowlisting (ALLOWED_NAMESPACES) prevents arbitrary code execution

Code Quality

  • Comprehensive error handling: All error paths include actionable messages with fix guidance
  • Strong type safety: No Any types, proper use of Path, frozenset for constants
  • Excellent documentation: Module, function, and inline comments are clear and thorough
  • Consistent naming: load_handler_routing_subcontract vs load_handler_class_info_from_contract clearly indicates return type differences

Testing

  • 35 new unit tests with excellent coverage of happy paths, error conditions, and edge cases
  • Well-organized test structure: Clear test class grouping by concern
  • Fixtures for reusability: conftest.py provides shared test infrastructure
  • Edge case coverage: Tests for underscores, acronyms, empty strings, etc.

🔴 Critical Issues

1. Transport Type Mismatch in Registry ⚠️ MAJOR

Location: registry_infra_node_registration_orchestrator.py:310

ctx = ModelInfraErrorContext(
    transport_type=EnumInfraTransportType.DATABASE,  # ❌ INCORRECT
    operation="create_registry",
    target_name="RegistryInfraNodeRegistrationOrchestrator",
)

Issue: Registry creation is not a database operation - it's a runtime operation. This misleads error monitoring/alerting systems.

Fix: Change to EnumInfraTransportType.RUNTIME (matches the _load_handler_class function at line 147).

Impact: Error categorization, monitoring dashboards, and incident response workflows may incorrectly classify registry failures as database issues.


2. File Size Limit Not Enforced 🛡️ SECURITY (NITPICK)

Location: handler_routing_loader.py:149-150

Issue: Per CLAUDE.md Handler Plugin Loader security patterns, contracts should have a 10MB file size limit to prevent memory exhaustion attacks. The current implementation loads files with yaml.safe_load() without size validation.

Recommendation: Add file size check before loading:

file_size = contract_path.stat().st_size
if file_size > 10_485_760:  # 10MB
    raise ProtocolConfigurationError(
        f"Contract file exceeds 10MB limit: {file_size} bytes",
        context=ctx,
    )

Rationale: Defense-in-depth security. While yaml.safe_load() prevents deserialization attacks, unbounded file sizes can still cause memory exhaustion.


🟡 Major Issues

3. Code Duplication in YAML Loading 🔄 DRY Violation

Location: handler_routing_loader.py:112-210 and 270-369

Issue: Both load_handler_routing_subcontract and load_handler_class_info_from_contract contain nearly identical YAML loading/validation logic (98 lines duplicated).

Impact:

  • Maintenance burden (bug fixes need to be applied twice)
  • Inconsistent error messages if divergence occurs
  • Violates DRY principle

Recommendation: Extract to shared function:

def _load_and_validate_contract(contract_path: Path, operation: str) -> dict[str, object]:
    """Load and validate contract.yaml with common error handling."""
    try:
        with contract_path.open("r", encoding="utf-8") as f:
            contract = yaml.safe_load(f)
    except FileNotFoundError as e:
        # ... shared error handling
    # ... shared validation
    return contract

Then both public functions call this helper. This reduces from ~180 lines to ~120 lines with better maintainability.


4. Missing Validation for Routing Strategy ⚠️ CONTRACT VALIDATION

Location: handler_routing_loader.py:251-260

Issue: Invalid routing strategies fall back to "payload_type_match" with only a warning. This creates silent misconfigurations.

Current Behavior:

handler_routing:
  routing_strategy: "typo_in_strategy"  # Silent fallback to payload_type_match

Recommendation: Use validation level flag or fail-fast:

if routing_strategy not in VALID_ROUTING_STRATEGIES:
    raise ProtocolConfigurationError(
        f"Invalid routing_strategy '{routing_strategy}' in contract. "
        f"Valid values: {', '.join(VALID_ROUTING_STRATEGIES)}.",
        context=ctx,
    )

Justification: Configuration errors should fail early and loudly, not silently degrade to defaults. This aligns with ONEX fail-fast principles.


5. Incomplete Dependency Validation 🔍 RUNTIME SAFETY

Location: registry_infra_node_registration_orchestrator.py:371-382

Issue: The handler_dependencies map is hardcoded in the registry, creating a potential mismatch with actual handler constructors.

Risk Scenario:

  1. Handler constructor signature changes (adds/removes parameter)
  2. Developer forgets to update handler_dependencies map
  3. Runtime error: TypeError: __init__() got an unexpected keyword argument

Current Mitigation: Try/except catches TypeError but only at runtime during handler instantiation.

Recommendation: Add static validation or move dependency declarations to contract.yaml:

handler_routing:
  handlers:
    - event_model: {...}
      handler: {...}
      dependencies:  # Explicit in contract
        - projection_reader
        - projector
        - consul_handler

This makes dependencies contract-driven (source of truth) rather than code-driven.


🟢 Minor Issues / Nitpicks

6. Inconsistent Error Message Format 📝 STYLE

Location: Various error messages

Issue: Some error messages end with periods, others don't:

  • Line 162: "Ensure the contract.yaml exists..." (with period)
  • Line 155: "handler routing cannot be loaded" (no period)

Recommendation: Standardize to always include terminal periods for consistency.


7. Handler Dependencies Filtering Logic Unclear 💭 READABILITY

Location: registry_infra_node_registration_orchestrator.py:384-397

Issue: The comment explaining dependency filtering is dense and could be clearer.

Current:

# Filter dependencies for handler instantiation.
# Logic:
# - projection_reader: ALWAYS included (required by all handlers, even if
#   None triggers validation). We keep it to ensure handlers receive it
#   and can perform their own validation if needed.
# - projector: Only included if provided...

Recommendation: Add concrete example:

# Filter dependencies for handler instantiation.
# - projection_reader: ALWAYS passed (even if None) so handlers can validate
# - projector: Only passed if provided (HandlerNodeHeartbeat requires it)
# - consul_handler: Only passed if provided (HandlerNodeIntrospected requires it)
#
# Example: HandlerRuntimeTick gets {"projection_reader": reader}
#          HandlerNodeHeartbeat gets {"projection_reader": reader, "projector": proj}

8. VALID_ROUTING_STRATEGIES Not Used in Tests ⚠️ TEST COVERAGE

Location: test_handler_routing_loader.py

Issue: Tests don't verify that invalid routing strategies are rejected (or handled with warnings). The VALID_ROUTING_STRATEGIES constant exists but isn't exercised by tests.

Recommendation: Add test cases:

def test_invalid_routing_strategy_logs_warning(caplog, tmp_path):
    """Test that invalid routing strategy triggers warning."""
    contract = tmp_path / "contract.yaml"
    contract.write_text('''
handler_routing:
  routing_strategy: "invalid_strategy"
  handlers: []
''')
    result = load_handler_routing_subcontract(contract)
    assert "Unknown routing_strategy" in caplog.text
    assert result.routing_strategy == "payload_type_match"  # Default fallback

📊 Performance Considerations

9. Repeated Contract Parsing ⏱️ OPTIMIZATION (OPTIONAL)

Location: registry_infra_node_registration_orchestrator.py:325-326

Observation: Each time create_registry() is called, it re-parses the contract.yaml from disk.

Impact: Negligible for current usage (called once at startup), but could matter if registries are created frequently.

Recommendation: Consider caching parsed contracts with a TTL or using @lru_cache if this becomes a hot path. NOT required for this PR, but document as future optimization opportunity.


🔒 Security Review

✅ Security Controls Implemented

  1. Namespace allowlisting: ALLOWED_NAMESPACES restricts dynamic imports to trusted namespaces
  2. YAML safe loading: yaml.safe_load() prevents deserialization attacks
  3. Error sanitization: Raw YAML errors are not exposed (line 172-178)
  4. Correlation ID tracking: All operations loggable with correlation context

⚠️ Security Gaps

  1. File size limit not enforced (see Critical Issue Add Claude Code GitHub Workflow #2)
  2. No validation of handler constructor signatures - malicious contract could trigger unexpected behavior during instantiation

🛡️ Deployment Security Checklist (from CLAUDE.md)

Ensure the following are addressed before production deployment:

  • Contract directories mounted read-only at runtime
  • File permissions: contract files readable only by runtime user
  • Enable INFO-level logging for handler loader audit trail
  • Run onex validate in CI pipeline to catch malformed contracts
  • Consider enabling allowed_namespaces parameter if not already enforced

🧪 Test Coverage Assessment

Coverage Summary

  • 625 test lines for 406 production lines = 1.54x test-to-code ratio ✅ Excellent
  • 4 test classes with clear separation of concerns
  • Edge cases covered: Empty strings, underscores, acronyms, missing fields, invalid YAML

Test Gaps

  1. No tests for namespace validation: Should test that modules outside ALLOWED_NAMESPACES are rejected
  2. No tests for invalid routing strategies: See Minor Issue feat: migrate 96 models to domain-organized structure #8
  3. No integration tests: Tests are unit-level only. Consider adding integration test that loads real contract.yaml from nodes/node_registration_orchestrator/

Recommendation: Add integration test:

def test_load_real_registration_orchestrator_contract():
    """Test loading the actual production contract.yaml."""
    contract_path = Path(__file__).parent.parent.parent.parent /                     "src/omnibase_infra/nodes/node_registration_orchestrator/contract.yaml"
    routing = load_handler_routing_subcontract(contract_path)
    
    # Verify expected handlers are present
    handler_keys = {entry.handler_key for entry in routing.handlers}
    assert "handler-node-introspected" in handler_keys
    assert "handler-runtime-tick" in handler_keys
    assert "handler-node-registration-acked" in handler_keys
    assert "handler-node-heartbeat" in handler_keys

📝 Documentation Review

✅ Excellent Documentation

  • Module docstrings with usage examples
  • Inline comments explaining design decisions
  • Error messages include actionable fix guidance
  • CLAUDE.md updated with Handler Plugin Loader patterns

🔍 Documentation Gaps

  1. No ADR for this refactoring: Consider creating docs/decisions/adr-handler-routing-loader.md documenting:

    • Why extract to shared utility vs keep inline
    • Security trade-offs of dynamic loading
    • Alternative approaches considered (decorators, metaclasses)
  2. Error code documentation: Error codes like NAMESPACE_NOT_ALLOWED (HANDLER_LOADER_013) should be documented in a centralized error code registry


🎯 Recommendations Summary

Must Fix Before Merge (Critical)

  1. ✅ Change transport type from DATABASE to RUNTIME in registry error context (line 310)

Should Fix Before Merge (Major)

  1. ✅ Extract shared YAML loading logic to eliminate duplication
  2. ✅ Make invalid routing strategy fail-fast instead of silent fallback
  3. ✅ Add file size limit enforcement (10MB)

Nice to Have (Minor)

  1. ✅ Standardize error message formatting (terminal periods)
  2. ✅ Improve dependency filtering comment with concrete example
  3. ✅ Add test coverage for routing strategy validation
  4. ✅ Add integration test with real contract.yaml

Future Enhancements (Not Blocking)

  1. ✅ Consider contract caching for performance
  2. ✅ Move handler dependencies to contract.yaml for single source of truth
  3. ✅ Create ADR documenting design decisions

🎖️ Final Verdict

APPROVE with minor requested changes

This PR represents high-quality engineering with excellent test coverage, strong security controls, and clear documentation. The refactoring successfully achieves its goals:

✅ Eliminates code duplication
✅ Establishes contract-driven pattern
✅ Maintains backward compatibility
✅ Comprehensive test coverage
✅ Security-conscious implementation

The critical issues identified are minor and easily addressable. The major issues are technical debt that could be addressed in follow-up PRs.

Great work on OMN-1316! 🚀


Reviewed by: Claude Sonnet 4.5
Review Date: 2026-01-15
Confidence Level: High (comprehensive analysis of 1544 additions, 235 deletions)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py`:
- Around line 76-83: VALID_ROUTING_STRATEGIES includes unimplemented
"first_match" and "all_match"; change the constant VALID_ROUTING_STRATEGIES to
only include "payload_type_match" so it matches the implemented behavior and the
type on ModelRoutingSubcontract.routing_strategy
(Literal["payload_type_match"]), and update any loader validation that
references VALID_ROUTING_STRATEGIES to rely solely on that single allowed value.
🧹 Nitpick comments (3)
src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (1)

328-346: Handler dependency map creates partial coupling to contract.yaml.

While the handler classes are now loaded dynamically from contract.yaml, the handler_dependencies map still requires code changes when adding new handlers. This is acceptable given the PR's stated approach to "preserve DI/constructor parameters" without making contracts a secondary DI container. However, consider adding a comment noting that new handlers require an entry here.

💡 Suggested documentation improvement
         # Map of handler dependencies by handler class name
         # Each handler class has specific dependencies based on its constructor
+        # NOTE: When adding a new handler to contract.yaml, also add an entry here
+        # with the handler's required constructor arguments.
         handler_dependencies: dict[str, dict[str, object]] = {
tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (1)

132-153: Underscore test documents surprising but correct behavior.

The test correctly documents that convert_class_to_handler_key("My_Handler") produces "my_-handler" due to how the regex operates on case boundaries. While technically correct, this produces suboptimal output. Since underscores in Python class names are atypical (as the comment notes), this is acceptable, but if underscore-named classes ever appear in contracts, you may want to sanitize the output.

💡 Optional: Clean up double punctuation in output

In convert_class_to_handler_key, you could add a post-processing step:

# After the regex transformations:
return result.replace("_-", "-").replace("-_", "-")
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (1)

270-398: Consider reducing duplication between loader functions.

load_handler_class_info_from_contract duplicates the YAML loading and validation logic from load_handler_routing_subcontract. While this is acceptable for clarity, a shared internal helper could reduce maintenance burden if the contract format evolves.

Additionally, the return type list[dict[str, str]] could benefit from a TypedDict for better IDE support and type safety.

💡 Optional: Use TypedDict for return type
from typing import TypedDict

class HandlerClassInfo(TypedDict):
    handler_class: str
    handler_module: str

def load_handler_class_info_from_contract(
    contract_path: Path,
) -> list[HandlerClassInfo]:
    ...
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 0b918f0 and 7feb97c.

📒 Files selected for processing (4)
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Never use Any type - use object for generic payloads in function parameters and return types
Use X | None (PEP 604) instead of Optional[X] for nullable types
All services must use ModelONEXContainer for dependency injection via __init__(self, container: ModelONEXContainer)
Use @allow_any decorator with documented reason as exemption mechanism for Any type violations
Use JsonType from omnibase_core.types as the canonical type alias for JSON-compatible values
Use ModelEventEnvelope[object] for generic dispatcher interfaces and object for generic payloads
Infrastructure error handling must use OnexError base class and never expose passwords, API keys, PII, or connection strings with credentials in error messages
Use InfraConnectionError, InfraTimeoutError, InfraAuthenticationError, InfraUnavailableError for transport failures with proper ModelInfraErrorContext
Correlation IDs must be propagated from incoming requests, auto-generated with uuid4() if missing, and included in all error contexts
External service adapters must implement MixinAsyncCircuitBreaker with appropriate threshold and reset_timeout configuration
Protocol resolution must use duck typing via protocols, never use isinstance checks

Files:

  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
**/registry_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Registry files must follow naming convention: node-specific registries use registry_infra_<node_name>.py → RegistryInfra<NodeName>, standalone registries use registry_<purpose>.py → Registry<Purpose>

Files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
**/handler_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Handlers must NOT have direct event bus access - only orchestrators may have bus parameters and publish events

Files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
🧠 Learnings (18)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contracts/ : Organize contract subcomponents into separate files (contract_actions.yaml, contract_models.yaml, contract_validation.yaml, etc.) and reference them from main contract.yaml
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/__init__.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/**/*.py : Use `omnibase_infra` handlers for OmniIntelligence queries via HttpRestAdapter envelope pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/nodes/**/*.py : Use NodeEffect, NodeCompute, NodeReducer, and NodeOrchestrator base classes with declarative YAML contracts; import from omnibase_core.nodes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/registry_*.py : Registry files must follow naming convention: node-specific registries use `registry_infra_<node_name>.py` → `RegistryInfra<NodeName>`, standalone registries use `registry_<purpose>.py` → `Registry<Purpose>`

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/nodes/node_orchestrator.py : Implement NodeOrchestrator with ModelAction Pattern for lease-based single-writer semantics and workflow-driven actions

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/reducer/**/*.py : FSM behavior must be defined in YAML contracts, not as multiple Python reducer classes; use one NodeReducer class that loads different FSM contracts

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contract.yaml : The main contract.yaml file serves as the source of truth for node interfaces and should reference subcontracts using $ref patterns

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contracts/ : Organize contract subcomponents into separate files (contract_actions.yaml, contract_models.yaml, contract_validation.yaml, etc.) and reference them from main contract.yaml

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contract.yaml : Use shared schema references with project root paths in contract definitions (e.g., schemas/onex_field_model.schema.yaml, schemas/semver_model.schema.yaml)

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-06T17:57:00.689Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_spi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T17:57:00.689Z
Learning: Applies to src/omnibase_spi/protocols/handlers/**/*.py : Protocol naming convention: Handler protocols must follow `Protocol{Type}Handler` pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/protocols/protocol_*.py : Use Protocol for tool interfaces and plugin APIs based on method shape (structural typing), not Pydantic models with inheritance

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/*.py : Use `InfraConnectionError`, `InfraTimeoutError`, `InfraAuthenticationError`, `InfraUnavailableError` for transport failures with proper `ModelInfraErrorContext`

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
🧬 Code graph analysis (3)
src/omnibase_infra/runtime/contract_loaders/__init__.py (1)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (3)
  • convert_class_to_handler_key (86-109)
  • load_handler_class_info_from_contract (270-398)
  • load_handler_routing_subcontract (112-267)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (5)
src/omnibase_infra/enums/enum_infra_transport_type.py (1)
  • EnumInfraTransportType (32-60)
src/omnibase_infra/models/errors/model_infra_error_context.py (1)
  • ModelInfraErrorContext (17-96)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/models/routing/model_routing_entry.py (1)
  • ModelRoutingEntry (14-49)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (4)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (2)
  • convert_class_to_handler_key (86-109)
  • load_handler_routing_subcontract (112-267)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/nodes/node_registration_orchestrator/node.py (1)
  • _create_handler_routing_subcontract (70-88)
🔇 Additional comments (11)
src/omnibase_infra/runtime/contract_loaders/__init__.py (1)

1-40: Well-structured package initializer with clean public API exposure.

The module correctly re-exports the shared utilities from handler_routing_loader, provides clear documentation with usage examples, and maintains a consistent __all__ declaration. This aligns with the PR objective to expose load_handler_routing_subcontract as a shared utility and follows the Handler Plugin Loader pattern from learnings.

src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (4)

70-77: Good security practice with namespace allowlisting.

The ALLOWED_NAMESPACES tuple provides defense-in-depth against arbitrary code execution via malicious contract.yaml configurations. The documentation clearly references the security pattern and error code.


143-156: Namespace validation is secure but consider edge case.

The namespace check using startswith() is appropriate. However, be aware that paths like omnibase_infra_malicious.evil would pass validation since they start with omnibase_infra. The current implementation is safe because Python's import system would reject such module names if they don't exist, and the allowlist is restrictive enough. The error message correctly includes the error code for traceability.


399-412: Solid error handling for handler instantiation failures.

Catching TypeError specifically handles constructor signature mismatches from **filtered_deps unpacking. The error message is actionable, directing developers to check the handler_dependencies map.


393-397: Clarify why projection_reader requires special handling in filtered_deps.

The filtering logic intentionally keeps projection_reader even when None (line 396: if v is not None or k == "projection_reader"), while filtering other None values. This special case suggests projection_reader can be None, but if the parameter type annotation doesn't include | None, this inconsistency could be confusing. Either the type annotation should be updated to reflect that projection_reader accepts None, or the reason for the special case should be documented with a clarifying comment in the code.

tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (3)

160-272: Comprehensive happy path test coverage.

Tests cover the essential success scenarios: valid contracts, minimal contracts, default values, handler key conversion, and empty handler lists. The fixture-based approach keeps tests clean and maintainable.


394-558: Thorough edge case coverage with appropriate test utilities.

The edge case tests properly use caplog for warning verification, inline YAML for specific scenarios, and monkeypatch.chdir for relative path testing. The skipped entry tests correctly verify graceful degradation behavior.


565-612: Valuable integration tests against real contract structure.

Testing against the actual node_registration_orchestrator contract validates that the loader works with production data. The handler key format assertions (lowercase, kebab-case, "handler-" prefix) document and enforce naming conventions.

src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (3)

86-109: Correct CamelCase to kebab-case conversion.

The two-step regex approach properly handles standard CamelCase, acronyms like HTTP, and numeric boundaries. The docstring examples accurately reflect the output.


148-267: Well-implemented contract loader with comprehensive error handling.

The function correctly:

  • Uses FILESYSTEM transport type for all error contexts (addressing past review feedback)
  • Sanitizes YAML errors to avoid leaking file contents (line 173)
  • Gracefully skips malformed entries with warnings
  • Validates and defaults routing_strategy

401-406: Consistent public API exports.

The __all__ list matches the re-exports in __init__.py, maintaining a clear and consistent public API surface.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py Outdated
- Remove unimplemented routing strategies (first_match, all_match) from
  VALID_ROUTING_STRATEGIES constant, keeping only payload_type_match
- Add file size limit enforcement (10MB max) to prevent memory exhaustion
- Reduce code duplication by extracting _load_and_validate_contract_yaml helper
- Fix transport type: DATABASE → RUNTIME for handler config validation
- Add TestValidRoutingStrategies with 12 tests for strategy validation
- Add TestFileSizeEnforcement with 11 tests for security controls
- Refactor underscore handling test to parametrized with 7 edge cases

Note: Pre-commit hook failure is for unrelated file (projector_plugin_loader.py:241)
@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

Code Review - PR #153: Contract-Driven Handler Loading (OMN-1316)

Summary

This PR successfully extracts handler routing loader logic to a shared utility, implementing the Handler Plugin Loader pattern. The refactoring eliminates code duplication and establishes a consistent contract-driven approach for orchestrator handler loading.


✅ Strengths

1. Excellent Architecture & Design

  • Clean Separation of Concerns: The new handler_routing_loader.py module is well-isolated with clear, single-purpose functions
  • Reusability: Shared utility (load_handler_routing_subcontract, load_handler_class_info_from_contract) eliminates duplication
  • Follows ONEX Patterns: Fully declarative, contract-driven approach per CLAUDE.md
  • Backward Compatibility: Old orchestrator code seamlessly migrates to use shared utility via thin wrapper

2. Security-First Implementation

  • File Size Limits: 10MB max contract size prevents memory exhaustion attacks (MAX_CONTRACT_FILE_SIZE_BYTES)
  • Namespace Allowlisting: ALLOWED_NAMESPACES tuple restricts dynamic imports to trusted modules
  • YAML Safe Loading: Uses yaml.safe_load() to prevent deserialization attacks
  • Error Code Standardization: Consistent error codes (e.g., HANDLER_LOADER_013, HANDLER_LOADER_050) for observability
  • Comprehensive Security Comments: Clear documentation of security controls and threat model

3. Robust Error Handling

  • Fail-Fast Validation: Early detection of missing contracts, invalid YAML, missing sections
  • Rich Error Context: All errors include ModelInfraErrorContext with operation, transport type, and target
  • Informative Messages: Error messages guide developers to resolution with specific suggestions
  • Sanitized Logging: Avoids leaking sensitive contract contents in error logs

4. Comprehensive Testing

  • 35 New Tests: Extensive coverage in test_handler_routing_loader.py
  • Test Fixtures: Well-organized conftest.py with reusable contract YAML constants
  • Edge Cases Covered: Empty contracts, invalid YAML, missing fields, namespace violations
  • Happy Path + Error Paths: Tests both successful loading and all failure modes

5. Code Quality

  • No Any Types: Fully compliant with ONEX strict typing rules (verified)
  • PEP 604 Unions: Uses X | None consistently
  • Clear Documentation: Excellent docstrings with examples, security notes, and usage patterns
  • Type Safety: Strong typing throughout with proper return type annotations

🔍 Observations & Suggestions

1. Handler Dependencies Map (Minor) - registry_infra_node_registration_orchestrator.py:328-346

Current Implementation:

handler_dependencies: dict[str, dict[str, object]] = {
    "HandlerNodeIntrospected": {
        "projection_reader": projection_reader,
        "projector": projector,
        "consul_handler": consul_handler,
    },
    # ... 3 more handlers
}

Observation: The handler_dependencies map is hardcoded in Python, which partially contradicts the contract-driven philosophy. If a new handler is added to contract.yaml, a developer must remember to update this Python dict.

Suggestions:

  1. Add a Validation Check: Detect if a handler in contract.yaml has no entry in handler_dependencies and log a warning
  2. Consider Contract Extensions (future): Explore defining constructor parameters in contract.yaml (though this may over-complicate)
  3. Documentation: Add a comment linking the map to contract.yaml handlers for maintainability

Priority: Low (current approach is pragmatic and well-documented)


2. Dependency Filtering Logic - registry_infra_node_registration_orchestrator.py:384-397

Current Implementation:

filtered_deps = {
    k: v
    for k, v in deps.items()
    if v is not None or k == "projection_reader"
}

Observation: The special case for projection_reader (included even if None) is subtle and relies on inline comments for clarity.

Suggestions:

  1. Extract to Helper Function: _filter_handler_dependencies(deps: dict) -> dict with explicit docstring explaining the policy
  2. Explicit Constants: Define REQUIRED_DEPS = frozenset({"projection_reader"}) at module level
  3. Testing: Add unit tests specifically for this filtering logic

Priority: Low (current implementation works, but could be more maintainable)


3. File Size Check Timing - handler_routing_loader.py:82-120

Current Implementation:

def _check_file_size(contract_path: Path, operation: str) -> None:
    try:
        file_size = contract_path.stat().st_size
    except FileNotFoundError:
        return  # Let caller handle

Observation: File size check happens before opening the file, which is good. However, FileNotFoundError is silently ignored, relying on caller to handle it.

Suggestions:

  1. Consider Failing Fast: Raise ProtocolConfigurationError immediately on FileNotFoundError with consistent error context
  2. Alternatively: Document this pattern explicitly in the docstring to clarify the two-phase error handling

Priority: Very Low (current approach is reasonable, just a style consideration)


4. Protocol Validation - registry_infra_node_registration_orchestrator.py:80-120

Excellent Implementation: The _validate_handler_protocol function uses duck typing (hasattr checks) instead of isinstance, fully aligned with ONEX protocol-driven philosophy.

Suggestion: Consider extracting this to a shared utility (omnibase_infra.utils.protocol_validation) if other registries need similar validation logic.

Priority: Very Low (nice-to-have for future reuse)


5. Routing Strategy Validation - handler_routing_loader.py:227-231

Current Implementation:

VALID_ROUTING_STRATEGIES: frozenset[str] = frozenset({
    "payload_type_match",  # Only implemented strategy
})

Observation: The code gracefully handles unknown strategies by logging a warning and falling back to payload_type_match. This is good defensive programming.

Suggestion: Consider if fail-fast (raise error) would be better for production deployments to catch misconfigurations early. Current approach is more forgiving but might hide contract typos.

Priority: Very Low (current approach is reasonable, discuss with team)


🛡️ Security Review

✅ Security Controls Properly Implemented

  1. ✅ YAML safe loading prevents deserialization attacks
  2. ✅ 10MB file size limit prevents memory exhaustion
  3. ✅ Namespace allowlisting restricts dynamic imports
  4. ✅ Error messages sanitized (no raw YAML content leakage)
  5. ✅ Fail-fast validation prevents runtime surprises

⚠️ Security Considerations

  1. Import Side Effects: Dynamic importlib.import_module() executes module-level code. This is documented in CLAUDE.md but worth emphasizing in deployment docs.
  2. Contract File Permissions: Deployment checklist should include filesystem permissions guidance (contracts should be read-only at runtime).
  3. Namespace Allowlist Maintenance: ALLOWED_NAMESPACES is hardcoded. Consider if this should be configurable for plugin ecosystems.

Recommendation: All security concerns are acknowledged and documented. No additional changes needed for this PR.


🧪 Testing

✅ Test Coverage Strengths

  • 35 new unit tests covering happy paths and error cases
  • Comprehensive edge case coverage (empty contracts, invalid YAML, missing fields)
  • Well-organized fixtures in conftest.py
  • Tests verify both load_handler_routing_subcontract and load_handler_class_info_from_contract

📋 Test Suggestions (Optional)

  1. Integration Test: Add a test verifying the full flow from contract.yaml → loader → registry → handler instantiation
  2. Performance Test: Validate behavior with max-size (10MB) contracts
  3. Security Test: Attempt to load handlers from disallowed namespaces and verify error code HANDLER_LOADER_013

Priority: Low (current coverage is strong)


📝 Code Quality

✅ Compliant with ONEX Standards

  • ✅ No Any types (verified via grep)
  • ✅ PEP 604 unions (X | None)
  • ✅ Proper Pydantic models
  • ✅ Strong typing throughout
  • ✅ Error hierarchy follows ONEX patterns
  • ✅ Declarative approach (contract-driven)
  • ✅ Container injection pattern respected

✅ Documentation Quality

  • Excellent module-level docstrings with usage examples
  • Security considerations clearly documented
  • Error codes standardized and documented
  • Inline comments explain non-obvious logic

🚀 Performance Considerations

✅ Efficient Implementation

  • File operations are atomic (single read per contract)
  • No unnecessary re-parsing or caching complexity
  • Validation happens early (fail-fast)

💡 Future Optimization (Not for this PR)

  • Consider caching parsed contracts if multiple orchestrators share the same contract.yaml
  • Profile handler instantiation if there are 10+ handlers in a single registry

🎯 Recommendation

APPROVE ✅

This PR represents high-quality refactoring that:

  1. Eliminates code duplication
  2. Establishes a reusable, contract-driven pattern
  3. Maintains backward compatibility
  4. Includes comprehensive tests
  5. Follows ONEX architectural principles
  6. Implements proper security controls
  7. Has excellent documentation

Before Merge (Optional)

  1. Consider adding integration test for full handler loading flow
  2. Verify all 35 new tests pass in CI
  3. Run scripts/validate.py any_types to confirm no Any type violations

Post-Merge Follow-Up (Future Work)

  1. Extract _validate_handler_protocol to shared utility if other registries need it
  2. Document contract file permission requirements in deployment guide
  3. Consider contract schema validation (e.g., JSON Schema or Pydantic model for contract.yaml structure)

📊 Change Summary

Metric Value
Files Changed 7
Lines Added +2,158
Lines Deleted -236
Net Change +1,922
New Tests 35
Test Files 3

Impact: Medium (refactoring improves maintainability, no functional changes to runtime behavior)


Excellent work on this refactoring! The code quality is very high and the architecture is sound. 🎉

…-1316]

Addresses all PR review issues including critical, major, minor, and nitpicks:

- Add error codes to all error messages (HANDLER_LOADER_010-023)
- Document handler_dependencies maintenance requirements
- Clarify filter logic for required vs optional dependencies
- Add FileRegistry pattern rationale note
- Enhance underscore handling test as characterization test
- Add security note for file size check timing

All 88 tests pass (64 contract loader + 24 orchestrator).
@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

PR Review: Handler Routing Loader Extraction (OMN-1316)

Summary

This PR successfully extracts handler routing logic into a shared utility and implements contract-driven handler loading, eliminating the dual source of truth between contract.yaml and hardcoded Python imports. The implementation is well-aligned with ONEX's declarative patterns and Handler Plugin Loader security model.


✅ Strengths

1. Excellent Architecture & Pattern Adherence

  • Contract-Driven Loading: Perfectly implements the Handler Plugin Loader pattern from CLAUDE.md
  • Single Source of Truth: Eliminates duplication between contract.yaml and Python code
  • Shared Utility Pattern: load_handler_routing_subcontract() is now reusable across orchestrators
  • Clear Separation: node.py becomes a thin wrapper, delegating to the shared loader

2. Security-First Implementation

Per CLAUDE.md Handler Plugin Loader security patterns, the implementation includes:

✅ YAML Safe Loading: Uses yaml.safe_load() to prevent deserialization attacks
✅ File Size Limits: 10MB limit enforced (MAX_CONTRACT_FILE_SIZE_BYTES)
✅ Namespace Allowlisting: ALLOWED_NAMESPACES restricts dynamic imports
✅ Protocol Validation: _validate_handler_protocol() ensures handlers implement all 5 required methods
✅ Error Containment: Comprehensive error codes (HANDLER_LOADER_010-050)
✅ Correlation Tracking: All operations use ModelInfraErrorContext

Security Note: The namespace allowlisting is a critical defense-in-depth control. Consider documenting in the module docstring that this is REQUIRED for production deployments (not optional).

3. Exceptional Test Coverage

  • 1,096 test lines across 35+ test cases
  • Comprehensive scenarios: Happy path, error handling, edge cases (underscores, empty contracts, oversized files)
  • Characterization tests: Documents actual regex behavior for underscore handling (lines 169-194)
  • Fixture-based: Uses conftest.py for reusable test contracts

4. Error Handling Excellence

All error paths include:

  • Standardized error codes (e.g., CONTRACT_NOT_FOUND (HANDLER_LOADER_020))
  • Actionable messages ("Ensure the contract.yaml exists...")
  • Proper context (ModelInfraErrorContext with transport type, operation, target)
  • Security-conscious sanitization (no raw YAML errors exposed, line 191)

5. Strong Type Safety

  • Zero Any types ✅ (enforced by CI via scripts/validate.py any_types)
  • PEP 604 unions: Uses X | None throughout
  • Proper return types: list[dict[str, str]] instead of generic list

6. Documentation Quality

  • Module docstrings: Clear purpose, usage examples, contract structure
  • Inline comments: Explain "why" (e.g., lines 402-421 on dependency filtering logic)
  • Maintenance notes: Module docstring warns about handler_dependencies updates (lines 29-33)

🔍 Issues & Recommendations

1. CRITICAL: Maintenance Burden - Two-Location Handler Registration

Issue: When adding a new handler to contract.yaml, developers must also update the handler_dependencies dict in registry_infra_node_registration_orchestrator.py (lines 340-363). This creates a second source of truth for handler constructor arguments.

Current Code (registry line 347):

handler_dependencies: dict[str, dict[str, object]] = {
    "HandlerNodeIntrospected": {
        "projection_reader": projection_reader,
        "projector": projector,
        "consul_handler": consul_handler,
    },
    # ... 3 more handlers
}

Problem:

  • If a developer adds a handler to contract.yaml but forgets to update handler_dependencies, the system raises ProtocolConfigurationError at runtime (line 395)
  • This is not detected by CI or contract validation
  • The module docstring warning (line 29-33) may be overlooked during development

Recommendation: Consider one of these approaches:

Option A: Contract-Driven Dependencies (Recommended)
Add a dependencies field to contract.yaml handler entries:

handler_routing:
  handlers:
    - event_model:
        name: "ModelNodeIntrospectionEvent"
      handler:
        name: "HandlerNodeIntrospected"
        module: "omnibase_infra.nodes..."
      dependencies:  # NEW
        - projection_reader
        - projector
        - consul_handler

Then resolve dependencies dynamically in the registry:

deps = {
    dep_name: globals()[dep_name]  # Or use a dependency resolver
    for dep_name in handler_config.get("dependencies", [])
}

Option B: Convention-Based Injection
Use introspection to detect constructor parameters automatically:

import inspect
sig = inspect.signature(handler_cls.__init__)
deps = {
    param: available_deps.get(param)
    for param in sig.parameters if param \!= "self"
}

Option C: Fail-Fast Validation (Minimum Viable Fix)
Add a pre-commit hook or CI check that validates all handlers in contract.yaml have entries in handler_dependencies:

# In scripts/validate.py
def validate_handler_dependencies():
    contract_handlers = load_handler_class_info_from_contract(path)
    registry_deps = RegistryInfraNodeRegistrationOrchestrator._get_handler_deps()
    missing = set(contract_handlers) - set(registry_deps.keys())
    if missing:
        raise ValidationError(f"Missing handler_dependencies: {missing}")

Impact: Medium-High. This issue will surface during development (runtime error), but could be prevented entirely with contract-driven dependencies.


2. MINOR: Inconsistent Transport Type for Runtime Operations

Issue: The registry uses EnumInfraTransportType.RUNTIME for handler loading errors (line 153), but the loader uses EnumInfraTransportType.FILESYSTEM (line 105) and EnumInfraTransportType.DATABASE historically in node.py.

Current:

  • Loader: FILESYSTEM (handler_routing_loader.py:105)
  • Registry: RUNTIME (registry_infra_node_registration_orchestrator.py:153)

Recommendation: Standardize on EnumInfraTransportType.FILESYSTEM for file I/O operations (YAML loading) and RUNTIME for handler instantiation/validation. This distinction clarifies where the error occurred.

Proposed Mapping:

# File operations (contract loading)
transport_type=EnumInfraTransportType.FILESYSTEM

# Handler operations (import, instantiation, validation)
transport_type=EnumInfraTransportType.RUNTIME

Impact: Low. Error context is already clear from operation names.


3. MINOR: Optional Dependencies Filtering Logic Could Be Simplified

Issue: The dependency filtering logic (registry lines 422-426) uses a complex conditional:

filtered_deps = {
    k: v
    for k, v in deps.items()
    if v is not None or k == "projection_reader"
}

While the comment explains the "why" (lines 402-421), the condition v is not None or k == "projection_reader" is slightly unintuitive.

Recommendation: Make the intent more explicit:

# Always include projection_reader (required), exclude other None values
filtered_deps = {
    k: v
    for k, v in deps.items()
    if k == "projection_reader" or v is not None
}

Or use a whitelist approach:

REQUIRED_DEPS = {"projection_reader"}
filtered_deps = {
    k: v
    for k, v in deps.items()
    if k in REQUIRED_DEPS or v is not None
}

Impact: Very Low. Current code works correctly and is well-commented.


4. MINOR: Missing Docstring for _check_file_size

Issue: The _check_file_size function (line 82) lacks a "Why" explanation in its docstring.

Current Docstring:

"Check that contract file does not exceed maximum allowed size."

Recommended Addition:

"""Check that contract file does not exceed maximum allowed size.

This is a SECURITY CONTROL to prevent memory exhaustion attacks via
oversized YAML files. The 10MB limit is enforced before YAML parsing
to prevent the parser from consuming excessive memory.

Per CLAUDE.md Handler Plugin Loader security patterns, this check
is MANDATORY for all contract loaders.
"""

Impact: Very Low. Documentation improvement only.


5. SUGGESTION: Consider Contract Validation CI Job

Issue: The current implementation validates contracts at runtime (when the orchestrator starts). Syntax errors or missing handlers are only discovered during deployment.

Recommendation: Add a CI job that validates all contract.yaml files:

# In .github/workflows/validate.yml
- name: Validate Contracts
  run: |
    poetry run python scripts/validate.py contracts

Validation checks:

  1. YAML syntax validity
  2. Handler routing section completeness
  3. Handler class/module paths exist
  4. Handler classes implement ProtocolMessageHandler
  5. All handlers in contract have handler_dependencies entries

Impact: Low. Optional improvement for earlier error detection.


🧪 Test Coverage Analysis

Test Quality: Excellent ✅

Strengths:

  • 35+ test cases covering happy path, errors, edge cases
  • Characterization tests for regex edge cases (underscores)
  • Security tests: File size limits, namespace validation
  • Error code coverage: All HANDLER_LOADER_* codes tested

Gaps (Optional):

  1. Integration test: Load a real orchestrator contract end-to-end
  2. Performance test: Validate 10MB file size limit enforcement speed
  3. Concurrency test: Multiple concurrent contract loads (thread safety)

📊 Code Quality Metrics

Aspect Rating Notes
Architecture ⭐⭐⭐⭐⭐ Excellent separation, shared utility pattern
Security ⭐⭐⭐⭐⭐ Defense-in-depth, namespace allowlisting, validation
Type Safety ⭐⭐⭐⭐⭐ Zero Any types, proper return types
Error Handling ⭐⭐⭐⭐⭐ Comprehensive codes, actionable messages
Documentation ⭐⭐⭐⭐☆ Good overall, minor gaps noted above
Test Coverage ⭐⭐⭐⭐⭐ 1,096 test lines, comprehensive scenarios
Maintenance ⭐⭐⭐☆☆ Two-location handler registration (Issue #1)

✅ CLAUDE.md Compliance Checklist

Rule Status Notes
Zero Any types ✅ Enforced by CI
PEP 604 unions ✅ Uses X | None throughout
Container DI ✅ Uses ModelONEXContainer pattern
Protocol validation ✅ Duck typing via _validate_handler_protocol
OnexError hierarchy ✅ Uses ProtocolConfigurationError with context
No backwards compat ✅ Breaking changes acceptable
Declarative nodes ✅ Node is thin wrapper to shared loader
Security patterns ✅ Namespace allowlisting, file size limits
Error sanitization ✅ No raw YAML errors (line 191)

🎯 Verdict

Recommendation: APPROVE with minor suggestions

This PR demonstrates excellent engineering practices:

  • Strong architectural patterns
  • Comprehensive security controls
  • Exceptional test coverage
  • Clear documentation

The only medium-priority issue (#1 - two-location handler registration) is a maintenance concern rather than a functional bug. It can be addressed in a follow-up PR without blocking this change.

Suggested Follow-Up Tickets:

  1. OMN-XXXX: Contract-driven handler dependency injection (eliminates handler_dependencies dict)
  2. OMN-XXXX: CI contract validation job
  3. OMN-XXXX: Standardize transport type usage across error contexts

🙏 Kudos

Outstanding work on:

  • Security-first design with defense-in-depth controls
  • Comprehensive testing (35+ test cases, 1,096 lines)
  • Clear error codes (HANDLER_LOADER_010-050)
  • Detailed inline comments explaining "why" not just "what"

This PR sets a high standard for contract-driven infrastructure! 🚀

- Add comprehensive documentation for handler dependency map design trade-off
- Add error codes to all ProtocolConfigurationError messages
- Enhance error messages with actionable fix instructions
- Add TODO for future FileRegistry refactoring
- Document routing strategy validation order (before model construction)
- Add tests verifying VALID_ROUTING_STRATEGIES is used in validation
- Enhance underscore test with characterization test documentation
- Add error handling tests for edge cases
@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

Code Review: Handler Routing Loader Refactor (OMN-1316)

Summary

This PR successfully extracts handler routing loader functionality into a shared utility, eliminating code duplication and establishing a contract-driven handler loading pattern. The implementation is well-architected with comprehensive error handling, security controls, and excellent test coverage (1441 lines of tests for 464 lines of production code).

✅ Strengths

1. Excellent Architecture & Design

  • Clean separation of concerns: The shared loader in runtime/contract_loaders/ is properly abstracted and reusable
  • Single source of truth: Contract.yaml is now the authoritative source for handler routing
  • Fail-fast design: Configuration errors are caught at startup with clear, actionable error messages
  • Well-documented trade-offs: The explicit dependency map pattern is thoroughly justified

2. Security Implementation (Aligns with CLAUDE.md)

  • ✅ Namespace allowlisting (ALLOWED_NAMESPACES) prevents arbitrary code execution (Error code: HANDLER_LOADER_013)
  • ✅ File size limits (10MB cap) prevent memory exhaustion attacks (Error code: HANDLER_LOADER_050)
  • ✅ YAML safe loading prevents deserialization attacks
  • ✅ Proper sanitization in error messages
  • ✅ Security ordering: File size checked BEFORE YAML parsing (line 168)

3. Error Handling Excellence

  • Comprehensive error codes for all failure modes (HANDLER_LOADER_010-062)
  • Rich error context using ModelInfraErrorContext with transport type, operation, and target tracking
  • Actionable error messages with fix suggestions
  • Proper exception chaining with "from e" preserving stack traces

4. Test Coverage

  • 1441 lines of tests for 464 lines of production code (~3.1:1 ratio) 🎉
  • Tests organized by category (happy path, errors, edge cases, validation)
  • Comprehensive fixture setup in conftest.py
  • Characterization tests documented

5. Documentation

  • Excellent module docstrings with usage examples
  • Inline comments explain non-obvious design decisions
  • Cross-references to ADRs and related patterns

6. Code Quality

  • ✅ Zero Any types - complies with CLAUDE.md zero-tolerance policy
  • ✅ PEP 604 unions - uses X | None instead of Optional[X]
  • ✅ Type safety - proper type hints throughout
  • ✅ Consistent error transport types

🔍 Minor Issues & Suggestions

1. Dependency Filtering Logic Clarity

File: registry_infra_node_registration_orchestrator.py:456-461

The comment could be clearer about why projection_reader=None is intentionally passed:

  • Current: Says "projection_reader is a REQUIRED dependency" and should "always pass"
  • Reality: None is passed intentionally for handler-level validation

Suggestion: Clarify that None is passed so handlers can validate and raise clear errors.

2. Contract File Ambiguity Check

File: handler_routing_loader.py:424-426

CLAUDE.md mentions FAIL-FAST behavior for ambiguous contract configurations (HANDLER_LOADER_040) when both handler_contract.yaml and contract.yaml exist in the same directory.

Question: Is this ambiguity check implemented in the shared loader? Verify if this check is needed for this loader or if it only applies to a different loader pattern.

3. TODO Comment Needs Tracking

File: handler_routing_loader.py:139-145

The TODO mentions refactoring to use RegistryFileBased once available in omnibase_core.

Suggestion: Create a tracking ticket and reference it in the comment to make this technical debt actionable.

📊 Code Metrics

Metric Value Assessment
Lines added +2624 ✅ Mostly tests
Lines deleted -239 ✅ Net reduction
Test coverage ratio 3.1:1 ✅ Excellent
Files changed 7 ✅ Focused scope
Error codes defined 10+ ✅ Comprehensive
Security controls 4 ✅ Complete

🔒 Security Review

✅ Implemented Controls

  1. ✅ YAML safe loading (line 173)
  2. ✅ File size limits enforced BEFORE parsing (line 168)
  3. ✅ Namespace allowlisting (lines 103-110, 224-240)
  4. ✅ Protocol validation (lines 459-478 in registry)
  5. ✅ Error containment
  6. ✅ Correlation tracking

⚠️ Deployment Checklist

The PR correctly implements loader-level controls. Remind deployers to:

  • Set file permissions (contracts readable only by runtime user)
  • Mount contract directories read-only at runtime
  • Enable INFO-level logging for handler loader
  • Run onex validate in CI

🧪 Test Coverage Analysis

Test Categories:

  1. ✅ TestConvertClassToHandlerKey
  2. ✅ TestLoadHandlerRoutingSubcontractHappyPath
  3. ✅ TestLoadHandlerRoutingSubcontractErrors
  4. ✅ TestLoadHandlerRoutingSubcontractEdgeCases
  5. ✅ TestValidRoutingStrategies

Minor Gap: Add an integration test that loads the actual production contract.yaml from node_registration_orchestrator/ to ensure end-to-end functionality.

✨ Highlights

Best Practices Observed:

  1. Error code standardization (HANDLER_LOADER_XXX prefix)
  2. Logging discipline (debug for success, error for failures)
  3. Type safety (no Any types)
  4. Defensive programming
  5. Documentation-driven development

🚀 Recommendation

APPROVE with minor follow-ups

This PR is production-ready. The minor issues noted above are suggestions for improvement, not blockers. The implementation:

  • ✅ Follows ONEX patterns and CLAUDE.md conventions
  • ✅ Has excellent test coverage
  • ✅ Implements all required security controls
  • ✅ Provides clear error messages and documentation
  • ✅ Reduces technical debt by eliminating code duplication

Suggested Follow-ups (Non-blocking)

  1. Verify if HANDLER_LOADER_040 check is needed
  2. Add integration test loading real production contract.yaml
  3. Create tracking ticket for RegistryFileBased refactor TODO

Great work on this refactor! 🎉 The contract-driven handler loading pattern is now reusable and well-tested.

- Clarify projection_reader dependency comment to explain intentional
  None passing for handler-level validation and fail-fast behavior
- Add OMN-1352 ticket reference to TODO comments for RegistryFileBased
  refactoring technical debt tracking
- Add validation exemption for ProjectorPluginLoader.__init__ params
…tead-of-programmatic

Resolved conflicts:
- node.py: Keep refactored version using shared load_handler_routing_subcontract()
- registry_infra_node_registration_orchestrator.py: Keep contract-driven handler loading
- validation_exemptions.yaml: Merge exemption entries, keeping cleaner documentation
@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

Code Review: Handler Routing Loader Refactor [OMN-1316]

Summary

This PR successfully extracts handler routing logic into a shared utility module, eliminating code duplication and establishing a contract-driven pattern for handler loading. The implementation is well-architected with strong security controls and comprehensive testing.


✅ Strengths

1. Excellent Architecture & Design

  • Single Responsibility: The new handler_routing_loader.py module is focused solely on contract loading/parsing
  • DRY Principle: Eliminates duplication between node.py and registry by extracting shared logic
  • Fail-Fast Design: All error paths are well-defined with clear error codes (e.g., HANDLER_LOADER_010-062)
  • Security-First: File size limits (10MB), namespace allowlisting, and YAML safe loading built-in

2. Robust Error Handling

  • Structured Error Codes: Every error path has a unique code (HANDLER_LOADER_XXX) for debugging
  • Rich Context: All errors include ModelInfraErrorContext with operation/target details
  • User-Friendly Messages: Error messages explain what went wrong and how to fix it
  • Sanitized Output: YAML parse errors don't leak file contents (security best practice)

3. Security Controls

Strong adherence to CLAUDE.md security patterns:

  • ✅ File size validation before parsing (prevents memory exhaustion)
  • ✅ yaml.safe_load() prevents deserialization attacks
  • ✅ Namespace allowlisting via ALLOWED_NAMESPACES tuple
  • ✅ Protocol validation ensures handlers implement required interface
  • ✅ No Any types used (strict typing throughout)

4. Comprehensive Testing

  • 1,441 lines of tests for the core loader module
  • 35 test cases covering happy paths, edge cases, and error conditions
  • Fixture-based approach in conftest.py provides reusable test contracts
  • Clear test organization with descriptive class/method names

5. Documentation Quality

  • Module docstrings explain the why behind design decisions
  • Inline comments clarify non-obvious logic (e.g., filtered_deps rationale)
  • Handler Dependency Map docstring explicitly defends the explicit-wiring trade-off
  • Error messages include resolution steps

🔍 Observations & Considerations

1. Intentional Design Trade-offs (Well-Justified)

The handler_dependencies map requires manual updates when adding handlers to contract.yaml. The PR correctly identifies this as an INTENTIONAL trade-off:

Pros:

  • Type safety (validated at startup, not runtime)
  • Testability (easy to mock dependencies)
  • Security (no reflection-based injection)
  • Auditability (clear record of wiring)

Cons:

  • Manual maintenance (must update map when adding handlers)
  • Risk of contract.yaml / registry mismatch

Verdict: ✅ Acceptable trade-off - The fail-fast error on missing dependency config (HANDLER_LOADER_061) makes this safe. The error message even shows developers exactly how to fix it.

2. Handler Dependency Filtering Logic

Lines 442-456 in registry_infra_node_registration_orchestrator.py:

filtered_deps = {
    k: v
    for k, v in deps.items()
    if v is not None or k == "projection_reader"
}

Why projection_reader is always included (even if None):

  • The comment explains this is intentional so handlers can perform their own validation
  • Handlers should fail-fast with clear error messages rather than silently missing the parameter

Suggestion: Consider adding a runtime assertion or debug log when projection_reader=None to catch unexpected cases during development.

3. Security: Namespace Allowlisting

The ALLOWED_NAMESPACES tuple (lines 107-110) restricts dynamic imports to trusted prefixes:

ALLOWED_NAMESPACES: tuple[str, ...] = (
    "omnibase_infra.",
    "omnibase_core.",
)

Considerations:

  • ✅ Prevents arbitrary code execution via malicious contract.yaml
  • ✅ Clear error message when namespace not allowed (HANDLER_LOADER_013)
  • ⚠️ Risk: If third-party plugins are needed in the future, this will require careful expansion
  • 💡 Suggestion: Document the process for adding trusted namespaces (e.g., require security review)

4. Contract File Size Limit

10MB limit for contract files (line 79):

MAX_CONTRACT_FILE_SIZE_BYTES: int = 10 * 1024 * 1024  # 10MB

Analysis:

  • ✅ Reasonable limit for YAML contracts (typical contracts are <100KB)
  • ✅ Prevents memory exhaustion attacks
  • ✅ File size checked BEFORE YAML parsing (critical security control)
  • ⚠️ Potential Issue: If contracts grow to include large embedded data (base64 blobs, etc.), this could be hit legitimately
  • 💡 Recommendation: Monitor actual contract sizes in production and adjust if needed

🐛 Potential Issues (Minor)

1. Missing Import in Registry Example Docstring

Line 419 in handler_routing_loader.py:

handler_infos = load_handler_class_info_from_contract(contract_path)

for info in handler_infos:
    handler_cls = importlib.import_module(info["handler_module"])  # ← This is wrong
    handler = getattr(handler_cls, info["handler_class"])

Issue: importlib.import_module() returns a module, not a class. Should be:

module = importlib.import_module(info["handler_module"])
handler_cls = getattr(module, info["handler_class"])

Impact: Low (documentation only, doesn't affect runtime behavior)

2. Potential Race Condition in Handler Registration

Lines 360-400 in registry_infra_node_registration_orchestrator.py:

The handler instantiation loop doesn't appear to have thread-safety controls. If create_registry() is called from multiple threads concurrently:

  • Shared imports via importlib.import_module() are thread-safe (Python guarantees this)
  • ✅ The ServiceHandlerRegistry is frozen after creation, so registration is safe
  • ✅ No shared mutable state is modified during instantiation

Verdict: ✅ Thread-safe - No actual issue here.

3. Heartbeat Handler Special Case

Lines 369-380 handle the special case where HandlerNodeHeartbeat requires a projector:

if handler_class_name == "HandlerNodeHeartbeat":
    if projector is None:
        logger.warning(
            "HandlerNodeHeartbeat NOT registered: require_heartbeat_handler=False. "
            "This creates a contract.yaml mismatch (4 handlers defined, only 3 registered). "
            # ...
        )
        continue

Analysis:

  • ✅ Clear warning message explains the mismatch
  • ✅ Documented as "testing only" configuration
  • ⚠️ Concern: String-based handler name check is brittle (could break if handler renamed)
  • 💡 Suggestion: Consider using a handler metadata attribute (e.g., requires_projector=True) instead of hardcoding the class name

🚀 Performance Considerations

Contract Loading Performance

  • ✅ File I/O is minimal (single read per contract)
  • ✅ YAML parsing is efficient (using yaml.safe_load)
  • ✅ Regex operations in convert_class_to_handler_key() are lightweight
  • ✅ Dynamic imports are cached by Python's import system

No performance concerns identified.


🔐 Security Assessment

Threat Model Coverage

Threat Mitigation Status
Deserialization attacks yaml.safe_load() ✅ Mitigated
Memory exhaustion 10MB file size limit ✅ Mitigated
Arbitrary code execution Namespace allowlisting ✅ Mitigated
Path traversal Module path validation ✅ Mitigated
Protocol violations Duck-type validation ✅ Mitigated

Security posture: STRONG ✅


📊 Test Coverage Analysis

Test File Structure

  • test_handler_routing_loader.py: 1,441 lines
  • conftest.py: 384 lines of fixtures
  • Total: ~1,825 lines of test code for a 464-line module

Test-to-code ratio: 3.9:1 (excellent coverage)

Test Categories

  • ✅ Happy path tests (successful loading)
  • ✅ Error handling tests (invalid YAML, missing sections, etc.)
  • ✅ Edge case tests (empty strings, malformed data)
  • ✅ Security tests (file size limits, namespace validation)
  • ✅ Conversion tests (convert_class_to_handler_key)

Test coverage: EXCELLENT ✅


📝 Recommendations

High Priority

  1. Fix docstring example (line 419 in handler_routing_loader.py) - incorrect usage of importlib.import_module()

Medium Priority

  1. Add debug logging when projection_reader=None is passed to handlers (helps catch unexpected cases)
  2. Document namespace allowlist policy - add comments explaining how to add new trusted namespaces

Low Priority (Future Enhancements)

  1. Consider handler metadata instead of hardcoding "HandlerNodeHeartbeat" string check
  2. Monitor contract file sizes in production - adjust 10MB limit if needed
  3. Type safety for dependency map - consider using TypedDict or Pydantic model for handler_dependencies

✅ Final Verdict

APPROVE with minor documentation fix recommended.

Alignment with CLAUDE.md

  • ✅ No Any types - strict typing throughout
  • ✅ Contract-driven - all routing defined in YAML
  • ✅ Security patterns - namespace allowlisting, file size limits, safe YAML loading
  • ✅ Error handling - structured error codes, rich context
  • ✅ PEP 604 unions - uses X | None not Optional[X]
  • ✅ Protocol resolution - duck typing for handler validation
  • ✅ Fail-fast design - errors raised immediately with clear messages

Code Quality Metrics

  • ✅ Modularity: Excellent separation of concerns
  • ✅ Maintainability: Clear code structure, comprehensive docs
  • ✅ Testability: High test coverage with good fixture design
  • ✅ Security: Strong security controls throughout
  • ✅ Performance: No performance concerns identified

🎯 Summary

This is a high-quality refactor that successfully:

  1. Eliminates code duplication via shared utility extraction
  2. Establishes contract-driven handler loading as a reusable pattern
  3. Implements strong security controls (namespace allowlisting, file size limits)
  4. Provides comprehensive test coverage (3.9:1 test-to-code ratio)
  5. Maintains backward compatibility while enabling future extensibility

The single documentation fix is minor and doesn't affect runtime behavior. The intentional design trade-offs (explicit dependency wiring, manual maintenance) are well-justified and properly documented.

Recommendation: Merge after fixing the docstring example.

Great work! 🚀

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py`:
- Around line 179-230: The ModelInfraErrorContext instances created in
_load_handler_class and create_registry lack correlation IDs; replace every
direct ModelInfraErrorContext(...) call (all 8 occurrences) with
ModelInfraErrorContext.with_correlation(...), preserving the existing keyword
args (transport_type=EnumInfraTransportType.RUNTIME,
operation="load_handler_class" or the appropriate operation string,
target_name=f"{module_path}.{class_name}" or the appropriate target) so the
resulting ctx passed into ProtocolConfigurationError (and raised "from e" cases)
includes an auto-generated correlation ID for distributed tracing.
- Around line 156-176: The return type annotation of _load_handler_class should
be changed from the unparameterized `type` to the parameterized `type[object]`
to avoid implicit Any typing; update the function signature `def
_load_handler_class(class_name: str, module_path: str) -> type:` to use `->
type[object]` and adjust any related type hints/usages within that function (and
add a typing import if needed) so strict typing rules are satisfied while
keeping the same behaviour.

In `@src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py`:
- Around line 103-121: Replace direct instantiations of ModelInfraErrorContext
with its with_correlation factory so a correlation_id is always included; e.g.,
where code currently does
ModelInfraErrorContext(transport_type=EnumInfraTransportType.FILESYSTEM,
operation=operation, target_name=str(contract_path)) (and the four other similar
sites), call
ModelInfraErrorContext.with_correlation(transport_type=EnumInfraTransportType.FILESYSTEM,
operation=operation, target_name=str(contract_path)) and pass that ctx into the
raised ProtocolConfigurationError (and any other error paths using ctx) so all
error contexts in this module include an auto-generated correlation_id.
- Around line 124-127: The return annotation of _load_and_validate_contract_yaml
is too broad (tuple[dict, dict]) and allows implicit Any; change it to
tuple[dict[str, JsonType], dict[str, JsonType]] to express JSON-compatible
key/value types, update the function signature accordingly, and add or import
JsonType (from your project's types or typing helpers) at the top of the module
so yaml.safe_load() results and the returned dicts are typed explicitly.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between cde4cab and 55ae48e.

📒 Files selected for processing (5)
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
  • src/omnibase_infra/runtime/handler_contract_source.py
  • src/omnibase_infra/validation/validation_exemptions.yaml
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Never use Any type - use object for generic payloads in function parameters and return types
Use X | None (PEP 604) instead of Optional[X] for nullable types
All services must use ModelONEXContainer for dependency injection via __init__(self, container: ModelONEXContainer)
Use @allow_any decorator with documented reason as exemption mechanism for Any type violations
Use JsonType from omnibase_core.types as the canonical type alias for JSON-compatible values
Use ModelEventEnvelope[object] for generic dispatcher interfaces and object for generic payloads
Infrastructure error handling must use OnexError base class and never expose passwords, API keys, PII, or connection strings with credentials in error messages
Use InfraConnectionError, InfraTimeoutError, InfraAuthenticationError, InfraUnavailableError for transport failures with proper ModelInfraErrorContext
Correlation IDs must be propagated from incoming requests, auto-generated with uuid4() if missing, and included in all error contexts
External service adapters must implement MixinAsyncCircuitBreaker with appropriate threshold and reset_timeout configuration
Protocol resolution must use duck typing via protocols, never use isinstance checks

Files:

  • src/omnibase_infra/runtime/handler_contract_source.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
**/handler_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Handlers must NOT have direct event bus access - only orchestrators may have bus parameters and publish events

Files:

  • src/omnibase_infra/runtime/handler_contract_source.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
**/registry_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Registry files must follow naming convention: node-specific registries use registry_infra_<node_name>.py → RegistryInfra<NodeName>, standalone registries use registry_<purpose>.py → Registry<Purpose>

Files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
🧠 Learnings (24)
📓 Common learnings
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contracts/ : Organize contract subcomponents into separate files (contract_actions.yaml, contract_models.yaml, contract_validation.yaml, etc.) and reference them from main contract.yaml
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : All TODOs must reference a Linear ticket in the format # TODO(OMN-XXXX): description; use # TODO(OMN-TBD): [NEEDS TICKET] as temporary marker during triage

Applied to files:

  • src/omnibase_infra/runtime/handler_contract_source.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : All type: ignore comments must include specific error codes (e.g., [arg-type], [return-value]) and an explanation comment on the line above with format: # NOTE(OMN-XXXX): reason. Safe because <invariant>.

Applied to files:

  • src/omnibase_infra/runtime/handler_contract_source.py
📚 Learning: 2026-01-15T13:12:08.552Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.552Z
Learning: Applies to src/omnibase_core/**/*.py : Document exception handlers with standardized markers: # fallback-ok:, # catch-all-ok:, # cleanup-resilience-ok:, # boundary-ok:, # init-errors-ok:, # tool-resilience-ok:

Applied to files:

  • src/omnibase_infra/runtime/handler_contract_source.py
  • src/omnibase_infra/validation/validation_exemptions.yaml
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/**/*.py : Use FileRegistry for loading YAML contracts: registry = FileRegistry(); contract = registry.load(Path(...)); handle FileRegistry error codes: FILE_NOT_FOUND, FILE_READ_ERROR, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, DUPLICATE_REGISTRATION, DIRECTORY_NOT_FOUND

Applied to files:

  • src/omnibase_infra/runtime/handler_contract_source.py
  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-07T17:50:13.678Z
Learnt from: CR
Repo: OmniNode-ai/omniintelligence PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T17:50:13.678Z
Learning: Applies to **/contracts/*.yaml : Validate contracts using the contract linter tool: python -m omniintelligence.tools.contract_linter for YAML contract definitions

Applied to files:

  • src/omnibase_infra/runtime/handler_contract_source.py
📚 Learning: 2025-11-24T16:32:55.606Z
Learnt from: CR
Repo: OmniNode-ai/omninode_bridge PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T16:32:55.606Z
Learning: Applies to **/*contract*.yaml : All ONEX nodes must have validated YAML contracts following the contract-driven development pattern with input_state and output_state schema definitions

Applied to files:

  • src/omnibase_infra/runtime/handler_contract_source.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/{tools,registry}/**.py : Constructor-based dependency injection must be used for all tool and node classes; validate that required dependencies are not None, raising OnexError with specific error code if missing

Applied to files:

  • src/omnibase_infra/validation/validation_exemptions.yaml
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/!(tool_)+(handler_|utils_|core_)*.py : Do not use prefix patterns `handler_`, `utils_`, or `core_` for file names; use `tool_` prefix instead for business logic files

Applied to files:

  • src/omnibase_infra/validation/validation_exemptions.yaml
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handlers must be discovered and loaded dynamically via YAML contracts using plugin pattern, not hardcoded registries

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-15T13:12:08.553Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T13:12:08.553Z
Learning: Applies to src/omnibase_core/nodes/**/*.py : Use NodeEffect, NodeCompute, NodeReducer, and NodeOrchestrator base classes with declarative YAML contracts; import from omnibase_core.nodes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/**/*.py : Use `omnibase_infra` handlers for OmniIntelligence queries via HttpRestAdapter envelope pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Workflow coordination must be defined in YAML contracts loaded by `NodeAgentOrchestrator`, not as separate Python classes

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:23:49.777Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/node_standards.mdc:0-0
Timestamp: 2025-11-24T17:23:49.777Z
Learning: Applies to **/node_*/v[0-9]*_[0-9]*_[0-9]*/contracts/contract_*.yaml : All ONEX node contract definitions must use the new subcontract architecture pattern, breaking down complex contracts into separate contract_actions.yaml, contract_models.yaml, contract_validation.yaml, and optional contract_cli.yaml and contract_capabilities.yaml files for separation of concerns, maintainability, reusability, modularity, and future tool-as-a-service readiness

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: When both `handler_contract.yaml` and `contract.yaml` exist in the same directory, the loader must raise an error (AMBIGUOUS_CONTRACT_CONFIGURATION)

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/handler_contract.yaml : Handler contract files must declare handler routing with `routing_strategy`, event models, handler classes, and handler modules

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
  • tests/unit/runtime/contract_loaders/test_handler_routing_loader.py
  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/reducer/**/*.py : FSM behavior must be defined in YAML contracts, not as multiple Python reducer classes; use one NodeReducer class that loads different FSM contracts

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contract.yaml : The main contract.yaml file serves as the source of truth for node interfaces and should reference subcontracts using $ref patterns

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contracts/ : Organize contract subcomponents into separate files (contract_actions.yaml, contract_models.yaml, contract_validation.yaml, etc.) and reference them from main contract.yaml

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:22:32.195Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/canonical_patterns.mdc:0-0
Timestamp: 2025-11-24T17:22:32.195Z
Learning: Applies to **/v[0-9]_[0-9]_[0-9]/contract.yaml : Use shared schema references with project root paths in contract definitions (e.g., schemas/onex_field_model.schema.yaml, schemas/semver_model.schema.yaml)

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-06T17:57:00.689Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_spi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T17:57:00.689Z
Learning: Applies to src/omnibase_spi/protocols/handlers/**/*.py : Protocol naming convention: Handler protocols must follow `Protocol{Type}Handler` pattern

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-11-24T17:24:41.687Z
Learnt from: CR
Repo: OmniNode-ai/omniclaude PR: 0
File: .cursor/rules/standards.mdc:0-0
Timestamp: 2025-11-24T17:24:41.687Z
Learning: Applies to **/protocols/protocol_*.py : Use Protocol for tool interfaces and plugin APIs based on method shape (structural typing), not Pydantic models with inheritance

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2025-12-06T22:21:32.649Z
Learnt from: CR
Repo: OmniNode-ai/omniagent PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T22:21:32.649Z
Learning: Applies to nodes/effect/**/*.py : Use handler envelopes from `omnibase_infra` for all I/O operations (HTTP, database, Kafka) instead of custom clients

Applied to files:

  • src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py
📚 Learning: 2026-01-11T17:31:33.550Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_infra PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T17:31:33.550Z
Learning: Applies to **/*.py : Use `InfraConnectionError`, `InfraTimeoutError`, `InfraAuthenticationError`, `InfraUnavailableError` for transport failures with proper `ModelInfraErrorContext`

Applied to files:

  • src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py
🧬 Code graph analysis (2)
tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (5)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (3)
  • convert_class_to_handler_key (255-278)
  • load_handler_routing_subcontract (281-381)
  • load_handler_class_info_from_contract (384-455)
tests/unit/runtime/contract_loaders/conftest.py (12)
  • valid_contract_path (164-172)
  • minimal_contract_path (176-184)
  • contract_with_empty_handlers_path (200-208)
  • nonexistent_contract_path (260-266)
  • invalid_yaml_path (224-232)
  • empty_contract_path (236-244)
  • whitespace_only_contract_path (248-256)
  • contract_without_routing_path (188-196)
  • contract_with_incomplete_handler_path (212-220)
  • contract_with_invalid_routing_strategy_path (270-280)
  • contract_with_unknown_routing_strategy_path (284-292)
  • oversized_contract_path (296-311)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/nodes/node_registration_orchestrator/node.py (1)
  • _create_handler_routing_subcontract (70-88)
src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (5)
src/omnibase_infra/enums/enum_infra_transport_type.py (1)
  • EnumInfraTransportType (32-60)
src/omnibase_infra/models/errors/model_infra_error_context.py (1)
  • ModelInfraErrorContext (17-96)
src/omnibase_infra/errors/error_infra.py (1)
  • ProtocolConfigurationError (154-189)
src/omnibase_infra/models/routing/model_routing_entry.py (1)
  • ModelRoutingEntry (14-49)
src/omnibase_infra/models/routing/model_routing_subcontract.py (1)
  • ModelRoutingSubcontract (21-67)
🔇 Additional comments (5)
src/omnibase_infra/runtime/handler_contract_source.py (1)

453-464: Expanded TODO rationale is clear and actionable.

The added context makes the future FileRegistry refactor intent easy to follow.

src/omnibase_infra/validation/validation_exemptions.yaml (1)

1542-1548: Exemption rationale update looks consistent.

The updated reasoning and ticket reference read cleanly.

src/omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py (1)

29-61: Handler dependency map trade-off is well documented.

Clear rationale and maintenance guidance make the explicit wiring decision easy to audit.

tests/unit/runtime/contract_loaders/test_handler_routing_loader.py (1)

180-242: Characterization test documentation is excellent.

The underscore behavior rationale is explicit and should prevent accidental regressions.

src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py (1)

167-172: This review comment targets the wrong file. The ambiguity detection for AMBIGUOUS_CONTRACT_CONFIGURATION is already fully implemented in src/omnibase_infra/runtime/handler_plugin_loader.py (lines 1969-1990), not in handler_routing_loader.py.

handler_routing_loader.py is a utility module designed specifically to load and validate contract.yaml files for routing information. It is not a handler discovery mechanism—it only loads a single contract.yaml file when explicitly invoked by orchestrators. The handler_plugin_loader.py, which is the comprehensive handler discovery and loading mechanism that deals with both handler_contract.yaml and contract.yaml, already contains the required ambiguity check with proper error handling and is tested in tests/integration/runtime/test_handler_plugin_loader_ambiguous_contract.py.

Adding this check to handler_routing_loader.py would be architecturally incorrect and redundant.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +124 to +127
def _load_and_validate_contract_yaml(
contract_path: Path,
operation: str,
) -> tuple[dict, dict]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "handler_routing_loader.py" -path "*/contract_loaders/*"

Repository: OmniNode-ai/omnibase_infra

Length of output: 141


🏁 Script executed:

cat -n src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | head -150

Repository: OmniNode-ai/omnibase_infra

Length of output: 6724


🏁 Script executed:

grep -n "def _load_and_validate_contract_yaml" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 111


🏁 Script executed:

cat -n src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | sed -n '124,250p'

Repository: OmniNode-ai/omnibase_infra

Length of output: 6295


🏁 Script executed:

grep -A 80 "def _load_and_validate_contract_yaml" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | head -100

Repository: OmniNode-ai/omnibase_infra

Length of output: 3534


🏁 Script executed:

grep -r "JsonType" src/omnibase_infra/ --include="*.py" | head -20

Repository: OmniNode-ai/omnibase_infra

Length of output: 2572


🏁 Script executed:

grep -r "class JsonType\|JsonType =" src/omnibase_core/ --include="*.py" 2>/dev/null | head -10

Repository: OmniNode-ai/omnibase_infra

Length of output: 52


🏁 Script executed:

grep -r "from omnibase_core.types import" src/omnibase_infra/ --include="*.py" | head -10

Repository: OmniNode-ai/omnibase_infra

Length of output: 1091


🏁 Script executed:

grep -A 5 "JsonType = " src/omnibase_core/types.py 2>/dev/null || grep -A 5 "JsonType" src/omnibase_core/types/__init__.py 2>/dev/null | head -20

Repository: OmniNode-ai/omnibase_infra

Length of output: 52


🏁 Script executed:

find . -name "types.py" -o -name "types" -type d | grep omnibase_core

Repository: OmniNode-ai/omnibase_infra

Length of output: 52


🏁 Script executed:

cat src/omnibase_infra/models/types/__init__.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 2952


🏁 Script executed:

grep -n "ModelInfraErrorContext(" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 279


🏁 Script executed:

grep -B 5 -A 5 "correlation_id" src/omnibase_infra/errors.py | head -40

Repository: OmniNode-ai/omnibase_infra

Length of output: 131


🏁 Script executed:

grep -A 10 "class ModelInfraErrorContext" src/omnibase_infra/errors.py | head -20

Repository: OmniNode-ai/omnibase_infra

Length of output: 131


🏁 Script executed:

find . -name "*.py" -type f | xargs grep -l "class ModelInfraErrorContext" | head -5

Repository: OmniNode-ai/omnibase_infra

Length of output: 133


🏁 Script executed:

grep -r "class ModelInfraErrorContext" src/omnibase_infra --include="*.py"

Repository: OmniNode-ai/omnibase_infra

Length of output: 172


🏁 Script executed:

grep -n "from omnibase_infra" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | head -5

Repository: OmniNode-ai/omnibase_infra

Length of output: 390


🏁 Script executed:

cat -n src/omnibase_infra/models/errors/model_infra_error_context.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 4443


🏁 Script executed:

grep -n "correlation_id" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 52


🏁 Script executed:

find . -name "__init__.py" -path "*/omnibase_infra/errors*" -exec cat {} \;

Repository: OmniNode-ai/omnibase_infra

Length of output: 5568


🏁 Script executed:

cat -n src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | sed -n '1,80p'

Repository: OmniNode-ai/omnibase_infra

Length of output: 3303


🏁 Script executed:

grep -n "handler_contract\|contract_handler" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 148


🏁 Script executed:

ls -la src/omnibase_infra/runtime/contract_loaders/

Repository: OmniNode-ai/omnibase_infra

Length of output: 321


🏁 Script executed:

grep -B 5 "def load_handler_routing_subcontract" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | head -15

Repository: OmniNode-ai/omnibase_infra

Length of output: 369


🏁 Script executed:

grep -A 20 "def load_handler_routing_subcontract" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | head -30

Repository: OmniNode-ai/omnibase_infra

Length of output: 1059


🏁 Script executed:

grep -A 50 "def load_handler_routing_subcontract" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | grep -A 40 "contract, handler_routing ="

Repository: OmniNode-ai/omnibase_infra

Length of output: 597


🏁 Script executed:

sed -n '283,400p' src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 4414


🏁 Script executed:

grep -n "handler_contract.yaml\|contract.yaml" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | head -20

Repository: OmniNode-ai/omnibase_infra

Length of output: 1478


🏁 Script executed:

ls src/omnibase_infra/runtime/contract_loaders/

Repository: OmniNode-ai/omnibase_infra

Length of output: 107


🏁 Script executed:

cat src/omnibase_infra/runtime/contract_loaders/__init__.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 1407


🏁 Script executed:

grep -A 30 "def load_handler_class_info_from_contract" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | tail -25

Repository: OmniNode-ai/omnibase_infra

Length of output: 959


🏁 Script executed:

python3 << 'EOF'
# Check what yaml.safe_load returns
import yaml

# Sample contract YAML
sample = """
handler_routing:
  routing_strategy: "payload_type_match"
  handlers:
    - event_model:
        name: "ModelNodeIntrospectionEvent"
"""

result = yaml.safe_load(sample)
print("Type of result:", type(result))
print("Result:", result)
print("Type of result['handler_routing']:", type(result['handler_routing']))
print("Handler routing value:", result['handler_routing'])
EOF

Repository: OmniNode-ai/omnibase_infra

Length of output: 156


🏁 Script executed:

grep -B 5 "uuid4" src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py

Repository: OmniNode-ai/omnibase_infra

Length of output: 52


🏁 Script executed:

grep -r "uuid4\|uuid4()" src/omnibase_infra --include="*.py" -l | head -5

Repository: OmniNode-ai/omnibase_infra

Length of output: 317


🏁 Script executed:

cat -n src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py | sed -n '260,420p'

Repository: OmniNode-ai/omnibase_infra

Length of output: 7283


Improve return type precision in _load_and_validate_contract_yaml to avoid implicit Any.

The return type tuple[dict, dict] omits key and value types, implying Any. Since yaml.safe_load() and dictionary access return JSON-compatible values, use tuple[dict[str, JsonType], dict[str, JsonType]] to maintain explicit typing and satisfy the no-Any rule.

♻️ Proposed fix
 from omnibase_core.models.primitives.model_semver import ModelSemVer
+from omnibase_core.types import JsonType
@@
 def _load_and_validate_contract_yaml(
     contract_path: Path,
     operation: str,
-) -> tuple[dict, dict]:
+) -> tuple[dict[str, JsonType], dict[str, JsonType]]:
🤖 Prompt for AI Agents
In `@src/omnibase_infra/runtime/contract_loaders/handler_routing_loader.py` around
lines 124 - 127, The return annotation of _load_and_validate_contract_yaml is
too broad (tuple[dict, dict]) and allows implicit Any; change it to
tuple[dict[str, JsonType], dict[str, JsonType]] to express JSON-compatible
key/value types, update the function signature accordingly, and add or import
JsonType (from your project's types or typing helpers) at the top of the module
so yaml.safe_load() results and the returned dicts are typed explicitly.

Address PR #153 review feedback:
- Replace unparameterized `type` with `type[object]` in _load_handler_class
- Add correlation IDs to all ModelInfraErrorContext instantiations using
  .with_correlation() factory method for distributed tracing support
@claude

claude Bot commented Jan 15, 2026

Copy link
Copy Markdown

Code Review - PR #153: Handler Routing Loader Refactor

Summary

This PR successfully extracts handler routing logic from individual orchestrators into a shared, reusable utility that follows the Handler Plugin Loader pattern. The refactoring eliminates duplicate code, improves maintainability, and aligns with ONEX's contract-driven architecture.

Overall Assessment: ✅ APPROVED with minor observations


Strengths

1. Excellent Architecture & Design 🎯

  • Shared utility pattern: The extracted handler_routing_loader.py consolidates YAML loading logic that was previously duplicated across orchestrators
  • Clean separation: load_handler_routing_subcontract() for routing config, load_handler_class_info_from_contract() for class metadata
  • Fail-fast validation: File size checks, YAML parsing, and protocol validation all happen at startup
  • Clear error codes: Every error path has a unique code (e.g., HANDLER_LOADER_013, HANDLER_LOADER_050)

2. Security Controls 🔒

  • File size limits: 10MB max to prevent memory exhaustion (HANDLER_LOADER_050)
  • Namespace allowlisting: ALLOWED_NAMESPACES restricts dynamic imports to trusted modules
  • YAML safe loading: Uses yaml.safe_load() to prevent deserialization attacks
  • Size check ordering: File size validated BEFORE parsing (line 168 in loader)

3. Documentation Quality 📚

  • Extensive docstrings: Every function has clear Args/Returns/Raises sections
  • Design rationale: Module-level docs explain the "Handler Dependency Map" trade-off
  • Security notes: Explicit warnings about dynamic import risks
  • Error context: All ProtocolConfigurationError instances include helpful troubleshooting guidance

4. Test Coverage ✅

  • 1,441 lines of tests covering happy paths, edge cases, and error conditions
  • Characterization tests: Documents existing behavior (e.g., underscore handling in convert_class_to_handler_key)
  • Security tests: File size enforcement, namespace validation
  • Conftest fixtures: Well-organized test data (384 lines)

Observations & Recommendations

1. Handler Dependency Map - Intentional Manual Maintenance

File: registry_infra_node_registration_orchestrator.py:85-116

The handler_dependencies dict requires manual updates when adding handlers to contract.yaml. This is intentional and well-documented, but worth highlighting:

Why manual over auto-discovery:

  • ✅ Type safety: Validated at startup, not runtime
  • ✅ Testability: Dependencies easily mocked
  • ✅ Security: No reflection-based injection attacks
  • ✅ Auditability: Clear record of handler wiring

Maintenance requirement: When adding a handler to contract.yaml, you MUST update handler_dependencies. Missing entries trigger HANDLER_LOADER_061 at startup.

Recommendation: This is the right trade-off. Consider adding a pre-commit hook or CI check that validates contract.yaml handlers match the dependency map keys.

2. Regex-Based Kebab-Case Conversion

File: handler_routing_loader.py:255-278

The convert_class_to_handler_key() function uses regex to convert CamelCase to kebab-case. Characterization tests document "surprising" behavior with underscores:

"My_Handler" -> "my_-handler"  # Mixed underscore-hyphen

This is acceptable since:

  • Handler class names should NOT contain underscores (PEP 8)
  • Tests explicitly document this as characterization (not ideal)
  • No production impact if naming conventions are followed

Recommendation: No action needed. The characterization test serves as a "change detector" and documents edge cases.

3. Transport Type for File Operations

File: handler_routing_loader.py:175-176

File loading errors use EnumInfraTransportType.FILESYSTEM, but contract validation uses RUNTIME. This is slightly inconsistent but acceptable:

# File loading - uses FILESYSTEM
transport_type=EnumInfraTransportType.FILESYSTEM,
operation="load_handler_routing_contract",

# Registry creation - uses RUNTIME
transport_type=EnumInfraTransportType.RUNTIME,
operation="create_registry",

Recommendation: Consider documenting the distinction in error context guidelines. FILESYSTEM = file I/O errors, RUNTIME = logical/validation errors.

4. Optional Dependency Filtering Logic

File: registry_infra_node_registration_orchestrator.py:453-476

The filtered_deps logic has excellent inline documentation explaining why projection_reader is ALWAYS passed (even if None) but projector/consul_handler are conditionally passed. This is very clear and follows best practices.

Observation: This is exemplary inline documentation. No changes needed.

5. File Size Check Before Parsing

File: handler_routing_loader.py:167-168

The file size check happens BEFORE yaml.safe_load(), which is critical for security:

# Check file size before loading (security control)
_check_file_size(contract_path, operation)

# Load YAML file
with contract_path.open("r", encoding="utf-8") as f:
    contract = yaml.safe_load(f)

Observation: This is the correct order. Prevents memory exhaustion attacks. Well done.


Code Quality Checklist

Aspect Status Notes
Follows ONEX patterns ✅ Contract-driven, declarative, fail-fast
Type annotations ✅ No Any usage, proper X | None patterns
Error handling ✅ All errors raise ProtocolConfigurationError with context
Security ✅ File size limits, namespace allowlisting, safe YAML loading
Documentation ✅ Comprehensive docstrings, design rationale, security notes
Test coverage ✅ 1,441 lines of tests, happy paths + edge cases
Backwards compatibility ✅ Breaking changes acceptable per CLAUDE.md policy

Security Review ✅

All security controls from CLAUDE.md Handler Plugin Loader pattern are implemented:

Control Status Evidence
YAML deserialization attacks ✅ yaml.safe_load() blocks \!\!python/object
Memory exhaustion ✅ 10MB file size limit enforced
Arbitrary class loading ✅ Protocol validation requires 5 methods
Namespace restrictions ✅ ALLOWED_NAMESPACES parameter
Path traversal ✅ contract_path must be Path object

Performance Considerations

  • File I/O: Contract loading happens once at startup (acceptable)
  • Regex conversion: convert_class_to_handler_key() is O(n) but called rarely
  • Dynamic imports: importlib.import_module() caches modules automatically
  • No runtime overhead: All validation is startup-time only

Alignment with CLAUDE.md

Rule Status Evidence
Declarative nodes ✅ Contract-driven routing, no imperative logic
No backwards compatibility ✅ Breaking changes acceptable (version bump)
Strong typing ✅ No Any usage, proper Pydantic models
Protocol resolution ✅ Duck typing for ProtocolMessageHandler
Container injection ✅ ModelONEXContainer passed to handlers

Final Recommendations

Must-Do Before Merge

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

Nice-to-Have Follow-ups

  1. CI Check for Dependency Map: Add a validator that ensures handler_dependencies keys match contract.yaml handlers
  2. Transport Type Guidelines: Document when to use FILESYSTEM vs RUNTIME in error contexts
  3. Registry File Pattern: Track OMN-1352 to refactor file I/O to use RegistryFileBased once available in omnibase_core

Conclusion

This is high-quality infrastructure refactoring that follows best practices:

  • ✅ Eliminates code duplication (158 lines removed from node.py)
  • ✅ Improves security (namespace allowlisting, file size limits)
  • ✅ Enhances maintainability (shared utility, clear error codes)
  • ✅ Excellent test coverage (35 new tests, 1,441 lines)
  • ✅ Comprehensive documentation (design rationale, security notes)

Approval Status: ✅ APPROVED

Great work on this refactor! The design trade-offs are well-documented, security controls are robust, and the test coverage is excellent. 🎉


Related Issues: Closes OMN-1316
Test Results: All 99 related tests pass (per PR description)

@jonahgabriel
jonahgabriel merged commit 4071c53 into main Jan 15, 2026
11 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-1316-load-subcontract-from-contractyaml-instead-of-programmatic branch April 4, 2026 02:06
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