Appearance
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
- User lifecycle — invite (find-or-grant), list, read, suspend, restore.
users/tenant_userstables and the tenant-membership trigger already exist; no controller or use-cases yet forPOST/GET /v1/users,GET /v1/users/{id},POST .../suspend,POST .../restore.suspendedanddeactivatedwere two undifferentiatedUserStatusvalues 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. - Groups — listing only, plus access propagation.
group/group_membershiptables don't exist yet (new). BuildGET /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'spermissionSetUserAssignmentrow(s) for whatever Permission Set(s) the group holds, tagged withsourceGroupId, 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/groupsbacks 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. - Permission Sets — list, read, edit only.
permission_setstable already exists. BuildGET /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. - Role permission matrix — new, dedicated
GET /v1/permission-sets/matrixendpoint: 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). - Permission Set User Assignments —
permission_set_user_assignmentstable already exists. BuildPOST/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). - Restriction-collection mechanism —
clientGroup/clientGroupMembershipentities and tables (new), arestrictionscolumn onpermission_set_user_assignments(new), evaluator support to resolve and cascadebuilding/clientGrouprestrictions 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 thebuildingentry fromrestrictionsentirely (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. - 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), requiredclientGrouprestriction on Admin-manažer assignments (400 ERR_USERS_ACCESS_CLIENT_GROUP_REQUIRED), and the restriction-ceiling check (⊆) on both dimensions — all onPOST /v1/usersandPOST /v1/permission-set-user-assignments(the role-level ceiling also applies toPATCH /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. - Self-service tenant/capability listing —
GET /v1/me/tenantsalready exists.GET /v1/me/capabilitiesdoesn't; an existingGET /v1/me/permissionscontroller covers different, narrower ground and needs reconciling, not reusing as-is. FE: consumed app-wide for capability gating, not a dedicated admin screen. - Permission diagnostics —
GET /v1/permission-model/diagnosedoesn't exist yet. FE: none yet — supplements a future "effective access" admin UI that isn't designed as part of this analysis. - 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: anypermissionSetUserAssignmentchange (direct or propagated, story 2/5); a Permission Set'srules/activechanging (story 3); an actor suspended/deleted (story 1); a tenant leavingactive. No FE — backs the evaluation path and story 8's capabilities endpoint.
Initial Permission Sets migration
- Seed the initial Permission Sets — 8 tenant + 3 platform, migrated from EM2's roles; replaces the currently-seeded
managerset, addsHost expert. No FE — data migration only, surfaced through the Roles list screen already covered by story 3. - Register new permission codes — the catalog-only codes with no enforcing endpoint yet. No FE.
- Split buildings/gauges field-level write codes — dedicated
parameters.write/invoicing.writecodes on the existingbuilding-parameters.controller.ts/gauge-formula.controller.tsendpoints. No FE — enforcement only. - 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)
- 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. - 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 unrestrictedorganization-visibility set: one group per active tenant plus itspermissionSetGroupAssignment, 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). - Permission Set Group Assignments —
permissionSetGroupAssignmenttable (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 intopermission_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).