feat(db): DB-backed UsageStore + agent_calls table extension [STORY-060]#19
Merged
feat(db): DB-backed UsageStore + agent_calls table extension [STORY-060]#19
Conversation
Drizzle migration 0002 extends `agent_calls` with the STORY-012 columns (`session_id`, `task` via new `agent_task` enum, `cached_tokens`, `cost_usd numeric(18,8)`, `pricing_version`, `tool_used`). `DrizzleLLMTelemetrySink` writes one row per `LLMTelemetryEvent` into `agent_calls`. Inserts are fire-and-forget so a Postgres outage can't take down an LLM call — failures are logged via the configured logger (default: console.error) and swallowed. cost_usd is passed as a string via `toFixed(8)` to avoid float64 round-trip through the pg driver. `DrizzleUsageStore.today()` aggregates `sum(input_tokens + output_tokens) WHERE user_id = $1 AND called_at >= start_of_utc_day`. `record()` is intentionally a no-op since the sink is the single source of truth (otherwise every call would double-count). Tests: 6 unit tests for the sink (full-mapping / optional-omit / org_id stamping / cost formatting / error swallowing / unparseable decided_at fallback) + 5 integration tests for the store (zero state / aggregation / yesterday boundary / per-user isolation / record no-op) gated by DATABASE_URL so `pnpm test` still passes in CI without a DB. Schema test extended to assert all new columns present + `agent_task` enum mirrors `LLMTelemetryEventSchema.task`. The 3 API-wiring ACs (`GET /llm/usage/today`, 429 mapping, manual smoke) are deferred to STORY-005 since they need the auth middleware that doesn't exist yet — explicitly allowed by the Story spec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
0002_agent_calls_telemetry.sqlextends theagent_callstable with the STORY-012 telemetry columns:session_id,task(newagent_taskenum: complete/stream/embed/tool_call),cached_tokens,cost_usd numeric(18,8),pricing_version,tool_used. Existing columns from STORY-013 are preserved.DrizzleLLMTelemetrySink(inpackages/db/src/llm-telemetry-sink.ts) writes one row perLLMTelemetryEvent. Failures are logged + swallowed via the configuredloggerso a Postgres outage can't take down the LLM call path.cost_usdpasses through as atoFixed(8)string to avoid float64 round-trip.DrizzleUsageStore(inpackages/db/src/llm-usage-store.ts) implementstoday()assum(input + output) WHERE user_id = $1 AND called_at >= start_of_utc_day.record()is a deliberate no-op — the sink is the single source of truth, otherwise every call would double-count.DATABASE_URLsopnpm teststill passes in CI without a DB). Schema test extended to assert the new columns +agent_taskenum.Deferred to STORY-005
3 ACs require auth middleware (
user_idfrom a logged-in session) and are explicitly allowed to defer by the Story spec:GET /llm/usage/todayrouteTokenBudgetExceededErrorThese land with the API wiring in STORY-005.
Story
Closes STORY-060 (table + sink + store; the 3 API-wiring ACs above ride along with STORY-005).
Test plan
pnpm --filter @learnpro/db test→ 29 passed, 5 skipped (gated integration)pnpm format:checkcleanpnpm lintcleanpnpm typecheckcleanpnpm test→ 12 tasks, all greenDATABASE_URLset → 5 integration tests pass against a real Postgres (run by user when convenient)🤖 Generated with Claude Code