feat(permissions): per-desk till guards, jobs in the role composer, permission-scoped live feed; role reassignment applies without re-login
Permissions matrix rethink (wiki/decisions/venue-modules.md §"Permissions matrix", open-questions #16) — the grid stays the enforcement layer: - Move 1: each desk's money is guarded by that desk's own permissions. Manifest tillGuards {read, shift, cash}: booth = shift:read / shift:create / drawer:create (unchanged), carwash = carwash:read / carwash:cash (new). Shift + drawer routes resolve the guard FROM THE TILL (requireTill); a wash role holds no shift:* and cannot touch the booth by construction. Replaces the session:read borrowing (tillPermission). /api/shift/tills lists the role's readable tills with canWork; history/movements without a till filter return the union of readable tills. - Move 2: jobs — manifest permission bundles (booth-operator, booth-supervisor, merchant, wash-operator) as one-click chips in Setup → Roles, with "mixes desks" and "partial job" lints (warnings, never blocks). - Move 3: the live WebSocket admits any watch permission (event/session/device read or a module's feedPermission) and filters every push per role; report:read is the reports screen only. Auth: the token's roleId is only a hint — refreshRole() after every jwtVerify resolves the user's CURRENT role (cached, bumped on role/user writes), so reassigning a user's role applies on the next request and a deleted user's session ends with 401. Tests: till guards + look-only role, feed rules, every job's permissions exist, role reassignment without re-login. 353/353. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
+11
-7
@@ -294,13 +294,17 @@ manifest (Car Wash → `carwash`; a future Bar → `bar`). Rules:
|
||||
- "Take money at the bay" requires the **carwash** shift, not the booth's; the wash desk
|
||||
carries its own shift control. The header button stays the booth's. The shift hub lists
|
||||
every open shift with a till badge; the drawer hub switches tills.
|
||||
- **Working a till needs that till's module permission** (added 2026-09-05 after the user
|
||||
found a wash user could open the *booth's* shift): the manifest names it
|
||||
(`tillPermission` — booth: `session:read`, carwash: `carwash:read`), `tillsFor()` in
|
||||
`@parking/shared` resolves a role's tills, the shift/drawer routes refuse the rest with
|
||||
`403 till_forbidden`, and `/api/shift/tills` + `current.tills` return only the role's
|
||||
tills — so the header button, the hub's start buttons and the drawer switch never offer a
|
||||
till the server would refuse. `shift:create` alone opens nothing.
|
||||
- **Each desk's money is guarded by that desk's own permissions** (2026-09-05, after the
|
||||
user found a wash user could open the *booth's* shift; design on [[venue-modules]]
|
||||
§"Permissions matrix"). The manifest declares `tillGuards { read, shift, cash }`: booth =
|
||||
`shift:read` / `shift:create` / `drawer:create` (unchanged), carwash = `carwash:read` /
|
||||
`carwash:cash` / `carwash:cash`. The shift + drawer routes resolve the guard FROM THE TILL
|
||||
(`requireTill(kind)`; `403 till_forbidden`), `/api/shift/tills` lists the tills a role may
|
||||
read with a `canWork` flag, and history / movements without a till filter return the
|
||||
union of the role's readable tills. So a wash role holds no `shift:*` at all and cannot
|
||||
touch the booth by construction; the header button, the hub's start buttons and the
|
||||
drawer switch never offer a till the server would refuse. (A first cut that borrowed
|
||||
`session:read` as "works the booth till" lived for a few hours and is gone.)
|
||||
- Not done: the per-shift *activity log* is still a time window over the whole chain (money
|
||||
figures are per till, the event list is not); bay slips print on the booth printer.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user