---
phase: 13-registry-mode-enum
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - python/opendeviationbar/ouroboros.py
  - python/opendeviationbar/symbol_registry.py
  - python/opendeviationbar/data/symbols.toml
  - python/opendeviationbar/config/population.py
  - tests/test_ouroboros.py
  - tests/test_symbol_registry.py
autonomous: true
requirements: [REG-01, REG-02, REG-03, REG-05]

must_haves:
  truths:
    - "OuroborosMode.AION exists as a first-class enum variant with value 'aion'"
    - "Every symbol in symbols.toml has an ouroboros_mode field"
    - "get_symbol_entries()['BTCUSDT'].ouroboros_mode returns OuroborosMode.AION"
    - "Attempting ouroboros_mode='aion' on a forex symbol raises ValueError"
    - "validate_ouroboros_mode('aion') returns 'aion' without error"
  artifacts:
    - path: "python/opendeviationbar/ouroboros.py"
      provides: "OuroborosMode enum with DAY and AION variants"
      contains: 'AION = "aion"'
    - path: "python/opendeviationbar/data/symbols.toml"
      provides: "ouroboros_mode field on every symbol"
      contains: "ouroboros_mode"
    - path: "python/opendeviationbar/symbol_registry.py"
      provides: "SymbolEntry with ouroboros_mode field, validation"
      contains: "ouroboros_mode"
  key_links:
    - from: "python/opendeviationbar/symbol_registry.py"
      to: "python/opendeviationbar/ouroboros.py"
      via: "OuroborosMode import for type"
      pattern: "OuroborosMode"
    - from: "python/opendeviationbar/symbol_registry.py"
      to: "python/opendeviationbar/data/symbols.toml"
      via: "TOML parsing of ouroboros_mode field"
      pattern: "ouroboros_mode"
---

<objective>
Add OuroborosMode.AION enum variant, wire ouroboros_mode into symbols.toml and SymbolEntry, expand validation to accept "aion", and enforce crypto-only constraint.

Purpose: Foundation for all downstream Aion consumers (sidecar, kintsugi, heartbeat, validation). Every module queries the registry for a symbol's ouroboros mode.
Output: Updated enum, registry, symbols.toml, validation, and PopulationConfig with tests.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md

@python/opendeviationbar/ouroboros.py
@python/opendeviationbar/symbol_registry.py
@python/opendeviationbar/data/symbols.toml
@python/opendeviationbar/config/population.py
@tests/test_ouroboros.py
@tests/test_symbol_registry.py

<interfaces>
<!-- Current OuroborosMode enum (ouroboros.py line 27-34) -->
```python
class OuroborosMode(str, Enum):
    DAY = "day"
```

<!-- Current SymbolEntry dataclass (symbol_registry.py line 162-246) -->

```python
@dataclass(frozen=True)
class SymbolEntry:
    symbol: str
    enabled: bool
    asset_class: str  # "crypto", "forex", "equities"
    exchange: str
    market: str
    listing_date: date
    # ... other fields ...
    kintsugi_priority: int = 1
    # NOTE: no ouroboros_mode field yet
```

<!-- Current validate_ouroboros_mode (ouroboros.py line 275-299) -->

```python
def validate_ouroboros_mode(mode: str) -> Literal["day"]:
    if mode != "day":
        raise ValueError(...)
    return mode
```

<!-- Current PopulationConfig (config/population.py line 57) -->

```python
ouroboros_mode: Literal["day"] = "day"
```

<!-- Current get_symbol_entries parsing (symbol_registry.py line 328-349) -->

```python
entries[symbol] = SymbolEntry(
    symbol=symbol,
    enabled=data["enabled"],
    asset_class=data["asset_class"],
    # ... no ouroboros_mode parsing ...
    kintsugi_priority=data.get("kintsugi_priority", 1),
)
```

</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add OuroborosMode.AION, expand validation, update PopulationConfig</name>
  <files>python/opendeviationbar/ouroboros.py, python/opendeviationbar/config/population.py, tests/test_ouroboros.py</files>
  <read_first>
    - python/opendeviationbar/ouroboros.py
    - python/opendeviationbar/config/population.py
    - tests/test_ouroboros.py
  </read_first>
  <behavior>
    - Test: OuroborosMode.AION.value == "aion"
    - Test: OuroborosMode("aion") == OuroborosMode.AION
    - Test: validate_ouroboros_mode("aion") returns "aion"
    - Test: validate_ouroboros_mode("day") returns "day" (backward compat)
    - Test: validate_ouroboros_mode("week") raises ValueError
    - Test: get_ouroboros_boundaries with mode="aion" returns empty list (Aion has no boundaries)
    - Test: iter_ouroboros_segments with mode="aion" yields single segment spanning full range
  </behavior>
  <action>
    1. In `ouroboros.py`:
       - Add `AION = "aion"` to OuroborosMode enum (line ~34, after DAY)
       - Update OuroborosMode docstring: "DAY resets at UTC midnight. AION runs continuously (no reset boundaries). Aion is crypto-only."
       - Update `validate_ouroboros_mode()` return type from `Literal["day"]` to `Literal["day", "aion"]` and change body:
         ```python
         valid = {"day", "aion"}
         if mode not in valid:
             msg = f"Invalid ouroboros mode: {mode!r}. Valid modes: {sorted(valid)}"
             raise ValueError(msg)
         return mode
         ```
       - Update `get_ouroboros_boundaries()` signature: `mode: Literal["day", "aion"] = "day"`
         - When mode == "aion": return empty list (no boundaries)
       - Update `iter_ouroboros_segments()` signature: `mode: Literal["day", "aion"] = "day"`
         - When mode == "aion": yield single segment covering full range with no boundary
       - Update `get_operational_ouroboros_mode()` return type to `Literal["day", "aion"]`

    2. In `config/population.py`:
       - Change `ouroboros_mode: Literal["day"] = "day"` to `ouroboros_mode: Literal["day", "aion"] = "day"`
       - The `_validate_ouroboros` field_validator already delegates to `validate_ouroboros_mode()` so it auto-accepts "aion"

    3. In `tests/test_ouroboros.py`:
       - Add tests for AION variant existence and string value
       - Add tests for validate_ouroboros_mode accepting "aion"
       - Add tests for get_ouroboros_boundaries returning [] for mode="aion"
       - Add test for iter_ouroboros_segments yielding single segment for mode="aion"

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_ouroboros.py -x -v 2>&1 | tail -30</automated>
  </verify>
  <acceptance_criteria>
    - grep -q 'AION = "aion"' python/opendeviationbar/ouroboros.py
    - grep -q 'Literal\["day", "aion"\]' python/opendeviationbar/ouroboros.py
    - grep -q 'Literal\["day", "aion"\]' python/opendeviationbar/config/population.py
    - uv run --python 3.13 python -c "from opendeviationbar.ouroboros import OuroborosMode; assert OuroborosMode.AION.value == 'aion'"
    - uv run --python 3.13 python -c "from opendeviationbar.ouroboros import validate_ouroboros_mode; assert validate_ouroboros_mode('aion') == 'aion'"
  </acceptance_criteria>
  <done>OuroborosMode has AION variant, validation accepts both "day" and "aion", PopulationConfig Literal expanded, boundary functions handle Aion (no boundaries), all tests pass</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Wire ouroboros_mode into symbols.toml and SymbolEntry with crypto-only validation</name>
  <files>python/opendeviationbar/data/symbols.toml, python/opendeviationbar/symbol_registry.py, tests/test_symbol_registry.py</files>
  <read_first>
    - python/opendeviationbar/data/symbols.toml
    - python/opendeviationbar/symbol_registry.py
    - tests/test_symbol_registry.py
  </read_first>
  <behavior>
    - Test: get_symbol_entries()["BTCUSDT"].ouroboros_mode == OuroborosMode.AION
    - Test: get_symbol_entries()["ETHUSDT"].ouroboros_mode == OuroborosMode.AION
    - Test: Every crypto symbol has ouroboros_mode == OuroborosMode.AION
    - Test: Every forex symbol has ouroboros_mode == OuroborosMode.DAY
    - Test: Registry loading does not crash (round-trip parse)
    - Test: Validation rejects aion for forex asset_class (ValueError with clear message)
  </behavior>
  <action>
    1. In `symbol_registry.py`:
       - Add import: `from opendeviationbar.ouroboros import OuroborosMode`
       - Add field to SymbolEntry dataclass (after `kintsugi_priority`):
         ```python
         ouroboros_mode: OuroborosMode = OuroborosMode.DAY
         ```
       - In `get_symbol_entries()` parsing block (line ~329), add ouroboros_mode parsing with validation:
         ```python
         raw_mode = data.get("ouroboros_mode", "day")
         parsed_mode = OuroborosMode(raw_mode)
         # REG-05: crypto-only validation
         asset_class = data["asset_class"]
         if parsed_mode == OuroborosMode.AION and asset_class != "crypto":
             msg = (
                 f"Symbol '{symbol}' has ouroboros_mode='aion' but asset_class='{asset_class}'. "
                 f"Aion mode is only valid for crypto symbols (asset_class='crypto')."
             )
             raise ValueError(msg)
         ```
         Then pass `ouroboros_mode=parsed_mode` to the SymbolEntry constructor.
       - Add `get_registered_symbols` filter capability: add `ouroboros_mode: str | None = None` parameter. When set, filter entries by `entry.ouroboros_mode.value == ouroboros_mode`.

    2. In `symbols.toml`:
       - Add `ouroboros_mode = "aion"` to ALL crypto symbols (asset_class = "crypto"). There are 18 crypto symbols: BTCUSDT, ETHUSDT, BNBUSDT, SOLUSDT, XRPUSDT, DOGEUSDT, ADAUSDT, AVAXUSDT, LINKUSDT, BCHUSDT, LTCUSDT, NEARUSDT, UNIUSDT, FILUSDT, AAVEUSDT, SUIUSDT, WIFUSDT, WLDUSDT.
       - Add `ouroboros_mode = "day"` to ALL forex symbols (if any exist in the file — check during execution).
       - Place the field after `kintsugi_priority` for consistency.

    3. In `tests/test_symbol_registry.py`:
       - Add test that all crypto symbols have OuroborosMode.AION
       - Add test that non-crypto symbols (if any) have OuroborosMode.DAY
       - Add test that registry round-trip parses ouroboros_mode correctly
       - Add test that a hypothetical forex symbol with ouroboros_mode="aion" raises ValueError during loading (mock or use a temp TOML)

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_symbol_registry.py -x -v 2>&1 | tail -30</automated>
  </verify>
  <acceptance_criteria>
    - grep -q 'ouroboros_mode' python/opendeviationbar/symbol_registry.py
    - grep -c 'ouroboros_mode = "aion"' python/opendeviationbar/data/symbols.toml returns 18 (all crypto symbols)
    - uv run --python 3.13 python -c "from opendeviationbar.symbol_registry import get_symbol_entries; e = get_symbol_entries(); assert e['BTCUSDT'].ouroboros_mode.value == 'aion'"
    - uv run --python 3.13 python -c "from opendeviationbar.symbol_registry import get_symbol_entries; e = get_symbol_entries(); assert all(v.ouroboros_mode.value == 'aion' for v in e.values() if v.asset_class == 'crypto')"
  </acceptance_criteria>
  <done>SymbolEntry has ouroboros_mode field, symbols.toml has ouroboros_mode on every symbol, crypto=aion forex=day, crypto-only validation rejects aion for non-crypto, all tests pass</done>
</task>

</tasks>

<verification>
1. Full test suite passes: `uv run --python 3.13 pytest tests/test_ouroboros.py tests/test_symbol_registry.py -x -v`
2. Import smoke test: `uv run --python 3.13 python -c "from opendeviationbar import OuroborosMode; print(OuroborosMode.AION)"`
3. Registry round-trip: `uv run --python 3.13 python -c "from opendeviationbar.symbol_registry import get_symbol_entries; entries = get_symbol_entries(); print(f'{len(entries)} symbols loaded, BTCUSDT mode: {entries[\"BTCUSDT\"].ouroboros_mode}')"`
</verification>

<success_criteria>

- OuroborosMode enum has both DAY and AION variants
- Every symbol in symbols.toml has ouroboros_mode field (crypto=aion, forex=day)
- SymbolEntry dataclass exposes ouroboros_mode as OuroborosMode enum
- validate_ouroboros_mode accepts both "day" and "aion"
- Registry rejects aion on non-crypto symbols with clear error
- PopulationConfig accepts Literal["day", "aion"]
- All existing tests still pass (backward compatibility)
  </success_criteria>

<output>
After completion, create `.planning/phases/13-registry-mode-enum/13-01-SUMMARY.md`
</output>
