Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,779 changes: 2,779 additions & 0 deletions sandbox-morph/Cargo.lock

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions sandbox-morph/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[package]
name = "iii-sandbox-morph"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/iii-hq/workers"
authors = ["iii contributors"]
publish = false

[lib]
name = "sandbox_morph"
path = "src/lib.rs"

[[bin]]
name = "iii-sandbox-morph"
path = "src/main.rs"

[dependencies]
iii-sdk = "=0.11.6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "signal"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
anyhow = "1"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
clap = { version = "4", features = ["derive"] }
async-trait = "0.1"
base64 = "0.22"

[dev-dependencies]
wiremock = "0.6"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "test-util"] }

[lints.rust]
unsafe_code = "forbid"

[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
module_name_repetitions = "allow"
must_use_candidate = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
too_many_lines = "allow"
unused_async = "allow"
uninlined_format_args = "allow"
needless_pass_by_value = "allow"
similar_names = "allow"
match_same_arms = "allow"
doc_markdown = "allow"
53 changes: 53 additions & 0 deletions sandbox-morph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# sandbox-morph

Narrow iii worker that wraps [Morph Cloud](https://cloud.morph.so) microVM sandboxes via Morph's REST API. Morph's wedge is **Infinibranch** — branching a running sandbox into N siblings with live process state preserved in roughly 250 ms. This worker is the only sandbox provider in the family that registers `branch`.

Registers the canonical `sandbox::*` ABI under the `sandbox::provider::morph::*` namespace so callers can spawn and drive Morph sandboxes through `iii.trigger(...)` without depending on Morph's SDK.

## Functions

| Function id | Purpose |
|---|---|
| `sandbox::provider::morph::create` | Boot a sandbox; returns `{sandbox_id, image, capabilities}` |
| `sandbox::provider::morph::exec` | Run a command inside a live sandbox |
| `sandbox::provider::morph::stop` | Tear down a sandbox |
| `sandbox::provider::morph::list` | Enumerate live sandboxes plus concurrency status |
| `sandbox::provider::morph::snapshot` | Snapshot a sandbox (chainable with `setup` upstream) |
| `sandbox::provider::morph::branch` | Branch a running sandbox into N siblings (Infinibranch) |
| `sandbox::provider::morph::expose_port` | Return a public URL for a port inside the sandbox |

`create` advertises capabilities `["branch", "snapshot", "expose_port"]`. `fs::read` and `fs::write` are not registered for v0 — Morph's filesystem ops are reachable via SSH-shape APIs that don't map cleanly to the channel-based FS surface used by other sandbox workers; revisit when consensus emerges.

## Configuration

`config.yaml` next to the binary, or pass `--config <path>`:

```yaml
api_base: "https://cloud.morph.so/api"
api_key_env: MORPH_API_KEY
max_concurrent_sandboxes: 10
default_idle_timeout_secs: 300
image_allowlist: []
```

`MORPH_API_KEY` must be present in the environment when the worker starts. Header sent on every upstream call: `Authorization: Bearer <MORPH_API_KEY>`.

## S-codes

Provider failures map onto a stable code space shared with the rest of the sandbox worker family:

| Code | Cause |
|---|---|
| `S100` | Image not in `image_allowlist` |
| `S400` | Concurrency cap reached |
| `S404` | Capability not supported |
| `S500` | Provider returned 429 (rate-limited) |
| `S501` | Provider returned 402 / quota exhausted |
| `S502` | Provider returned 5xx |
| `S503` | Provider returned 401 / 403 |

## Status

v0.1 ships the function registrations (including `branch`), types, error mapping, concurrency cap, and a smoke test. The HTTP call bodies that talk to Morph are stubbed and return `S502` until the next iteration wires them to the real REST endpoints. The ABI is stable.

When the real client lands, a follow-up benchmark must verify that `sandbox::provider::morph::branch` round-trips inside ~300 ms p99 against raw Morph; the wedge dies if iii's trigger envelope adds noticeable overhead.
13 changes: 13 additions & 0 deletions sandbox-morph/iii.worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
iii: v1
name: sandbox-morph
language: rust
deploy: binary
manifest: Cargo.toml
bin: iii-sandbox-morph
description: Narrow iii worker that exposes Morph microVM sandboxes via the sandbox::provider::morph::* trigger family.
config:
api_base: "https://cloud.morph.so/api"
api_key_env: MORPH_API_KEY
max_concurrent_sandboxes: 10
default_idle_timeout_secs: 300
image_allowlist: []
Loading
Loading