feat(auth): migrate contacts + billing to TenantId (PMS-139 sweep batch 5) #164

Merged
longjacksonle merged 1 commit from feat/pms-139-sweep-contacts-billing into main 2026-06-11 14:11:14 +02:00

What

Batch 5 of the PMS-139 typed-tenant sweep: migrate contacts and billing, the two remaining leaf consumers, from bare Uuid tenant ids to the TenantId newtype. This leaves only the hub modules (audit, notifications, tickets) for the final batch.

How

  • contacts, billing - handlers use user.tenant() (TenantScoped, now imported in each routes.rs); every public service method takes tenant_id: TenantId. Transparent newtype -> SQL binds and %tenant_id tracing unchanged.
  • audit hub - the audit_write calls (9 contacts, 11 billing) unwrap with tenant_id.get() transitionally until the audit hub is swept last. The PaymentResponse DTO field is populated from tenant_id.get() (the response model carries a plain Uuid).
  • FromRow projections (CompanyRow, ContactRow, SiteRow, PaymentRow, InvoiceRow) keep their Uuid fields - DB decodes, not the handler boundary.
  • Three non-claim bridges use from_trusted + SAFETY note: the out-of-scope portal invoice feed (verified contact-JWT tenant, like batch 4's KB feed), the demo seeder (trusted system actor seeding a claimed tenant id), and the billing recurring-invoice cross-tenant sweep (tenant ids read off the tenants table).
  • tests - tests/recurring_invoicing.rs wraps its literal tenant ids with from_trusted; tests/billing.rs and tests/contacts.rs drive the HTTP layer and needed no change.

Verification

  • cargo check --all-targets, cargo clippy --all-targets: clean.
  • cargo test --test contacts (7), --test billing (3), --test recurring_invoicing (5): pass.
  • cargo test --doc: compile_fail newtype guard holds (2 pass / 3 ignored).

Progress: 13 of ~17 modules migrated. The final batch sweeps audit / notifications / tickets (the audit_write / notifications::dispatch / TicketService hubs); flipping those removes every transitional tenant_id.get() from batches 1-5.

🤖 Generated with Claude Code

## What Batch 5 of the PMS-139 typed-tenant sweep: migrate `contacts` and `billing`, the two remaining leaf consumers, from bare `Uuid` tenant ids to the `TenantId` newtype. This leaves only the hub modules (`audit`, `notifications`, `tickets`) for the final batch. ## How - **contacts, billing** - handlers use `user.tenant()` (`TenantScoped`, now imported in each `routes.rs`); every public service method takes `tenant_id: TenantId`. Transparent newtype -> SQL binds and `%tenant_id` tracing unchanged. - **audit hub** - the `audit_write` calls (9 contacts, 11 billing) unwrap with `tenant_id.get()` transitionally until the audit hub is swept last. The `PaymentResponse` DTO field is populated from `tenant_id.get()` (the response model carries a plain `Uuid`). - **FromRow projections** (`CompanyRow`, `ContactRow`, `SiteRow`, `PaymentRow`, `InvoiceRow`) keep their `Uuid` fields - DB decodes, not the handler boundary. - **Three non-claim bridges** use `from_trusted` + SAFETY note: the out-of-scope portal invoice feed (verified contact-JWT tenant, like batch 4's KB feed), the demo `seed`er (trusted system actor seeding a claimed tenant id), and the billing recurring-invoice cross-tenant sweep (tenant ids read off the `tenants` table). - **tests** - `tests/recurring_invoicing.rs` wraps its literal tenant ids with `from_trusted`; `tests/billing.rs` and `tests/contacts.rs` drive the HTTP layer and needed no change. ## Verification - `cargo check --all-targets`, `cargo clippy --all-targets`: clean. - `cargo test --test contacts` (7), `--test billing` (3), `--test recurring_invoicing` (5): pass. - `cargo test --doc`: `compile_fail` newtype guard holds (2 pass / 3 ignored). Progress: 13 of ~17 modules migrated. The final batch sweeps `audit` / `notifications` / `tickets` (the `audit_write` / `notifications::dispatch` / `TicketService` hubs); flipping those removes every transitional `tenant_id.get()` from batches 1-5. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(auth): migrate contacts + billing to TenantId (PMS-139 sweep batch 5)
Some checks failed
Check / fmt + clippy + compile + tests (pull_request) Failing after 11s
E2E (staging) / Playwright against staging (pull_request) Successful in 33s
Create release / Create release from merged PR (pull_request) Has been skipped
Build OCI container / Build and push mokosh-api image (push) Successful in 3m54s
3deb53e455
Sweep batch 5 of the typed-tenant rollout, covering the two remaining leaf consumers before the hubs. Handlers in `contacts` and `billing` derive scope via `user.tenant()` (`TenantScoped`); every public service method takes `tenant_id: TenantId`. The transparent newtype binds/decodes/Displays as its inner `Uuid`, so SQL binds and `%tenant_id` tracing are unchanged. The `audit_write` calls (9 in contacts, 11 in billing) unwrap with `tenant_id.get()` transitionally until the audit hub is swept last; the `PaymentResponse` DTO field is populated from `tenant_id.get()` since the response model carries a plain `Uuid`.

FromRow projections (`CompanyRow`, `ContactRow`, `SiteRow`, `PaymentRow`, `InvoiceRow`) keep their `Uuid` fields - they are DB decodes, not the handler boundary.

Three non-claim bridges use `from_trusted` with SAFETY notes: the out-of-scope portal invoice feed (verified contact-JWT tenant, like the KB feed from batch 4), the demo seeder (a trusted system actor seeding a claimed tenant id), and the billing recurring-invoice sweep (cross-tenant, reading tenant ids off the `tenants` table).

Direct-construction test `tests/recurring_invoicing.rs` wraps its literal tenant ids with `TenantId::from_trusted(...)`; `tests/billing.rs` and `tests/contacts.rs` drive the HTTP layer and needed no change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/pms-139-sweep-contacts-billing 2026-06-11 14:11:15 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!164
No description provided.