Add the get_permissions plugin hook (contract 0.10.0)
All checks were successful
CI / backend (push) Successful in 1m20s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s

Plugins declare their own RBAC permissions instead of core accumulating
them: 36 permissions moved out of the core catalog into the 9 owning
plugins (core keeps the 19 its own blueprints enforce). The catalog is
resolved dynamically (core + enabled plugins) and feeds the roles grid,
the token scope picker and ceiling, and flask seed permissions;
installing or enabling a plugin seeds its permissions automatically. A
disabled plugin drops out of the assignable catalog while existing role
links keep working. New plugins - bundled or external - now bring their
permissions with zero core edits.

781 tests pass; live-verified with a machines.edit-scoped token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 09:29:55 -04:00
parent 12175169e4
commit 7dfbe7bf8a
22 changed files with 439 additions and 90 deletions

View File

@@ -100,9 +100,12 @@ class ApiToken(BaseModel):
@staticmethod
def unknown_scope_names(names) -> list:
"""Return the subset of names that are not in the permission catalog."""
from shopdb.core.models.user import Permission
known = {name for name, _desc, _cat in Permission.PERMISSIONS}
"""Return the subset of names that are not in the permission catalog.
The catalog is core plus every ENABLED plugin's permissions, so a scope
naming a disabled plugin's permission is treated as unknown."""
from shopdb.core.models.user import full_permission_catalog
known = {name for name, _desc, _cat in full_permission_catalog()}
return [n for n in names if n not in known]
def to_dict(self, include_owner: bool = False) -> dict: