Story-1482 pramaan-admin Staff generated hooks

Staff keeps its mature table and changes the data source underneath

This story migrates the staff list, staff detail, and staff status workflows to the generated-hook architecture without rebuilding the staff table into the firms client island. The working useDataTable path remains the URL/table owner; generated hooks now read from the staff BFF behind it.

What changed

The staff surface now follows the same trust boundary as migrated firms: browser generated hooks call /api/internal/staff/*, those BFF routes validate generated request schemas, and staffGateway calls src/lib/gateway.ts before parsing upstream JSON with assertContract from @/gen/_assert.

List data-source swap

src/components/staff/staff-table.tsx still uses useDataTable. Its shallow nuqs URL state now derives the useAdminListStaff request key, with placeholderData: keepPreviousData; the old RSC listStaff fetch is gone.

Hydrated detail

The staff detail page prefetches useAdminGetStaff and useAdminGetStaffGrantDetail using server-side staffGateway query functions, then hydrates the generated hooks. The UI adapter combines generated staff-row and grant-detail payloads without inventing unavailable producer fields.

Server-confirmed writes

Suspend, reactivate, offboard, and revoke-all-grants use generated mutation hooks. There is no onMutate; success invalidates staff list, detail, and grant-detail queries after the server confirms.

Operational behavior

Filter, search, and pagination changes stay client-side after hydration. The list URL changes through useDataTable shallow nuqs state and feeds the generated staff hook query key; it does not call router.push or router.replace for list state. Search remains debounced and filter changes reset page to 1 through the existing table hook.

The old staff cache-tag read path is retired for migrated surfaces: src/lib/actions/staff.ts, STAFF_CACHE_TAGS, and src/lib/queries/staff-queries.ts are removed. The firms page and firm-detail CSM dropdowns still work by reading active staff through staffGateway.list server-side, with no broader firms refactor.

Story #1485 is still the shared list-chrome coordination point, so this story does not create or fork shared list chrome. It reuses the existing staff table, filters, header, and danger-zone components.

Verification proof

Focused proof already added: staff gateway/BFF tests cover generated schema validation, x-pramaan-* header forwarding, generated browser-client routing to /api/internal/staff, and server-confirmed lifecycle calls. The invalidation tests now assert staff-list collection invalidation does not invalidate staff-detail keys.

Observability stays on the pramaan-docs ADR-104 bridge: staff reads and writes flow through /api/internal/staff/* into src/lib/gateway.ts, and the BFF forwards upstream diagnostics headers with the shared internal helper. No new tracer, OTel wrapper, or middleware was added.

Before PR handoff, the local gate for this story is pnpm exec tsc --noEmit, pnpm run lint, pnpm run test, pnpm build, git diff --check, git status --short, and pnpm check:story-walkthrough.