Firm detail grant create and revoke now use generated hooks
This is the final deferred firm-detail slice from Story-1483: create and single-revoke support grants moved off server actions while revoke-all and the support-access monitor stayed untouched.
What changed
FirmDetailView now creates support grants with useAdminCreateGrant. The modal still sends the fixed { expiresAt, reason } contract and disables submit while the generated mutation is pending.
/api/internal/firms/[firmId]/grants validates generated params and body schemas, calls firmDetailGateway.createGrantWithResponseHeaders, returns 201, and forwards x-pramaan-* headers.
/api/internal/firms/[firmId]/grants/[grantId] validates generated revoke schemas and delegates to firmDetailGateway.revokeGrantWithResponseHeaders. The UI sends the operator's reason in the DELETE body.
Both mutations avoid onMutate and setQueryData. Success invalidates the firm detail query and firms list so operators see confirmed server state after refetch.
What stayed out
Revoke-all remains on the existing generated hook path from Story-1399. The support-access monitor, shared revoke dialog copy, src/gen, OpenAPI snapshots, and producer service were not changed.
Verification proof
Unit coverage asserts generated create/revoke routing through the internal BFF, reason preservation on single revoke, runtime response parsing in firmDetailGateway, and no optimistic mutation handlers in the migrated firm-detail slice.
Observability rides the existing pramaan-docs ADR-104 bridge: internal routes forward upstream identity headers through the shared helper and all backend traffic still crosses src/lib/gateway.ts. No raw tracer APIs or new observability libraries were added.
Local gate: pnpm lint, pnpm exec tsc --noEmit, pnpm test, NEXT_PUBLIC_ENV=local pnpm build, pnpm build-storybook, and git diff --check.