spacesafeml-certification-b.../spacesafeml_certification_b.../dsl.py

167 lines
5.1 KiB
Python

"""Top-level SB-DSL convenience copy for imports during tests.
This mirrors the implementation in src/ so tests and consumers can `import spacesafeml_certification_benchmark_and_`.
The implementation is intentionally small and duplicated to avoid packaging complexities in the MVP seed.
"""
from dataclasses import dataclass, asdict, field
from typing import List, Dict, Optional, Any
import json
import hashlib
@dataclass
class AgentCapabilities:
name: str
version: str
compute_arch: Optional[str] = None
sensors: List[str] = field(default_factory=list)
actuators: List[str] = field(default_factory=list)
@dataclass
class LocalCapabilities:
name: str
capabilities: List[str] = field(default_factory=list)
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class SafetyContract:
contract_id: Optional[str] = None
local_capabilities: List[LocalCapabilities] = field(default_factory=list)
budgets: Optional["ResourceBudgets"] = None
data_policy: Optional["DataSharingPolicy"] = None
version: Optional[str] = None
scenarios: List["Scenario"] = field(default_factory=list)
@dataclass
class SafetyPreCondition:
description: str
expression: Optional[str] = None
@dataclass
class SafetyPostCondition:
description: str
expression: Optional[str] = None
# Backwards-compatible aliases used by older modules/tests
SafetyPreConditions = SafetyPreCondition
SafetyPostConditions = SafetyPostCondition
@dataclass
class ResourceBudgets:
cpu_ms: Optional[int] = None
memory_mb: Optional[int] = None
energy_joules: Optional[float] = None
# compatibility fields
cpu_cores: Optional[float] = None
memory_gb: Optional[float] = None
energy_wh: Optional[float] = None
time_seconds: Optional[int] = None
@dataclass
class DataSharingPolicy:
allow_telemetry: bool = True
allowed_partners: List[str] = field(default_factory=list)
retention_seconds: Optional[int] = None
policy_id: Optional[str] = None
allowed_data: List[str] = field(default_factory=list)
@dataclass
class TelemetrySchema:
version: str
signals: Dict[str, str] = field(default_factory=dict)
@dataclass
class Scenario:
id: Optional[str] = None
description: Optional[str] = None
fault_model: Dict[str, Any] = field(default_factory=dict)
replay_hooks: List[str] = field(default_factory=list)
scenario_id: Optional[str] = None
sensors: List[str] = field(default_factory=list)
environment: Dict[str, Any] = field(default_factory=dict)
deterministic_replay: bool = False
seed: Optional[int] = None
@dataclass
class Contract:
agent: AgentCapabilities
schema_version: str = "sb-dsl-0.1"
pre_conditions: List[SafetyPreCondition] = field(default_factory=list)
post_conditions: List[SafetyPostCondition] = field(default_factory=list)
resource_budgets: Optional[ResourceBudgets] = None
data_sharing: Optional[DataSharingPolicy] = None
telemetry: Optional[TelemetrySchema] = None
scenario: Optional[Scenario] = None
def to_json(self) -> str:
return json.dumps(asdict(self), sort_keys=True, separators=(',', ':'))
@classmethod
def from_json(cls, payload: str) -> "Contract":
d = json.loads(payload)
def _construct_agent(obj):
if obj is None:
return None
return AgentCapabilities(**obj)
def _construct_resource(obj):
return ResourceBudgets(**obj) if obj is not None else None
def _construct_data_sharing(obj):
return DataSharingPolicy(**obj) if obj is not None else None
def _construct_telemetry(obj):
return TelemetrySchema(**obj) if obj is not None else None
def _construct_scenario(obj):
return Scenario(**obj) if obj is not None else None
pre = [SafetyPreCondition(**pc) for pc in d.get('pre_conditions', [])]
post = [SafetyPostCondition(**pc) for pc in d.get('post_conditions', [])]
return cls(
agent=_construct_agent(d['agent']),
schema_version=d.get('schema_version', 'sb-dsl-0.1'),
pre_conditions=pre,
post_conditions=post,
resource_budgets=_construct_resource(d.get('resource_budgets')),
data_sharing=_construct_data_sharing(d.get('data_sharing')),
telemetry=_construct_telemetry(d.get('telemetry')),
scenario=_construct_scenario(d.get('scenario')),
)
class SchemaRegistry:
def __init__(self):
self._store: Dict[str, Dict[str, Any]] = {}
def register(self, name: str, version: str, contract: Contract) -> str:
key = f"{name}:{version}"
self._store[key] = {
"contract": json.loads(contract.to_json()),
"fingerprint": manifest_fingerprint(contract),
}
return key
def get(self, name: str, version: str) -> Optional[Dict[str, Any]]:
return self._store.get(f"{name}:{version}")
def list_entries(self) -> List[str]:
return list(self._store.keys())
def manifest_fingerprint(contract: Contract) -> str:
payload = contract.to_json().encode('utf-8')
return hashlib.sha256(payload).hexdigest()