Implement SAND-WP-0002 meta-framework foundation (T01–T09)

Add meta-framework spec, pydantic schemas, profile/extension YAML, extension
registry, ext.compose-ssh backend, SandboxManager with State Hub events, CLI
commands, integration docs, capability registry entry, and compose-e2e runbook.
Nine unit tests pass. T10 remote smoke test remains for operator.
This commit is contained in:
tegwick 2026-06-22 23:27:31 +02:00
parent b0a57cf9d3
commit d6d3155792
28 changed files with 1796 additions and 15 deletions

View file

@ -0,0 +1,52 @@
"""Persistent sandbox status store (JSON file)."""
from __future__ import annotations
import json
import os
from datetime import UTC, datetime
from pathlib import Path
from sandboxer.models import SandboxStatus
def _default_store_path() -> Path:
base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
return base / "sandboxer" / "sandboxes.json"
class SandboxStore:
def __init__(self, path: Path | None = None) -> None:
self.path = path or _default_store_path()
self.path.parent.mkdir(parents=True, exist_ok=True)
def _read(self) -> dict[str, dict]:
if not self.path.exists():
return {}
return json.loads(self.path.read_text())
def _write(self, data: dict[str, dict]) -> None:
self.path.write_text(json.dumps(data, indent=2, default=str))
def save(self, status: SandboxStatus) -> None:
data = self._read()
data[status.sandbox_id] = status.model_dump(mode="json")
self._write(data)
def get(self, sandbox_id: str) -> SandboxStatus | None:
raw = self._read().get(sandbox_id)
if not raw:
return None
return SandboxStatus.model_validate(raw)
def list_all(self) -> list[SandboxStatus]:
return [SandboxStatus.model_validate(v) for v in self._read().values()]
def delete(self, sandbox_id: str) -> None:
data = self._read()
data.pop(sandbox_id, None)
self._write(data)
def utcnow() -> datetime:
return datetime.now(UTC)