from __future__ import annotations from dataclasses import dataclass, field, asdict from typing import Any, Dict, List, Optional import hashlib import json import time def _canonical_json(payload: Any) -> str: return json.dumps(payload, sort_keys=True, separators=(",", ":"), default=str) def _fingerprint(payload: Any) -> str: return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest() @dataclass class LocalProblem: name: str region: Optional[str] = None objective: str = "" constraints: Dict[str, Any] = field(default_factory=dict) metadata: Dict[str, Any] = field(default_factory=dict) created_at: float = field(default_factory=lambda: time.time()) def __post_init__(self) -> None: if not self.name.strip(): raise ValueError("LocalProblem.name must not be empty") def __repr__(self) -> str: return f"LocalProblem(name={self.name!r}, region={self.region!r}, objective={self.objective!r})" def to_dict(self) -> Dict[str, Any]: return asdict(self) def fingerprint(self) -> str: return _fingerprint(self.to_dict()) @dataclass class SharedVariables: description: str = "" data: Dict[str, Any] = field(default_factory=dict) aggregate_type: str = "sum" privacy_budget_tag: Optional[str] = None source_regions: List[str] = field(default_factory=list) created_at: float = field(default_factory=lambda: time.time()) def __repr__(self) -> str: return f"SharedVariables(desc={self.description!r}, keys={list(self.data.keys())}, aggregate={self.aggregate_type!r})" def to_dict(self) -> Dict[str, Any]: return asdict(self) def fingerprint(self) -> str: return _fingerprint(self.to_dict()) @dataclass class PlanDelta: delta_id: str actions: List[Dict[str, Any]] = field(default_factory=list) timestamp: float = field(default_factory=lambda: time.time()) actor_did: Optional[str] = None causality: Dict[str, int] = field(default_factory=dict) policy_tags: List[str] = field(default_factory=list) privacy_budget_tag: Optional[str] = None annotations: Dict[str, Any] = field(default_factory=dict) signature: Optional[str] = None def __post_init__(self) -> None: if not self.delta_id.strip(): raise ValueError("PlanDelta.delta_id must not be empty") if not isinstance(self.actions, list): raise TypeError("PlanDelta.actions must be a list") def __repr__(self) -> str: return f"PlanDelta(delta_id={self.delta_id!r}, actions={len(self.actions)} actions, actor={self.actor_did!r})" def to_dict(self) -> Dict[str, Any]: return asdict(self) def fingerprint(self) -> str: return _fingerprint(self.to_dict()) @dataclass class Policy: policy_id: str rules: Dict[str, Any] = field(default_factory=dict) effective_at: float = field(default_factory=lambda: time.time()) scope: str = "global" approved_by: Optional[str] = None def __repr__(self) -> str: return f"Policy(id={self.policy_id!r}, rules={len(self.rules)}, scope={self.scope!r})" def to_dict(self) -> Dict[str, Any]: return asdict(self) @dataclass class AttestationHint: hint_id: str scheme: str = "dummy" payload: Dict[str, Any] = field(default_factory=dict) expires_at: Optional[float] = None def __repr__(self) -> str: return f"AttestationHint(id={self.hint_id!r}, scheme={self.scheme!r})" def to_dict(self) -> Dict[str, Any]: return asdict(self) @dataclass class AuditLog: entry_id: str message: str timestamp: float = field(default_factory=lambda: time.time()) signature: Optional[str] = None anchor: Optional[str] = None def __repr__(self) -> str: return f"AuditLog(id={self.entry_id!r}, ts={self.timestamp})" def to_dict(self) -> Dict[str, Any]: return asdict(self) @dataclass class PrivacyBudget: budget_id: str limit: float used: float = 0.0 def consume(self, amount: float) -> bool: if amount < 0: raise ValueError("PrivacyBudget.consume requires a non-negative amount") if self.used + amount > self.limit: return False self.used += amount return True def can_consume(self, amount: float) -> bool: return amount >= 0 and self.used + amount <= self.limit def remaining(self) -> float: return self.limit - self.used def to_dict(self) -> Dict[str, Any]: return asdict(self) def __repr__(self) -> str: return f"PrivacyBudget(id={self.budget_id!r}, remaining={self.remaining()})"