Lab Journal
Platform Admin lands in processflow: tenant management, RBAC plumbing, and a new /platform area

Platform Admin lands in processflow: tenant management, RBAC plumbing, and a new /platform area

lab-journalhackerlabs.devprocessflow

Coming off yesterday's monster processflow rewrite, today felt almost focused by comparison: one feature, end to end. Phase B.2 — the Platform Admin layer — went from spec to working /platform area in a single sitting.

hackerlabs.dev

Just the routine bit: published yesterday's lab journal (the big graph-engine writeup) and dropped in the recap image. Nothing else here today — all the energy was on processflow.

processflow — Platform Admin (Phase B.2)

This one started in docs, which I'm increasingly glad about. I wrote both the spec and the implementation plan before touching any code — docs/superpowers/2026-05-25-tenant-handoff.md from yesterday set the multi-tenancy direction, and today's job was building the cross-tenant admin surface on top of it.

The schema changes were small but load-bearing: isPlatformAdmin on User and isActive on Tenant. Two booleans, but they unlock everything else. admin@processflow.local got seeded as the first Platform Admin so I'd have something to log in as.

From there it was a layered build:

  • Auth layer. isPlatformAdmin flows through the JWT and session callbacks, then out into authorize(). Also wired in the inactive-tenant login block — if a tenant's isActive is false, nobody from that tenant can log in. Easy to forget that one, glad I caught it early.
  • Capability layer. useCapabilities() now returns isPlatformAdmin alongside the existing can() checks. The nav uses it to conditionally render a "Platform" link, and DeniedToast got a new platform reason so the redirect-with-message pattern works consistently.
  • Server guard. A withPlatformAdmin wrapper for API routes, with unit tests. This is the equivalent of the existing role wrappers but for the cross-tenant case — it has to bypass the usual tenant scoping, which is exactly the kind of thing I want gated behind one well-tested function rather than scattered around.
  • The /platform area itself. Layout, dashboard, tenants list, create form, detail/edit page with a deactivate toggle. Four routes, plus the matching /api/platform/tenants endpoints for list/create/get/edit.

The tricky bit was the unscoped Prisma whitelist. I have a security test that fails the build if any code uses the raw, non-tenant-scoped Prisma client outside an explicit allowlist. Platform admin routes legitimately need that — they're operating across tenants — so I extended the whitelist to cover src/app/api/platform/**. It feels right: the rule isn't "never use unscoped Prisma," it's "only use it in places that have been audited to need it," and the test enforces that.

I also added a test specifically for the tenant deactivation login behavior, with an authenticateCredentials helper to make it readable. That's the kind of behavior I really don't want to regress silently.

One small process note: the session started with the AI assistant offering to do a Q&A-first design pass before any code, with a hard gate of "no implementation until design is approved." I went with that, and it paid off — the spec and plan docs meant the actual coding was almost mechanical. Fifteen commits, all in a sensible order, none of the "wait, I need to refactor that" backtracking I usually get when I dive in.

Closing thought

Two days, two very different shapes of work: yesterday was a wide structural rewrite, today was a narrow vertical slice through a single feature. The platform admin layer is the kind of thing that's invisible when it works and catastrophic when it doesn't, so I'm happy it landed with tests on the auth boundary, the route guard, and the deactivation flow. Tomorrow I'll probably start exercising it with a second seeded tenant to make sure the scoping really holds.