chore: initialize EverOS 1.0.0
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.
This commit is contained in:
@ -0,0 +1,83 @@
|
||||
"""``CascadeLifespanProvider`` — startup builds orchestrator, shutdown stops it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
|
||||
from everos.entrypoints.api.lifespans import cascade as cascade_lifespan_mod
|
||||
from everos.entrypoints.api.lifespans.cascade import CascadeLifespanProvider
|
||||
|
||||
|
||||
class _StubOrchestrator:
|
||||
def __init__(self, *args: object, **kwargs: object) -> None:
|
||||
self.start_calls = 0
|
||||
self.stop_calls = 0
|
||||
|
||||
async def start(self) -> None:
|
||||
self.start_calls += 1
|
||||
|
||||
async def stop(self) -> None:
|
||||
self.stop_calls += 1
|
||||
|
||||
|
||||
def test_provider_metadata() -> None:
|
||||
p = CascadeLifespanProvider(order=42)
|
||||
assert p.name == "cascade"
|
||||
assert p.order == 42
|
||||
|
||||
|
||||
async def test_startup_constructs_and_starts_orchestrator(
|
||||
tmp_path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("EVEROS_MEMORY__ROOT", str(tmp_path))
|
||||
monkeypatch.setenv("EVEROS_EMBEDDING__MODEL", "stub-model")
|
||||
monkeypatch.setenv("EVEROS_EMBEDDING__BASE_URL", "http://stub.invalid/v1")
|
||||
monkeypatch.setenv("EVEROS_EMBEDDING__API_KEY", "stub-key")
|
||||
|
||||
captured: list[_StubOrchestrator] = []
|
||||
|
||||
def fake_orch(**kwargs: object) -> _StubOrchestrator:
|
||||
o = _StubOrchestrator()
|
||||
captured.append(o)
|
||||
return o
|
||||
|
||||
monkeypatch.setattr(cascade_lifespan_mod, "CascadeOrchestrator", fake_orch)
|
||||
|
||||
p = CascadeLifespanProvider()
|
||||
result = await p.startup(FastAPI())
|
||||
assert len(captured) == 1
|
||||
assert result is captured[0]
|
||||
assert captured[0].start_calls == 1
|
||||
|
||||
|
||||
async def test_shutdown_without_startup_is_noop() -> None:
|
||||
p = CascadeLifespanProvider()
|
||||
await p.shutdown(FastAPI()) # must not raise
|
||||
|
||||
|
||||
async def test_shutdown_stops_orchestrator_and_clears_reference(
|
||||
tmp_path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("EVEROS_MEMORY__ROOT", str(tmp_path))
|
||||
monkeypatch.setenv("EVEROS_EMBEDDING__MODEL", "stub-model")
|
||||
monkeypatch.setenv("EVEROS_EMBEDDING__BASE_URL", "http://stub.invalid/v1")
|
||||
monkeypatch.setenv("EVEROS_EMBEDDING__API_KEY", "stub-key")
|
||||
|
||||
captured: list[_StubOrchestrator] = []
|
||||
|
||||
def fake_orch(**kwargs: object) -> _StubOrchestrator:
|
||||
o = _StubOrchestrator()
|
||||
captured.append(o)
|
||||
return o
|
||||
|
||||
monkeypatch.setattr(cascade_lifespan_mod, "CascadeOrchestrator", fake_orch)
|
||||
|
||||
p = CascadeLifespanProvider()
|
||||
app = FastAPI()
|
||||
await p.startup(app)
|
||||
await p.shutdown(app)
|
||||
assert captured[0].stop_calls == 1
|
||||
# Second shutdown is a no-op (reference cleared).
|
||||
await p.shutdown(app)
|
||||
assert captured[0].stop_calls == 1
|
||||
@ -0,0 +1,45 @@
|
||||
"""LLMLifespanProvider — startup raises on missing credentials, otherwise resolves."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
|
||||
from everos.component.llm import LLMNotConfiguredError
|
||||
from everos.entrypoints.api.lifespans import LLMLifespanProvider
|
||||
|
||||
|
||||
async def test_startup_raises_when_credentials_missing() -> None:
|
||||
provider = LLMLifespanProvider()
|
||||
app = FastAPI()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"everos.entrypoints.api.lifespans.llm.get_llm_client",
|
||||
side_effect=LLMNotConfiguredError("missing api_key"),
|
||||
),
|
||||
pytest.raises(LLMNotConfiguredError),
|
||||
):
|
||||
await provider.startup(app)
|
||||
|
||||
|
||||
async def test_startup_returns_client_when_configured() -> None:
|
||||
provider = LLMLifespanProvider()
|
||||
app = FastAPI()
|
||||
sentinel = object()
|
||||
|
||||
with patch(
|
||||
"everos.entrypoints.api.lifespans.llm.get_llm_client",
|
||||
return_value=sentinel,
|
||||
):
|
||||
result = await provider.startup(app)
|
||||
|
||||
assert result is sentinel
|
||||
|
||||
|
||||
async def test_shutdown_is_noop() -> None:
|
||||
provider = LLMLifespanProvider()
|
||||
# Should not raise; the algo client is stateless.
|
||||
await provider.shutdown(FastAPI())
|
||||
@ -0,0 +1,34 @@
|
||||
"""OmeLifespanProvider — startup wires engine, shutdown stops it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
|
||||
from everos.entrypoints.api.lifespans import OmeLifespanProvider
|
||||
|
||||
|
||||
async def test_lifespan_starts_and_stops_engine(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
from everos.core.persistence import MemoryRoot
|
||||
|
||||
svc = importlib.import_module("everos.service.memorize")
|
||||
|
||||
monkeypatch.setattr(
|
||||
MemoryRoot, "default", classmethod(lambda cls: MemoryRoot(root=tmp_path))
|
||||
)
|
||||
monkeypatch.setattr(svc, "_ome_engine", None, raising=False)
|
||||
|
||||
provider = OmeLifespanProvider()
|
||||
app = FastAPI()
|
||||
|
||||
engine = await provider.startup(app)
|
||||
assert engine is not None
|
||||
assert engine._started is True # noqa: SLF001 — test introspection
|
||||
|
||||
await provider.shutdown(app)
|
||||
assert engine._started is False # noqa: SLF001
|
||||
@ -0,0 +1,72 @@
|
||||
"""SQLite + LanceDB lifespan providers — startup wires singletons, shutdown disposes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
|
||||
from everos.entrypoints.api.lifespans import (
|
||||
LanceDBLifespanProvider,
|
||||
SqliteLifespanProvider,
|
||||
)
|
||||
from everos.infra.persistence.lancedb import lancedb_manager
|
||||
from everos.infra.persistence.sqlite import sqlite_manager
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def _reset(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Redirect both managers at an isolated memory-root."""
|
||||
monkeypatch.setenv("EVEROS_MEMORY__ROOT", str(tmp_path))
|
||||
sqlite_manager._engine = None
|
||||
sqlite_manager._session_factory = None
|
||||
lancedb_manager._conn = None
|
||||
lancedb_manager._tables.clear()
|
||||
yield
|
||||
await sqlite_manager.dispose_engine()
|
||||
await lancedb_manager.dispose_connection()
|
||||
|
||||
|
||||
async def test_sqlite_provider_startup_builds_engine_and_creates_schema(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
provider = SqliteLifespanProvider()
|
||||
app = FastAPI()
|
||||
|
||||
engine = await provider.startup(app)
|
||||
|
||||
assert engine is sqlite_manager.get_engine() # singleton wired
|
||||
assert (
|
||||
tmp_path / ".index" / "sqlite" / "system.db"
|
||||
).exists() # schema create_all opened the file
|
||||
|
||||
|
||||
async def test_sqlite_provider_shutdown_disposes_singleton() -> None:
|
||||
provider = SqliteLifespanProvider()
|
||||
app = FastAPI()
|
||||
await provider.startup(app)
|
||||
assert sqlite_manager._engine is not None
|
||||
|
||||
await provider.shutdown(app)
|
||||
assert sqlite_manager._engine is None
|
||||
|
||||
|
||||
async def test_lancedb_provider_startup_opens_connection(tmp_path: Path) -> None:
|
||||
provider = LanceDBLifespanProvider()
|
||||
app = FastAPI()
|
||||
|
||||
conn = await provider.startup(app)
|
||||
|
||||
assert conn is await lancedb_manager.get_connection() # singleton wired
|
||||
assert (tmp_path / ".index" / "lancedb").is_dir()
|
||||
|
||||
|
||||
async def test_lancedb_provider_shutdown_disposes_singleton() -> None:
|
||||
provider = LanceDBLifespanProvider()
|
||||
app = FastAPI()
|
||||
await provider.startup(app)
|
||||
assert lancedb_manager._conn is not None
|
||||
|
||||
await provider.shutdown(app)
|
||||
assert lancedb_manager._conn is None
|
||||
Reference in New Issue
Block a user