Skip to content

perf(session): aggregate session.list SQL-side; bound persistUserMessage read (v2.0.2)#155

Open
rbrasier wants to merge 8 commits into
mainfrom
claude/code-quality-hot-paths-tjzebo
Open

perf(session): aggregate session.list SQL-side; bound persistUserMessage read (v2.0.2)#155
rbrasier wants to merge 8 commits into
mainfrom
claude/code-quality-hot-paths-tjzebo

Conversation

@rbrasier

@rbrasier rbrasier commented Jul 5, 2026

Copy link
Copy Markdown
Owner

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

claude added 4 commits July 5, 2026 20:39
…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
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

📊 Playwright E2E Report

View full report with screenshots and traces

Run #377

claude added 4 commits July 6, 2026 03:35
… 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants