--- id: SAND-WP-0002 type: workplan title: "Meta-framework foundation and first extension" domain: infotech repo: sand-boxer status: finished owner: codex topic_slug: custodian created: "2026-06-22" updated: "2026-06-23" state_hub_workstream_id: "1cdfc6ba-6cd2-4d10-a78f-8fa5996ad567" --- # Meta-framework foundation and first extension Establish sand-boxer as a meta-framework: unified API contract, profile catalog, extension platform, and the first self-hosted backend (`ext.compose-ssh`) migrated from `the-custodian/e2e-framework/`. **Charter:** `INTENT.md` **Research:** `research/03-meta-framework-synthesis.md` **Predecessor:** SAND-WP-0001 (bootstrap — finished; dev workflow scaffold in place; T03 package layout partially satisfied) ## Design meta-framework contracts ```task id: SAND-WP-0002-T01 status: done priority: high state_hub_task_id: "5a45289c-3130-40f2-99e0-fbb533f56bda" ``` Author `docs/meta-framework.md` (or `specs/meta-framework.md`) defining: - Resource model: Profile, Extension, Host, Sandbox, Snapshot, Route, Meter - Lifecycle states and State Hub event mapping - Core API operations: `create`, `get`, `list`, `extend_ttl`, `recreate`, `destroy` (snapshot/restore deferred to SAND-WP-0003) - Consumer attribution schema (`adm` / `agt` / `atm`, calling project id) - Extension interface: `provision`, `wait_ready`, `teardown`, optional `estimate_cost` - Routing policy vocabulary (`prefer-self-hosted`, `lowest-cost`, `explicit`) - Security limits statement (blast-radius vs intent — per research) Derive from `research/03-meta-framework-synthesis.md`; do not duplicate harness, validator, or codegen concerns. ## Define profile and extension schemas ```task id: SAND-WP-0002-T02 status: done priority: high state_hub_task_id: "5747603d-bed2-4d4b-9287-9949befff0c2" ``` Add machine-readable schemas (JSON Schema or Python pydantic models) for: - `Profile` — extension binding, isolation, network, workspace mode, scope, TTL, resources, setup metadata, placement, reachability, cost class - `Extension` — id, capabilities, isolation levels, pricing model, regions - `SandboxCreateRequest` / `SandboxStatus` response shapes Ship `profiles/profile.compose-e2e.yaml` as the reference profile (successor to `e2e/e2e.yml` inputs; validation semantics stay with wise-validator). Register extension stub `extensions/ext.compose-ssh.yaml` with capability metadata. ## Scaffold package and developer workflow ```task id: SAND-WP-0002-T03 status: done priority: high state_hub_task_id: "c90d6aaf-8dc7-4ea2-8c78-b3c9255e331e" ``` Create Python package layout (aligned with e2e-framework lineage): ``` src/sandboxer/ # or sandboxer/ at repo root — pick one, document in AGENTS.md api/ profiles/ extensions/ lifecycle/ tests/ pyproject.toml ``` Document in `AGENTS.md`: install (`uv sync` or equivalent), test, lint, format, and CLI entry point. Satisfies SAND-WP-0001-T02 if not already done. ## Implement extension registry and loader ```task id: SAND-WP-0002-T04 status: done priority: high state_hub_task_id: "578105e8-947b-4755-aeff-181ddb85d750" ``` Implement extension discovery and registration: - Load extension definitions from `extensions/` - Plugin entry-point or explicit registry for `ext.compose-ssh` - Validate extension declares required capability fields before registration - Unit tests for load failures and duplicate ids No SaaS extensions in this workplan — self-hosted only. ## Implement ext.compose-ssh (e2e-framework lineage) ```task id: SAND-WP-0002-T05 status: done priority: high state_hub_task_id: "6262786d-1019-46a2-b745-b111dfe83620" ``` Extract and adapt provision/teardown from `the-custodian/e2e-framework/`: - SSH to configured host; isolated directory per sandbox id - Unique compose project name; `compose up` / `compose down` (idempotent) - Default-deny network posture per profile (document host-side requirements) - Host placement: read `placement.prefer` / `fallback` from profile - **Do not** port test execution, health polling, or State Hub result reporting — those are wise-validator responsibilities Provide a compatibility note in extension README for interim `make e2e` callers. ## Implement API v0 and CLI ```task id: SAND-WP-0002-T06 status: done priority: high state_hub_task_id: "79b22b16-17f3-48eb-a4ad-9eae88f94202" ``` Ship minimal establishment surface: **CLI** (primary for v0): ```bash sandbox create --profile profile.compose-e2e --input repo=/path/to/repo sandbox get sandbox list sandbox recreate sandbox destroy ``` **HTTP** (optional in v0; stub acceptable if CLI calls core library directly): - `POST /v1/sandboxes`, `GET /v1/sandboxes/{id}`, `DELETE /v1/sandboxes/{id}` Core library must be harness-agnostic — glas-harness, wise-validator, and snuggle-inventor call the same functions. ## State Hub lifecycle registration ```task id: SAND-WP-0002-T07 status: done priority: medium state_hub_task_id: "79312c62-1213-4045-8bf6-84030f6b9aa7" ``` On sandbox state transitions, emit State Hub progress events (or dedicated registration endpoint when available): - `requested`, `provisioning`, `ready`, `active`, `destroying`, `destroyed` - Include: `sandbox_id`, `profile_id`, `extension_id`, `host`, `consumer`, `actor_type`, timestamps Extend the `build-agent` self-register pattern sketch for generic sandbox identities. Document contract in meta-framework spec. ## Document sibling integration contracts ```task id: SAND-WP-0002-T08 status: done priority: medium state_hub_task_id: "27221d26-7900-46e4-8c4e-1012023afb65" ``` Add `docs/integrations/` with one page per planned sibling: | Doc | Contents | |-----|----------| | `glas-harness.md` | Sandbox handle + reachability; harness owns exec | | `wise-validator.md` | `profile.compose-e2e`; validator owns e2e.yml + health + tests | | `snuggle-inventor.md` | Setup metadata + secret_refs; no codegen in sand-boxer | Each doc: example request, response fields, ownership table, out-of-scope list. Cross-link from `INTENT.md` Coulomb boundaries section. ## Register capability and fix registry scaffold ```task id: SAND-WP-0002-T09 status: done priority: medium state_hub_task_id: "e2b089b2-3742-4feb-86c3-788a1f6ffb81" ``` - Author `registry/capabilities/execution.sandbox-provision.md` - Add row to `registry/indexes/capabilities.yaml` (`domain: infotech`) - Run `reuse-surface validate` when CLI available - Notify operator: `make fix-consistency REPO=sand-boxer` from `~/state-hub` ## Verification and migration smoke test ```task id: SAND-WP-0002-T10 status: done priority: medium state_hub_task_id: "a95755fb-cda6-4741-847c-78ef7e073cab" ``` End-to-end proof on CoulombCore or sandboxer01 (when reachable): 1. `sandbox create` with `profile.compose-e2e` for a repo with `e2e/` layout 2. Confirm `ready` state and reachability descriptor 3. Manual or scripted compose health check (not wise-validator — just proves environment exists) 4. `sandbox destroy` — confirm idempotent cleanup (no leftover compose projects or `/tmp` dirs) 5. Document runbook in `docs/runbooks/profile-compose-e2e.md` Record gaps for wise-validator migration (SAND-WP-0003) and `the-custodian` shim (SAND-WP-0004). Remote smoke passed on CoulombCore (`92.205.130.254`, `podman-compose`): `sandbox_id=4e542c51` via `./scripts/smoke-compose-e2e.sh` (2026-06-23). Migration gaps recorded in `docs/migration-gaps.md`. --- ## Out of scope (follow-on workplans) | Item | Target workplan | |------|-----------------| | wise-validator extraction + e2e test orchestration | SAND-WP-0003 | | `the-custodian` Makefile shim + deprecation timeline | SAND-WP-0004 | | `ext.vm-packer` (build-machines) | SAND-WP-0005 | | SaaS extensions + payments layer | SAND-WP-0006 | | Snapshot / restore / checkpoint profiles | SAND-WP-0007 | | Host telemetry, self-canary, stale sandbox inventory | SAND-WP-0008 | | Coulomb-native runtime (phase 5) | Backlog | ## Completion criteria - Meta-framework spec and schemas merged - `ext.compose-ssh` provisions and tears down a compose sandbox via CLI - State Hub receives lifecycle events for at least one full create→destroy cycle - Sibling integration docs published - `capability.execution.sandbox-provision` registered and validated - All tasks `done`; workplan `status: finished`; operator runs fix-consistency