feat(auth): typed TenantId scoping foundation + reports migration (PMS-139) #158
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pms-139-tenant-id-foundation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Phase 1 (foundation) of PMS-139: make a forgotten or wrong-tenant service call a compile error, and establish the migration pattern with one fully-migrated module. The remaining ~17 modules follow as separate PRs (the ticket explicitly allows splitting).
The type
src/modules/auth/tenant.rsaddsTenantId:#[sqlx(transparent)]newtype overUuid, so.bind(tenant_id)andquery_asdecoding are unchanged, and itDerefs toUuid+impl Displayso the rare leaf that needs the raw value uses*tenant_id/tenant_id.get().TenantId::newispub(crate), reached solely throughTenantScoped::tenant()on an authenticatedCurrentUser. So aTenantIdalways traces back to a verified tenant claim - it can't be conjured from request input.pub fn from_trusted(Uuid)is the explicitly-named escape hatch for super-admin cross-tenant operations and tests (the AC permits this; it's greppable for review).TenantScope.tenant_idis retyped fromUuidtoTenantId.Reference migration:
reportsFully migrated end-to-end as the pattern the sweep will follow:
RequireReports { user: u, .. }->u.tenant()passed to the service.service.rs(6 methods) +custom::runtaketenant_id: TenantId. Every.bind(tenant_id)and#[tracing::instrument(fields(tenant_id = %tenant_id))]works unchanged.The guarantee, pinned
A
compile_faildoctest onTenantIdasserts a rawUuidis rejected where aTenantIdis required. I chose a doctest overtrybuildbecause it needs no.stderrfixture (no rust 1.94-vs-1.95 drift). CI gains acargo test --docstep so it actually runs (--libdoes not execute doctests); the full doctest suite is green.Acceptance criteria (this phase)
TenantIdnewtype added;TenantId::newispub(crate), external callers go throughTenantScope/from_trusted.reports) fully onTenantIdas the reference; its public service signatures takeTenantId, notUuid.Uuidcannot be passed where aTenantIdis required.--lib115,reports8, doctests green; clippy--all-targetsclean).Follow-ups
Per-module batches: contacts/tickets/billing/projects/assets/contracts/calendar/knowledge_base/rmm/time_tracking/settings/notifications/portal/sla/audit/tenants/auth. Each is the same mechanical change (
u.tenant()in handlers,tenant_id: TenantIdinservice.rs), compiles independently, and the tests that construct services directly switch toTenantId::from_trusted(...).🤖 Generated with Claude Code