v0.4.0 — Multi-tenant cost attribution
v0.4.0 — Multi-tenant cost attribution
VoiceGateway now tags every voice session with an optional tenant_id so a single deployment can serve many customers and account for each one separately. Three independent surfaces set the tenant: an attach_session(tenant_id=...) kwarg, an inference.set_tenant("…") ContextVar API, and scoped virtual API keys that auto-attribute at the auth layer. Every cost row, metric row, and replay event for an attributed session lands tagged; pre-v0.4.0 rows stay in the "unattributed" bucket (NULL tenant_id).
Highlights
- REQ-VG-TENANT-001 (tag sessions with a tenant dimension):
tenant_id_ctxContextVar +attach_sessionkwarg +log_requestUPSERT withCOALESCEso the first tenant-bearing request stamps the session for its lifetime. Every cost / metric / replay row tagged via T08's repo-level propagation. - REQ-VG-TENANT-002 (slice the dashboard by tenant): new
/api/tenants+/api/tenants/{id}endpoints; existing/api/costs,/api/sessions,/api/metrics,/api/logs,/api/latencyaccept atenantquery param. NewFilterBar+TenantFilter(200ms-debounced typeahead) +TenantPillcomponents with URL persistence. Costs / Sessions / Metrics pages and the SessionDetail modal all rescope when a tenant is selected. - REQ-VG-TENANT-003 (issue virtual API keys): new
virtual_keystable (bcrypt-hashed at cost 12, 8-char visible prefix indexed) +virtual_keys_repo(issue / verify / soft-revoke / list / mark-used / list-stale) +VirtualKeys.tsxdashboard page with the show-key-once modal.voicegw tenant list / show <id>CLI is read-only by design. - REQ-VG-TENANT-004 (auto-attribute calls with virtual key): auth middleware in
voicegateway/server/main.py::build_appdetectsvk_-prefixed bearer tokens, resolves them, setstenant_id_ctxif scoped, returns 403 on body-tenant + key-scope conflict.
Migration notes
None breaking. Migration 0005 is idempotent and adds tenant_id nullable across sessions, requests, and (conditionally on prior migrations having run) turns, dead_air_events, and the four replay_* tables. Pre-v0.4.0 rows stay tenant_id = NULL and the FE renders them as a muted "unattributed" pill — no backfill happens. The new bcrypt>=4.0 dependency lands automatically with pip install -e ".[dev]" or any cloud-extra reinstall.
Five Foundry Open Questions locked in this release: vk_+32 base32 key shape (OQ1), 128-char UTF-8 tenant cap (OQ2), no backfill (OQ3), sqlite_master ALTER guard (OQ4), soft revoke (OQ5).
Out of scope this release: no automatic backfill of pre-v0.4.0 sessions, no CLI key issuance, no voicegw costs --tenant flag, no re-tag affordance for already-attributed sessions, no RBAC scopes on virtual keys. The multi-tenant quickstart guide enumerates these explicitly.
Refinery and Foundry