feat(auth): migrate rmm + time_tracking to TenantId (PMS-139 sweep batch 1) #159

Merged
longjacksonle merged 1 commit from feat/pms-139-sweep-rmm-time into main 2026-06-11 05:23:26 +02:00

What

First per-module batch of the PMS-139 typed-tenant-scoping sweep (on top of the merged foundation #158). Migrates rmm and time_tracking - both self-contained (no external callers of their services) and free of the audit_write hub, so they're a clean, low-risk start.

Pattern (the same one every later batch follows)

  • Handlers (routes.rs): RequireRmm { user: u, .. } / RequireTimeTracking { user, .. } -> pass u.tenant() instead of user.tenant_id.
  • Services (service.rs): every method (and the module-local helpers in time_tracking) takes tenant_id: TenantId instead of Uuid. .bind(tenant_id) and #[tracing::instrument(fields(tenant_id = %tenant_id))] are unchanged thanks to the transparent newtype + Display.

Two boundary seams worth a look in review

  • RMM ingest webhook (POST /rmm/alerts) is unauthenticated (RMM agents, HMAC-signed) - there's no CurrentUser. It parses the tenant from the signed X-Tenant-Id header and authenticates by HMAC against that tenant's per-connection secret. This uses TenantId::from_trusted with an explicit // SAFETY: comment - exactly the named escape hatch the design reserves for out-of-band scope.
  • rmm -> tickets/audit: the alert path creates a ticket via the not-yet-migrated TicketService + AuditCtx::system, so it unwraps with tenant_id.get() transitionally. These .get()s disappear when the hub modules are swept.

Tests

--lib, --test rmm (3), --test time_tracking (1), and the doctest guard all green; clippy --all-targets clean. Both modules' integration tests go through HTTP, so no from_trusted changes were needed in tests this batch.

Progress

Migrated: reports (foundation), rmm, time_tracking. Remaining ~14 modules in subsequent batches; dev-docs cross-cutting #8 updated.

🤖 Generated with Claude Code

## What First per-module batch of the PMS-139 typed-tenant-scoping sweep (on top of the merged foundation #158). Migrates **`rmm`** and **`time_tracking`** - both self-contained (no external callers of their services) and free of the `audit_write` hub, so they're a clean, low-risk start. ## Pattern (the same one every later batch follows) - **Handlers** (`routes.rs`): `RequireRmm { user: u, .. }` / `RequireTimeTracking { user, .. }` -> pass `u.tenant()` instead of `user.tenant_id`. - **Services** (`service.rs`): every method (and the module-local helpers in time_tracking) takes `tenant_id: TenantId` instead of `Uuid`. `.bind(tenant_id)` and `#[tracing::instrument(fields(tenant_id = %tenant_id))]` are unchanged thanks to the transparent newtype + `Display`. ## Two boundary seams worth a look in review - **RMM ingest webhook** (`POST /rmm/alerts`) is unauthenticated (RMM agents, HMAC-signed) - there's no `CurrentUser`. It parses the tenant from the signed `X-Tenant-Id` header and authenticates by HMAC against that tenant's per-connection secret. This uses `TenantId::from_trusted` with an explicit `// SAFETY:` comment - exactly the named escape hatch the design reserves for out-of-band scope. - **rmm -> tickets/audit**: the alert path creates a ticket via the not-yet-migrated `TicketService` + `AuditCtx::system`, so it unwraps with `tenant_id.get()` transitionally. These `.get()`s disappear when the hub modules are swept. ## Tests `--lib`, `--test rmm` (3), `--test time_tracking` (1), and the doctest guard all green; `clippy --all-targets` clean. Both modules' integration tests go through HTTP, so no `from_trusted` changes were needed in tests this batch. ## Progress Migrated: `reports` (foundation), **`rmm`**, **`time_tracking`**. Remaining ~14 modules in subsequent batches; dev-docs cross-cutting #8 updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(auth): migrate rmm + time_tracking to TenantId (PMS-139 sweep batch 1)
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
E2E (staging) / Playwright against staging (pull_request) Successful in 1m7s
Check / fmt + clippy + compile + tests (pull_request) Successful in 4m30s
Build OCI container / Build and push mokosh-api image (push) Successful in 9m48s
961251ccd6
First per-module batch of the typed-tenant-scoping sweep on top of the PMS-139 foundation. Both modules are self-contained (no external callers of their services) and don't touch the audit_write hub, so the change is mechanical: handlers read u.tenant() instead of user.tenant_id, and every service + module-local helper signature takes tenant_id: TenantId instead of Uuid (.bind and #[tracing::instrument(fields(tenant_id = %tenant_id))] are unchanged via the transparent newtype + Display).

Two boundary seams: the unauthenticated RMM ingest webhook (machine HMAC, tenant from a signed header, no CurrentUser) uses TenantId::from_trusted with a // SAFETY: comment; and rmm's alert-ingest path that creates a ticket through the not-yet-migrated TicketService / AuditCtx unwraps with tenant_id.get() until those hubs are swept. dev-docs #8 updated. Green: --lib, rmm, time_tracking, doctests; clippy --all-targets clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/pms-139-sweep-rmm-time 2026-06-11 05:23:26 +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!159
No description provided.