Skip to content

feat(web): per-user Bedrock spend attribution table on the Usage page#15

Merged
iaj6 merged 2 commits into
mainfrom
feat/bedrock-per-user-table
Jun 17, 2026
Merged

feat(web): per-user Bedrock spend attribution table on the Usage page#15
iaj6 merged 2 commits into
mainfrom
feat/bedrock-per-user-table

Conversation

@iaj6

@iaj6 iaj6 commented Jun 17, 2026

Copy link
Copy Markdown
Owner

What & why

The "who is burning the Bedrock spend?" view — the per-principal attribution AWS Cost Explorer structurally can't give you (it bills by IAM principal, with no user / agent / session concept). Builds directly on the backend segmentation from #14.

Changes

  • GET /api/usage/by-user (requireAdmin, b33122b) — groups all runs by user, splits each user's spend by backend (bedrock / anthropic / unknown), ranked by Bedrock spend desc. Runs with no resolved userId collapse into a single "Unattributed" row that always sorts last — never folded into a real user. Returns the parity-estimate flag from feat: segment agent spend by backend (Bedrock vs direct) on the Usage page #14. Admin-only by design: members are always self-scoped (resolveViewScope), so a team-wide breakdown is an admin artifact — mirrors /api/budgets.
  • "Spend by user" table on the Usage page (6dee19a) — useBedrockByUser returns null on the route's 403, so non-admins never render the section (no client role check needed). Bedrock column carries the "Bedrock est." badge; Unattributed renders as a muted row.

Attribution gate — verified

A per-user table is only worth building if user_id actually populates. Confirmed against current main: SDK mode always stamps userId (bearer token); local mode resolves it via resolveLocalUserId (creds-email → sole-user fallback), NULL only in multi-user-without-creds setups. Those NULL runs are shown honestly as "Unattributed" rather than hidden.

Verification

  • tsc build green, web lint clean.
  • New tests (auth-gaps.test.ts): member → 403; admin sees per-user backend buckets, ranked by Bedrock, with the Unattributed row last + estimate flag.
  • ⚠️ Local DB tests could not be executed — this machine is on Node v26.3.0 and [email protected] has no compatible binary. The tests will first run on CI (Node 20). Flagging explicitly.

Deferred (out of scope)

Budget-vs-actual stays on /admin/budgets (avoids conflating a total budget with Bedrock spend); period-scoping (all-time matches the page today); real per-region Bedrock rates; run_metrics.backend column.

🤖 Generated with Claude Code

iaj6 and others added 2 commits June 17, 2026 13:42
GET /api/usage/by-user (requireAdmin) groups all runs by user and splits each
user's spend by backend (bedrock / anthropic / unknown), ranked by Bedrock
spend. Runs with no resolved userId collapse into a single "Unattributed" row
that always sorts last — never folded into a real user. Returns the same
parity-estimate flag as /api/usage/local so the UI can caveat Bedrock dollars.
Admin-only by design: members are always scoped to themselves (resolveViewScope),
so a team-wide breakdown is an admin artifact, mirroring /api/budgets.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Adds a "Spend by user" table — the "who is burning the Bedrock spend" view AWS
Cost Explorer can't give you (it attributes by IAM principal, with no
user/agent concept). The useBedrockByUser hook returns null on the route's 403,
so non-admins never render the section. The Bedrock column carries the "Bedrock
est." badge; Unattributed runs render as a muted row, never hidden.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@iaj6 iaj6 force-pushed the feat/bedrock-per-user-table branch from 6dee19a to 8d4e528 Compare June 17, 2026 17:43
@iaj6 iaj6 merged commit 88d1a51 into main Jun 17, 2026
3 checks passed
@iaj6 iaj6 deleted the feat/bedrock-per-user-table branch June 17, 2026 18:13
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.

1 participant