feat(roles): roles remember the jobs they follow (re-appliable), every role edit is signed
Closes the permissions-matrix loose ends (venue-modules.md §Permissions matrix): - `role_jobs` (migration 0029): a role stores the manifest jobs it was composed from (chips on at save + any bundle fully present). `jobById` / `jobsBehind` in @parking/shared surface a followed job whose bundle grew past the role in a later release; the roles list shows a "behind <job>" badge with a one-click "Update to job" (the union, nothing removed) and the editor lints it. Never a runtime union: the grid stays the explicit enforcement layer and an update never widens a role without a click. - Every role create/update/delete appends a `config_change` (`role.<id>`, prev/value = name + sorted permissions + jobs, operator); a no-op resave signs nothing. roleRoutes now takes the ledger. - booth-supervisor already carries subscription:*; the stale open note is closed. Tests: routes/roles.test.ts. Wiki: venue-modules status, local-jwt-auth, log. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
@@ -439,9 +439,23 @@ resources — the till already IS that copy. Role *templates stored in the DB*
|
||||
(they change with the module), roles are data; keep that line.
|
||||
|
||||
**Status.** Moves 1, 2 and 3 built 2026-09-05 (see the Tills as-built below and [[shift]]
|
||||
§Tills). Open: whether `booth-supervisor` should carry `subscription:*` by default; whether a
|
||||
job should be *re-applicable* after a module update (today a chip only adds/removes the bundle
|
||||
as it is now); an audit `config_change` on role edits.
|
||||
§Tills). The three loose ends closed 2026-09-06:
|
||||
- `booth-supervisor` DOES carry `subscription:read/create/update` (plus `tariff:read`,
|
||||
`validation:read`) — it already did; the note was stale. Decided: a supervisor sells and
|
||||
edits subscriptions by default.
|
||||
- **Jobs are remembered and re-appliable.** A role stores the jobs it follows (`role_jobs`:
|
||||
the chips on at save, plus any bundle fully present). `jobsBehind(jobs, has)` in
|
||||
`@parking/shared` lists a followed job whose bundle has GROWN past the role (a newer
|
||||
release added a permission); the roles list shows a "behind <job>" badge with a one-click
|
||||
"Update to job" (the union; nothing removed), the editor lints it. Deliberately NOT a
|
||||
runtime union: what a role may do is always the explicit grid, and a software update never
|
||||
changes it without an admin's click — see the threat model. The first failure of this kind
|
||||
was the wash operator's empty price list (the settings read needed `site:read`; now
|
||||
`carwash:read` OR `site:read`, `requireAnyPermission`).
|
||||
- **Role edits are signed.** Create/update/delete each append one `config_change`
|
||||
(`setting: role.<id>`, `value`/`prev` = name + sorted permissions + jobs, `operator`); a
|
||||
no-op resave signs nothing. A role edit is a privilege change and was the one setting an
|
||||
admin could alter without a trace.
|
||||
|
||||
## Tills: shifts per money-taking module — BUILT (raised + built 2026-09-05)
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ Authentication and authorization, kept **fully local** — a direct consequence
|
||||
`bumpPermsCache()`, which user update/delete now call), so REASSIGNING a user's role — or deleting
|
||||
the user (→ 401 on their next request) — applies immediately too. Found when a user moved to a new
|
||||
wash role kept the old role's rights until logout.
|
||||
**Role edits are signed (2026-09-06):** every create/update/delete of a role appends a
|
||||
`config_change` (`role.<id>`, before/after shape, operator) to the ledger, and a role remembers
|
||||
the manifest JOBS it was composed from (`role_jobs`) so a job that grows in a later release can
|
||||
be re-applied with one click rather than expanding silently — [[venue-modules]] §Permissions
|
||||
matrix.
|
||||
- **Protected built-in `admin` role** (`id='admin'`, `builtin=1`): non-editable, non-deletable, and
|
||||
always resolves to the FULL permission set in code. The app refuses to delete or downgrade the
|
||||
**last user holding admin** — administration can never be locked out of the appliance.
|
||||
|
||||
+10
@@ -3070,3 +3070,13 @@ category/service pickers stayed empty. Cause: `GET /api/carwash/settings` was gu
|
||||
`site:read` only — the price list is Setup's data AND the desk's working data. Fixed with a
|
||||
new `requireAnyPermission(...)` guard (auth.ts): the read opens to `carwash:read` OR
|
||||
`site:read`; the write stays `site:update`. Regression test in carwash.test.ts.
|
||||
|
||||
## [2026-09-06] ingest | Permissions matrix loose ends: jobs remembered + re-appliable, role edits signed
|
||||
Roles now store the jobs they follow (`role_jobs`, migration 0029); `jobsBehind()` in
|
||||
`@parking/shared` surfaces a followed job whose bundle grew past the role; the roles list shows a
|
||||
"behind <job>" badge + "Update to job" (union, nothing removed) and the editor lints it. Not a
|
||||
runtime union by decision (the grid stays explicit; an update never widens a role without a
|
||||
click). Every role create/update/delete appends a `config_change` (`role.<id>`, prev/value =
|
||||
name + permissions + jobs, operator); a no-op resave signs nothing. The stale "should
|
||||
booth-supervisor carry subscription:*" note is closed — it already does. Tests: routes/roles.test.ts.
|
||||
Updated [[venue-modules]] §Permissions matrix status, [[local-jwt-auth]].
|
||||
|
||||
Reference in New Issue
Block a user