Implement SAND-WP-0005: extension SDK and ext.vm-packer

Add SandboxExtension base class, extension SDK docs, vm-packer attach mode
for build-machines VMs, profile.vm-haskell-build, SSH port support, tests,
and migration docs.
This commit is contained in:
2026-06-24 01:47:07 +02:00
parent c8126672ee
commit cec0fc6348
20 changed files with 679 additions and 16 deletions

102
docs/extension-sdk.md Normal file
View File

@@ -0,0 +1,102 @@
# Extension SDK
Author guide for sand-boxer backend adapters. Version 0.1 — SAND-WP-0005.
## Contract
Every extension implements three methods:
```text
provision(profile, inputs, host) → handle dict
wait_ready(handle) → reachability dict
teardown(handle) → cleanup report dict
```
Optional (SaaS, deferred): `estimate_cost(profile, duration) → MeterQuote`
### Base class
```python
from sandboxer.extensions.base import SandboxExtension
class MyExtension(SandboxExtension):
def provision(self, profile, inputs, host): ...
def wait_ready(self, handle): ...
def teardown(self, handle): ...
```
Reference implementations:
| Extension | Module | Mode |
|-----------|--------|------|
| `ext.compose-ssh` | `compose_ssh.py` | Remote compose stack |
| `ext.vm-packer` | `vm_packer.py` | Attach workspace on pre-built VM |
## Registration
1. Add `extensions/ext.<name>.yaml`:
```yaml
id: ext.my-backend
title: My Backend
handler: sandboxer.extensions.my_backend:MyExtension
capabilities:
isolation_levels: [container]
pricing_model: self-hosted
config:
key: value
```
2. Add a profile binding in `profiles/profile.<slug>.yaml` with `extension: ext.my-backend`.
3. Register capability metadata in `registry/` when ready for reuse-surface.
Loader validates `capabilities.isolation_levels` and `capabilities.pricing_model`
at startup (`sandboxer.extensions.registry`).
## Handle and reachability
**Handle** (returned by `provision`, stored in manager): opaque dict passed to
`wait_ready` and `teardown`. Include at minimum:
- `sandbox_id`
- `host` (placement host)
- Fields your extension needs for SSH/API (e.g. `remote_dir`, `vm_target`)
**Reachability** (returned by `wait_ready`): exposed on `SandboxStatus.reachability`:
- `ssh` — SSH destination string
- `remote_dir` — workspace path on remote
- `host` — placement host
- `compose_project` — compose-ssh only
## Inputs convention
Profiles declare semantics; extensions validate required `inputs` keys:
| Extension | Required inputs | Optional |
|-----------|-----------------|----------|
| compose-ssh | `repo` | `sandbox_id` |
| vm-packer | `vm` or `ssh_target` | `repo`, `tunnel_port`, `ssh_port`, `workspace_dir` |
Consumer attribution travels on `SandboxCreateRequest.consumer`, not extension inputs.
## Testing
Mock SSH/subprocess in unit tests. See `tests/test_compose_ssh.py`, `tests/test_vm_packer.py`.
Pattern:
```python
with patch.object(SSHConfig, "run", return_value=(0, "ready")):
ext = VMPackerExtension()
handle = ext.provision(profile, {"vm": "haskell-build"}, "localhost")
```
## Deferred
| Feature | Workplan |
|---------|----------|
| Packer build orchestration from `create` | Future WP |
| SaaS adapters + `estimate_cost` | SAND-WP-0006 |
| Multi-backend routing engine | SAND-WP-0006 |
| Snapshot / restore hooks | SAND-WP-0007 |

View File

@@ -0,0 +1,62 @@
# Migration — build-machines → ext.vm-packer
Maps `the-custodian/infra/build-machines/` to sand-boxer `profile.vm-haskell-build`.
## What moved
| Legacy (build-machines) | sand-boxer v0 |
|-------------------------|---------------|
| Packer OVA build | **Unchanged** — operator runs Packer in the-custodian |
| VM boot + build-agent registration | **Unchanged** — systemd agent on VM |
| `make remote-build PROJECT=` | `sandboxer create` + SSH into `reachability.remote_dir` |
| Isolated workspace `/build/<project>` | `/build/sbx-<sandbox_id>/` per create |
| `make bridge-status` | `ssh -p 12222 build@localhost` or `sandboxer inspect` (future) |
## v0 attach workflow
1. Build/import VM per [build-machines README](~/the-custodian/infra/build-machines/README.md).
2. Ensure tunnel is up (`make bridge-status` in build-machines).
3. Create sand-boxer workspace:
```bash
export SANDBOXER_VM_TUNNEL_PORT=12222 # or use SSH alias
sandboxer create \
--profile profile.vm-haskell-build \
--input vm=haskell-build \
--input repo=~/projects/my-haskell-app \
--host localhost
```
4. Run builds on VM:
```bash
ssh haskell-build "cd <remote_dir> && source ~/.ghcup/env && cabal build all"
```
5. Destroy workspace (VM stays running):
```bash
sandboxer destroy <sandbox_id>
```
## Inputs
| Input | Purpose |
|-------|---------|
| `vm` | SSH config alias (e.g. `haskell-build`) |
| `ssh_target` | Alias for `vm` |
| `tunnel_port` | Local reverse-tunnel port (default via `SANDBOXER_VM_TUNNEL_PORT`) |
| `repo` | Optional rsync source to workspace |
| `workspace_dir` | Override workspace path on VM |
## Not migrated yet
- Automated Packer `create` trigger from sand-boxer API
- State Hub capability-catalog sync from build-agent (agent unchanged)
- Port registry automation (`port-registry.yml`)
- `make remote-build` Makefile targets in the-custodian (add shim in follow-on if needed)
## Runbook
`docs/runbooks/profile-vm-haskell-build.md`

View File

@@ -28,9 +28,21 @@ Recorded after SAND-WP-0002-T10 remote verification on CoulombCore (`92.205.130.
**e2e-framework migration arc complete** (provision: sand-boxer, validation:
wise-validator, operator entry: `make e2e`).
## build-machines (SAND-WP-0005) — attach mode delivered
| Legacy (`build-machines`) | sand-boxer today | Notes |
|---------------------------|------------------|-------|
| Packer OVA build | Operator-driven (unchanged) | Not triggered by `create` |
| `make remote-build` rsync + SSH | `sandboxer create --profile profile.vm-haskell-build` | Workspace `/build/sbx-<id>/` |
| VM teardown | N/A | `destroy` removes workspace only; VM persists |
| Extension author contract | `docs/extension-sdk.md` | `SandboxExtension` base class |
Deferred: Packer orchestration from API, `make remote-build` shim.
## sand-boxer follow-ons
| Item | Workplan |
|------|----------|
| Self-canary + host telemetry | SAND-WP-0008 |
| Default `sandboxer create` without repo | SAND-WP-0008-T06 |
| SaaS extensions + payments | SAND-WP-0006 |
| Snapshot / restore | SAND-WP-0007 |
| TTL enforcement + scheduled reap | TBD |

View File

@@ -0,0 +1,50 @@
# profile.vm-haskell-build — Runbook
Attach an isolated Haskell build workspace on a pre-built VM (build-machines lineage).
## Prerequisites
- VM built/imported per `the-custodian/infra/build-machines/`
- SSH tunnel up (`make bridge-status` in build-machines)
- `~/.ssh/config` entry for `haskell-build` (or `tunnel_port` / `SANDBOXER_VM_TUNNEL_PORT`)
- `sandboxer` on PATH
## Create workspace
```bash
# Via SSH alias (recommended):
sandboxer create \
--profile profile.vm-haskell-build \
--input vm=haskell-build \
--input repo=~/projects/my-app \
--host localhost
# Via tunnel port:
export SANDBOXER_VM_TUNNEL_PORT=12222
sandboxer create \
--profile profile.vm-haskell-build \
--input vm=build@localhost \
--input tunnel_port=12222 \
--input repo=~/projects/my-app \
--host localhost
```
## Build on VM
Use `reachability.remote_dir` from create output:
```bash
ssh haskell-build "cd /build/sbx-<id> && source ~/.ghcup/env && cabal build all"
```
## Destroy
```bash
sandboxer destroy <sandbox_id>
```
Removes workspace only; the VM keeps running.
## Migration reference
`docs/migration-build-machines.md`