refactor: remove dead session code

This commit is contained in:
h
2026-05-20 14:23:01 +02:00
parent 0128191ac3
commit a7827b2fa6
5 changed files with 27 additions and 84 deletions
+2 -2
View File
@@ -210,8 +210,8 @@ async def _require_token(
SDK sends — LibreChat, the CLI, third-party clients) and
``Authorization: Bearer <token>`` (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 = (
+6 -2
View File
@@ -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).
"""
+6 -14
View File
@@ -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",
]
+1 -43
View File
@@ -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",
]
+12 -23
View File
@@ -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"]