md-first memory extraction framework for AI agents. Markdown is the single source of truth; SQLite holds state and LanceDB provides the rebuildable vector + BM25 + scalar index. The codebase follows a single-direction DDD layering (entrypoints -> service -> memory -> infra, with component / core / config cross-cutting) enforced by import-linter. Engineering surface: - Coding conventions in .claude/rules/ (path-scoped) and workflows in .claude/skills/ (/commit, /new-branch, /pr). - GitHub Actions CI runs make lint + test + integration; pre-commit mirrors the gates locally (ruff, hygiene hooks, gitlint commit-msg). - Commit messages follow Conventional Commits, enforced by gitlint. - make lint also enforces datetime two-zone discipline and OpenAPI drift.
216 lines
7.0 KiB
Python
216 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from everos.infra.ome._dispatch.dispatcher import EventDispatcher
|
|
from everos.infra.ome._dispatch.registry import StrategyRegistry
|
|
from everos.infra.ome._stores.counter import CounterStore
|
|
from everos.infra.ome._stores.storage import OMEStorage
|
|
from everos.infra.ome.context import StrategyContext
|
|
from everos.infra.ome.decorator import offline_strategy
|
|
from everos.infra.ome.events import BaseEvent, CronTick
|
|
from everos.infra.ome.gates import Counter
|
|
from everos.infra.ome.triggers import Cron, Immediate
|
|
|
|
|
|
class _E(BaseEvent):
|
|
user_id: str
|
|
|
|
|
|
def _make_strategy(name: str, **kw):
|
|
@offline_strategy(name=name, trigger=Immediate(on=[_E]), emits=[], **kw)
|
|
async def _f(event: Any, ctx: StrategyContext) -> None:
|
|
return None
|
|
|
|
return _f
|
|
|
|
|
|
@pytest.fixture
|
|
async def dispatcher(tmp_path: Path) -> EventDispatcher:
|
|
storage = OMEStorage(db_path=tmp_path / "ome.db")
|
|
await storage.init()
|
|
registry = StrategyRegistry()
|
|
counter = CounterStore(storage=storage)
|
|
return EventDispatcher(registry=registry, counter_store=counter)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_passes_when_no_gate(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(_make_strategy("s_pass"))
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
|
assert [m.name for m, _ in routes] == ["s_pass"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_skips_disabled(dispatcher: EventDispatcher) -> None:
|
|
dispatcher._registry.register(_make_strategy("s_off", enabled=False))
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
|
assert routes == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_applies_to_string(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(
|
|
_make_strategy("s", applies_to="user_id"),
|
|
)
|
|
routes_empty = await dispatcher.dispatch(_E(user_id=""))
|
|
routes_set = await dispatcher.dispatch(_E(user_id="u1"))
|
|
assert routes_empty == []
|
|
assert len(routes_set) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_applies_to_callable(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
def is_paid(e: _E) -> bool:
|
|
return e.user_id.startswith("paid_")
|
|
|
|
dispatcher._registry.register(_make_strategy("s", applies_to=is_paid))
|
|
assert await dispatcher.dispatch(_E(user_id="free_a")) == []
|
|
assert len(await dispatcher.dispatch(_E(user_id="paid_a"))) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_counter_gate(dispatcher: EventDispatcher) -> None:
|
|
dispatcher._registry.register(
|
|
_make_strategy("s", gate=Counter(threshold=3, event_field="user_id"))
|
|
)
|
|
for _ in range(2):
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
|
assert routes == []
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
|
assert len(routes) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_inspect_returns_route_info(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(
|
|
_make_strategy("s", gate=Counter(threshold=3, event_field="user_id"))
|
|
)
|
|
infos = await dispatcher.inspect(_E(user_id="u1"))
|
|
assert len(infos) == 1
|
|
assert infos[0].counter_progress == (1, 3)
|
|
assert infos[0].will_run is False
|
|
|
|
|
|
def _make_cron_strategy(name: str):
|
|
@offline_strategy(name=name, trigger=Cron(expr="0 * * * *"), emits=[])
|
|
async def _f(event: Any, ctx: StrategyContext) -> None:
|
|
return None
|
|
|
|
return _f
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_routes_engine_tick_to_named_strategy_only(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(_make_cron_strategy("cron_a"))
|
|
dispatcher._registry.register(_make_cron_strategy("cron_b"))
|
|
routes = await dispatcher.dispatch(CronTick(strategy_name="cron_a"))
|
|
assert [m.name for m, _ in routes] == ["cron_a"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_inspect_engine_tick_skips_non_target_strategy(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(_make_cron_strategy("cron_a"))
|
|
dispatcher._registry.register(_make_cron_strategy("cron_b"))
|
|
infos = await dispatcher.inspect(CronTick(strategy_name="cron_b"))
|
|
assert [i.strategy_name for i in infos] == ["cron_b"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_force_enabled_bypasses_enabled_gate(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(_make_strategy("s_off", enabled=False))
|
|
assert await dispatcher.dispatch(_E(user_id="u1")) == []
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"), force_enabled=True)
|
|
assert [m.name for m, _ in routes] == ["s_off"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_force_enabled_still_applies_applies_to_and_counter(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(
|
|
_make_strategy(
|
|
"s",
|
|
enabled=False,
|
|
applies_to="user_id",
|
|
gate=Counter(threshold=2, event_field="user_id"),
|
|
),
|
|
)
|
|
assert await dispatcher.dispatch(_E(user_id=""), force_enabled=True) == []
|
|
assert await dispatcher.dispatch(_E(user_id="u1"), force_enabled=True) == []
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"), force_enabled=True)
|
|
assert len(routes) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_strategy_filter_scopes_to_single_strategy(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(_make_strategy("s_a"))
|
|
dispatcher._registry.register(_make_strategy("s_b"))
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"), strategy_filter="s_a")
|
|
assert [m.name for m, _ in routes] == ["s_a"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_strategy_filter_unknown_raises(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
dispatcher._registry.register(_make_strategy("s_a"))
|
|
with pytest.raises(KeyError):
|
|
await dispatcher.dispatch(_E(user_id="u1"), strategy_filter="missing")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_isolates_faulty_applies_to_callable(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
"""A single strategy's buggy ``applies_to`` callable must not tank
|
|
the fan-out for siblings subscribed to the same event class.
|
|
"""
|
|
|
|
def _boom(_e: _E) -> bool:
|
|
raise RuntimeError("applies_to is buggy")
|
|
|
|
dispatcher._registry.register(_make_strategy("s_buggy", applies_to=_boom))
|
|
dispatcher._registry.register(_make_strategy("s_healthy"))
|
|
|
|
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
|
|
|
# s_buggy is treated as not-applies; s_healthy still routes.
|
|
assert [m.name for m, _ in routes] == ["s_healthy"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_inspect_isolates_faulty_applies_to_callable(
|
|
dispatcher: EventDispatcher,
|
|
) -> None:
|
|
def _boom(_e: _E) -> bool:
|
|
raise RuntimeError("applies_to is buggy")
|
|
|
|
dispatcher._registry.register(_make_strategy("s_buggy", applies_to=_boom))
|
|
dispatcher._registry.register(_make_strategy("s_healthy"))
|
|
|
|
infos = await dispatcher.inspect(_E(user_id="u1"))
|
|
|
|
by_name = {i.strategy_name: i for i in infos}
|
|
assert by_name["s_buggy"].applies_to_pass is False
|
|
assert by_name["s_healthy"].applies_to_pass is True
|