Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/validation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ INFRA_SRC_PATH = "src/omnibase_infra/"
INFRA_NODES_PATH = "src/omnibase_infra/nodes/"
INFRA_MAX_UNIONS = 20
INFRA_MAX_VIOLATIONS = 0
INFRA_PATTERNS_STRICT = True
INFRA_PATTERNS_STRICT = False
INFRA_UNIONS_STRICT = False
```

Expand Down
2 changes: 1 addition & 1 deletion docs/validation/framework_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ INFRA_NODES_PATH = "src/omnibase_infra/nodes/"

# Infrastructure thresholds (stricter than core defaults)
INFRA_MAX_VIOLATIONS = 0 # Zero tolerance for architecture violations
INFRA_PATTERNS_STRICT = True # Strict pattern enforcement
INFRA_PATTERNS_STRICT = False # Relaxed pattern enforcement for infrastructure

# Infrastructure allowances (more permissive than core defaults)
INFRA_MAX_UNIONS = 20 # Infrastructure needs typed handlers
Expand Down
10 changes: 5 additions & 5 deletions docs/validation/validator_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def validate_infra_patterns(
### Parameters

- **directory** (`str | Path`, optional): Directory to validate. Defaults to `"src/omnibase_infra/"`.
- **strict** (`bool`, optional): Enable strict mode. Defaults to `True`.
- **strict** (`bool`, optional): Enable strict mode. Defaults to `False`.

### Returns

Expand Down Expand Up @@ -339,7 +339,7 @@ def validate_infra_union_usage(
### Parameters

- **directory** (`str | Path`, optional): Directory to validate. Defaults to `"src/omnibase_infra/"`.
- **max_unions** (`int`, optional): Maximum allowed complex unions. Defaults to `20`.
- **max_unions** (`int`, optional): Maximum allowed complex unions. Defaults to `185`.
- **strict** (`bool`, optional): Enable strict mode. Defaults to `False`.

### Returns
Expand All @@ -364,7 +364,7 @@ Infrastructure code needs typed unions for:
- Message routing and handler dispatch
- Service integration type safety

**Why max_unions=20**: Infrastructure has many service adapters with typed handlers. 20 allows necessary unions while preventing excessive complexity.
**Why max_unions=185**: Infrastructure has many service adapters with typed handlers, protocol implementations, message routing, and registration event models. The higher threshold accommodates RuntimeHostProcess, handler wiring, strongly-typed optional model wrappers (PEP 604 `X | None` syntax), and ONEX service integration patterns while preventing excessive complexity.

### Example Usage

Expand Down Expand Up @@ -611,11 +611,11 @@ INFRA_SRC_PATH = "src/omnibase_infra/"
INFRA_NODES_PATH = "src/omnibase_infra/nodes/"

# Validation thresholds
INFRA_MAX_UNIONS = 20 # Maximum allowed complex union types
INFRA_MAX_UNIONS = 185 # Maximum allowed complex union types
INFRA_MAX_VIOLATIONS = 0 # Zero tolerance for architecture violations

# Strict mode flags
INFRA_PATTERNS_STRICT = True # Strict pattern enforcement
INFRA_PATTERNS_STRICT = False # Relaxed pattern enforcement for infrastructure
INFRA_UNIONS_STRICT = False # Allow necessary unions for infrastructure
```

Expand Down
18 changes: 18 additions & 0 deletions src/omnibase_infra/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 OmniNode Team
"""ONEX Infrastructure Models.

This module provides shared models for the omnibase_infra package.
"""

from omnibase_infra.models.registration import (
ModelNodeHeartbeatEvent,
ModelNodeIntrospectionEvent,
ModelNodeRegistration,
)

__all__ = [
"ModelNodeHeartbeatEvent",
"ModelNodeIntrospectionEvent",
"ModelNodeRegistration",
]
25 changes: 25 additions & 0 deletions src/omnibase_infra/models/registration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 OmniNode Team
"""Registration models for ONEX 2-way registration pattern."""

from omnibase_infra.models.registration.model_node_capabilities import (
ModelNodeCapabilities,
)
from omnibase_infra.models.registration.model_node_heartbeat_event import (
ModelNodeHeartbeatEvent,
)
from omnibase_infra.models.registration.model_node_introspection_event import (
ModelNodeIntrospectionEvent,
)
from omnibase_infra.models.registration.model_node_metadata import ModelNodeMetadata
from omnibase_infra.models.registration.model_node_registration import (
ModelNodeRegistration,
)

__all__ = [
"ModelNodeCapabilities",
"ModelNodeHeartbeatEvent",
"ModelNodeIntrospectionEvent",
"ModelNodeMetadata",
"ModelNodeRegistration",
]
169 changes: 169 additions & 0 deletions src/omnibase_infra/models/registration/model_node_capabilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 OmniNode Team
"""Node Capabilities Model.

This module provides ModelNodeCapabilities for strongly-typed node capabilities
in the ONEX 2-way registration pattern.
"""

from __future__ import annotations

from pydantic import BaseModel, ConfigDict, Field


class ModelNodeCapabilities(BaseModel):
"""Strongly-typed node capabilities model.

Replaces dict[str, Any] with explicit capability fields.
Uses extra="allow" to support custom capabilities while
providing type safety for known fields.

Known capability fields are typed explicitly. Additional custom
capabilities can be added via the extra="allow" config, and they
will be stored as model extra fields accessible via model_extra.

Attributes:
postgres: Whether node has PostgreSQL capability.
read: Whether node has read capability.
write: Whether node has write capability.
database: Whether node has generic database capability.
processing: Whether node has processing capability.
batch_size: Optional batch size limit.
max_batch: Optional maximum batch size.
supported_types: List of supported data types.
routing: Whether node has routing capability.
config: Nested configuration dictionary.
transactions: Whether node supports transactions.
feature: Generic feature flag.

Example:
>>> caps = ModelNodeCapabilities(
... postgres=True,
... read=True,
... write=True,
... )
>>> caps.postgres
True

>>> # Custom capabilities via extra="allow"
>>> caps = ModelNodeCapabilities(
... custom_capability=True, # type: ignore[call-arg]
... another_field="value", # type: ignore[call-arg]
... )
>>> caps.model_extra["custom_capability"]
True
"""

model_config = ConfigDict(
extra="allow", # Accept additional fields not explicitly defined
frozen=False, # Allow updates (ModelNodeRegistration is mutable)
from_attributes=True,
)

# Database capabilities
postgres: bool = Field(default=False, description="PostgreSQL capability")
read: bool = Field(default=False, description="Read capability")
write: bool = Field(default=False, description="Write capability")
database: bool = Field(default=False, description="Generic database capability")
transactions: bool = Field(default=False, description="Transaction support")

# Processing capabilities
processing: bool = Field(default=False, description="Processing capability")
batch_size: int | None = Field(default=None, description="Batch size limit")
max_batch: int | None = Field(default=None, description="Maximum batch size")
supported_types: list[str] = Field(
default_factory=list, description="Supported data types"
)

# Network capabilities
routing: bool = Field(default=False, description="Routing capability")

# Generic feature flag (used in tests)
feature: bool = Field(default=False, description="Generic feature flag")

# Configuration (nested) - using constrained types instead of Any
config: dict[str, int | str | bool | float] = Field(
default_factory=dict, description="Nested configuration"
)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
def __getitem__(self, key: str) -> object:
"""Enable dict-like access to capabilities.

Args:
key: The capability name to retrieve.

Returns:
The capability value (from known field or model_extra).

Raises:
KeyError: If key is not found in known fields or model_extra.

Example:
>>> caps = ModelNodeCapabilities(postgres=True, custom=42)
>>> caps["postgres"]
True
>>> caps["custom"] # Custom capability from model_extra
42
"""
if key in type(self).model_fields:
return getattr(self, key)
extra = self.model_extra or {}
if key in extra:
return extra[key]
raise KeyError(key)

def __contains__(self, key: object) -> bool:
"""Enable membership testing for capabilities.

Returns True if key is a known field OR exists in model_extra.

Args:
key: The capability name to check.

Returns:
True if the key exists as a known field or in model_extra.

Example:
>>> caps = ModelNodeCapabilities(postgres=True, custom=42)
>>> "postgres" in caps
True
>>> "custom" in caps # Custom capability in model_extra
True
>>> "unknown" in caps
False
"""
if not isinstance(key, str):
return False

# Check known fields first (access via class to avoid deprecation)
if key in type(self).model_fields:
return True

# Check model_extra for custom capabilities
return bool(self.model_extra and key in self.model_extra)

def get(self, key: str, default: object = None) -> object:
"""Safely get a capability value with optional default.

Args:
key: The capability name to retrieve.
default: Value to return if key is not found (defaults to None).

Returns:
The capability value if found, otherwise the default value.

Example:
>>> caps = ModelNodeCapabilities(postgres=True)
>>> caps.get("postgres")
True
>>> caps.get("unknown", False)
False
>>> caps.get("unknown") # Returns None by default
"""
try:
return self[key]
except KeyError:
return default


__all__ = ["ModelNodeCapabilities"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 OmniNode Team
"""Node Heartbeat Event Model.

This module provides ModelNodeHeartbeatEvent for periodic node heartbeat broadcasts
in the ONEX 2-way registration pattern.
"""

from __future__ import annotations

from datetime import UTC, datetime
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field, field_validator

from omnibase_infra.utils.util_semver import validate_semver as _validate_semver


class ModelNodeHeartbeatEvent(BaseModel):
"""Event model for periodic node heartbeat broadcasts.

Nodes publish this event periodically to indicate they are alive and
report current health metrics. Used by the Registry node to detect
node failures and track resource usage.

Attributes:
node_id: Node identifier.
node_type: ONEX node type string.
node_version: Semantic version of the node emitting this event.
uptime_seconds: Node uptime in seconds (must be >= 0).
active_operations_count: Number of active operations (must be >= 0).
memory_usage_mb: Optional memory usage in megabytes.
cpu_usage_percent: Optional CPU usage percentage (0-100).
correlation_id: Request correlation ID for tracing.
timestamp: Event timestamp.

Example:
>>> from uuid import uuid4
>>> event = ModelNodeHeartbeatEvent(
... node_id=uuid4(),
... node_type="effect",
... node_version="1.2.3",
... uptime_seconds=3600.5,
... active_operations_count=5,
... memory_usage_mb=256.0,
... cpu_usage_percent=15.5,
... )
"""

model_config = ConfigDict(
frozen=True,
extra="forbid",
from_attributes=True,
)

# Required fields
node_id: UUID = Field(..., description="Node identifier")
# Design Note: node_type uses relaxed `str` validation (not `Literal`) to support
# custom node types during development. This is intentional - heartbeats may come
# from experimental or plugin nodes not in the standard ONEX type set. For strict
# validation, see ModelNodeIntrospectionEvent which uses Literal["effect", "compute",
# "reducer", "orchestrator"]. Tests explicitly verify custom types are accepted.
node_type: str = Field(..., description="ONEX node type")
node_version: str = Field(
default="1.0.0",
description="Semantic version of the node emitting this event",
)

@field_validator("node_version")
@classmethod
def validate_semver(cls, v: str) -> str:
"""Validate that node_version follows semantic versioning."""
return _validate_semver(v, "node_version")

# Health metrics
uptime_seconds: float = Field(..., ge=0, description="Node uptime in seconds")
active_operations_count: int = Field(
default=0, ge=0, description="Number of active operations"
)

# Resource usage (optional)
memory_usage_mb: float | None = Field(
default=None, ge=0, description="Memory usage in megabytes"
)
cpu_usage_percent: float | None = Field(
default=None, ge=0, le=100, description="CPU usage percentage (0-100)"
)

# Metadata
correlation_id: UUID | None = Field(
default=None, description="Request correlation ID for tracing"
)
timestamp: datetime = Field(
default_factory=lambda: datetime.now(UTC), description="Event timestamp"
)


__all__ = ["ModelNodeHeartbeatEvent"]
Loading
Loading