"""Tenant-scoped Hermes runtime contracts.""" from __future__ import annotations import json import uuid from dataclasses import dataclass, field from datetime import datetime, timezone from enum import Enum from typing import Any, Mapping def utc_now() -> str: return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") def _required(value: str, label: str) -> str: normalized = str(value or "").strip() if not normalized: raise ValueError(f"{label} is required") return normalized def _safe_mapping(value: Mapping[str, Any] | None) -> dict[str, Any]: return json.loads(json.dumps(dict(value or {}), ensure_ascii=False, sort_keys=True, default=str)) class HermesJobState(str, Enum): QUEUED = "QUEUED" CLAIMED = "CLAIMED" RUNNING = "RUNNING" COMPLETED = "COMPLETED" FAILED = "FAILED" CANCELLED = "CANCELLED" ALLOWED_TRANSITIONS: Mapping[HermesJobState, frozenset[HermesJobState]] = { HermesJobState.QUEUED: frozenset({HermesJobState.CLAIMED, HermesJobState.CANCELLED}), HermesJobState.CLAIMED: frozenset({HermesJobState.RUNNING, HermesJobState.CANCELLED}), HermesJobState.RUNNING: frozenset( {HermesJobState.COMPLETED, HermesJobState.FAILED, HermesJobState.CANCELLED} ), HermesJobState.COMPLETED: frozenset(), HermesJobState.FAILED: frozenset(), HermesJobState.CANCELLED: frozenset(), } TERMINAL_STATES = frozenset( {HermesJobState.COMPLETED, HermesJobState.FAILED, HermesJobState.CANCELLED} ) @dataclass(frozen=True) class HermesJob: job_id: str tenant_id: str cell_id: str profile_id: str task_id: str trace_id: str action: str payload: Mapping[str, Any] = field(default_factory=dict) idempotency_key: str = "" priority: int = 100 state: HermesJobState = HermesJobState.QUEUED revision: int = 1 assigned_worker_id: str | None = None lease_expires_at: str | None = None created_at: str = field(default_factory=utc_now) updated_at: str = field(default_factory=utc_now) def __post_init__(self) -> None: for name in ( "job_id", "tenant_id", "cell_id", "profile_id", "task_id", "trace_id", "action", "idempotency_key", "created_at", "updated_at", ): object.__setattr__(self, name, _required(getattr(self, name), name)) object.__setattr__(self, "payload", _safe_mapping(self.payload)) object.__setattr__(self, "state", HermesJobState(self.state)) if not 0 <= int(self.priority) <= 1000: raise ValueError("priority must be between 0 and 1000") if self.revision < 1: raise ValueError("revision must be >= 1") @classmethod def create( cls, *, tenant_id: str, cell_id: str, profile_id: str, task_id: str, trace_id: str, action: str, payload: Mapping[str, Any] | None, idempotency_key: str, priority: int = 100, ) -> "HermesJob": return cls( job_id=f"hermes_{uuid.uuid4().hex}", tenant_id=tenant_id, cell_id=cell_id, profile_id=profile_id, task_id=task_id, trace_id=trace_id, action=action, payload=_safe_mapping(payload), idempotency_key=idempotency_key, priority=priority, ) @dataclass(frozen=True) class HermesResult: job_id: str tenant_id: str task_id: str trace_id: str state: HermesJobState worker_id: str | None = None output: Mapping[str, Any] | None = None error: Mapping[str, Any] | None = None started_at: str | None = None finished_at: str | None = None def __post_init__(self) -> None: for name in ("job_id", "tenant_id", "task_id", "trace_id"): object.__setattr__(self, name, _required(getattr(self, name), name)) object.__setattr__(self, "state", HermesJobState(self.state)) if self.output is not None: object.__setattr__(self, "output", _safe_mapping(self.output)) if self.error is not None: object.__setattr__(self, "error", _safe_mapping(self.error)) @dataclass(frozen=True) class HermesHealth: runtime_id: str status: str version: str heartbeat_at: str active_workers: int = 0 active_jobs: int = 0 queued_jobs: int = 0 tenant_partitions: int = 0 profile_isolation_enforced: bool = True details: Mapping[str, Any] = field(default_factory=dict) def __post_init__(self) -> None: for name in ("runtime_id", "status", "version", "heartbeat_at"): object.__setattr__(self, name, _required(getattr(self, name), name)) for name in ("active_workers", "active_jobs", "queued_jobs", "tenant_partitions"): if int(getattr(self, name)) < 0: raise ValueError(f"{name} must be >= 0") object.__setattr__(self, "details", _safe_mapping(self.details))