ADR-022 — UsersPage V2 spec
1. Context
Current UsersPage table has 8 columns: Name, Department, Platforms, Interactions, PII Detections, Avg Risk, Top Platform, Last Seen. Gaps:
- No view of which surfaces (extension / agent / proxy) each user is enrolled into — operators can't tell "who's actually being protected" vs "who's only registered".
- No Role / Status visible — admin actions require external query.
- No MFA status — security hygiene blind spot.
- "PII Detections" label conflicts with ADR-015 lexicon refactor (PII → Risk Signals).
- No bulk actions — supporting onboarding cohorts is manual.
- No alternative views (department pivot, onboarding funnel, risk anomaly) — operators can't answer "which department lags adoption" without exporting CSV.
This ADR fixes the table spec officially so issues can reference it and the implementation lands consistent across migrations.
2. Decision summary
- 16 columns total in the column model (default visible: 8; rest behind column-visibility toggle from TanStack Table per ADR-021).
- Entitlements column = three chips per user (Ext / Agent / Proxy), color-coded by enrollment state.
- Lexicon alignment with ADR-015: "PII Detections" → "Risk events". Header keys + i18n strings updated.
- Alternative views introduced as page-level tabs alongside the main table:
Table(default — V2 spec below)Department pivot— adoption% per deptOnboarding funnel— invited → joined → ext-installed → first-detection → habitual-useRisk anomaly— top users deviating from peer baseline
- All views share the same DataTable component for consistency; alternative views layer aggregations on top.
3. Column model
| Pos | Col | Type | Default visible | Tier | Source / Notes |
|---|---|---|---|---|---|
| 1 | Name + avatar + email | identity, sticky-left | yes | all | existing |
| 2 | Department | chip | yes | all | existing (#39 backfill in roadmap if needed) |
| 3 | Role | chip (Admin/Analyst/Viewer/Tenant Admin/Noxys Staff) | yes | all | NEW — needs users.role confirmed in backend |
| 4 | Status | dot+text (Active / Invited / Suspended / Deactivated) | yes | all | NEW — needs users.status |
| 5 | Entitlements | 3 chips (Ext, Agent, Proxy) | yes | all | NEW — sources from per-surface enrollment tables |
| 6 | MFA | icon (✓ / ✗ / pending) | yes | all | NEW — users.mfa_enabled boolean + method enum |
| 7 | Risk events 30d | number + delta arrow | yes | Pro+ | replaces "PII Detections"; aggregate over 30d |
| 8 | Avg risk score | bar 0-100 | no | Pro+ | existing — moved to optional default |
| 9 | Top services (3) | chip stack | no | all | existing |
| 10 | AI cost 30d | currency | no | Pro+ | NEW — LiteLLM rollup per user (USERS-7) |
| 11 | Last seen | relative time | yes | all | existing — backfill via roadmap #26 |
| 12 | Last enrolled (Ext) | relative | no | Pro+ | NEW — extension first-enroll timestamp |
| 13 | Coaching status | icon (none / pending / escalated) | no | Pro+ | NEW — pending review queue |
| 14 | Region pin | flag emoji + code | no | Enterprise | per ADR-019 (only when tenant region_pin is multi or per-user override exists) |
| 15 | Manager | link to user | no | all | V2 — depends on org-chart import (CSV upload or directory sync) |
| 16 | Actions | menu, sticky-right | yes | all | invite/suspend/reset MFA/edit role/delete + bulk-action target |
Default visible (8 cols): 1, 2, 3, 4, 5, 6, 7, 11, 16. Other 7 cols user-toggleable; preference persisted per-user via localStorage (key noxys.users.cols.<userId>).
4. Entitlements chip details
Each user row's "Entitlements" col renders three mini chips horizontally:
- 🌐
Ext— Extension enrolled - 💻
Agent— Endpoint agent enrolled - 🛡️
Proxy— Proxy capturing this user's traffic
States (each chip):
- Active (filled brand-cyan): enrolled + heartbeat received within 24h
- Stale (filled amber): enrolled but no heartbeat >24h <7d
- Inactive/Disconnected (filled red): enrolled but no heartbeat >7d
- Not enrolled (gray outline only)
Hover → tooltip with: enrolled date, version, last heartbeat, channel (stable/staging per ADR-013).
Click chip → context modal:
- If
Not enrolled: action "Send enrollment instructions" (email user + show install link) - If
Active: "Force re-enroll" / "Unenroll" / "View device details"
5. Bulk actions (toolbar above table)
When 1+ rows selected:
- Suspend / Reactivate
- Reset MFA
- Force re-enroll surface (Ext / Agent / Proxy)
- Assign role
- Set department
- Add tags
- Export selection (CSV / JSON)
- Delete (with confirm)
Bulk action availability gated by RBAC: Tenant Admin can do user-mgmt actions; Noxys Staff can do all + cross-tenant actions.
6. Filter toolbar
- Search (name + email + department)
- Department (multi-select)
- Role (multi-select)
- Status (multi-select)
- Entitlement state — dropdown: "missing extension" / "missing agent" / "missing proxy" / "stale heartbeats" / "all enrolled"
- Risk events 30d (slider min)
- Region pin (Enterprise only)
- Tags
7. Alternative views (tabs at top of page)
7.1 Department pivot (Department × Adoption)
Aggregation table: | Department | Headcount | Ext% | Agent% | Proxy% | Risk events 30d | Top services |
Click row → drill to filtered Users table for that department.
7.2 Onboarding funnel
Stage breakdown chart + table:
- Invited (sent email)
- Joined (accepted invite)
- Ext installed (first heartbeat)
- First detection (any risk event)
- Habitual user (≥10 events past 30d)
Per-stage count + drop-off %. Alert if stage 2→3 drop >40% (likely install friction).
7.3 Risk anomaly board
Top N users with risk score significantly deviating from same-department peers (z-score > 2). Surfaces "outlier behavior" — could be insider threat OR new high-value user, depends on context. Pro+ feature.
8. Backend gaps to fill
users.rolecolumn oruser_rolestable (confirm exists; if not, add).users.statusenum (Active / Invited / Suspended / Deactivated).users.mfa_enabled+mfa_method(TOTP / WebAuthn / sms).- Per-user enrollment rollup endpoint:
GET /api/v1/users/:id/entitlements→{extension: {state, version, last_heartbeat}, agent: {...}, proxy: {...}}- List endpoint augmented with rollup so table doesn't N+1.
- AI cost per user aggregate (LiteLLM telemetry → user_id rollup, materialized view 1h refresh).
- Coaching status table (
coaching_reviews) — separate spec, optional. - Manager FK (
users.manager_user_idnullable) + UI for admin to set / CSV import.
9. Sprint plan — 7 sprints, 9 days
| ID | Title | Repo | Window | Effort | Priority |
|---|---|---|---|---|---|
| USERS-1 | Backend — per-user entitlements rollup endpoint (Ext / Agent / Proxy enrollment states + heartbeat) | noxys-api | W23-24 | 1.5d | P1 |
| USERS-2 | UsersPage V2 — TanStack Table migration + new cols (Role, Status, Entitlements, MFA, Region) | noxys-console | W25-26 | 2d | P1 |
| USERS-3 | UsersPage V2 — col visibility toggle, bulk actions, advanced filters | noxys-console | W25-26 | 1.5d | P2 |
| USERS-4 | UsersPage V2 — Department pivot view + Onboarding funnel | noxys-console | W27-28 | 1.5d | P2 |
| USERS-5 | UsersPage V2 — Risk anomaly board (peer comparison via z-score) | noxys-console | W27-28 | 1d | P3 |
| USERS-6 | UsersPage — Lexicon refactor: "PII Detections" → "Risk events" (aligns EXT-UX-1) | noxys-console | W23-24 | 0.25d | P1 |
| USERS-7 | Backend — AI cost per user aggregate (LiteLLM telemetry rollup) | noxys-api | W27-28 | 1.5d | P2 |
10. Dependencies
- USERS-2 depends on USERS-1 (entitlements endpoint).
- USERS-2 depends on TANT-1 (DataTable wrapper, ADR-021).
- USERS-6 coordinates with EXT-UX-1 (lexicon refactor).
- USERS-7 depends on ADR-012 LiteLLM landed (PROXY-LITELLM-1/2).
- Roadmap #26 (last_seen backfill) precondition for USERS-2 col accuracy.
11. Open questions
- Coaching status table: separate ADR or absorb in this one? Recommend defer — single coaching pipeline ADR later when product is shaped.
- Manager / org-chart: directory sync (Entra/Google Workspace) preferred over manual CSV. Defer to USERS-V3 (post-W30).
- Tags model: free-form per-tenant strings or controlled vocabulary? Recommend free-form V1, controlled vocab V2 if tenants ask.
- localStorage col preferences: persist per-user or per-device? Recommend per-userId for cross-device consistency, with localStorage as fallback offline.
12. Non-goals
- Replacing the user invite flow (separate page).
- Cross-tenant user merge (rare, sales-touch only).
- User analytics dashboards (separate from UsersPage).
- Per-user policy editor (lives in PoliciesPage).
13. Refs
- ADR-012 LiteLLM bundled in proxy (cost per user telemetry source)
- ADR-013 Extension versioning (heartbeat data drives entitlement chip state)
- ADR-015 Extension UX modes (lexicon binding)
- ADR-019 Storage tiers (region pin col)
- ADR-021 TanStack Query + Table adoption (DataTable wrapper consumed)
- ADR-024 Authentication & MFA strategy (TOTP/WebAuthn/backup codes encrypted via KMS pattern; tenant token reveal + tenant-wide MFA enforcement)
- Roadmap #26 (last_seen backfill precondition)
- Roadmap #39 (inventory parallel pattern)
- Existing:
src/pages/UsersPage.tsx(27KB, baseline for V2 refactor) - Future ADR-027 CRM choice (renumbered through ADR-019..ADR-026 — ADR-026 = API/OpenAPI policy)
- ADR-026 API completeness & OpenAPI policy (every endpoint defined in this ADR must have OpenAPI 3.1 spec entry; backfill obligation in §9)