> **Navigation**: [Home](../index.md) > [Decisions](README.md) > Any Type Pydantic Workaround

# ADR: Use of Any Type as Pydantic 2.x JsonType Workaround

**Status**: Superseded
**Date**: 2026-01-06
**Related Tickets**: OMN-1104, OMN-1262, OMN-1263, OMN-1274

## Resolution

**Resolution Date**: 2026-01-08

This ADR is now **superseded** - the workaround documented herein is no longer needed.

**What Changed**:
- **Migration completed**: All 33+ files have been migrated from `Any` to `JsonType` as part of OMN-1274
- **omnibase_core fix available**: Version 0.6.3 of omnibase_core provides a working `JsonType` implementation that Pydantic 2.x can handle correctly
- **No more workaround needed**: The `Any` type workaround for Pydantic's recursive type alias limitation is no longer necessary

**Migration Details**:
- All files listed in the "Affected Files" section have been updated to use `JsonType` from `omnibase_core.types`
- The `NOTE: Using Any instead of JsonType` comment pattern has been removed from migrated files
- Type safety has been restored for JSON-serializable fields in Pydantic models

**Going Forward**:
- New code should use `JsonType` from `omnibase_core.types` for JSON-serializable fields
- The CLAUDE.md rule "NEVER use Any" is now fully enforceable without exceptions
- This ADR remains as historical documentation of the workaround and migration path

**Tracking Issues**:
- [OMN-1262](https://linear.app/omninode/issue/OMN-1262) - Any type migration tracking
- [OMN-1263](https://linear.app/omninode/issue/OMN-1263) - Integration test coverage and pre-existing test failures

---

## CRITICAL: Scope Boundaries and CLAUDE.md Alignment

> **HISTORICAL NOTE**: This section documented the scope of the temporary exception that was in effect from 2026-01-06 to 2026-01-08. The exception is **no longer active** - use `JsonType` from `omnibase_core.types` instead of `Any`. This section is preserved for historical reference.

### CLAUDE.md Rule Remains Absolute

**CLAUDE.md states**: `NEVER use Any - Use object for generic payloads`

**This rule is ABSOLUTE and remains in FULL EFFECT.** ~~This ADR grants a NARROW, TEMPORARY EXCEPTION for exactly ONE context: Pydantic model `Field()` type annotations for JSON-serializable data.~~ **UPDATE (2026-01-08)**: The exception has ended. Use `JsonType` instead.

### Strict Scope Definition

| Context | Allowed Type | Governing Rule | Rationale |
|---------|--------------|----------------|-----------|
| **Pydantic model `Field()` definitions** | `Any` (with required `NOTE:` comment) | This ADR exception | Pydantic 2.x cannot handle recursive `JsonType` |
| **Function parameters** | `object` ONLY | CLAUDE.md (absolute) | Static type safety |
| **Function return types** | `object` or specific type ONLY | CLAUDE.md (absolute) | Static type safety |
| **Variable annotations** | `object` or specific type ONLY | CLAUDE.md (absolute) | Static type safety |
| **Type aliases** | `object` ONLY | CLAUDE.md (absolute) | Prevents type erosion |
| **Protocol method signatures** | `object` ONLY | CLAUDE.md (absolute) | Contract clarity |
| **Generic containers** (outside Pydantic) | `object` ONLY | CLAUDE.md (absolute) | Type checker support |
| **dataclasses, TypedDicts, NamedTuples** | `object` or specific type ONLY | CLAUDE.md (absolute) | Not Pydantic models |

### Why This Exception Does NOT Contradict CLAUDE.md

1. **Limited to Pydantic runtime validation context**: Pydantic's `Field()` provides runtime type validation that compensates for static type checker blindness to `Any`
2. **Temporary workaround**: Will be removed when `JsonType` fix is available in omnibase_core
3. **Explicit documentation required**: The mandatory `NOTE:` comment ensures traceability and prevents silent proliferation
4. **No expansion permitted**: This exception CANNOT be extended to other contexts without a new ADR

### Critical Clarification: `Any` vs `object` Scope

**This ADR does NOT permit `Any` anywhere except Pydantic `Field()` definitions.**

The CLAUDE.md rule "NEVER use `Any` - Use `object` for generic payloads" remains in **full effect** for:

- **Function signatures**: `def process(data: object) -> object:` NOT `def process(data: Any) -> Any:`
- **Method parameters**: `async def handle(envelope: ModelEventEnvelope[object]):`
- **Return types**: `-> str | None` or `-> object` NOT `-> Any`
- **Local variables**: `result: object = ...` NOT `result: Any = ...`
- **Type aliases**: `PayloadType = dict[str, object]` NOT `PayloadType = dict[str, Any]`
- **Protocol definitions**: Use `object` for generic payload types
- **All non-Pydantic data structures**: dataclasses, TypedDicts, NamedTuples

**The ONLY place `Any` is permitted**:

```python
class SomePydanticModel(BaseModel):
    # NOTE: Using Any instead of JsonType from omnibase_core to avoid Pydantic 2.x
    # recursion issues with recursive type aliases.
    json_data: Any = Field(default=None, description="JSON-serializable data")
```

**Anywhere else, use `object`**:

```python
# CORRECT: object in function signature
def serialize_payload(data: object) -> str:
    ...
```

### Enforcement

- **New code with `Any` outside Pydantic model fields**: **AUTOMATIC PR REJECTION**
- **New code with `Any` in Pydantic field without `NOTE:` comment**: **AUTOMATIC PR REJECTION**
- **Legacy code violations**: Tracked under OMN-1262 for mandatory cleanup

---

## Context

The CLAUDE.md coding standards explicitly state:

> **NEVER use `Any`** - Use `object` for generic payloads

This rule exists to enforce strong typing throughout the codebase and prevent type erosion. However, PR #116 introduces `Any` across 33+ files as a necessary workaround for a Pydantic 2.x compatibility issue.

### The Technical Problem: Recursive Type Alias Resolution

`omnibase_core` defines a `JsonType` recursive type alias for representing arbitrary JSON-serializable data:

```python
# From omnibase_core/types/json_types.py
JsonType = dict[str, "JsonType"] | list["JsonType"] | str | int | float | bool | None
```

This is a **recursive type alias** - the type references itself in its definition. This pattern is valid Python typing and works correctly with static type checkers like mypy and pyright. However, it causes severe issues with Pydantic 2.x.

### Why Pydantic 2.x Triggers Infinite Recursion

When Pydantic 2.x processes a model containing a `JsonType` field, it performs the following at **class definition time** (not runtime):

1. **Schema Generation**: Pydantic builds a JSON schema for each field to enable validation
2. **Type Resolution**: For each type annotation, Pydantic resolves forward references and expands type aliases
3. **Validator Construction**: Pydantic generates validators based on the resolved types

For `JsonType`, this process becomes:

```
JsonType
  -> dict[str, JsonType] | list[JsonType] | str | int | float | bool | None
    -> dict[str, (dict[str, JsonType] | list[JsonType] | ...)]
      -> dict[str, dict[str, (dict[str, JsonType] | list[JsonType] | ...)]]
        -> ... (infinite expansion)
```

The result is a `RecursionError`:

```
RecursionError: maximum recursion depth exceeded
  File ".../pydantic/_internal/_generate_schema.py", line 987, in generate_schema
  File ".../pydantic/_internal/_generate_schema.py", line 1342, in _union_schema
  File ".../pydantic/_internal/_generate_schema.py", line 987, in generate_schema
  ...
```

**Key insight**: This error occurs at **model class definition time**, not when creating instances. Simply having `class MyModel(BaseModel): data: JsonType` triggers the recursion before any code using the model runs.

### Pydantic 2.x vs 1.x Behavior

| Behavior | Pydantic 1.x | Pydantic 2.x |
|----------|--------------|--------------|
| Type alias resolution | Lazy (at validation time) | Eager (at class definition) |
| Recursive types | Generally tolerated | Causes `RecursionError` |
| Schema generation | On-demand | At class definition |

This is a known Pydantic 2.x limitation. See [Pydantic GitHub Issue #3278](https://github.com/pydantic/pydantic/issues/3278) for discussion.

### Affected Use Cases

The recursion issue manifests in:

1. **Model validation** - When Pydantic builds validators for `JsonType` fields
2. **Schema generation** - When generating JSON schemas for OpenAPI/documentation
3. **Serialization** - When serializing models with `JsonType` fields to JSON

### Workaround Attempts

Several alternatives were evaluated before accepting `Any`:

| Alternative | Result |
|------------|--------|
| `object` type | Does not serialize correctly in Pydantic 2.x |
| `dict[str, object]` | Loses support for non-dict JSON roots (arrays, primitives) |
| Custom validators | Still triggers recursion during schema building |
| `typing.ForwardRef` | Pydantic 2.x resolves forward refs eagerly |
| Pydantic `JsonValue` | Not available in all Pydantic 2.x versions used |
| `pydantic.Json[Any]` | Different semantics (expects JSON string input) |
| `TypeAdapter` wrapper | Adds complexity; still issues with nested models |

## Decision

**Use `Any` as a temporary workaround in place of `JsonType` for Pydantic model fields.**

This decision explicitly deviates from the CLAUDE.md "NEVER use Any" rule due to technical constraints in Pydantic 2.x's handling of recursive type aliases.

### Scope of Deviation

**STRICT POLICY: `Any` is ONLY permitted in Pydantic model field type annotations.**

This exception is EXTREMELY NARROW. The `Any` type is permitted ONLY when ALL of these conditions are met:

1. **Inside a Pydantic `BaseModel` class definition**
2. **As a field type annotation** (the `Field()` definition context)
3. **For fields that would otherwise use `JsonType`** from omnibase_core
4. **With the required `NOTE:` comment** documenting the workaround

**CRITICAL**: Any use of `Any` outside these conditions is a **violation of CLAUDE.md** and MUST use `object` instead. The `object` type is Python's proper "unknown type" marker and must be used for:

- All function parameters accepting unknown/generic data
- All function return types returning unknown/generic data
- All variable annotations for unknown types
- All type aliases for generic payloads
- All protocol method signatures
- All non-Pydantic data structures (dataclasses, TypedDicts, NamedTuples)

**Examples of PERMITTED usage (inside Pydantic models only):**

```python
from typing import Any
from pydantic import BaseModel, Field

# NOTE: Using Any instead of JsonType from omnibase_core to avoid Pydantic 2.x
# recursion issues with recursive type aliases.

class ModelDLQEvent(BaseModel):
    """Dead letter queue event with arbitrary payload."""
    payload: Any = Field(default=None, description="Original event payload")
    metadata: Any = Field(default=None, description="Event metadata")
```

The `Any` type is **STRICTLY FORBIDDEN** (per CLAUDE.md) for:

1. **Function parameter types** - MUST use `object` instead
2. **Function return types** - MUST use `object` or specific types
3. **Variable type annotations** - MUST use `object` for unknown types
4. **Type aliases** - MUST use `object` in union types (e.g., `dict[str, object]`)
5. **Generic containers** - MUST use `object` for unknown payload types
6. **Non-Pydantic data structures** (dataclasses, TypedDicts, NamedTuples, etc.)
7. **Protocol method signatures** - MUST use `object` for generic parameters

**VIOLATION OF THESE RULES IS A PR REJECTION CRITERION.**

**Examples of FORBIDDEN usage and correct alternatives:**

```python
# WRONG: Any in function signature
def process_event(payload: Any) -> Any:  # FORBIDDEN
    ...

# CORRECT: Use object for generic payloads
# ONEX: Using object instead of Any per ADR guidelines
def process_event(payload: object) -> object:
    ...

# WRONG: Any in variable annotation
result: Any = some_function()  # FORBIDDEN

# CORRECT: Use object or specific type
result: object = some_function()  # CORRECT
result: ModelSpecificType = some_function()  # BEST

# WRONG: Any in type alias
PayloadType = dict[str, Any]  # FORBIDDEN outside Pydantic

# CORRECT: Use object in type aliases
PayloadType = dict[str, object]  # CORRECT
```

### When to Use `dict[str, object]` vs `dict[str, Any]`

> **SCOPE BOUNDARY**: This section documents type choices within Pydantic model definitions ONLY.
> - `dict[str, Any]` is permitted ONLY in Pydantic `Field()` definitions
> - `dict[str, object]` MUST be used in function signatures, return types, and all non-Pydantic contexts
> - This is NOT a general license to use `Any` - the CLAUDE.md "NEVER use Any" rule remains absolute outside Pydantic fields

**IMPORTANT CLARIFICATION**: This section does NOT contradict the CLAUDE.md "NEVER use Any" guideline. The `Any` usage described here is ONLY permitted within Pydantic model `Field()` definitions as documented in the "Scope of Deviation" section above. All other contexts MUST use `object`.

| Context | Type to Use | Reason |
|---------|-------------|--------|
| Pydantic model field | `dict[str, Any]` | Required for JSON serialization with Pydantic 2.x |
| Function parameter | `dict[str, object]` | Maintains type safety in signatures |
| Function return type | `dict[str, object]` | Preserves type checking at call sites |
| Protocol method signature | `dict[str, object]` | Protocols define contracts, not Pydantic models |
| Generic event envelope payload | `object` | Allows any payload type with type safety |

**Examples:**

```python
# Pydantic model - Any is acceptable
class ModelEventPayload(BaseModel):
    # NOTE: Using Any instead of JsonType from omnibase_core to avoid Pydantic 2.x
    # recursion issues with recursive type aliases.
    data: dict[str, Any] = Field(default_factory=dict)

# Function signature - use object
# ONEX: Using object instead of Any per ADR guidelines
def extract_metadata(payload: dict[str, object]) -> dict[str, object]:
    ...

# Protocol method - use object
class ProtocolEventHandler(Protocol):
    # ONEX: Using object instead of Any per ADR guidelines
    def handle(self, payload: object) -> object: ...

# Generic envelope - use object for payload type parameter
class ModelEventEnvelope(BaseModel, Generic[T]):
    payload: T  # T should be bound to object, not Any
```

**Why This Distinction Matters:**

- Pydantic's `Field()` context has runtime validation that provides type safety
- Function signatures/return types rely solely on static type checking
- Using `Any` outside Pydantic defeats the purpose of type hints entirely
- `object` is the proper "unknown type" marker in Python's type system
- `dict[str, object]` still allows any value but enables type checker warnings on unsafe operations

### Migration Path: JsonType to object

When migrating code that previously used `JsonType` (or `Any` as a workaround) in non-Pydantic contexts, use `object`:

**Before (incorrect):**
```python
# WRONG - using JsonType or Any outside Pydantic
from omnibase_core.types import JsonType

def parse_json_payload(data: JsonType) -> JsonType:
    ...
```

**After (correct):**
```python
# CORRECT - using object for generic payloads
# ONEX: Using object instead of Any per ADR guidelines
def parse_json_payload(data: object) -> object:
    ...
```

**For structured JSON data**, consider using `TypedDict` for stronger typing:
```python
from typing import TypedDict

class PayloadDict(TypedDict, total=False):
    """Typed dictionary for structured payloads."""
    event_type: str
    timestamp: str
    data: object  # Nested unknown data uses object

# ONEX: Using TypedDict for stronger return type safety
def parse_structured_payload(raw: object) -> PayloadDict:
    ...
```

### Annotation Patterns

This ADR defines **two distinct comment patterns** for different contexts.

**SCOPE REMINDER**: This ADR authorizes `Any` usage ONLY within Pydantic `BaseModel` class definitions, specifically for `Field()` type annotations. The patterns below document how to properly annotate both:
1. The narrow exception (Any in Pydantic fields)
2. The standard practice (object everywhere else)

**Implementation Status**: The 33 files listed in this ADR's "Affected Files" section are compliant - they use `Any` exclusively in Pydantic model field definitions. However, legacy violations (Any in function signatures, return types, or type aliases) may exist elsewhere in the codebase and are tracked under [OMN-1262](https://linear.app/omninode/issue/OMN-1262) for mandatory cleanup. New code introducing such violations will be rejected in PR review.

#### Pattern 1: `Any` in Pydantic Models (JsonType Workaround)

When using `Any` as a workaround for `JsonType` in Pydantic model fields:

```python
from typing import Any

# NOTE: Using Any instead of JsonType from omnibase_core to avoid Pydantic 2.x
# recursion issues with recursive type aliases.

class MyModel(BaseModel):
    metadata: Any = None
```

The `NOTE:` comment pattern serves as:
- Documentation explaining the deviation
- Search anchor for future migration (`grep -r "NOTE: Using Any instead of JsonType"`)
- Code review signal that this is intentional

#### Pattern 2: `object` in Function Signatures

When using `object` instead of `Any` for generic payloads in function signatures, method parameters, or return types:

```python
# ONEX: Using object instead of Any per ADR guidelines
def process_payload(data: object) -> object:
    """Process arbitrary payload data."""
    ...

# ONEX: Using object instead of Any per ADR guidelines
async def handle_event(envelope: ModelEventEnvelope[object]) -> str | None:
    """Handle any event type with generic payload."""
    ...
```

The `ONEX:` comment pattern serves as:
- Documentation that `object` is intentional, not a placeholder
- Indication that the developer considered and rejected `Any`
- Search anchor for auditing type safety compliance (`grep -r "ONEX: Using object"`)

#### When to Use Each Pattern

| Situation | Comment Pattern |
|-----------|-----------------|
| `Any` in Pydantic `Field()` for JSON data | `# NOTE: Using Any instead of JsonType...` |
| `object` in function parameter | `# ONEX: Using object instead of Any per ADR guidelines` |
| `object` in return type | `# ONEX: Using object instead of Any per ADR guidelines` |
| `object` in protocol method | `# ONEX: Using object instead of Any per ADR guidelines` |
| `dict[str, object]` in non-Pydantic context | `# ONEX: Using object instead of Any per ADR guidelines` |

**Note**: The `ONEX:` comment is optional for simple cases where `object` usage is obvious from context. It is recommended when:
- The code previously used `Any` and was migrated
- Code reviewers might question why `object` was chosen over a specific type
- The pattern is establishing precedent for similar code

## Rationale

### Why Accept This Deviation

1. **Technical necessity** - No viable alternative exists within Pydantic 2.x constraints
2. **Limited scope** - Affects only JSON-serializable fields, not business logic types
3. **Temporary measure** - Clear path to resolution exists (see Migration Path)
4. **Runtime safety** - JSON serialization/deserialization provides implicit type checking

### Why Not Delay Migration

1. The codebase requires Pydantic 2.x for other critical features
2. Blocking on `JsonType` fix would delay multiple dependent work items
3. The workaround is localized and documented

## Consequences

### Positive

- Unblocks migration to Pydantic 2.x
- Maintains runtime functionality for JSON serialization
- Clear documentation of deviation via this ADR
- Searchable pattern (`NOTE: Using Any instead of JsonType`) for future cleanup

### Negative

- **Reduced type safety** - Static type checkers cannot validate `Any` fields
- **Documentation debt** - 33+ files contain this deviation
- **Rule erosion risk** - May encourage broader use of `Any` without justification
- **Future migration cost** - All uses must be updated when fix is available

### Type Safety Implications

The use of `Any` means:

| Static Analysis | Impact |
|----------------|--------|
| mypy | Will not catch type mismatches in `Any` fields |
| pyright | Same as mypy |
| IDE autocomplete | No suggestions for `Any` field contents |
| Runtime | Pydantic still validates JSON serialization |

## Migration Path

### Short-Term (Current State) - COMPLETED

**Timeline**: Immediate (PR #116) - **COMPLETED 2026-01-08**

**Status**: This phase has been **completed**. The workaround is no longer in use.

**What Was Done**:
- Used `Any` with documentation comment pattern as a temporary measure
- Tracked all usages via the standard `NOTE:` comment
- This ADR served as the canonical reference during the workaround period

**Historical Comment Pattern** (no longer needed):
```python
# NOTE: Using Any instead of JsonType from omnibase_core to avoid Pydantic 2.x
# recursion issues with recursive type aliases.
```

### Medium-Term (omnibase_core Fix) - COMPLETED

**Timeline**: ~~Pending omnibase_core 0.7.x or later~~ **COMPLETED in omnibase_core v0.6.3**

**Status**: The fix is **now available** and has been applied. omnibase_core v0.6.3 provides a working `JsonType` implementation.

**What Was Implemented**: `JsonType` using `typing.TypeAlias` pattern

omnibase_core v0.6.3 implemented the `JsonType` fix using the `TypeAlias` approach:

```python
from typing import TypeAlias

JsonType: TypeAlias = "dict[str, JsonType] | list[JsonType] | str | int | float | bool | None"
```

**Completed Prerequisites**:
- [x] omnibase_core updated to use TypeAlias pattern
- [x] omnibase_core v0.6.3 released with the fix
- [x] omnibase_infra updated to depend on fixed version

**Migration Steps Completed** (OMN-1274):
1. [x] Updated `omnibase_core` dependency to v0.6.3
2. [x] Searched for `NOTE: Using Any instead of JsonType` pattern
3. [x] Replaced `Any` with `JsonType` import in all 33+ files
4. [x] Removed the `NOTE:` comments
5. [x] Ran full type checking (`mypy`, `pyright`)
6. [x] Ran full test suite
7. [x] Updated this ADR status to "Superseded"

### Long-Term (Pydantic Native Support)

**Timeline**: Pydantic 3.x or later

Pydantic may introduce native recursive type support or a built-in `JsonValue` type:

```python
from pydantic import JsonValue  # Future Pydantic version

class MyModel(BaseModel):
    metadata: JsonValue = None
```

**Monitoring**:
- Watch [Pydantic changelog](https://docs.pydantic.dev/latest/changelog/)
- Track [GitHub Issue #3278](https://github.com/pydantic/pydantic/issues/3278)
- Check for `JsonValue` in new Pydantic releases

### Migration Verification

After any migration phase, run:

```bash
# Verify no workaround comments remain
grep -r "NOTE: Using Any instead of JsonType" src/
# Should return empty after full migration

# Verify no unintended Any usage
grep -rn ": Any" src/ | grep -v "test" | grep -v "__pycache__"
# Review any remaining uses

# Run type checking
mypy src/
pyright src/

# Run full test suite
pytest tests/
```

## Affected Files

### Migration Status Summary

| Category | Files | Status |
|----------|-------|--------|
| Event Bus | 4 | **MIGRATED** to `JsonType` (OMN-1274) |
| Handlers | 4 | **MIGRATED** to `JsonType` (OMN-1274) |
| Handler Mixins | 6 | **MIGRATED** to `JsonType` (OMN-1274) |
| Handler Models | 4 | **MIGRATED** to `JsonType` (OMN-1274) |
| Mixins | 1 | **MIGRATED** to `JsonType` (OMN-1274) |
| Models | 2 | **MIGRATED** to `JsonType` (OMN-1274) |
| Nodes | 1 | **MIGRATED** to `JsonType` (OMN-1274) |
| Plugins | 3 | **MIGRATED** to `JsonType` (OMN-1274) |
| Runtime | 8 | **MIGRATED** to `JsonType` (OMN-1274) |
| **TOTAL** | **33** | **COMPLETED - All files migrated to `JsonType`** |

**Migration Tracking**: [OMN-1262](https://linear.app/omninode/issue/OMN-1262), [OMN-1274](https://linear.app/omninode/issue/OMN-1274)

### File List

The following 33 files **previously used** `Any` as a workaround for `JsonType` and have now been **migrated to use `JsonType`**:

### Event Bus (4 files)
- `src/omnibase_infra/event_bus/inmemory_event_bus.py`
- `src/omnibase_infra/event_bus/kafka_event_bus.py`
- `src/omnibase_infra/event_bus/models/model_dlq_event.py`
- `src/omnibase_infra/event_bus/models/model_dlq_metrics.py`

### Handlers (4 files)
- `src/omnibase_infra/handlers/handler_consul.py`
- `src/omnibase_infra/handlers/handler_db.py`
- `src/omnibase_infra/handlers/handler_http.py`
- `src/omnibase_infra/handlers/handler_vault.py`

### Handler Mixins (6 files)
- `src/omnibase_infra/handlers/mixins/mixin_consul_initialization.py`
- `src/omnibase_infra/handlers/mixins/mixin_consul_kv.py`
- `src/omnibase_infra/handlers/mixins/mixin_consul_service.py`
- `src/omnibase_infra/handlers/mixins/mixin_vault_initialization.py`
- `src/omnibase_infra/handlers/mixins/mixin_vault_secrets.py`
- `src/omnibase_infra/handlers/mixins/mixin_vault_token.py`

### Handler Models (4 files)
- `src/omnibase_infra/handlers/models/http/model_http_get_payload.py`
- `src/omnibase_infra/handlers/models/http/model_http_post_payload.py`
- `src/omnibase_infra/handlers/models/model_db_query_payload.py`
- `src/omnibase_infra/handlers/models/vault/model_vault_secret_payload.py`

### Mixins (1 file)
- `src/omnibase_infra/mixins/mixin_envelope_extraction.py`

### Models (2 files)
- `src/omnibase_infra/models/registration/model_node_capabilities.py`
- `src/omnibase_infra/models/registry/model_message_type_entry.py`

### Nodes (1 file)
- `src/omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py`

### Plugins (3 files)
- `src/omnibase_infra/plugins/examples/plugin_json_normalizer.py`
- `src/omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py`
- `src/omnibase_infra/plugins/models/model_plugin_context.py`

### Runtime (8 files)
- `src/omnibase_infra/runtime/envelope_validator.py`
- `src/omnibase_infra/runtime/kernel.py`
- `src/omnibase_infra/runtime/models/model_health_check_response.py`
- `src/omnibase_infra/runtime/models/model_health_check_result.py`
- `src/omnibase_infra/runtime/protocol_policy.py`
- `src/omnibase_infra/runtime/runtime_host_process.py`
- `src/omnibase_infra/runtime/validation.py`
- `src/omnibase_infra/runtime/wiring.py`

## Migration Execution Plan - COMPLETED

### Files That Were Migrated (OMN-1274)

The following **33 files** have been successfully migrated from `Any` to `JsonType`:

**Migration Completed**: [OMN-1274](https://linear.app/omninode/issue/OMN-1274) (2026-01-08)

**Migration Script Used** (historical reference):
```bash
# These commands were used during the migration:
# 1. Update import statements
find src/ -name "*.py" -exec sed -i 's/from typing import Any/from omnibase_core.types import JsonType/g' {} \;
# 2. Replace type annotations (manual review was performed for each file)
# 3. Remove NOTE: comments
grep -rl "NOTE: Using Any instead of JsonType" src/ | xargs -I {} sed -i '/NOTE: Using Any instead of JsonType/d' {}
```

**All 33 files are listed in the "Affected Files" section above with categorization.**

### Prerequisites for Migration - COMPLETED

All prerequisites were met before migration:

1. [x] **omnibase_core Fix**: New `JsonType` implementation using TypeAlias
2. [x] **omnibase_core Release**: Published version 0.6.3 with the fix
3. [x] **Dependency Update**: `omnibase_infra` updated to depend on v0.6.3

### Migration Steps (Per File) - COMPLETED

For each of the 33 affected files, the following steps were performed:

1. [x] **Update imports**: Replaced `from typing import Any` with `from omnibase_core.types import JsonType`
2. [x] **Replace type annotations**: Changed `: Any` to `: JsonType` in Pydantic fields
3. [x] **Remove workaround comments**: Deleted the `NOTE: Using Any instead of JsonType` comment
4. [x] **Run type checker**: Verified with `mypy` and `pyright`
5. [x] **Run tests**: Ensured all unit and integration tests pass

### Migration Priority Order - COMPLETED

All phases were completed on 2026-01-08:

**Phase 1 - Core Models** (High Risk): [x] COMPLETED
1. [x] `models/registration/model_node_capabilities.py`
2. [x] `models/registry/model_message_type_entry.py`
3. [x] `event_bus/models/model_dlq_event.py`
4. [x] `event_bus/models/model_dlq_metrics.py`

**Phase 2 - Handlers** (Medium Risk): [x] COMPLETED
1. [x] All files in `handlers/` directory
2. [x] All files in `handlers/mixins/` directory
3. [x] All files in `handlers/models/` directory

**Phase 3 - Runtime** (High Integration Risk): [x] COMPLETED
1. [x] `runtime/envelope_validator.py`
2. [x] `runtime/kernel.py`
3. [x] `runtime/runtime_host_process.py`
4. [x] Remaining runtime files

**Phase 4 - Plugins & Nodes** (Lower Risk): [x] COMPLETED
1. [x] Plugin files
2. [x] Node handler files

### Test Coverage Requirements - VERIFIED

**Integration tests verified during migration:**

| Test Area | Status | Tracking |
|-----------|--------|----------|
| Intent emission from declarative reducer | PASSED | OMN-1263 |
| Envelope validation with JsonType fields | PASSED | OMN-1274 |
| RuntimeHostProcess with typed payloads | PASSED | OMN-1274 |
| DLQ event handling with JsonType | PASSED | OMN-1274 |
| Kafka event bus serialization | PASSED | OMN-1274 |

**Note**: See [OMN-1263](https://linear.app/omninode/issue/OMN-1263) for any remaining integration test improvements tracked separately from this migration.

### Rollback Plan (Historical)

**Note**: Migration was successful - rollback was not needed.

If migration had caused issues, the plan was:

1. Revert `omnibase_core` dependency to pre-fix version
2. Re-apply `Any` workaround with comment pattern
3. Document specific failure in this ADR under "Migration Attempts" section

## Verification

### Verifying Migration is Complete

```bash
# Verify NO files still use Any workaround (should return empty)
grep -rn "NOTE: Using Any instead of JsonType" src/

# Verify count is 0
grep -rl "NOTE: Using Any instead of JsonType" src/ | wc -l
# Expected: 0

# Verify JsonType is now imported from omnibase_core
grep -rn "from omnibase_core.types import JsonType" src/ | wc -l
# Expected: 33+ files
```

### Historical Reference: Original Verification Commands

The following commands were used during the workaround period to track compliance:

```bash
# Find all files using Any for this workaround (primary method)
grep -rn "NOTE: Using Any instead of JsonType" src/

# Count affected files
grep -rl "NOTE: Using Any instead of JsonType" src/ | wc -l

# Find Any imports that may need review
grep -rn "from typing import.*Any" src/ | grep -v "__pycache__"
```

### Post-Migration Compliance

**For new code using `JsonType`:**
New uses of JSON-serializable fields should import `JsonType` from `omnibase_core.types`:

```python
from omnibase_core.types import JsonType

class MyModel(BaseModel):
    metadata: JsonType = None
```

**For `object` usage in function signatures:**
The `ONEX:` comment pattern for `object` usage remains valid for generic payload types:

```bash
# Find compliant object usage with comment
grep -rn "ONEX: Using object instead of Any" src/

# Find object usage in signatures (may or may not need comment)
grep -rn "def.*object" src/ | grep -v "__pycache__"
```

### Code Review Checklist - POST-MIGRATION

#### For PRs with `Any` usage (NOW FULLY FORBIDDEN):

Since the migration is complete, `Any` is **no longer permitted** in any context:

- [ ] **REJECT** any use of `Any` - use `JsonType` for JSON-serializable Pydantic fields
- [ ] **REJECT** any use of `Any` in function signatures - use `object`
- [ ] **REJECT** any use of `Any` in return types - use `object` or specific type
- [ ] The workaround exception no longer applies

#### For PRs with `JsonType` usage (recommended):

- [ ] Is `JsonType` imported from `omnibase_core.types`?
- [ ] Is `JsonType` used appropriately for JSON-serializable fields?
- [ ] Could a more specific type be used instead of `JsonType`?

#### For PRs with `object` usage in signatures:

- [ ] Is `object` the correct choice? (Could a more specific type be used?)
- [ ] Is the `ONEX:` comment present for non-obvious usage?
- [ ] Is `object` used consistently across the interface? (e.g., both parameter and return)
- [ ] Are type narrowing patterns used where the actual type is known?

**Automatic rejection criteria (post-migration):**

- `Any` usage anywhere (use `JsonType` for Pydantic fields, `object` for function signatures)
- `JsonType` not imported from `omnibase_core.types`
- Missing type annotations

### Current Implementation State - POST-MIGRATION

**Status**: Migration completed 2026-01-08. All `Any` workarounds have been replaced with `JsonType`.

#### Current Compliant Patterns

The codebase now uses the following compliant patterns:

1. **Pydantic model fields**: Use `JsonType` from `omnibase_core.types` for JSON-serializable data
2. **Method signatures**: Use `object` for generic payload parameters
3. **Protocol definitions**: Use `object` for payload types
4. **Generic envelope types**: `ModelEventEnvelope[object]` pattern for dispatchers

**Example of current compliant implementation:**

```python
from omnibase_core.types import JsonType
from pydantic import BaseModel, Field

class ModelDLQEvent(BaseModel):
    """Dead letter queue event with typed payload."""
    payload: JsonType = Field(default=None, description="Original event payload")
    metadata: JsonType = Field(default=None, description="Event metadata")

# From dispatcher code - uses object for generic parameters
async def process_event(envelope: ModelEventEnvelope[object]) -> str | None:
    """Process any event type - uses object for generic payloads."""
    ...
```

#### Post-Migration Verification

All previous policy violations have been resolved:

| Aspect | Pre-Migration | Post-Migration | Status |
|--------|---------------|----------------|--------|
| `Any` in Pydantic fields | Used with `NOTE:` comment | Replaced with `JsonType` | RESOLVED |
| `Any` in function params | Some legacy violations | All use `object` | RESOLVED |
| `Any` in return types | Some legacy violations | All use specific types or `object` | RESOLVED |
| `Any` in type aliases | Some legacy violations | All use `object` | RESOLVED |
| `object` in signatures | Implemented in new code | Standard practice | COMPLIANT |

**Verification commands:**

```bash
# Verify no Any workarounds remain (should return empty)
grep -rn "NOTE: Using Any instead of JsonType" src/

# Verify JsonType imports
grep -rn "from omnibase_core.types import JsonType" src/

# Verify no Any in function signatures (should return empty or minimal)
grep -rn "def.*Any" src/ | grep -v "__pycache__"
```

**Enforcement** (post-migration):
- **All code**: MUST NOT use `Any` - use `JsonType` for Pydantic fields, `object` for signatures
- **PRs with `Any`**: Will be rejected - the workaround exception no longer applies
- **Legacy tracking**: [OMN-1262](https://linear.app/omninode/issue/OMN-1262) - closed as complete

## Integration Test Status

Integration tests for the registration reducer and related infrastructure are tracked in **[OMN-1263](https://linear.app/omninode/issue/OMN-1263)**.

### Postponement Justification

Integration tests are postponed to OMN-1263 for the following reasons:

1. **Unit tests provide comprehensive coverage**: The RegistrationReducer has 100% coverage of FSM transitions, state machine logic, and intent generation through unit tests. All edge cases, error conditions, and state transitions are verified.

2. **Integration tests require additional infrastructure**: Full integration tests require:
   - `RuntimeHostProcess` for intent dispatch
   - Mock Consul and PostgreSQL services
   - Event bus infrastructure for end-to-end event flow
   - `RegistrationProjector` for FSM state persistence

3. **Separation of concerns**: This PR (OMN-1104) focuses on the declarative reducer refactoring. Integration testing is a distinct scope that deserves dedicated attention.

### OMN-1263 Integration Test Matrix

The following integration tests are tracked under OMN-1263:

| Test Area | Description | Status |
|-----------|-------------|--------|
| Intent emission through RuntimeHostProcess | Verify intents are correctly dispatched | TODO |
| End-to-end registration with Consul mocks | Full registration flow with mocked Consul | TODO |
| End-to-end registration with PostgreSQL mocks | Full registration flow with mocked PostgreSQL | TODO |
| FSM state persistence via RegistrationProjector | Verify state is correctly persisted | TODO |
| DLQ handling for failed registrations | Verify failed events are correctly queued | TODO |

### Pre-existing Test Failures

**27 test failures in CI are pre-existing issues unrelated to this PR.**

These failures existed before the OMN-1104 work began and are documented in the PR description. They are caused by:
- Missing infrastructure dependencies in CI environment
- Incomplete mock configurations for external services
- Test fixtures that require database connections

These failures are tracked under OMN-1263 for resolution as part of the broader integration test effort.

---

## NOTE Comment Audit - HISTORICAL

**Status**: This section is now historical. All `NOTE:` comments have been removed as part of the migration.

### Historical Audit Confirmation

Prior to migration (2026-01-08):
- **All 33 files** listed in the "Affected Files" section contained the required `NOTE:` comment
- **Comment pattern was**:
  ```python
  # NOTE: Using Any instead of JsonType from omnibase_core to avoid Pydantic 2.x
  # recursion issues with recursive type aliases.
  ```
- **No `Any` usage in function signatures**: Verified that `Any` was not used outside Pydantic model fields

### Files That Were Migrated

The following files **previously used** `Any` and have been **migrated to `JsonType`**:

**Event Bus Models** (4 files):
- `src/omnibase_infra/event_bus/inmemory_event_bus.py` - Now uses `JsonType`
- `src/omnibase_infra/event_bus/kafka_event_bus.py` - Now uses `JsonType`
- `src/omnibase_infra/event_bus/models/model_dlq_event.py` - Now uses `JsonType`
- `src/omnibase_infra/event_bus/models/model_dlq_metrics.py` - Now uses `JsonType`

**Handler Models** (8 files):
- All handler models migrated to use `JsonType`

**Runtime Models** (8 files):
- All runtime models migrated to use `JsonType`

**Registration/Plugin Models** (5 files):
- All registration and plugin models migrated to use `JsonType`

### Post-Migration Verification Commands

```bash
# Verify NO NOTE comments remain (migration complete)
grep -rl "NOTE: Using Any instead of JsonType" src/omnibase_infra/ | grep -v __pycache__
# Expected: empty (all migrated)

# Verify JsonType is imported
grep -rn "from omnibase_core.types import JsonType" src/omnibase_infra/ | wc -l
# Expected: 33+ files

# Verify no Any in Pydantic fields
grep -rn ": Any" src/omnibase_infra/ | grep -v __pycache__
# Expected: empty or minimal (legacy only)
```

### Ongoing Enforcement (Post-Migration)

- **PR reviews**: Reject any use of `Any` - use `JsonType` instead
- **CI checks**: Lint for `Any` imports and flag as errors
- **No exceptions**: The workaround period has ended

---

## References

- CLAUDE.md "Strong Typing & Models" section
- `omnibase_core` JsonType definition (v0.6.3+)
- [PEP 695 - Type Parameter Syntax](https://peps.python.org/pep-0695/)
- [Pydantic GitHub Issue #3278: Recursive type support](https://github.com/pydantic/pydantic/issues/3278)
- [Pydantic Documentation on JSON Types](https://docs.pydantic.dev/latest/concepts/json/)
- PR #116: Initial introduction of this workaround
- OMN-1104: Refactor RegistrationReducer to be fully declarative
- [OMN-1262](https://linear.app/omninode/issue/OMN-1262): Migration tracking issue for Any type cleanup
- [OMN-1263](https://linear.app/omninode/issue/OMN-1263): Pre-existing test failures and integration test coverage
- **[OMN-1274](https://linear.app/omninode/issue/OMN-1274): Migration completion - Any to JsonType (2026-01-08)**
