259 lines
10 KiB
Python
259 lines
10 KiB
Python
"""Declarative, non-executing operation registry and timeout safety rules."""
|
|
from __future__ import annotations
|
|
|
|
from copy import deepcopy
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone
|
|
import math
|
|
import re
|
|
from typing import Any, Iterable, Mapping, Sequence
|
|
|
|
|
|
EFFECT_CLASSES = frozenset(
|
|
{"READ_ONLY", "IDEMPOTENT", "CONDITIONAL", "RECONCILABLE", "IRREVERSIBLE"}
|
|
)
|
|
_FORBIDDEN_EXECUTION_FIELDS = frozenset({"command", "shell", "argv", "executable", "script"})
|
|
_HANDLER_REF = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{0,126}$")
|
|
|
|
|
|
def _invalid(code: str, field: str = "") -> ValueError:
|
|
return ValueError(f"{code}:{field}" if field else code)
|
|
|
|
|
|
def _text(value: object, field: str) -> str:
|
|
if not isinstance(value, str) or not value.strip():
|
|
raise _invalid("field_invalid", field)
|
|
return value.strip()
|
|
|
|
|
|
def _policy(value: object, field: str, allowed_modes: set[str]) -> dict[str, Any]:
|
|
if not isinstance(value, Mapping):
|
|
raise _invalid("policy_invalid", field)
|
|
result = deepcopy(dict(value))
|
|
mode = _text(result.get("mode"), f"{field}.mode").upper()
|
|
if mode not in allowed_modes:
|
|
raise _invalid("policy_mode_invalid", f"{field}.mode")
|
|
result["mode"] = mode
|
|
return result
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class OperationDefinition:
|
|
operation_id: str
|
|
version: int | str
|
|
effect_class: str
|
|
handler_ref: str
|
|
input_schema: str | Mapping[str, Any]
|
|
required_permissions: tuple[str, ...]
|
|
timeout_seconds: float
|
|
readback: Mapping[str, Any]
|
|
retry: Mapping[str, Any]
|
|
reconcile: Mapping[str, Any]
|
|
rollback: Mapping[str, Any]
|
|
owner_boundary: Mapping[str, Any]
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Mapping[str, Any]) -> "OperationDefinition":
|
|
if not isinstance(data, Mapping):
|
|
raise _invalid("operation_definition_invalid")
|
|
forbidden = sorted(_FORBIDDEN_EXECUTION_FIELDS.intersection(data))
|
|
if forbidden:
|
|
raise _invalid("arbitrary_execution_forbidden", forbidden[0])
|
|
operation_id = _text(data.get("operation_id"), "operation_id")
|
|
version = data.get("version")
|
|
if (
|
|
isinstance(version, bool)
|
|
or not isinstance(version, (int, str))
|
|
or (isinstance(version, int) and version < 1)
|
|
or (isinstance(version, str) and not version.strip())
|
|
):
|
|
raise _invalid("field_invalid", "version")
|
|
effect_class = _text(data.get("effect_class"), "effect_class").upper()
|
|
if effect_class not in EFFECT_CLASSES:
|
|
raise _invalid("effect_class_invalid", "effect_class")
|
|
handler_ref = _text(data.get("handler_ref", data.get("handler")), "handler_ref")
|
|
if not _HANDLER_REF.fullmatch(handler_ref):
|
|
raise _invalid("handler_ref_invalid", "handler_ref")
|
|
input_schema = data.get("input_schema")
|
|
if not isinstance(input_schema, (str, Mapping)) or not input_schema:
|
|
raise _invalid("field_invalid", "input_schema")
|
|
input_schema = deepcopy(input_schema)
|
|
permissions = data.get("required_permissions", data.get("permissions"))
|
|
if not isinstance(permissions, Sequence) or isinstance(permissions, (str, bytes, bytearray)):
|
|
raise _invalid("field_invalid", "required_permissions")
|
|
required_permissions = tuple(
|
|
_text(permission, f"required_permissions[{index}]")
|
|
for index, permission in enumerate(permissions)
|
|
)
|
|
if len(set(required_permissions)) != len(required_permissions):
|
|
raise _invalid("duplicate_permission", "required_permissions")
|
|
timeout_seconds = data.get("timeout_seconds")
|
|
if (
|
|
isinstance(timeout_seconds, bool)
|
|
or not isinstance(timeout_seconds, (int, float))
|
|
or not math.isfinite(timeout_seconds)
|
|
or timeout_seconds <= 0
|
|
):
|
|
raise _invalid("field_invalid", "timeout_seconds")
|
|
|
|
readback = _policy(data.get("readback"), "readback", {"NONE", "STATE", "EXACT"})
|
|
if "required" in readback and not isinstance(readback["required"], bool):
|
|
raise _invalid("field_invalid", "readback.required")
|
|
retry = _policy(
|
|
data.get("retry"),
|
|
"retry",
|
|
{"NEVER", "SAFE", "AFTER_READBACK", "RECONCILE_FIRST"},
|
|
)
|
|
attempts = retry.get("max_attempts", 1)
|
|
if isinstance(attempts, bool) or not isinstance(attempts, int) or attempts < 1:
|
|
raise _invalid("field_invalid", "retry.max_attempts")
|
|
retry["max_attempts"] = attempts
|
|
reconcile = _policy(
|
|
data.get("reconcile"), "reconcile", {"NONE", "EXACT_READBACK", "CUSTOM", "REQUIRED"}
|
|
)
|
|
rollback = _policy(
|
|
data.get("rollback"), "rollback", {"NONE", "OPERATION", "COMPENSATE"}
|
|
)
|
|
if rollback["mode"] == "OPERATION":
|
|
_text(rollback.get("operation_id"), "rollback.operation_id")
|
|
owner_boundary = _policy(
|
|
data.get("owner_boundary"),
|
|
"owner_boundary",
|
|
{"NONE", "WITHIN_GRANT", "EXPLICIT_AUTHORIZATION", "CROSS_BOUNDARY_DENIED"},
|
|
)
|
|
|
|
restart_operation = (
|
|
"service.restart" in operation_id
|
|
or "service-restart" in operation_id
|
|
or handler_ref.endswith("service_restart")
|
|
)
|
|
if restart_operation and (
|
|
readback["mode"] != "EXACT"
|
|
or not readback.get("required", False)
|
|
or reconcile["mode"] == "NONE"
|
|
):
|
|
raise _invalid("service_restart_exact_readback_required")
|
|
|
|
return cls(
|
|
operation_id=operation_id,
|
|
version=version,
|
|
effect_class=effect_class,
|
|
handler_ref=handler_ref,
|
|
input_schema=input_schema,
|
|
required_permissions=required_permissions,
|
|
timeout_seconds=float(timeout_seconds),
|
|
readback=readback,
|
|
retry=retry,
|
|
reconcile=reconcile,
|
|
rollback=rollback,
|
|
owner_boundary=owner_boundary,
|
|
)
|
|
|
|
def timeout_action(self, outcome: str, attempts: int = 1) -> str:
|
|
return operation_timeout_action(self, outcome=outcome, attempts=attempts)
|
|
|
|
@property
|
|
def permissions(self) -> tuple[str, ...]:
|
|
return self.required_permissions
|
|
|
|
|
|
class OperationRegistry:
|
|
"""Lookup-only registry; it deliberately has no execute or shell interface."""
|
|
|
|
def __init__(self, definitions: Iterable[OperationDefinition | Mapping[str, Any]]) -> None:
|
|
self._definitions: dict[str, OperationDefinition] = {}
|
|
for raw in definitions:
|
|
definition = raw if isinstance(raw, OperationDefinition) else OperationDefinition.from_dict(raw)
|
|
if definition.operation_id in self._definitions:
|
|
raise _invalid("duplicate_operation_id", definition.operation_id)
|
|
self._definitions[definition.operation_id] = definition
|
|
|
|
def get(self, operation_id: str) -> OperationDefinition:
|
|
return self._definitions[_text(operation_id, "operation_id")]
|
|
|
|
@property
|
|
def definitions(self) -> tuple[OperationDefinition, ...]:
|
|
return tuple(self._definitions.values())
|
|
|
|
|
|
def operation_timeout_action(
|
|
definition: OperationDefinition,
|
|
outcome: str = "AMBIGUOUS",
|
|
attempts: int = 1,
|
|
) -> str:
|
|
"""Return ``RETRY``, ``RECONCILE`` or ``STOP`` after a timeout.
|
|
|
|
An unknown response from a mutating sink never authorizes a blind repeat.
|
|
Exact readback/reconciliation must first establish whether the effect happened.
|
|
"""
|
|
|
|
if not isinstance(definition, OperationDefinition):
|
|
raise _invalid("operation_definition_invalid")
|
|
normalized_outcome = _text(outcome, "outcome").upper()
|
|
if isinstance(attempts, bool) or not isinstance(attempts, int) or attempts < 1:
|
|
raise _invalid("field_invalid", "attempts")
|
|
if definition.effect_class == "IRREVERSIBLE":
|
|
return "STOP"
|
|
if definition.effect_class != "READ_ONLY" and normalized_outcome in {
|
|
"AMBIGUOUS",
|
|
"UNKNOWN",
|
|
"MAY_HAVE_SUCCEEDED",
|
|
}:
|
|
if definition.reconcile["mode"] != "NONE" or definition.readback.get("required", False):
|
|
return "RECONCILE"
|
|
return "STOP"
|
|
retry_mode = definition.retry["mode"]
|
|
if attempts >= definition.retry["max_attempts"] or retry_mode == "NEVER":
|
|
return "STOP"
|
|
if retry_mode in {"AFTER_READBACK", "RECONCILE_FIRST"} and normalized_outcome not in {
|
|
"NOT_OBSERVED",
|
|
"PROVEN_ABSENT",
|
|
}:
|
|
return "RECONCILE" if definition.reconcile["mode"] != "NONE" else "STOP"
|
|
return "RETRY"
|
|
|
|
|
|
def _timestamp(value: object) -> datetime | None:
|
|
if not isinstance(value, str):
|
|
return None
|
|
try:
|
|
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
except ValueError:
|
|
return None
|
|
if parsed.tzinfo is None or parsed.utcoffset() is None:
|
|
return None
|
|
return parsed.astimezone(timezone.utc)
|
|
|
|
|
|
def verify_service_restart(
|
|
before: Mapping[str, Any], after: Mapping[str, Any]
|
|
) -> dict[str, Any]:
|
|
"""Verify a restart with instance identity and start-time evidence.
|
|
|
|
Merely observing an active unit proves current activity, not that the requested
|
|
restart occurred.
|
|
"""
|
|
|
|
if not isinstance(before, Mapping) or not isinstance(after, Mapping):
|
|
return {"status": "UNKNOWN", "reasons": ["restart_observation_invalid"]}
|
|
if not isinstance(after.get("active"), bool):
|
|
return {"status": "UNKNOWN", "reasons": ["service_active_invalid"]}
|
|
if after["active"] is False:
|
|
return {"status": "DRIFT", "reasons": ["service_not_active"]}
|
|
before_id = before.get("instance_id")
|
|
after_id = after.get("instance_id")
|
|
before_started = _timestamp(before.get("started_at"))
|
|
after_started = _timestamp(after.get("started_at"))
|
|
if (
|
|
not isinstance(before_id, str)
|
|
or not before_id
|
|
or not isinstance(after_id, str)
|
|
or not after_id
|
|
or before_started is None
|
|
or after_started is None
|
|
):
|
|
return {"status": "UNKNOWN", "reasons": ["restart_identity_missing"]}
|
|
if before_id == after_id or after_started <= before_started:
|
|
return {"status": "DRIFT", "reasons": ["restart_not_observed"]}
|
|
return {"status": "VERIFIED", "reasons": []}
|