Owner-only endpoint to toggle can_cover_l1 on an engineer user. 422 if target
role is not engineer (owners/super_admins already see L1 surface; viewers/
l1_techs don't need this flag). 404 for cross-account targets.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Returns {engineer: SeatCheckResult, l1_tech: SeatCheckResult} for the
authenticated engineer's account. Powers the SeatCounterWidget UI in the
admin/users + account/users surfaces. Engineer+ access only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- oauth.py: use status.HTTP_402_PAYMENT_REQUIRED constant (was raw 402)
- accounts.py bulk-invite: catch HTTPException separately to preserve
structured detail dict in failed-row error (was stringified repr,
unparseable by clients)
- Add bulk-invite per-row 402 test verifying structured error preserved
T8 code review identified these as Important issues. Functional change is
the bulk-invite fix; clients can now parse seat-limit errors from bulk
responses. 13/13 seat-enforcement tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
For engineer + l1_tech roles, check_seat_available is called at each
mutation point. Returns 402 Payment Required with structured detail
{code: 'seat_limit_exceeded', role, current, limit, upgrade_url} when
seats are full. Grandfathering: existing over-seated accounts keep
existing users; only new mutations are blocked.
Also updates AccountInviteCreate and AccountRoleUpdate schemas to
accept l1_tech as a valid role value.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>