187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
"""Simple data contracts used by NovaPlan MVP.
|
|
|
|
- PlanDelta: delta between local and global plans.
|
|
- SharedSchedule: aggregated schedule signals from agents.
|
|
- ResourceUsage: energy, time, or other resource consumptions.
|
|
- PrivacyBudget: basic DP-like budget for an agent (simulated).
|
|
- AuditLog: lightweight log entries for governance.
|
|
"""
|
|
from __future__ import annotations
|
|
from dataclasses import dataclass, asdict
|
|
from typing import Dict, Any, List
|
|
import json
|
|
|
|
@dataclass
|
|
class PlanDelta:
|
|
agent_id: str
|
|
delta: Dict[str, float]
|
|
timestamp: float
|
|
|
|
def to_json(self) -> str:
|
|
return json.dumps(asdict(self))
|
|
|
|
@dataclass
|
|
class SharedSchedule:
|
|
schedule: Dict[str, Any]
|
|
timestamp: float
|
|
|
|
@dataclass
|
|
class ResourceUsage:
|
|
agent_id: str
|
|
resources: Dict[str, float]
|
|
timestamp: float
|
|
|
|
@dataclass
|
|
class PrivacyBudget:
|
|
agent_id: str
|
|
budget: float
|
|
timestamp: float
|
|
|
|
@dataclass
|
|
class AuditLog:
|
|
entry_id: str
|
|
message: str
|
|
timestamp: float
|
|
|
|
def serialize(obj: object) -> str:
|
|
if hasattr(obj, "__dict__"):
|
|
return json.dumps(obj.__dict__)
|
|
return json.dumps(obj)
|
|
|
|
# Lightweight contract registry for versioning and interoperability
|
|
class ContractRegistry:
|
|
_registry: Dict[str, int] = {}
|
|
_schemas: Dict[str, Dict[str, Dict[str, Any]]] = {}
|
|
|
|
@classmethod
|
|
def register(cls, name: str, version: int) -> None:
|
|
cls._registry[name] = int(version)
|
|
|
|
@classmethod
|
|
def version_of(cls, name: str, default: int | None = None) -> int | None:
|
|
return cls._registry.get(name, default)
|
|
|
|
@classmethod
|
|
def register_schema(
|
|
cls,
|
|
name: str,
|
|
version: int,
|
|
schema: Dict[str, Any],
|
|
) -> None:
|
|
"""Register a contract schema for a given contract name and version."""
|
|
cls.register(name, version)
|
|
cls._schemas.setdefault(name, {})[str(version)] = schema
|
|
|
|
@classmethod
|
|
def get_schema(cls, name: str, version: int) -> Dict[str, Any] | None:
|
|
return cls._schemas.get(name, {}).get(str(version))
|
|
|
|
@classmethod
|
|
def list_schemas(cls) -> List[Dict[str, Any]]:
|
|
results: List[Dict[str, Any]] = []
|
|
for name, versions in cls._schemas.items():
|
|
for ver, schema in versions.items():
|
|
results.append({"name": name, "version": int(ver), "schema": schema})
|
|
return results
|
|
|
|
@staticmethod
|
|
def validate_against_schema(data: Dict[str, Any], schema: Dict[str, Any]) -> bool:
|
|
"""Minimal validation: check required keys and basic type hints if provided."""
|
|
required = set(schema.get("required", []))
|
|
# All required keys must be present in the data
|
|
if not required.issubset(set(data.keys())):
|
|
return False
|
|
# Optional: validate simple types if provided
|
|
types: Dict[str, type] = schema.get("types", {})
|
|
for key, typ in types.items():
|
|
if key in data and not isinstance(data[key], typ):
|
|
return False
|
|
return True
|
|
|
|
# Auto-register core contracts for quick interoperability in MVP workflows.
|
|
# This ensures a minimal, versioned contract surface is available as soon as
|
|
# the module is imported, which benefits tooling and adapters that rely on
|
|
# contract versioning without requiring explicit setup code in downstream
|
|
# components.
|
|
for _name, _ver, _schema in [
|
|
("PlanDelta", 1, {"required": ["agent_id", "delta", "timestamp"], "types": {"agent_id": str, "delta": dict, "timestamp": (int, float)}}),
|
|
("SharedSchedule", 1, {"required": ["schedule", "timestamp"], "types": {"schedule": dict, "timestamp": (int, float)}}),
|
|
("ResourceUsage", 1, {"required": ["agent_id", "resources", "timestamp"], "types": {"agent_id": str, "resources": dict, "timestamp": (int, float)}}),
|
|
("PrivacyBudget", 1, {"required": ["agent_id", "budget", "timestamp"], "types": {"agent_id": str, "budget": (int, float), "timestamp": (int, float)}}),
|
|
("AuditLog", 1, {"required": ["entry_id", "message", "timestamp"], "types": {"entry_id": str, "message": str, "timestamp": (int, float)}}),
|
|
]:
|
|
ContractRegistry.register_schema(_name, _ver, _schema)
|
|
|
|
|
|
# Lightweight Adapter Registry (Graph-of-Contracts for adapters)
|
|
class AdapterRegistry:
|
|
"""Minimal registry to track adapter versions and their schemas.
|
|
|
|
This mirrors the contract registry pattern but for adapter software units
|
|
(e.g., rover HabitatAdapter, etc.). It enables plugging in vendor adapters
|
|
while keeping a versioned contract surface for interoperability tooling.
|
|
"""
|
|
|
|
_registry: Dict[str, int] = {}
|
|
_schemas: Dict[str, Dict[str, Dict[str, Any]]] = {}
|
|
|
|
@classmethod
|
|
def register_adapter(cls, name: str, version: int) -> None:
|
|
cls._registry[name] = int(version)
|
|
|
|
@classmethod
|
|
def version_of(cls, name: str, default: int | None = None) -> int | None:
|
|
return cls._registry.get(name, default)
|
|
|
|
@classmethod
|
|
def register_schema(
|
|
cls,
|
|
name: str,
|
|
version: int,
|
|
schema: Dict[str, Any],
|
|
) -> None:
|
|
cls.register_adapter(name, version)
|
|
cls._schemas.setdefault(name, {})[str(version)] = schema
|
|
|
|
@classmethod
|
|
def get_schema(cls, name: str, version: int) -> Dict[str, Any] | None:
|
|
return cls._schemas.get(name, {}).get(str(version))
|
|
|
|
@classmethod
|
|
def list_schemas(cls) -> List[Dict[str, Any]]:
|
|
results: List[Dict[str, Any]] = []
|
|
for name, versions in cls._schemas.items():
|
|
for ver, schema in versions.items():
|
|
results.append({"name": name, "version": int(ver), "schema": schema})
|
|
return results
|
|
|
|
@staticmethod
|
|
def validate_against_schema(data: Dict[str, Any], schema: Dict[str, Any]) -> bool:
|
|
required = set(schema.get("required", []))
|
|
if not required.issubset(set(data.keys())):
|
|
return False
|
|
types: Dict[str, type] = schema.get("types", {})
|
|
for key, typ in types.items():
|
|
if key in data and not isinstance(data[key], typ):
|
|
return False
|
|
return True
|
|
|
|
|
|
# Pre-register a couple of MVP adapter schemas to illustrate interoperability.
|
|
AdapterRegistry.register_schema(
|
|
name="RoverAdapter",
|
|
version=1,
|
|
schema={
|
|
"required": ["adapter_id", "status"],
|
|
"types": {"adapter_id": str, "status": dict},
|
|
},
|
|
)
|
|
AdapterRegistry.register_schema(
|
|
name="HabitatAdapter",
|
|
version=1,
|
|
schema={
|
|
"required": ["module_id", "status"],
|
|
"types": {"module_id": str, "status": dict},
|
|
},
|
|
)
|