Files
sand-boxer/docs/extension-sdk.md
tegwick 15f031fd65 feat: cloud adapters E2B/Modal and billing export (SAND-WP-0010)
Add credentialed E2B and Modal extensions, burst routing fallback,
fin-hub meter export hook, BYOK docs, and 77 tests.
2026-06-24 12:50:19 +02:00

113 lines
3.3 KiB
Markdown

# 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): `estimate_cost(profile, duration) → MeterQuote`
Optional (checkpoints): `supports_snapshots()`, `snapshot(handle)`,
`restore_from_snapshot(profile, snapshot_meta, inputs, host)`
### 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 + tar snapshots |
| `ext.vm-packer` | `vm_packer.py` | Attach workspace on pre-built VM |
| `ext.saas-stub` | `saas_stub.py` | Metered stub + metadata snapshots |
| `ext.e2b` | `e2b.py` | E2B cloud adapter |
| `ext.modal` | `modal.py` | Modal cloud adapter |
## 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")
```
## Metered extensions (SAND-WP-0006)
Implement `estimate_cost` and `meter_actual` on `SandboxExtension`. Register with
`pricing_model: metered`. See `docs/payments.md` and `ext.saas-stub`.
## Deferred
| Feature | Workplan |
|---------|----------|
| Packer build orchestration from `create` | Future WP |
| Daytona OSS cloud adapter | Future WP |
| fin-hub billing export | Future |
| Cross-host snapshot transfer | Future |