fix(ttp): E.3.18a worker hydrates per-lifter rule indexes via watch_store
Each per-source lifter holds its own RuleIndex and exposes an `async watch_store()` that loads the corpus and drains store change events forever. Until this commit nothing called `watch_store()` in production — every dispatch index stayed empty and no rule fired. - Add `WatchableTagger` runtime-checkable Protocol in `decnet.ttp.base`. - `CompositeTagger.iter_watchables()` yields lifters that satisfy it. - `run_ttp_worker_loop` fans out one task per watchable, cancelled and awaited alongside pump/heartbeat/control in the existing finally. - Watch failures log and exit the watch task without taking the worker down — mirrors the pump-task tolerance contract.
This commit is contained in:
@@ -23,7 +23,14 @@ import logging
|
||||
import os
|
||||
from typing import Final
|
||||
|
||||
from decnet.ttp.base import KNOWN_SOURCE_KINDS, Tagger, TaggerEvent
|
||||
from collections.abc import Iterator
|
||||
|
||||
from decnet.ttp.base import (
|
||||
KNOWN_SOURCE_KINDS,
|
||||
Tagger,
|
||||
TaggerEvent,
|
||||
WatchableTagger,
|
||||
)
|
||||
from decnet.web.db.models.ttp import TTPTag
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
@@ -66,6 +73,19 @@ class CompositeTagger(Tagger):
|
||||
self._warned_known: set[str] = set()
|
||||
self._informed_unknown: set[str] = set()
|
||||
|
||||
def iter_watchables(self) -> Iterator[WatchableTagger]:
|
||||
"""Yield every child lifter that hot-reloads from a RuleStore.
|
||||
|
||||
The worker (E.3.14) starts one ``asyncio.Task`` per yielded
|
||||
lifter so its dispatch index hydrates at startup; without this
|
||||
every index stays empty and no rule fires in production.
|
||||
Filtering on the structural :class:`WatchableTagger` protocol
|
||||
keeps the worker free of per-lifter type knowledge.
|
||||
"""
|
||||
for lifter in self._lifters:
|
||||
if isinstance(lifter, WatchableTagger):
|
||||
yield lifter
|
||||
|
||||
async def tag(self, event: TaggerEvent) -> list[TTPTag]:
|
||||
lifters = self._by_kind.get(event.source_kind, [])
|
||||
if not lifters:
|
||||
|
||||
Reference in New Issue
Block a user