feat(user-mgmt): user lifecycle (invite + deactivate) + activity, in Settings (#995)#996
Conversation
Public OSS-bundle frontend for the private user_management module (Abilityai/trinity-enterprise#2). Gated entirely server-side by the `user_management` entitlement — hidden in OSS-only builds and bounced by the route guard on direct URL visits. - views/enterprise/UserManagement.vue: org list + create, membership add/remove, seat counts. Light + dark. No algorithmic IP (CRUD glue over the private /api/enterprise/user-management/* endpoints). - stores/orgManagement.js: domain store, calls via shared axios + auth header (Invariants #6/#7). - router: /enterprise/user-management gated meta.requiresEntitlement: 'user_management' (mirrors the audit route). - views/enterprise/Index.vue: add the catalogue card (available). No public backend/schema/model changes — the entire data model + logic lives in the private submodule per the enterprise open-core split. The submodule pointer is intentionally NOT bumped here; it advances after trinity-enterprise#2 merges. Verified: all four files compile; the only build blocker is the pre-existing unrelated `mermaid` import in AgentWorkspace.vue (stale local node_modules; resolved by CI npm ci). Related to #995, #847 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ement (#995) Integrates the enterprise activity view INTO the existing OSS user management table (not a separate page). When user_management is entitled, each user row gets a "View activity" action opening a drawer with that user's audit summary + timeline, fetched from the private /api/enterprise/user-management/users/{id}/activity endpoint. - Gated entirely by enterpriseStore.isEntitled('user_management') — column + drawer hidden in OSS-only builds. - No change to the existing role-CRUD behaviour; purely additive column. - loadFeatureFlags() in onMounted (cached/no-op when NavBar already ran). Related to #995 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#995) Pivots #995 from Organizations to the real net-new gap — user onboarding/offboarding — integrated into the existing Settings → User Management table (not a separate page). OSS primitive (edition-agnostic, small): - users.suspended_at column + migration + surfaced in get_user/list_users. - get_current_user rejects suspended users (both JWT + MCP-key paths), so setting the column blocks new logins AND invalidates live tokens on the next request. - /api/users exposes suspended_at (read-only) so the gated UI can render Deactivate/Reactivate. Enterprise UI (gated by user_management entitlement, hidden in OSS): - Settings → User Management gains an "Invite user" form, per-row Deactivate/Reactivate (not for self or the built-in admin), and the per-user Activity drawer. All call the private /api/enterprise/user-management/* endpoints. Removed: the separate /enterprise/user-management Orgs page, its route, store, and Index card (orgs dropped — single-tenant). Index card now points at Settings. No change to the existing OSS role-CRUD behaviour. Verified live: /api/users carries suspended_at; suspend/reactivate/invite/activity all work; OSS-only builds hide every enterprise control. Related to #995, #847 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ble (#995) The users table wrapper was overflow-hidden; the extra entitlement-gated Management column pushed total width past the card and clipped the right-side action buttons. Switch to overflow-x-auto so the wider table scrolls within the card instead of clipping. Related to #995 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…995) Replace the overflow-x-auto stopgap with an actual fit: trim cell padding px-6→px-4 across the table and let the Management actions wrap within their column (flex-wrap, text-xs, no whitespace-nowrap). The 5-column table now fits the max-w-4xl settings card without clipping or a horizontal scrollbar. Related to #995 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #995 users.suspended_at primitive added the column to _USER_COLUMNS, so get_user_by_*() now SELECTs it. test_schedule_soft_delete builds its own users table with a hardcoded DDL that lacked the column, causing "no such column: suspended_at" (4 regression-diff failures). Mirror the schema change in the test DDL. Related to #995 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#995/#997) - users.suspended_at deactivation primitive (OSS column + enforcement; enterprise-only setter) on the users table + a callout. - Invariant #3 extended: enterprise migrates enterprise_* tables via a separate runner tracked in enterprise_schema_migrations (one file per migration; never ALTERs OSS tables; runs after OSS init). - feature-flags doc gains enterprise_features. - New "Enterprise Modules (#847 seam)" section: audit / user_management / siem entitlements, surfaces, and the gating model. Related to #995, #997, #847 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔧 Make-it-functional notes & merge orderThis PR is the OSS seam only (gated UI + the Cross-PR dependency: the Settings UI calls Deactivation needs both halves: this PR adds the OSS Merge order:
Deploy prereq: the dev/prod host must be able to fetch the private submodule (the Docs updated in |
/review — pre-landing reviewScope: CLEAN. Committed contents are exactly the 9 intended files (verified via Critical: none
Informational
Verdict: 0 critical — clear to merge in order (enterprise#2 → bump submodule pointer → this). |
…boot (#995/#997) main.py wrapped register_enterprise(app) in `except ImportError` only — so a bug in enterprise registration (schema init, migration, router mount, pusher start) would propagate and crash backend startup on an enterprise build. Add a broad `except Exception` that logs loudly + a traceback and continues in OSS-only mode. Modules registered before the failure stay active; the rest are simply absent from feature-flags. The core platform always boots. OSS-only builds are unaffected (still the ImportError path). Verified: happy path still boots (health 200) and registers ['audit','siem']. Related to #995, #997, #847 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Resolve by running |
vybe
left a comment
There was a problem hiding this comment.
Validated: suspended-user rejection correct on BOTH auth paths (JWT + MCP key fall through to 401, no None leak), main.py broad-except scoped only to enterprise registration, migration idempotent via _safe_add_column, schema+migration consistent, architecture.md thoroughly updated, UI fully entitlement-gated and OSS-inert. CI 15/15 green. APPROVE.
Resolve MIGRATIONS conflict in src/backend/db/migrations.py: #902 (_migrate_agent_loops_tables) and #996 (_migrate_users_suspended_at) both appended near the list tail. Kept both functions and both name-keyed MIGRATIONS entries — they touch disjoint tables and the runner is order-independent. All other files auto-merged. Verified: 66 migrations (no dup names), 34 migration smoke tests (incl. concurrent race) + 16 loop + soft-delete tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Enterprise user lifecycle (#995) — admin invite (sends a real invite email) + deactivate/reactivate, plus a per-user activity view — integrated into the existing Settings → User Management table. Pivoted from the earlier Organizations approach (dropped — single-tenant). Backend logic lives in the private
trinity-enterprisesubmodule (PR #2); this PR is the OSS seam.OSS primitive (edition-agnostic)
users.suspended_atcolumn + migration; surfaced inget_user/list_users+/api/users.get_current_userrejects suspended users on both the JWT and MCP-key paths → setting the column blocks new logins and invalidates live tokens on the next request (immediate sign-out). Reversible.Enterprise UI (gated by
user_management, hidden in OSS)admin), and a per-user Activity drawer./api/enterprise/user-management/*endpoints.max-w-4xlcard (trimmed padding + wrapping actions) — no clipping, no horizontal scrollbar.Removed
/enterprise/user-managementOrgs page + route + store + Index card. Index card now links to Settings.Behaviour notes
Verification (live + unit)
/api/usersexposessuspended_at; suspend writes it + reactivate clears; guards (self/admin → 400, unknown → 404); invite → 201 and the invite email fired (console-rendered: To/Subject/body,email_sent: true); dup → 409; revoke → 204; activity → 200.Submodule pointer not bumped here; advances after trinity-enterprise#2 merges. Related to #995, #847.
🤖 Generated with Claude Code