From a7827b2fa6b80aced0d8ba93e29468df7f7fc9ab Mon Sep 17 00:00:00 2001 From: h Date: Wed, 20 May 2026 14:23:01 +0200 Subject: [PATCH] refactor: remove dead session code --- src/beaver_gateway/frontends/anthropic.py | 4 +-- src/beaver_gateway/settings.py | 8 +++-- src/beaver_gateway/storage/__init__.py | 20 ++++------- src/beaver_gateway/storage/db.py | 44 +---------------------- src/beaver_gateway/storage/models.py | 35 +++++++----------- 5 files changed, 27 insertions(+), 84 deletions(-) diff --git a/src/beaver_gateway/frontends/anthropic.py b/src/beaver_gateway/frontends/anthropic.py index a533411..9e14e94 100644 --- a/src/beaver_gateway/frontends/anthropic.py +++ b/src/beaver_gateway/frontends/anthropic.py @@ -210,8 +210,8 @@ async def _require_token( SDK sends — LibreChat, the CLI, third-party clients) and ``Authorization: Bearer `` (curl, Cursor). 401 on missing / unknown token; 403 on a known token whose scope doesn't cover - ``scope`` (Phase 4.3 — bootstrap tokens get ``"*"`` and pass - everything). + ``scope``. Bootstrap tokens implicitly carry ``"*"`` and pass + every scope check. """ api_key = request.headers.get("x-api-key") identity = ( diff --git a/src/beaver_gateway/settings.py b/src/beaver_gateway/settings.py index f1b5d59..9947653 100644 --- a/src/beaver_gateway/settings.py +++ b/src/beaver_gateway/settings.py @@ -40,7 +40,11 @@ class Settings(BaseSettings): value per gateway since we open one Client total.""" bootstrap_tokens: str = "" - """Phase 1.3 seed: ``name1:value1,name2:value2``. Empty = no auth wired yet. + """Out-of-band token seed: ``name1:value1,name2:value2``. Empty is + fine — DB-issued tokens (admin UI) carry steady-state auth. - Phase 4 moves tokens into the DB. Until then this is the only source. + This env channel layers alongside the DB store and is kept for + first-run setup, disaster recovery (admin password lost), and + ``examples/`` smoke tests. Bootstrap tokens implicitly carry + scope ``"*"`` and never get a ``last_used_at`` stamp (no DB row). """ diff --git a/src/beaver_gateway/storage/__init__.py b/src/beaver_gateway/storage/__init__.py index 1e60965..de28541 100644 --- a/src/beaver_gateway/storage/__init__.py +++ b/src/beaver_gateway/storage/__init__.py @@ -1,40 +1,32 @@ -"""SQLModel-backed persistence (Phase 4.1). +"""SQLModel-backed persistence. -The storage layer carries three tables — :class:`Token`, :class:`Session`, -:class:`AuditLog` — and a thin :class:`Database` wrapper around a sync -SQLAlchemy engine. Phase 4.2 (auth migration) and Phase 4.3 (admin UI) -build on this; Phase 4.1 itself only schemas the data and exposes the -``Database`` on :class:`GatewayRuntime` so later phases can reach it. +Two tables — :class:`Token`, :class:`AuditLog` — plus a thin +:class:`Database` wrapper around an async SQLAlchemy engine. The +``GatewayRuntime`` carries the handle so auth (token verify / touch) +and admin (token CRUD + audit listing) can reach it. """ from beaver_gateway.storage.db import ( Database, append_audit, - close_session, create_token, list_active_tokens, list_audit_records, list_tokens, revoke_token, - touch_session, touch_token, - upsert_session, ) -from beaver_gateway.storage.models import AuditLog, Session, Token +from beaver_gateway.storage.models import AuditLog, Token __all__ = [ "AuditLog", "Database", - "Session", "Token", "append_audit", - "close_session", "create_token", "list_active_tokens", "list_audit_records", "list_tokens", "revoke_token", - "touch_session", "touch_token", - "upsert_session", ] diff --git a/src/beaver_gateway/storage/db.py b/src/beaver_gateway/storage/db.py index 8e7eee5..62dfeb1 100644 --- a/src/beaver_gateway/storage/db.py +++ b/src/beaver_gateway/storage/db.py @@ -22,7 +22,7 @@ from sqlalchemy.ext.asyncio import create_async_engine from sqlmodel import SQLModel, select from sqlmodel.ext.asyncio.session import AsyncSession -from beaver_gateway.storage.models import AuditLog, Session, Token +from beaver_gateway.storage.models import AuditLog, Token if TYPE_CHECKING: from collections.abc import Sequence @@ -143,45 +143,6 @@ async def touch_token(session: AsyncSession, *, token_id: int) -> None: await session.commit() -# ---- Session bookkeeping ------------------------------------------------ - - -async def upsert_session( - session: AsyncSession, *, session_id: str, agent_name: str, fingerprint: str -) -> Session: - """Insert-or-bump a Session row. Agent/fingerprint never change for an id.""" - row = await session.get(Session, session_id) - if row is None: - row = Session(id=session_id, agent_name=agent_name, fingerprint=fingerprint) - else: - row.last_active_at = _utcnow() - session.add(row) - await session.commit() - await session.refresh(row) - return row - - -async def touch_session(session: AsyncSession, *, session_id: str) -> None: - """Bump ``last_active_at`` without changing fingerprint/agent.""" - row = await session.get(Session, session_id) - if row is None: - return - row.last_active_at = _utcnow() - session.add(row) - await session.commit() - - -async def close_session(session: AsyncSession, *, session_id: str) -> bool: - """Mark a session closed. Returns ``False`` if no such row.""" - row = await session.get(Session, session_id) - if row is None or row.closed_at is not None: - return False - row.closed_at = _utcnow() - session.add(row) - await session.commit() - return True - - # ---- Audit -------------------------------------------------------------- @@ -225,13 +186,10 @@ async def list_audit_records( __all__ = [ "Database", "append_audit", - "close_session", "create_token", "list_active_tokens", "list_audit_records", "list_tokens", "revoke_token", - "touch_session", "touch_token", - "upsert_session", ] diff --git a/src/beaver_gateway/storage/models.py b/src/beaver_gateway/storage/models.py index 72ce0cc..a18938c 100644 --- a/src/beaver_gateway/storage/models.py +++ b/src/beaver_gateway/storage/models.py @@ -1,9 +1,15 @@ -"""SQLModel tables — see PRD §9. +"""SQLModel tables. -Three tables, all flat, no relationships modelled yet (Phase 4 talks -about ``actor`` and ``agent_name`` as strings — joining audit→token by -name is fine at this volume; we'll introduce FKs when the admin UI -actually demands them). +Two tables, both flat, no relationships modelled yet (``actor`` and +``agent_name`` are stored as strings — joining audit→token by name is +fine at this volume; we'll introduce FKs when the admin UI actually +demands them). + +A ``Session`` table originally lived here for live-session +observability. It was dropped after we decided the gateway stays +stateless about identity (claude-code-api's in-memory fingerprint pool +is the source of truth) and that conversation persistence belongs in a +future Obsidian-sync frontend, not a sessions table. Datetimes are stored UTC; we set ``default_factory`` rather than relying on DB defaults so SQLite + Postgres behave identically. Every row that @@ -39,23 +45,6 @@ class Token(SQLModel, table=True): revoked_at: datetime | None = Field(default=None) -class Session(SQLModel, table=True): - """Mirror of one live ``claude-code-api`` session. - - The id is the ``session_id`` claude itself assigns on the first - turn; we don't generate it. Rows here are for admin observability - (live count, last activity) — the actual pool lives in - ``claude_code_api.ClaudeCodeBackend`` and is the source of truth. - """ - - id: str = Field(primary_key=True) - agent_name: str = Field(index=True) - fingerprint: str = Field(index=True) - created_at: datetime = Field(default_factory=_utcnow) - last_active_at: datetime = Field(default_factory=_utcnow) - closed_at: datetime | None = Field(default=None) - - class AuditLog(SQLModel, table=True): """Append-only record of who-did-what. @@ -77,4 +66,4 @@ class AuditLog(SQLModel, table=True): detail_json: str = Field(default="{}") -__all__ = ["AuditLog", "Session", "Token"] +__all__ = ["AuditLog", "Token"]