Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cb651bb
feat(runtime): implement HandlerBootstrapSource for centralized handl…
jonahgabriel Jan 19, 2026
c9b102e
fix(runtime): address PR #176 review feedback for HandlerBootstrapSou…
jonahgabriel Jan 19, 2026
4f12625
fix(runtime): address remaining PR #176 review issues [OMN-1087]
jonahgabriel Jan 19, 2026
027a302
fix(runtime): add thread safety and import validation for HandlerBoot…
jonahgabriel Jan 19, 2026
8305c80
fix(runtime): address final PR #176 review items for HandlerBootstrap…
jonahgabriel Jan 20, 2026
cfb54fe
docs(runtime): address PR #176 review comments for HandlerBootstrapSo…
jonahgabriel Jan 20, 2026
7aaf6ea
fix(lint): add quotes to cast() type expressions for TC006 compliance…
jonahgabriel Jan 20, 2026
011f751
test(runtime): add model importability and thread safety tests [OMN-1…
jonahgabriel Jan 20, 2026
e76e93e
fix(runtime): improve model_rebuild error handling and comment clarit…
jonahgabriel Jan 20, 2026
7186971
Merge remote-tracking branch 'origin/main' into jonah/omn-1087-implem…
jonahgabriel Jan 20, 2026
cfe5291
refactor(runtime): remove unused graceful_mode parameter [OMN-1087]
jonahgabriel Jan 20, 2026
c4cda9a
feat(runtime): wire HandlerBootstrapSource into runtime bootstrap [OM…
jonahgabriel Jan 20, 2026
ef74b90
Merge main into jonah/omn-1087-implement-handlerbootstrapsource-descr…
jonahgabriel Jan 20, 2026
cd1390e
refactor(runtime): remove wire_handlers() fallback [OMN-1087]
jonahgabriel Jan 20, 2026
6387dc1
chore: remove Claude Code GitHub workflows
jonahgabriel Jan 20, 2026
bb4b0e3
chore(models): remove unused imports from ModelBootstrapHandlerDescri…
jonahgabriel Jan 20, 2026
c2839d1
refactor(models): simplify to_base_descriptor() using model_dump()
jonahgabriel Jan 20, 2026
7aa6c52
fix(runtime): address PR #176 code review improvements [OMN-1087]
jonahgabriel Jan 20, 2026
020e3ca
docs: strengthen no-backwards-compatibility policy in CLAUDE.md
jonahgabriel Jan 20, 2026
32be7be
refactor(runtime): simplify code from PR #176 review feedback [OMN-1087]
jonahgabriel Jan 20, 2026
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
44 changes: 0 additions & 44 deletions .github/workflows/claude-code-review.yml

This file was deleted.

50 changes: 0 additions & 50 deletions .github/workflows/claude.yml

This file was deleted.

17 changes: 14 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,31 @@

**ALL changes are breaking changes. NO backwards compatibility is maintained.**

- Breaking changes are **always** acceptable and encouraged
- Remove old patterns **immediately** - do not leave deprecated code
**Rationale**: There are no external users of this system yet. This is the optimal time to make breaking changes freely, clean up technical debt aggressively, and evolve the architecture without friction. Once external users exist, this policy will be revisited.

- Breaking changes are **always** acceptable and **strongly encouraged**
- **ALWAYS** remove deprecated functionality immediately - never leave dead code
- Remove old patterns **immediately** - do not leave deprecated code lying around
- **NO** backwards compatibility documentation required
- **NO** migration guides needed
- **NO** deprecation periods - old APIs are simply removed
- Downstream consumers are expected to update immediately
- **NO** `# TODO: remove in v2` comments - remove it NOW
- Version bumps may contain any breaking change without warning

**When you see deprecated or unused code: DELETE IT.** Do not:
- Leave it "for reference"
- Comment it out
- Add deprecation warnings
- Create compatibility shims
- Keep old function signatures with forwarding

This policy applies to:
- API changes (function signatures, class interfaces)
- Model changes (Pydantic field additions/removals/renames)
- Import path changes (module reorganization)
- Type changes (type aliases, generics)
- Configuration changes (environment variables, settings)
- Removing entire features or modules that are no longer needed

### No Versioned Directories
- **NEVER** create `v1_0_0/`, `v2/` directories
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ ignore = [
"PLR1722", # Sys exit alias (technical debt)
"ISC001", # Implicit string concatenation (conflicts with formatter)
"PLC0415", # Import not at top-level (intentional lazy imports in tests)
"TC006", # Add quotes to type expression in cast (style preference)
]

[tool.ruff.lint.isort]
Expand Down
4 changes: 2 additions & 2 deletions src/omnibase_infra/handlers/mixins/mixin_consul_kv.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def get_func() -> tuple[
get_func,
correlation_id,
)
index, data = cast(KVGetResult, result)
index, data = cast("KVGetResult", result)

# Handle response - data can be None if key doesn't exist
if data is None:
Expand Down Expand Up @@ -325,7 +325,7 @@ def put_func() -> bool:
put_func,
correlation_id,
)
success = cast(bool, result)
success = cast("bool", result)

typed_payload = ModelConsulKVPutPayload(
success=success,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,15 +567,15 @@ def _query_services() -> tuple[int, list[dict[str, object]]]:
service_infos: list[ModelServiceInfo] = []
for svc in services:
# Cast nested dicts for type safety
svc_data = cast(dict[str, object], svc.get("Service", {}))
node_data = cast(dict[str, object], svc.get("Node", {}))
svc_data = cast("dict[str, object]", svc.get("Service", {}))
node_data = cast("dict[str, object]", svc.get("Node", {}))
svc_id_str = str(svc_data.get("ID", ""))
svc_name = svc_data.get("Service", "")
address = svc_data.get("Address", "") or node_data.get("Address", "")
port_raw = svc_data.get("Port", 0)
port = int(port_raw) if isinstance(port_raw, int | float | str) else 0
svc_tags = cast(list[str], svc_data.get("Tags", []))
svc_meta = cast(dict[str, str], svc_data.get("Meta", {}))
svc_tags = cast("list[str]", svc_data.get("Tags", []))
svc_meta = cast("dict[str, str]", svc_data.get("Meta", {}))

if svc_id_str and svc_name and address and port:
# Convert Consul service ID string to UUID
Expand Down
2 changes: 1 addition & 1 deletion src/omnibase_infra/mixins/mixin_node_introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1353,7 +1353,7 @@ async def get_introspection_data(self) -> ModelNodeIntrospectionEvent:
# Update cache - cast the model_dump output to our typed dict since we know
# the structure matches (model_dump returns dict[str, Any] by default)
self._introspection_cache = cast(
IntrospectionCacheDict, event.model_dump(mode="json")
"IntrospectionCacheDict", event.model_dump(mode="json")
)
self._introspection_cached_at = current_time

Expand Down
2 changes: 1 addition & 1 deletion src/omnibase_infra/mixins/mixin_retry_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def _as_circuit_breaker(self) -> ProtocolCircuitBreakerAware:
This should only be called when _circuit_breaker_initialized is True,
which guarantees the circuit breaker methods are available.
"""
return cast(ProtocolCircuitBreakerAware, self)
return cast("ProtocolCircuitBreakerAware", self)

async def _record_circuit_failure_if_enabled(
self, operation: str, correlation_id: UUID
Expand Down
10 changes: 10 additions & 0 deletions src/omnibase_infra/models/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
Added ModelHandlerDescriptor and ModelContractDiscoveryResult for
OMN-1097 filesystem handler discovery.

.. versionchanged:: 0.6.4
Added ModelBootstrapHandlerDescriptor for OMN-1087 bootstrap handler
validation with required handler_class field.

Note:
ModelContractDiscoveryResult uses a forward reference to
ModelHandlerValidationError to avoid circular imports. The forward
Expand All @@ -20,17 +24,23 @@
tests/unit/runtime/test_handler_contract_source.py.
"""

from omnibase_infra.models.handlers.model_bootstrap_handler_descriptor import (
ModelBootstrapHandlerDescriptor,
)
from omnibase_infra.models.handlers.model_contract_discovery_result import (
ModelContractDiscoveryResult,
)
from omnibase_infra.models.handlers.model_handler_descriptor import (
LiteralHandlerKind,
ModelHandlerDescriptor,
)
from omnibase_infra.models.handlers.model_handler_identifier import (
ModelHandlerIdentifier,
)

__all__ = [
"LiteralHandlerKind",
"ModelBootstrapHandlerDescriptor",
"ModelContractDiscoveryResult",
"ModelHandlerDescriptor",
"ModelHandlerIdentifier",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 OmniNode Team
"""Bootstrap Handler Descriptor Model with Required handler_class.

This module provides ModelBootstrapHandlerDescriptor, a specialized handler
descriptor for bootstrap handlers that REQUIRES the handler_class field to be set.

Bootstrap handlers are hardcoded handlers that must always specify their
implementation class for dynamic import. Unlike contract-discovered handlers
where handler_class may be optional (inferred from convention), bootstrap
handlers have no contract file to derive the class from.

Part of OMN-1087: Implement HandlerBootstrapSource descriptor-based validation.

See Also:
- ModelHandlerDescriptor: Base descriptor with optional handler_class
- HandlerBootstrapSource: Source that uses this specialized descriptor
- BootstrapEffectDefinition: TypedDict for bootstrap handler definitions

.. versionadded:: 0.6.4
Created as part of OMN-1087 bootstrap handler validation.
"""

from __future__ import annotations

from pydantic import ConfigDict, Field

from omnibase_infra.models.handlers.model_handler_descriptor import (
ModelHandlerDescriptor,
)


class ModelBootstrapHandlerDescriptor(ModelHandlerDescriptor):
"""Handler descriptor for bootstrap handlers with required handler_class.

This specialized descriptor extends ModelHandlerDescriptor to enforce that
handler_class is always set. Bootstrap handlers are hardcoded and must
specify their implementation class since there is no contract file to
derive the class from.

The key difference from ModelHandlerDescriptor:
- handler_class: Required (str) instead of optional (str | None)

All other fields maintain the same constraints as the parent class.

Attributes:
handler_id: Unique identifier for the handler (e.g., "bootstrap.consul").
name: Human-readable name for the handler.
version: Semantic version (ModelSemVer). Accepts string, dict, or ModelSemVer.
handler_kind: Handler kind (compute, effect, reducer, orchestrator).
input_model: Fully qualified input model class path.
output_model: Fully qualified output model class path.
description: Optional description of the handler.
handler_class: REQUIRED fully qualified Python class path for dynamic import.
contract_path: Path to the source contract file (typically None for bootstrap).

Example:
Create a bootstrap handler descriptor:

>>> descriptor = ModelBootstrapHandlerDescriptor(
... handler_id="bootstrap.consul",
... name="Consul Handler",
... version="1.0.0",
... handler_kind="effect",
... input_model="omnibase_infra.models.types.JsonDict",
... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
... handler_class="omnibase_infra.handlers.handler_consul.HandlerConsul",
... )
>>> descriptor.handler_class
'omnibase_infra.handlers.handler_consul.HandlerConsul'

Missing handler_class raises ValidationError:

>>> from pydantic import ValidationError
>>> try:
... ModelBootstrapHandlerDescriptor(
... handler_id="bootstrap.consul",
... name="Consul Handler",
... version="1.0.0",
... handler_kind="effect",
... input_model="omnibase_infra.models.types.JsonDict",
... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
... # handler_class omitted - will fail
... )
... except ValidationError as e:
... print("Validation failed as expected")
Validation failed as expected

Raises:
ValidationError: If handler_class is not provided or is None.

.. versionadded:: 0.6.4
Created as part of OMN-1087 bootstrap handler validation.
"""

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

# Override handler_class to be required (no default, not optional)
# The Field() definition must include the pattern constraint from parent
handler_class: str = Field(
...,
min_length=3,
pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$",
description=(
"REQUIRED: Fully qualified Python class path for dynamic handler import. "
"Bootstrap handlers must always specify this field since they have no "
"contract file to derive the class from. "
"Example: 'omnibase_infra.handlers.handler_consul.HandlerConsul'"
),
)

def to_base_descriptor(self) -> ModelHandlerDescriptor:
"""Convert to base ModelHandlerDescriptor for API compatibility.

This method allows bootstrap descriptors to be used where the base
ModelHandlerDescriptor type is expected, while maintaining the
validation benefits of the bootstrap-specific model.

Implementation Notes:
Uses ``model_dump()`` without ``exclude_unset=True`` because:

1. **Field parity**: This child class has NO extra fields beyond the
parent. The only difference is ``handler_class`` type constraint
(required ``str`` vs optional ``str | None``).

2. **Type compatibility**: A ``str`` value from child is valid where
parent expects ``str | None``.

3. **Complete copy**: ``model_dump()`` ensures all fields are copied,
including those set to their default values.

Using ``exclude_unset=True`` would risk excluding fields that have
defaults but were explicitly set to those defaults during construction.

If future versions add child-specific fields not in parent, this
method MUST be updated to use ``exclude={'new_field'}`` or refactored.

Returns:
ModelHandlerDescriptor instance with all fields copied.

Example:
>>> bootstrap_desc = ModelBootstrapHandlerDescriptor(
... handler_id="bootstrap.consul",
... name="Consul Handler",
... version="1.0.0",
... handler_kind="effect",
... input_model="omnibase_infra.models.types.JsonDict",
... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
... handler_class="omnibase_infra.handlers.handler_consul.HandlerConsul",
... )
>>> base_desc = bootstrap_desc.to_base_descriptor()
>>> isinstance(base_desc, ModelHandlerDescriptor)
True
"""
return ModelHandlerDescriptor(**self.model_dump())


__all__ = ["ModelBootstrapHandlerDescriptor"]
Loading