diff --git a/src/govoplan_core/core/postbox.py b/src/govoplan_core/core/postbox.py index b306b4c..3b11e27 100644 --- a/src/govoplan_core/core/postbox.py +++ b/src/govoplan_core/core/postbox.py @@ -5,6 +5,8 @@ from dataclasses import dataclass, field from datetime import datetime from typing import Literal, Protocol, runtime_checkable +from govoplan_core.core.events import EventClassification + POSTBOX_MODULE_ID = "postbox" CAPABILITY_POSTBOX_DIRECTORY = "postbox.directory" @@ -14,9 +16,62 @@ CAPABILITY_POSTBOX_DELIVERY = "postbox.delivery" CAPABILITY_POSTBOX_EVIDENCE = "postbox.evidence" CAPABILITY_POSTBOX_ROUTING = "postbox.routing" -PostboxAction = Literal["discover", "read", "send", "acknowledge", "administer"] +PostboxAction = Literal[ + "discover", + "read", + "send", + "reply", + "acknowledge", + "administer", +] PostboxMessageListState = Literal["all", "unread", "read", "acknowledged"] PostboxMessageAvailability = Literal["available", "withdrawn", "expired"] +PostboxBindingStatus = Literal[ + "active", + "missing", + "not_effective", + "unit_missing", + "unit_inactive", + "unit_tenant_mismatch", + "function_missing", + "function_inactive", + "function_tenant_mismatch", + "function_reassigned", + "directory_unavailable", +] + +POSTBOX_CLASSIFICATIONS: tuple[EventClassification, ...] = ( + "public", + "internal", + "confidential", + "restricted", +) +_POSTBOX_CLASSIFICATION_RANK = { + value: rank for rank, value in enumerate(POSTBOX_CLASSIFICATIONS) +} + + +def normalize_postbox_classification( + value: str, +) -> EventClassification | None: + candidate = value.strip().casefold() + if candidate not in _POSTBOX_CLASSIFICATION_RANK: + return None + return candidate # type: ignore[return-value] + + +def postbox_classification_allows( + ceiling: str, + content: str, +) -> bool: + normalized_ceiling = normalize_postbox_classification(ceiling) + normalized_content = normalize_postbox_classification(content) + if normalized_ceiling is None or normalized_content is None: + return False + return ( + _POSTBOX_CLASSIFICATION_RANK[normalized_content] + <= _POSTBOX_CLASSIFICATION_RANK[normalized_ceiling] + ) @dataclass(frozen=True, slots=True) @@ -26,6 +81,9 @@ class PostboxActorRef: selected_assignment_id: str | None = None acting_for_account_id: str | None = None authorized_actions: frozenset[PostboxAction] = frozenset() + authorized_classifications: frozenset[EventClassification] = frozenset( + {"public", "internal"} + ) @dataclass(frozen=True, slots=True) @@ -91,6 +149,9 @@ class PostboxAccessDecisionRef: selected_assignment_id: str | None = None holder_count: int = 0 vacant: bool = True + classification: str = "internal" + classification_allowed: bool = True + binding_status: PostboxBindingStatus = "active" @dataclass(frozen=True, slots=True)