The Permissions page only offered Delete, so changing a role or scope
meant removing the grant and re-adding it from memory.
A permission's id is derived from (subjectType, subject, scope, domain),
so changing any of those is a different record rather than an update. The
new PUT creates the new grant and removes the superseded one in that
order, so an edit can never leave the old grant behind still conferring
access.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Rename Grant -> Permission end-to-end (model, routes, view, frontend,
bootstrap) and add an idempotent redis migration for existing records.
- utils/roles.js: glob domain matching (* = one label, ** = any depth) against
the full host; authz passes the full hostname.
- Local groups: LocalGroup model + admin routes/UI; membership merged into
Permission.effectiveFor so app groups behave like SSO groups.
- Subject autocomplete via GET /api/permission/subjects (users + derived groups).
- User profile page (/profile) and username in the navbar; /api/user/me now
returns merged/local/external groups.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>