Skip to content

fix(admin-costs): list a user's deleted conversations in the cost drill-down - #1127

Merged
philmerrell merged 1 commit into
developfrom
feature/admin-costs-deleted-sessions
Sep 16, 2026
Merged

philmerrell merged 1 commit into
developfrom
feature/admin-costs-deleted-sessions

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Why

A soft delete flips status and drops the sidebar GSI keys, but the session's C# cost rows and its share of the user's period total survive it. The per-user conversation list (GET /admin/costs/users/{id}/sessions) dropped those tombstones, so the audit could not account for the spend it was built to explain.

Found during the 2026-09-15 prod cost audit: one top-5 September user showed a single $3.77 conversation against $20.32 of period cost. Fleet-wide, 183 of 5,440 September-active sessions (3.4%) are deleted and carry $58 of $817.

What

  • Storage: get_user_session_diagnostics(..., include_deleted=False) — default unchanged, so callers that list what the user can still open are unaffected. The shared _query_user_session_rows body takes the same flag.
  • Admin service: asks for tombstones, normalises legacy deleted rows (no status flip) to status="deleted", and reports deletedSessionCount / deletedSessionCost on UserSessionsResponse. Deleted rows keep their shareOfUserPeriod, because they still take that share.
  • SPA: a deleted badge on the row (with a title explaining that its cost survives the delete) and N deleted ($X still counted in the total) on the summary line.
  • Profile and anatomy already resolve for a deleted session: the delete keeps GSI_PK/GSI_SK on the tombstone and only removes the GSI4 sidebar keys.

Content-free by construction: the deleted row goes through the same SESSION_ROW_PROJECTION + strip_content path (test asserts title is absent on the tombstone).

Tests

  • tests/costs/test_content_free_projections.py: default excludes; include_deleted=True lists the tombstone with deleted/status and no title.
  • tests/apis/app_api/admin/costs/test_user_sessions_service.py: deleted rows listed and flagged, legacy tombstones normalised, count/cost rollups, zero case.
  • SPA user-conversations.component.spec.ts: summary line and flagged rows.
  • Backend: 95 passed across tests/costs + tests/apis/app_api/admin/costs. SPA spec: 6 passed; tsc --noEmit clean.

🤖 Generated with Claude Code

…ll-down

A soft delete flips `status` and drops the sidebar GSI keys, but the
session's `C#` cost rows and its share of the user's period total survive
it. The per-user conversation list dropped those tombstones, so the audit
could not account for the spend it was built to explain: one prod user
showed a single $3.77 conversation against $20.32 of September cost.
Fleet-wide, 183 of 5,440 September-active sessions (3.4%) are deleted and
carry $58 of $817.

The storage reader gains `include_deleted` (default off, so callers that
list what the user can still open are unchanged); the admin service asks
for tombstones, normalises legacy `deleted` rows to `status="deleted"`,
and reports `deletedSessionCount` / `deletedSessionCost` on the response.
The SPA badges deleted rows and appends "N deleted ($X still counted in
the total)" to the summary line. Profile and anatomy still resolve for a
deleted session because `SessionLookupIndex` keys are kept on the
tombstone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit 311cde3 into develop Sep 16, 2026
6 checks passed
@philmerrell
philmerrell deleted the feature/admin-costs-deleted-sessions branch September 16, 2026 14:45
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