diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d8d6940..41136b5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,197 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.8.0 — Bytes-native output + +**Release date:** 2026-09-19 + +This release changes stdout and stderr from strings to raw bytes across every +layer of the stack. Python code can emit arbitrary binary data and it will +arrive intact — no more silent UTF-8 replacement at the sandbox boundary. +Convenience helpers make the common "just give me text" path a one-line change. + +This release also adds `SandboxPool` to the Python bindings, giving you bounded +concurrent execution with automatic lease lifecycle management. + +### Highlights + +- **stdout/stderr are now bytes** ([#448](https://github.com/eryx-org/eryx/pull/448)) — + `ExecuteResult.stdout` and `.stderr` carry raw bytes everywhere: `Vec` in + Rust, `bytes` in Python, `bytes` in the gRPC proto, and `Uint8Array` at the + WIT boundary. The JS wrapper still decodes to `string` for you. +- **SandboxPool** ([#456](https://github.com/eryx-org/eryx/pull/456)) — + factory-backed pool with bounded concurrency, automatic warm-up, per-request + callbacks/limits, and context-manager lease lifecycle. Available in Rust and + Python. +- **`NetRequest` is `#[non_exhaustive]`** ([#447](https://github.com/eryx-org/eryx/pull/447)) — + future fields won't be breaking. +- **Explicit connection cleanup** ([#446](https://github.com/eryx-org/eryx/pull/446)) — + reused sessions now release outbound connections between executions. + +### Migration guide — Rust (`eryx` crate) + +**`ExecuteResult.stdout` / `.stderr`: `String` → `Vec`** + +```rust +// Before (0.7.x) +println!("{}", result.stdout); +if result.stderr.contains("Warning") { /* ... */ } + +// After (0.8.0) — quick migration +println!("{}", result.stdout_text()); +if result.stderr_text().contains("Warning") { /* ... */ } + +// After (0.8.0) — strict UTF-8 +let output = result.stdout_utf8()?; +``` + +**`OutputHandler` trait: `&str` → `&[u8]`** + +```rust +// Before +async fn on_output(&self, chunk: &str) { /* ... */ } +async fn on_stderr(&self, chunk: &str) { /* ... */ } + +// After +async fn on_output(&self, chunk: &[u8]) { + let text = std::str::from_utf8(chunk).unwrap_or("�"); + // ... +} +async fn on_stderr(&self, chunk: &[u8]) { /* ... */ } +``` + +**`NetRequest` is `#[non_exhaustive]`** — add a `..` rest pattern if you +construct it directly: + +```rust +// Before +let req = NetRequest { url, method, headers, body }; + +// After +let req = NetRequest { url, method, headers, body, ..Default::default() }; +``` + +**`TraceEventKind` is `#[non_exhaustive]`** — add a wildcard arm to exhaustive +matches: + +```rust +match event.kind { + TraceEventKind::Line => { /* ... */ } + TraceEventKind::Call { .. } => { /* ... */ } + TraceEventKind::Return { .. } => { /* ... */ } + _ => { /* future variants */ } +} +``` + +### Migration guide — Python (`pyeryx`) + +**`ExecuteResult.stdout` / `.stderr`: `str` → `bytes`** + +```python +# Before (0.7.x) +print(result.stdout) +if "error" in result.stderr: + ... + +# After (0.8.0) — quick migration (UTF-8 with replacement) +print(result.stdout_text) +if "error" in result.stderr_text: + ... + +# After (0.8.0) — raw bytes +sys.stdout.buffer.write(result.stdout) +``` + +**Streaming callbacks: `str` → `bytes`** + +```python +# Before +def on_stdout(chunk: str) -> None: + sys.stdout.write(chunk) + +# After +def on_stdout(chunk: bytes) -> None: + sys.stdout.buffer.write(chunk) +``` + +### Migration guide — JavaScript (`@bsull/eryx`) + +**No breaking changes for most consumers.** The JS wrapper decodes +`Uint8Array` from the WASM boundary to `string` internally, so +`result.stdout` and `result.stderr` remain strings. + +If you use the **streaming `outputHandler`**, the callback signature is +unchanged — it still receives `(stream: number, data: string)`. The decoding +now happens inside the shim via `TextDecoder` with proper multi-byte sequence +handling across chunks. + +**If you access the raw WASM bindings directly** (bypassing the wrapper), the +WIT-level `report-output` import changed from `string` to `list`, and +`execute-output.stdout` / `.stderr` changed similarly. + +### Migration guide — gRPC (`eryx.v1`) + +**`ExecuteResult` and `OutputEvent` fields changed from `string` to `bytes`:** + +```diff + message OutputEvent { + OutputStream stream = 1; +- string data = 2; ++ bytes data = 2; + } + + message ExecuteResult { + bool success = 1; +- string stdout = 2; +- string stderr = 3; ++ bytes stdout = 2; ++ bytes stderr = 3; + ... + } +``` + +In most languages the generated types change from `string` to `bytes`/`[]byte`/ +`ByteString`. For Go clients: + +```go +// Before +fmt.Println(result.Stdout) + +// After +fmt.Println(string(result.Stdout)) +``` + +The wire format is backwards-compatible (protobuf `string` and `bytes` share +the same encoding), but **recompiling your client against the new proto is +required** to get the correct generated types. + +--- + +## `eryx-precompile` - [0.8.0](https://github.com/eryx-org/eryx/compare/eryx-precompile-v0.7.2...eryx-precompile-v0.8.0) - 2026-09-19 + +### Added +- [**breaking**] change stdout/stderr from string to bytes across the full stack ([#448](https://github.com/eryx-org/eryx/pull/448)) + +## `eryx` - [0.8.0](https://github.com/eryx-org/eryx/compare/eryx-v0.7.2...eryx-v0.8.0) - 2026-09-19 + +### Added +- *(python)* expose factory-backed SandboxPool with safe lease lifecycle ([#456](https://github.com/eryx-org/eryx/pull/456)) +- [**breaking**] change stdout/stderr from string to bytes across the full stack ([#448](https://github.com/eryx-org/eryx/pull/448)) +- *(net)* add explicit connection cleanup for reused sessions ([#446](https://github.com/eryx-org/eryx/pull/446)) + +### Other +- *(net)* make NetRequest non-exhaustive ([#447](https://github.com/eryx-org/eryx/pull/447)) + +## `eryx-vfs` - [0.8.0](https://github.com/eryx-org/eryx/compare/eryx-vfs-v0.7.2...eryx-vfs-v0.8.0) - 2026-09-19 + +### Other +- *(vfs)* replace cap-std with cap-primitives ([#436](https://github.com/eryx-org/eryx/pull/436)) + +## `eryx-runtime` - [0.8.0](https://github.com/eryx-org/eryx/compare/eryx-runtime-v0.7.2...eryx-runtime-v0.8.0) - 2026-09-19 + +### Added +- [**breaking**] change stdout/stderr from string to bytes across the full stack ([#448](https://github.com/eryx-org/eryx/pull/448)) + ## `eryx-precompile` - [0.7.2](https://github.com/eryx-org/eryx/compare/eryx-precompile-v0.7.1...eryx-precompile-v0.7.2) - 2026-09-11 ### Other diff --git a/Cargo.lock b/Cargo.lock index a879ec40..7374f58a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "eryx" -version = "0.7.2" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", @@ -1164,7 +1164,7 @@ dependencies = [ [[package]] name = "eryx-linker-wasm" -version = "0.7.2" +version = "0.8.0" dependencies = [ "wasm-bindgen", "wit-component 0.259.0", @@ -1172,7 +1172,7 @@ dependencies = [ [[package]] name = "eryx-macros" -version = "0.7.2" +version = "0.8.0" dependencies = [ "eryx", "proc-macro2", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "eryx-precompile" -version = "0.7.2" +version = "0.8.0" dependencies = [ "anyhow", "clap", @@ -1202,7 +1202,7 @@ dependencies = [ [[package]] name = "eryx-python" -version = "0.7.2" +version = "0.8.0" dependencies = [ "async-trait", "eryx", @@ -1220,7 +1220,7 @@ dependencies = [ [[package]] name = "eryx-runtime" -version = "0.7.2" +version = "0.8.0" dependencies = [ "anyhow", "futures", @@ -1241,7 +1241,7 @@ dependencies = [ [[package]] name = "eryx-server" -version = "0.7.2" +version = "0.8.0" dependencies = [ "async-trait", "clap", @@ -1275,7 +1275,7 @@ dependencies = [ [[package]] name = "eryx-vfs" -version = "0.7.2" +version = "0.8.0" dependencies = [ "ambient-authority", "anyhow", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "eryx-wasm-runtime" -version = "0.7.2" +version = "0.8.0" dependencies = [ "pyo3", "tokio", @@ -5255,7 +5255,7 @@ dependencies = [ [[package]] name = "wit-dylib-ffi" -version = "0.7.2" +version = "0.8.0" [[package]] name = "wit-parser" diff --git a/Cargo.toml b/Cargo.toml index 1b52dacf..0c2e7e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = ["crates/*"] # - cargo-nextest: Fast test runner (`cargo nextest run`) [workspace.package] -version = "0.7.2" +version = "0.8.0" edition = "2024" rust-version = "1.98.1" license = "MIT OR Apache-2.0" @@ -26,10 +26,10 @@ clap = { version = "^4.6.6", features = ["derive", "env"] } #unified criterion = { version = "0.8", features = ["async_tokio"] } # benchmarking crossterm = "0.29" # terminal control dashmap = "6" # concurrent maps -eryx = { path = "crates/eryx", version = "^0.7.2" } -eryx-macros = { path = "crates/eryx-macros", version = "^0.7.2" } -eryx-runtime = { path = "crates/eryx-runtime", version = "^0.7.2" } #unified -eryx-vfs = { path = "crates/eryx-vfs", version = "^0.7.2" } #unified +eryx = { path = "crates/eryx", version = "^0.8.0" } +eryx-macros = { path = "crates/eryx-macros", version = "^0.8.0" } +eryx-runtime = { path = "crates/eryx-runtime", version = "^0.8.0" } #unified +eryx-vfs = { path = "crates/eryx-vfs", version = "^0.8.0" } #unified flate2 = "1.0" futures = "0.3" futures-concurrency = "7.6"