7Runtime role guard for registry operations.
11Map registry operation namespaces to the actor slot kinds that may author
16- Keep the allow table in one auditable location.
17- Raise typed role errors before any operation-log mutation starts.
18- Provide an explicit system-actor bypass for enumerated internal rows.
30- A role mismatch is not an auth failure and does not write ``auth_events``.
31- Unknown test/private operation namespaces remain compatibility-allowed.
35- oc_chat.registry.errors (ActorRoleMismatchError raised on a role mismatch)
36- oc_diagnostics (structured debug logging via _dbg)
38from __future__
import annotations
40from oc_diagnostics
import _dbg
45_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
49ALL_SLOT_KINDS = frozenset({
"operator",
"orchestrator",
"worker"})
50SYSTEM_OPERATION_PREFIXES = (
"migrate.",
"heartbeat.")
51SYSTEM_OPERATION_TYPES = frozenset({
"doctor.diagnose"})
53ROLE_ALLOWSETS: dict[str, frozenset[SlotKind]] = {
54 "auth.register_key": frozenset({
"operator"}),
55 "auth.rotate_start": frozenset({
"operator"}),
56 "auth.rotate_finalize": frozenset({
"operator"}),
57 "auth.reset": frozenset({
"operator"}),
58 "audit.override": frozenset({
"operator",
"orchestrator"}),
59 "audit.verify": frozenset({
"operator",
"orchestrator"}),
60 "db.archive_log.start": frozenset({
"operator"}),
61 "db.archive_log.complete": frozenset({
"operator"}),
62 "doctor.repair": frozenset({
"operator"}),
63 "handover.accept": frozenset({
"orchestrator",
"operator"}),
64 "handover.initiate": frozenset({
"orchestrator",
"operator"}),
65 "handover.timeout": frozenset({
"operator"}),
66 "role.handover.complete": frozenset({
"orchestrator",
"operator"}),
67 "role.handover.start": frozenset({
"orchestrator",
"operator"}),
70PREFIX_ALLOWSETS: tuple[tuple[str, frozenset[SlotKind]], ...] = (
71 (
"plan.", frozenset({
"operator",
"orchestrator",
"worker"})),
72 (
"queue.", frozenset({
"operator",
"orchestrator"})),
73 (
"roadmap_", frozenset({
"operator",
"orchestrator"})),
74 (
"roadmap.", frozenset({
"operator",
"orchestrator"})),
75 (
"round.", frozenset({
"orchestrator"})),
76 (
"migrate.", frozenset({
"operator"})),
77 (
"db.", frozenset({
"operator"})),
78 (
"doctor.", frozenset({
"operator"})),
79 (
"_test.", ALL_SLOT_KINDS),
84 """Table-driven operation role checker."""
92 system_actor: bool =
False,
94 """Raise if ``actor_slot_kind`` cannot author ``operation_type``."""
99 if actor_slot_kind
not in allowed:
101 operation_type=operation_type,
102 actor_slot=actor_slot,
103 slot_kind=actor_slot_kind,
104 allowed_slot_kinds=tuple(sorted(allowed)),
108 """Return allowed slot kinds for an operation type."""
110 exact = ROLE_ALLOWSETS.get(operation_type)
111 if exact
is not None:
113 for prefix, allowed
in PREFIX_ALLOWSETS:
114 if operation_type.startswith(prefix):
116 return ALL_SLOT_KINDS
120 return operation_type
in SYSTEM_OPERATION_TYPES
or operation_type.startswith(
121 SYSTEM_OPERATION_PREFIXES
None check(self, str operation_type, str actor_slot_kind, *, str actor_slot, bool system_actor=False)
frozenset[SlotKind] allowed_slot_kinds(self, str operation_type)
bool _is_system_operation(str operation_type)