Coverage for src / lexigram / contracts / admin / principal.py: 100%
14 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Admin principal bridge — the app <-> panel identity seam (spec D3)."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from typing import Protocol, runtime_checkable
9@dataclass(frozen=True)
10class AdminPrincipal:
11 """A panel-view of an application principal (Strapi-style dual identity).
13 ``hashed_password`` is an optional write-through used by the panel's
14 app-mode store adapter: non-empty values are forwarded on
15 ``update_principal`` so implementers can persist panel-side password
16 mutations (the app's hashing policy still owns verification).
17 """
19 user_id: str
20 name: str
21 email: str
22 roles: list[str] = field(default_factory=list)
23 permissions: list[str] = field(default_factory=list)
24 is_active: bool = True
25 hashed_password: str = ""
28@runtime_checkable
29class AdminPrincipalProviderProtocol(Protocol):
30 """Bridge implemented ONCE by the application; consumed by lexigram-admin.
32 Distinct from ``RoleResolverProtocol`` (lexigram-web PEP for app-user API
33 routes): this is the PANEL identity seam.
34 """
36 async def principal_for(self, user_id: str) -> AdminPrincipal | None: ...
38 async def list_principals(self) -> list[AdminPrincipal]: ...
40 async def create_principal(
41 self, name: str, email: str, password: str, roles: list[str] | None = None
42 ) -> AdminPrincipal: ...
44 async def update_principal(self, principal: AdminPrincipal) -> None: ...
46 async def delete_principal(self, user_id: str) -> None: ...
48 async def authenticate(
49 self, email: str, password: str
50 ) -> AdminPrincipal | None: ...
52 async def sync_roles(self, user_id: str, roles: list[str]) -> None: ...
54 async def ensure_schema(self) -> None: ...