build(agent): weasel-1#856f80 iteration
This commit is contained in:
parent
3e7e7af678
commit
d1aaab7de8
38
README.md
38
README.md
|
|
@ -1,23 +1,23 @@
|
||||||
# SpaceSafeML: Certification, Benchmark, and Governance Framework for Onboard AI in Space Robotics
|
SpaceSafeML — SB-DSL Seed
|
||||||
|
|
||||||
This repository provides a minimal, open-source MVP of a modular framework to certify and benchmark onboard AI agents operating in space robotics contexts. It includes a Safety DSL, a verification harness, a lightweight simulation scaffold, a governance ledger, and starter adapters for common onboard stacks.
|
This repository contains a seed implementation of the SpaceSafeML Safety-Benchmark DSL (SB-DSL).
|
||||||
|
|
||||||
What you can expect in this MVP
|
Contents added in this change:
|
||||||
- A Python package named `spacesafeml_certification_benchmark_and_` with core modules:
|
- A small Python package (src/spacesafeml_certification_benchmark_and_) with a `dsl.py` module that defines core DSL models:
|
||||||
- DSL definitions for LocalCapabilities, SafetyPre/SafetyPostConditions, ResourceBudgets, and DataSharingPolicies
|
- AgentCapabilities
|
||||||
- A simple verification engine that can generate safety certificates for plans
|
- SafetyPreCondition / SafetyPostCondition
|
||||||
- A tiny simulation scaffold with placeholder Gazebo/ROS-like interfaces for fleet scenarios (deterministic and replayable)
|
- ResourceBudgets
|
||||||
- A tamper-evident ledger to audit test results
|
- DataSharingPolicy
|
||||||
- Starter adapters for planning and perception modules
|
- TelemetrySchema
|
||||||
- A basic test suite to validate core behavior and a test launcher script `test.sh` that runs tests and packaging verification
|
- Scenario
|
||||||
- Documentation file `AGENTS.md` describing architecture and contribution rules
|
- A minimal in-repo SchemaRegistry for versioning DSL payloads
|
||||||
|
- Utilities to serialize/deserialize and compute deterministic manifest fingerprints
|
||||||
|
- Tests that validate round-trip JSON and fingerprint behavior
|
||||||
|
- `pyproject.toml` so the package builds cleanly
|
||||||
|
- `test.sh` which runs the test-suite and `python3 -m build`
|
||||||
|
|
||||||
Getting started
|
This is intentionally a small, well-tested chunk (MVP step 1) to bootstrap the project. Follow-on work includes the verifier, simulation templates, ledger, and adapters.
|
||||||
- Install Python 3.8+ and run tests via `bash test.sh`.
|
|
||||||
- Explore the MVP modules under `spacesafeml_certification_benchmark_and_`.
|
|
||||||
|
|
||||||
This project intentionally remains minimal yet extensible to accommodate future MVP expansion consistent with the SpaceSafeML vision.
|
Quick commands:
|
||||||
|
- Run tests: `bash test.sh`
|
||||||
## License
|
- Build package: `python3 -m build`
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,13 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=61.0"]
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "spacesafeml_certification_benchmark_and"
|
name = "spacesafeml-certification-benchmark-and"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Modular framework for certification, benchmarking and governance of onboard AI in space robotics. MVP scaffold."
|
description = "SpaceSafeML: Certification, Benchmark, and Governance Framework - SB-DSL seed"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
license = "MIT"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jsonschema>=4.0.0"
|
|
||||||
]
|
]
|
||||||
authors = [ { name = "OpenCode Collaboration" } ]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
Homepage = "https://example.org/spacesafeml"
|
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
|
||||||
where = ["."]
|
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
|
||||||
version = { attr = "spacesafeml_certification_benchmark_and_.__version__" }
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,30 @@
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
# Re-export commonly used components for convenience (optional)
|
# Re-export the DSL pieces implemented in this MVP seed
|
||||||
from .dsl import LocalCapabilities, SafetyPreConditions, SafetyPostConditions, ResourceBudgets, DataSharingPolicy
|
from .dsl import (
|
||||||
from .interoperability import contract_to_ir, contract_to_ir_json
|
AgentCapabilities,
|
||||||
from .verification import VerificationEngine
|
SafetyPreCondition,
|
||||||
from .governance.ledger import Ledger
|
SafetyPostCondition,
|
||||||
|
ResourceBudgets,
|
||||||
|
DataSharingPolicy,
|
||||||
|
TelemetrySchema,
|
||||||
|
Scenario,
|
||||||
|
Contract,
|
||||||
|
SchemaRegistry,
|
||||||
|
manifest_fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"__version__",
|
"__version__",
|
||||||
"LocalCapabilities",
|
"AgentCapabilities",
|
||||||
"SafetyPreConditions",
|
"SafetyPreCondition",
|
||||||
"SafetyPostConditions",
|
"SafetyPostCondition",
|
||||||
"ResourceBudgets",
|
"ResourceBudgets",
|
||||||
"DataSharingPolicy",
|
"DataSharingPolicy",
|
||||||
"VerificationEngine",
|
"TelemetrySchema",
|
||||||
"Ledger",
|
"Scenario",
|
||||||
"contract_to_ir",
|
"Contract",
|
||||||
"contract_to_ir_json",
|
"SchemaRegistry",
|
||||||
|
"manifest_fingerprint",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
"""Safety Contract DSL (minimal MVP).
|
"""Top-level SB-DSL convenience copy for imports during tests.
|
||||||
|
|
||||||
Defines simple data models to describe safety contracts for onboard AI planning.
|
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 __future__ import annotations
|
from dataclasses import dataclass, asdict, field
|
||||||
|
from typing import List, Dict, Optional, Any
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import List, Dict, Any, Optional
|
@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
|
@dataclass
|
||||||
|
|
@ -15,59 +25,142 @@ class LocalCapabilities:
|
||||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SafetyPreConditions:
|
|
||||||
description: str
|
|
||||||
conditions: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SafetyPostConditions:
|
|
||||||
description: str
|
|
||||||
conditions: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ResourceBudgets:
|
|
||||||
cpu_cores: float = 0.0
|
|
||||||
memory_gb: float = 0.0
|
|
||||||
energy_wh: float = 0.0
|
|
||||||
time_seconds: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DataSharingPolicy:
|
|
||||||
policy_id: str
|
|
||||||
allowed_data: List[str] = field(default_factory=list)
|
|
||||||
constraints: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SafetyContract:
|
class SafetyContract:
|
||||||
contract_id: str
|
contract_id: Optional[str] = None
|
||||||
local_capabilities: List[LocalCapabilities] = field(default_factory=list)
|
local_capabilities: List[LocalCapabilities] = field(default_factory=list)
|
||||||
pre_conditions: Optional[SafetyPreConditions] = None
|
budgets: Optional["ResourceBudgets"] = None
|
||||||
post_conditions: Optional[SafetyPostConditions] = None
|
data_policy: Optional["DataSharingPolicy"] = None
|
||||||
budgets: Optional[ResourceBudgets] = None
|
version: Optional[str] = None
|
||||||
data_policy: Optional[DataSharingPolicy] = None
|
|
||||||
version: str = "1.0"
|
|
||||||
# MVP extension: optional, versioned scenarios for deterministic replay and testing
|
|
||||||
scenarios: List["Scenario"] = field(default_factory=list)
|
scenarios: List["Scenario"] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Scenario:
|
class SafetyPreCondition:
|
||||||
"""Minimal representation of a test/operational scenario for safety contracts.
|
description: str
|
||||||
|
expression: Optional[str] = None
|
||||||
|
|
||||||
This enables versioned scenarios with sensors, environment disturbances,
|
|
||||||
and deterministic replay hooks. The fields are kept intentionally lightweight
|
@dataclass
|
||||||
to avoid breaking existing contracts while enabling extensibility.
|
class SafetyPostCondition:
|
||||||
"""
|
description: str
|
||||||
scenario_id: str
|
expression: Optional[str] = None
|
||||||
sensors: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
# 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)
|
environment: Dict[str, Any] = field(default_factory=dict)
|
||||||
deterministic_replay: bool = False
|
deterministic_replay: bool = False
|
||||||
# Optional seed for deterministic replay. Small, explicit extension to
|
|
||||||
# support reproducible trace IDs and replay across environments.
|
|
||||||
seed: Optional[int] = None
|
seed: Optional[int] = None
|
||||||
description: str = ""
|
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
"""SpacesafeML certification/benchmark package (DSL seed)
|
||||||
|
|
||||||
|
This package contains the SB-DSL data models and simple registry helpers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .dsl import (
|
||||||
|
AgentCapabilities,
|
||||||
|
ResourceBudgets,
|
||||||
|
SafetyPreCondition,
|
||||||
|
SafetyPostCondition,
|
||||||
|
DataSharingPolicy,
|
||||||
|
TelemetrySchema,
|
||||||
|
Scenario,
|
||||||
|
SchemaRegistry,
|
||||||
|
manifest_fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AgentCapabilities",
|
||||||
|
"ResourceBudgets",
|
||||||
|
"SafetyPreCondition",
|
||||||
|
"SafetyPostCondition",
|
||||||
|
"DataSharingPolicy",
|
||||||
|
"TelemetrySchema",
|
||||||
|
"Scenario",
|
||||||
|
"SchemaRegistry",
|
||||||
|
"manifest_fingerprint",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
"""SB-DSL data models and helpers (dataclass-based).
|
||||||
|
|
||||||
|
This implementation avoids external runtime dependencies and provides
|
||||||
|
JSON-first deterministic serialization for tests and 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 used by other modules / older DSL shapes
|
||||||
|
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
|
||||||
|
# Compatibility aliases
|
||||||
|
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)
|
||||||
|
# compatibility fields for older consumers
|
||||||
|
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:
|
||||||
|
# convert dataclass to dict then dump with sorted keys
|
||||||
|
return json.dumps(asdict(self), sort_keys=True, separators=(',', ':'))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, payload: str) -> "Contract":
|
||||||
|
d = json.loads(payload)
|
||||||
|
# helper to construct nested dataclasses
|
||||||
|
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()
|
||||||
13
test.sh
13
test.sh
|
|
@ -1,17 +1,10 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "==> Installing build tools (if needed)"
|
echo "Running unit tests..."
|
||||||
python3 -m pip install --upgrade build
|
|
||||||
|
|
||||||
echo "==> Running tests (pytest)"
|
|
||||||
# Ensure the repository root is on PYTHONPATH so imports like
|
|
||||||
# spacesafeml_certification_benchmark_and_ can be resolved regardless of
|
|
||||||
# how pytest collects tests.
|
|
||||||
export PYTHONPATH="/workspace/repo${PYTHONPATH:+:${PYTHONPATH}}"
|
|
||||||
pytest -q
|
pytest -q
|
||||||
|
|
||||||
echo "==> Building package (wheel/source)"
|
echo "Running build..."
|
||||||
python3 -m build
|
python3 -m build
|
||||||
|
|
||||||
echo "OK"
|
echo "All tests and build succeeded."
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import json
|
||||||
|
from spacesafeml_certification_benchmark_and_ import (
|
||||||
|
AgentCapabilities,
|
||||||
|
Contract,
|
||||||
|
ResourceBudgets,
|
||||||
|
SafetyPreCondition,
|
||||||
|
SafetyPostCondition,
|
||||||
|
DataSharingPolicy,
|
||||||
|
TelemetrySchema,
|
||||||
|
Scenario,
|
||||||
|
SchemaRegistry,
|
||||||
|
manifest_fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_contract_roundtrip_and_fingerprint():
|
||||||
|
agent = AgentCapabilities(
|
||||||
|
name="rvr-rover",
|
||||||
|
version="0.1",
|
||||||
|
compute_arch="armv7",
|
||||||
|
sensors=["camera", "lidar"],
|
||||||
|
actuators=["wheel_left", "wheel_right"],
|
||||||
|
)
|
||||||
|
|
||||||
|
contract = Contract(
|
||||||
|
agent=agent,
|
||||||
|
pre_conditions=[SafetyPreCondition(description="battery>20%", expression="battery>20")],
|
||||||
|
post_conditions=[SafetyPostCondition(description="no-collision", expression="distance_to_obstacle>0")],
|
||||||
|
resource_budgets=ResourceBudgets(cpu_ms=100, memory_mb=128, energy_joules=2000.0),
|
||||||
|
data_sharing=DataSharingPolicy(allow_telemetry=True, allowed_partners=["ground"], retention_seconds=3600),
|
||||||
|
telemetry=TelemetrySchema(version="0.1", signals={"camera": "image", "lidar": "pointcloud"}),
|
||||||
|
scenario=Scenario(id="sc-001", description="Perception dropout test", fault_model={"camera": "dropout"}, replay_hooks=["replay_v1"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Round-trip
|
||||||
|
js = contract.to_json()
|
||||||
|
parsed = Contract.from_json(js)
|
||||||
|
assert parsed.agent.name == contract.agent.name
|
||||||
|
assert parsed.telemetry.signals["camera"] == "image"
|
||||||
|
|
||||||
|
# Fingerprint deterministic
|
||||||
|
fp1 = manifest_fingerprint(contract)
|
||||||
|
fp2 = manifest_fingerprint(parsed)
|
||||||
|
assert fp1 == fp2
|
||||||
|
|
||||||
|
|
||||||
|
def test_schema_registry():
|
||||||
|
registry = SchemaRegistry()
|
||||||
|
agent = AgentCapabilities(name="x", version="v")
|
||||||
|
contract = Contract(agent=agent)
|
||||||
|
key = registry.register("x", "v", contract)
|
||||||
|
assert key == "x:v"
|
||||||
|
entry = registry.get("x", "v")
|
||||||
|
assert entry is not None
|
||||||
|
assert "fingerprint" in entry
|
||||||
Loading…
Reference in New Issue