Skip to content
Updated Sep 25, 2026 by Barča Dvořáková · Owner: analysisdraftbreakdownusers-access Edit on GitHub

Users & Access — Jira stories to create ​

Full breakdown for the Users & Access feature (permission-model.md, initial-permission-sets.md, api-a.md), scoped to MVP, for review before creating issues in Jira under the Roles & Permissions epic. Verified against the em3 code repository: which tables and endpoints already exist vs. need building. Each story is BE + FE together, one screen's worth of work per story, not split into separate backend/frontend tickets. Post-MVP items are listed at the end, not silently dropped.

Core mechanism ​

  1. User lifecycle — invite (find-or-grant), list, read, suspend, restore. users/tenant_users tables and the tenant-membership trigger already exist; no controller or use-cases yet for POST/GET /v1/users, GET /v1/users/{id}, POST .../suspend, POST .../restore. suspended and deactivated were two undifferentiated UserStatus values with no documented difference and no separate endpoint for the latter; dropped down to one state, suspended. FE: Invite screen (Porsenna user and tenant user in one UI, clearly distinguished), user administration list screen, Platform "Clients" multi-tenant listing screen.
  2. Groups — listing only, plus access propagation. group/group_membership tables don't exist yet (new). Build GET /v1/groups, POST/DELETE /v1/groups/{id}/members, DELETE /v1/group-memberships/{id} — and the synchronous propagation engine adding/removing a membership must trigger: materializing/removing the member's permissionSetUserAssignment row(s) for whatever Permission Set(s) the group holds, tagged with sourceGroupId, all-or-nothing in the same transaction, with a genuine direct grant always winning a collision on the same row (see Group access propagation). Groups are seeded with their Permission Set fixed (story 17), so this is the only place propagation fires in MVP. FE: no group-management screen exists in MVP — Invite is the only screen that grants access at all. GET /v1/groups backs the group picker on the Invite screen (adding someone to an existing seeded group); removing a member is a separate admin action, not a dedicated screen.
  3. Permission Sets — list, read, edit only. permission_sets table already exists. Build GET /v1/permission-sets, GET /v1/permission-sets/{id}, PATCH /v1/permission-sets/{id} (rule editing) — GET /v1/permission-sets/{id} backs the Role Detail screen (single set, full record). FE: Roles list screen, Role detail screen, Edit rules screen.
  4. Role permission matrix — new, dedicated GET /v1/permission-sets/matrix endpoint: every active Permission Set's rules plus the full capability-catalog code list, in one call (not one call per set — the matrix always needs everything at once). FE: Role permission matrix screen (new, read-only — one row per permission code, one column per Permission Set, allow/deny/— per cell; view-only, editing still goes through the Edit rules screen from story 3).
  5. Permission Set User Assignments — permission_set_user_assignments table already exists. Build POST/DELETE/GET /v1/permission-set-user-assignments. FE: the Permission Set assignment section of the user administration detail screen (also used by the Invite screen to assign the initial Permission Set).
  6. Restriction-collection mechanism — clientGroup/clientGroupMembership entities and tables (new), a restrictions column on permission_set_user_assignments (new), evaluator support to resolve and cascade building/clientGroup restrictions through linked resources. FE: the building/clientGroup restriction picker embedded in the Invite and assignment screens (stories 1 and 5) — no separate screen of its own. Must implement the decided "Select All" control for the building picker: a distinct control from checking every listed building, since "Select All" omits the building entry from restrictions entirely (staying correct as buildings are added later), while checking every current box would submit a list that silently excludes future buildings — load-bearing, not cosmetic.
  7. Restriction enforcement on grant — role-level ceiling (an actor can only grant a Permission Set at or below their own highest held level, 403 ERR_USERS_ACCESS_ROLE_LEVEL_TOO_HIGH), required clientGroup restriction on Admin-manažer assignments (400 ERR_USERS_ACCESS_CLIENT_GROUP_REQUIRED), and the restriction-ceiling check (⊆) on both dimensions — all on POST /v1/users and POST /v1/permission-set-user-assignments (the role-level ceiling also applies to PATCH /v1/permission-sets/{id}, already in story 3). FE: validation/error messaging on the same Invite and assignment screens (stories 1 and 5) — no separate screen.
  8. Self-service tenant/capability listing — GET /v1/me/tenants already exists. GET /v1/me/capabilities doesn't; an existing GET /v1/me/permissions controller covers different, narrower ground and needs reconciling, not reusing as-is. FE: consumed app-wide for capability gating, not a dedicated admin screen.
  9. Permission diagnostics — GET /v1/permission-model/diagnose doesn't exist yet. FE: none yet — supplements a future "effective access" admin UI that isn't designed as part of this analysis.
  10. Permission evaluation caching — decided (Redis, 15-minute TTL floor) but not built: one cache entry per (actorId, tenantId) plus a platform-wide entry, holding the actor's decided capability codes. Explicit invalidation on write, on four triggers: any permissionSetUserAssignment change (direct or propagated, story 2/5); a Permission Set's rules/active changing (story 3); an actor suspended/deleted (story 1); a tenant leaving active. No FE — backs the evaluation path and story 8's capabilities endpoint.

Initial Permission Sets migration ​

  1. Seed the initial Permission Sets — 8 tenant + 3 platform, migrated from EM2's roles; replaces the currently-seeded manager set, adds Host expert. No FE — data migration only, surfaced through the Roles list screen already covered by story 3.
  2. Register new permission codes — the catalog-only codes with no enforcing endpoint yet. No FE.
  3. Split buildings/gauges field-level write codes — dedicated parameters.write/invoicing.write codes on the existing building-parameters.controller.ts/gauge-formula.controller.ts endpoints. No FE — enforcement only.
  4. Add last-record readings/invoices codes — write-last/delete-last, enforced by a domain guard in the existing write/delete use-cases. No FE — enforcement only.

Not in MVP (future) ​

  1. Group management (create, delete, membership listing) — POST /v1/groups, DELETE /v1/groups/{id}, GET /v1/group-memberships. Groups are pre-seeded only in MVP; add/ remove (story 2) is in MVP, browsing membership isn't. FE: the group-management screen this unlocks — create/delete a group, and a membership list view.
  2. Permission Set management (create, delete) — POST /v1/permission-sets, DELETE /v1/permission-sets/{id}. Permission Sets are seeded and rule-editable only in MVP. Creation also triggers auto-provisioning for an unrestricted organization-visibility set: one group per active tenant plus its permissionSetGroupAssignment, in the same transaction, and again whenever a tenant later activates. Deletion is blocked while anyone still holds the set (Permission Set deletion guard). FE: create/delete actions on the Roles screens (story 3).
  3. Permission Set Group Assignments — permissionSetGroupAssignment table (new); grant/revoke/list a Permission Set to a group (POST /v1/groups/{id}/permission-set-assignments, DELETE /v1/permission-set-group-assignments/{id}, GET /v1/permission-set-group-assignments), plus its synchronous propagation into permission_set_user_assignments (same engine as story 2). A group's Permission Set is fixed at seed time in MVP. FE: the "Assign role" screen for a group (only offers "change role" once the group already has one).