feat(cases): enforce purpose-bound case governance
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-08-24 20:05:31 +02:00
parent 9435116299
commit 17415d8831
28 changed files with 786 additions and 142 deletions
+30
View File
@@ -17,15 +17,19 @@ CASE_ACCESS_SUBJECT_KINDS = frozenset(
{
"account",
"identity",
"membership",
"group",
"role",
"function",
"function_assignment",
"organization_unit",
"service_account",
"delegation",
}
)
CASE_ACCESS_PERMISSIONS = frozenset({"read", "update", "share", "admin"})
DEFAULT_CASE_ACCESS_PURPOSE = "cases.casework"
MAX_CASE_ACCESS_PURPOSES = 100
@dataclass(frozen=True, slots=True)
@@ -33,6 +37,7 @@ class CaseGrant:
subject_kind: str
subject_id: str
permissions: tuple[str, ...] = ("read",)
allowed_purposes: tuple[str, ...] = (DEFAULT_CASE_ACCESS_PURPOSE,)
def __post_init__(self) -> None:
if self.subject_kind not in CASE_ACCESS_SUBJECT_KINDS:
@@ -43,12 +48,29 @@ class CaseGrant:
if not normalized or set(normalized) - CASE_ACCESS_PERMISSIONS:
raise InstitutionalContextError("Case access permissions are invalid.")
object.__setattr__(self, "permissions", normalized)
purposes = tuple(
dict.fromkeys(
str(value).strip() for value in self.allowed_purposes if str(value).strip()
)
)
if not purposes:
raise InstitutionalContextError(
"Case access grants require at least one allowed purpose."
)
if len(purposes) > MAX_CASE_ACCESS_PURPOSES or any(
len(value) > 255 for value in purposes
):
raise InstitutionalContextError(
"Case access grant purposes exceed the supported bounds."
)
object.__setattr__(self, "allowed_purposes", purposes)
def to_dict(self) -> dict[str, object]:
return {
"subject_kind": self.subject_kind,
"subject_id": self.subject_id,
"permissions": list(self.permissions),
"allowed_purposes": list(self.allowed_purposes),
}
@classmethod
@@ -58,10 +80,18 @@ class CaseGrant:
raise InstitutionalContextError(
"Case access permissions must be a list."
)
raw_purposes = value.get(
"allowed_purposes", (DEFAULT_CASE_ACCESS_PURPOSE,)
)
if not isinstance(raw_purposes, (list, tuple)):
raise InstitutionalContextError(
"Case access purposes must be a list."
)
return cls(
subject_kind=_text(value, "subject_kind"),
subject_id=_text(value, "subject_id"),
permissions=tuple(str(item) for item in raw_permissions),
allowed_purposes=tuple(str(item) for item in raw_purposes),
)