perf(session): aggregate session.list SQL-side; bound persistUserMessage read (v2.0.2)#155
Open
rbrasier wants to merge 8 commits into
Open
perf(session): aggregate session.list SQL-side; bound persistUserMessage read (v2.0.2)#155rbrasier wants to merge 8 commits into
rbrasier wants to merge 8 commits into
Conversation
…age read (v2.0.2) Group A (slice 1) of the code-quality hot-paths phase. Item 1 — session.list no longer loads every session's full message history to derive its list row. A new ISessionMessageRepository.summariseForSessionList port method returns per-session last-assistant-message + best-confidence-per-step computed SQL-side in a fixed two queries (DISTINCT ON … seq DESC and MAX(...) GROUP BY), regardless of session count or age. The tRPC router calls it once and looks each summary up in a map, replacing the per-session listBySession N+1. The derived lastMessage/stepInfo output is byte-for-byte unchanged. Item 2 — RunTurn.persistUserMessage inspected only the last message but loaded the whole history via listBySession; it now uses the bounded latestBySession(sessionId, 1). Query-side only: no schema change, no migration. PATCH 2.0.1 -> 2.0.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
…(v2.1.0) Group C (item 7 + item 8 first target) of the code-quality hot-paths phase. Item 7 — new IUnitOfWork domain port (withTransaction + TransactionalRepositories) implemented by DrizzleUnitOfWork over db.transaction. Because Drizzle only rolls back on a thrown error while the app signals failure with an error Result, an internal TransactionRollback carries the domain error out to trigger rollback and withTransaction unwraps it back into a Result — nothing commits unless the work returns success. The application layer still sees only ports (ADR-001). Item 8 — RunTurn.persistAssistantTurn wrote the assistant message and the session advance/complete/await as separate statements, so a crash between them left a half-applied turn. It now resolves edges before the transaction, then creates the message and applies the single session mutation inside one transaction, firing notifiers only after commit. Behaviour is otherwise identical. RunTurn's unused direct ISessionRepository dependency is dropped; the transactional sessions repo comes from the unit of work. DecideApproval and ApplyAutoNodeResult (remaining item-8 use cases) are deferred to a follow-up slice. No schema change. MINOR 2.0.2 -> 2.1.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
…(v2.2.0) Group F (item 19) of the code-quality hot-paths phase. New IRateLimiter domain port backed by a pure token-bucket entity (consumeToken) and an InMemoryRateLimiter adapter (bounded, insertion-ordered per-key buckets, injected IClock, same in-process pattern as TtlCache). The auth POST endpoint is throttled per IP and the chat stream POST per user id; a throttled request gets 429 + Retry-After. Both fail open on a limiter error so a bug can never lock users out. Capacity/refill are env-configurable (AUTH_/CHAT_RATE_LIMIT_*), with generous behaviour-neutral defaults; capacity 0 disables a limiter. The port is the seam the infrastructure phase promotes to a shared store (Redis) when instance count > 1. No schema change. MINOR 2.1.0 -> 2.2.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
Group B (item 5, first slice) of the code-quality hot-paths phase. The stream route's lazy branch-choice call used the AI SDK's generateObject wrapped in a hand-rolled llmGovernor.run + recordTokenUsage. It now goes through container.services.llm.generateObject: the governor (passed into LanguageModelAdapter), usage recording (withUsageTracking) and quota enforcement (withQuotaEnforcement) all apply as decorators, so the hand-rolled plumbing and the SDK generateObject import are removed from that path. Behaviour is preserved (same governor, model, schema, null-on-failure); the only difference is the vestigial conversation_id on that one usage row, which no spend-cap/dashboard query reads. The streaming turn (stream-turn.ts) is intentionally left on the SDK: it uses Anthropic cache_control prompt caching not yet exposed by the port, so porting it naively would regress caching. That, the remaining turn-helpers SDK calls, and the ExecuteTurn extraction (item 6) are best landed behind the chat e2e (the phase's stated mitigation for this riskiest group), which can't run in this sandbox. PATCH 2.2.0 -> 2.2.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
📊 Playwright E2E ReportView full report with screenshots and traces Run #377 |
… ADRs (v2.2.2) Group E (items 15, 17, 18) of the code-quality hot-paths phase. Item 15: eight drifted copies of the Better-Auth cookie parse (trpc.ts + 7 API routes) collapse into one getSessionTokenFromRequest in lib/session-token.ts. Item 17: the chat stream POST body is Zod-validated against a new streamTurnRequestSchema (@rbrasier/shared) instead of a bare cast — a malformed body is a clean 400 rather than a throw deep in the turn. Item 18: the duplicate ADR numbers (two 015s, two 026s) each gain a numbering note pointing at their twin and naming which ADR the code comments cite; not renumbered, since code cites the numbers. Items 14 and 16 deferred — both fall out of Group B's ExecuteTurn extraction (confirmStep depends on the turn-orchestration helpers). PATCH 2.2.1 -> 2.2.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
…les (v2.2.3) Group D item 9. Split apps/web/src/app/(admin)/admin/settings/page.tsx (2,183 lines) into one file per settings section under apps/web/src/components/settings/, plus a shared connectivity.tsx (useConnectivity hook, ConnectivityController, badge/test components). Whole component functions moved verbatim — byte-for-byte behaviour — so the page shrinks to 75 lines and leaves the validate.sh size allowlist. Adds an e2e that asserts every extracted section still renders (AI anchor, Test-all button, all six connectivity cards) to guard against a dropped card. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
…ttons The connectivity test button for the AI card renders only when the AI provider is configured (`aiConfigured && <ConnectivityTest ... />`), which is false in the seed env — so the previous assertion on `test-connectivity-ai` failed even though no card was dropped. Assert each extracted card's always-rendered <h3> title instead, which proves card presence independent of configuration state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
The concurrency-gated approval update and the session advance/route/cancel ran as two independent writes, so a crash between them left a decided approval sitting on a session that never moved. Wrap both in one transaction: TransactionalRepositories grows an `approvals` repo, and DecideApproval takes IUnitOfWork and commits updateIfPending + the session write together. Best-effort projection, audit, chat message, and notification now run only after the commit succeeds. ApplyAutoNodeResult was assessed and left unwrapped (single authoritative write); this completes Group C item 8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Group A (slice 1) of the code-quality hot-paths phase.
Item 1 — session.list no longer loads every session's full message history to
derive its list row. A new ISessionMessageRepository.summariseForSessionList
port method returns per-session last-assistant-message + best-confidence-per-step
computed SQL-side in a fixed two queries (DISTINCT ON … seq DESC and MAX(...)
GROUP BY), regardless of session count or age. The tRPC router calls it once and
looks each summary up in a map, replacing the per-session listBySession N+1. The
derived lastMessage/stepInfo output is byte-for-byte unchanged.
Item 2 — RunTurn.persistUserMessage inspected only the last message but loaded
the whole history via listBySession; it now uses the bounded
latestBySession(sessionId, 1).
Query-side only: no schema change, no migration. PATCH 2.0.1 -> 2.0.2.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01X3CoqMBHqraP5iZa8SZ2Ai