Revoke-all grants now require a reason and wait for the server
This story wires the remaining revoke-all placeholders without changing grant creation, single-grant revoke, or the support-access monitor. The controls now collect an audit reason, show the active-grant count, and update only after the generated mutation confirms success.
What changed
src/components/firms/firm-danger-zone.tsx replaces the inert revoke-all placeholder with a destructive confirmation. The dialog shows the firm active-grant count and requires a 10 to 1024 character reason before calling useAdminRevokeAllFirmGrants.
src/components/staff/staff-actions-menu.tsx now enables Revoke all active grants ({count}). In the detail view it opens the already wired StaffDangerZone revoke-all dialog, so staff has one mutation/form implementation.
/api/internal/firms/[firmId]/grants/revoke-all validates generated path and body schemas, delegates through firmDetailGateway.revokeAllGrantsWithResponseHeaders, parses GrantBulkRevokeResponse, and forwards x-pramaan-* headers.
Operational behavior
Both revoke-all paths are server-confirmed. There is no optimistic cache mutation; success invalidates generated firm or staff query keys after the server returns. Field-level backend errors for reason stay next to the textarea, and form-level failures stay inside the confirmation dialog.
The generated firm hook still reflects the producer route /admin/firms/{firm_id}/support_grants/revoke-all. The browser adapter maps that generated client call to the requested internal BFF route, keeping generated files untouched and avoiding codegen drift.
Verification proof
Unit coverage now asserts both revoke-all flows show the count, reject empty and too-short reasons before fetch, call the expected internal route only after valid submit, and keep typed server errors in the dialog.
BFF and gateway coverage asserts generated request validation, x-pramaan-* header forwarding, generated browser-client routing, and upstream response contract parsing for firm revoke-all.
Local gate for this story: pnpm run lint, pnpm exec tsc --noEmit, pnpm test, NEXT_PUBLIC_ENV=local pnpm build, pnpm build-storybook, pnpm run test:storybook, and git diff --check.