kobako 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +30 -0
  4. data/Cargo.lock +3 -3
  5. data/README.md +3 -3
  6. data/ROADMAP.md +26 -0
  7. data/crates/kobako-runtime/CHANGELOG.md +7 -0
  8. data/crates/kobako-runtime/Cargo.toml +1 -1
  9. data/crates/kobako-runtime/README.md +1 -1
  10. data/crates/kobako-runtime/src/error.rs +8 -0
  11. data/crates/kobako-wasmtime/CHANGELOG.md +7 -0
  12. data/crates/kobako-wasmtime/Cargo.toml +2 -2
  13. data/crates/kobako-wasmtime/README.md +1 -1
  14. data/crates/kobako-wasmtime/src/driver.rs +1 -1
  15. data/crates/kobako-wasmtime/src/frames.rs +1 -1
  16. data/crates/kobako-wasmtime/src/instance_pre.rs +7 -6
  17. data/crates/kobako-wasmtime/src/invocation.rs +4 -5
  18. data/crates/kobako-wasmtime/src/trap.rs +42 -21
  19. data/data/kobako.wasm +0 -0
  20. data/ext/kobako/Cargo.toml +1 -1
  21. data/lib/kobako/catalog/handles.rb +2 -2
  22. data/lib/kobako/catalog/namespaces.rb +2 -2
  23. data/lib/kobako/catalog/snippets.rb +1 -1
  24. data/lib/kobako/codec/decoder.rb +7 -7
  25. data/lib/kobako/codec/encoder.rb +2 -2
  26. data/lib/kobako/codec/error.rb +3 -3
  27. data/lib/kobako/codec/factory.rb +10 -10
  28. data/lib/kobako/codec/handle_walk.rb +17 -21
  29. data/lib/kobako/codec/utils.rb +9 -9
  30. data/lib/kobako/codec.rb +3 -3
  31. data/lib/kobako/errors.rb +14 -39
  32. data/lib/kobako/handle.rb +5 -2
  33. data/lib/kobako/namespace.rb +3 -3
  34. data/lib/kobako/outcome.rb +1 -0
  35. data/lib/kobako/sandbox.rb +10 -12
  36. data/lib/kobako/transport/dispatcher.rb +17 -14
  37. data/lib/kobako/transport/request.rb +2 -2
  38. data/lib/kobako/transport/response.rb +2 -2
  39. data/lib/kobako/transport/run.rb +1 -1
  40. data/lib/kobako/transport/yield.rb +3 -3
  41. data/lib/kobako/transport/yielder.rb +6 -6
  42. data/lib/kobako/version.rb +1 -1
  43. data/release-please-config.json +37 -7
  44. data/sig/kobako/codec/handle_walk.rbs +2 -2
  45. data/sig/kobako/codec.rbs +11 -0
  46. data/sig/kobako/handle.rbs +0 -2
  47. data/sig/kobako/transport/dispatcher.rbs +8 -8
  48. data/sig/kobako/transport/run.rbs +1 -1
  49. data/sig/kobako/transport/yielder.rbs +2 -2
  50. data/sig/kobako/transport.rbs +8 -0
  51. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 405db05c3ccf4250860f660bb571458adcedfeb3107120ab1cd87f7150e0b977
4
- data.tar.gz: e3d85aab1c493c3b2545c94bcdd47928d3d07eea8870f269d1107cbe8115b5cc
3
+ metadata.gz: f89723d7b8cc8160176c299eaf28548ff9c7efda380ce545a7676d5d7f7cfdb6
4
+ data.tar.gz: 3494ee4ff50aef160504b53eb75b0192e9e9c7108d165db39b3903fb4e7343df
5
5
  SHA512:
6
- metadata.gz: 6bd3b70b07a03d947b2b6142ec476bbba3f66699d1228ae0f26e148e6d7b9fbe0b4b2124a4033c4816008fd89254e7f971b87624d6a93e26f92cd16e070c6cb5
7
- data.tar.gz: 79a961448a3db38cd4a662159eb0f6df1aebdf628a54f0d1e1896f9095d668e71998926f610268cf31fe1f0a0c7091a4f31a970485f67e5f4d152852ad8f014f
6
+ metadata.gz: 26c982a047b82e32dcd4017ce603d6c4094bb6d4cd34f38e1df6eacba253cdb9589a8092fe0558e0ddcb64f9acacb4ef191f51916c37dffebf4f1ee0bc52a4fb
7
+ data.tar.gz: b593374ee6c18e132535813aa046a7059aca5a9f0d16c6b32519f8154a6d76b2fc5e7b2b4791e5213e26b7828748f64f3ec61b00029a9bb6cf0cc6336a48fdf0
@@ -1 +1 @@
1
- {".":"0.13.0","wasm/kobako-core":"0.7.0","wasm/kobako":"0.7.0","wasm/kobako-io":"0.7.0","wasm/kobako-json":"0.7.0","wasm/kobako-regexp":"0.7.0","wasm/kobako-baker":"0.7.0","crates/kobako-codec":"0.7.0","crates/kobako-runtime":"0.7.0","crates/kobako-wasmtime":"0.7.0"}
1
+ {".":"0.14.0","wasm/kobako-core":"0.8.0","wasm/kobako-mruby":"0.8.0","wasm/kobako-io":"0.8.0","wasm/kobako-json":"0.8.0","wasm/kobako-regexp":"0.8.0","wasm/kobako-baker":"0.8.0","crates/kobako-codec":"0.8.0","crates/kobako-runtime":"0.8.0","crates/kobako-wasmtime":"0.8.0","crates/kobako":"0.8.0"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.0](https://github.com/elct9620/kobako/compare/v0.13.0...v0.14.0) (2026-07-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * **codec:** add the Run invocation envelope to the wire tier ([dbdd760](https://github.com/elct9620/kobako/commit/dbdd760ddc258681669f7f620f63b75f36322687))
9
+ * **crates:** add the kobako host SDK skeleton ([8a99d09](https://github.com/elct9620/kobako/commit/8a99d09ef7068a6738d44f1a735d39516b24156b))
10
+ * **crates:** add the parity runner to the kobako SDK ([998f059](https://github.com/elct9620/kobako/commit/998f059abd308ef921c295658aaf8377febb44e2))
11
+ * **crates:** grow the SDK capability-Handle table ([f93fe8f](https://github.com/elct9620/kobako/commit/f93fe8f3dce2509cfa527229f8f593f4d816b940))
12
+ * **crates:** grow the SDK Member block-yield seam ([4404713](https://github.com/elct9620/kobako/commit/44047130f309a2c935198077fb4f7f86839355e7))
13
+ * **crates:** grow the SDK preload and run invocation seams ([d8d5fe2](https://github.com/elct9620/kobako/commit/d8d5fe268a56d45dad4f8b35a25e942a559dcd5f))
14
+ * **crates:** honor the respond_to_guest narrowing on the SDK Member seam ([0f5eff1](https://github.com/elct9620/kobako/commit/0f5eff16a1e9f2229ad9d9c9316bf94e92035301))
15
+ * **crates:** let a resolved Handle recover its concrete member type ([abd5502](https://github.com/elct9620/kobako/commit/abd55029a44a7631d323c3aec3b625d9692f9c5b))
16
+ * **crates:** mark the SDK Error taxonomy non_exhaustive ([001fc69](https://github.com/elct9620/kobako/commit/001fc69e637d2c046f55cb517f9d9cf931793715))
17
+ * **examples:** add the guest→host dispatch half to the Rust host ([5cdde68](https://github.com/elct9620/kobako/commit/5cdde68824352c5685aaa94ae464e909705a4648))
18
+ * **examples:** assemble a minimal Rust host on the published crates ([6b98680](https://github.com/elct9620/kobako/commit/6b98680bcb625f9790f44df839035427a576e266))
19
+ * **tasks:** add a rails-stats-style rake stats size report ([c02d530](https://github.com/elct9620/kobako/commit/c02d530d43a85594323b7801fdbe75aa95ae7f8c))
20
+ * **tasks:** gate parity coverage over the CORE anchor manifest ([d26972c](https://github.com/elct9620/kobako/commit/d26972c1ad51e05df951643e17d66a6c811ba862))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **crates:** give SetupError a Display so Error::Setup reads cleanly ([bc8b128](https://github.com/elct9620/kobako/commit/bc8b128c9e296319c7fef47441b412d2ce345dff))
26
+ * **crates:** keep the no-timeout epoch deadline within range ([a3255df](https://github.com/elct9620/kobako/commit/a3255df98a77825bb39b571a60fe6ff83d269d19))
27
+ * **crates:** reject trailing bytes on Request and Run decode ([8e4929b](https://github.com/elct9620/kobako/commit/8e4929b64f3ea690f38211888070c2511da84754))
28
+ * **lib:** close the Data #with seam on Handle construction ([c0b50ae](https://github.com/elct9620/kobako/commit/c0b50aec659e32a72c905aaf94c939ac009ad621))
29
+ * **release:** resume the gem push loop past already-live versions ([170ee9b](https://github.com/elct9620/kobako/commit/170ee9bae6da3a65b4016040259e31e4721dfcfb))
30
+ * **release:** treat a yanked-only version as unpublished when publishing ([3111cc0](https://github.com/elct9620/kobako/commit/3111cc072a7c9d18b129fd2762f267d855b48d55))
31
+ * **transport:** fold fault messages to UTF-8 before they ride the wire ([0be8d40](https://github.com/elct9620/kobako/commit/0be8d4068dc5647f86746e80b356b0b9d52051f7))
32
+
3
33
  ## [0.13.0](https://github.com/elct9620/kobako/compare/v0.12.2...v0.13.0) (2026-07-03)
4
34
 
5
35
 
data/Cargo.lock CHANGED
@@ -929,7 +929,7 @@ dependencies = [
929
929
 
930
930
  [[package]]
931
931
  name = "kobako"
932
- version = "0.13.0"
932
+ version = "0.14.0"
933
933
  dependencies = [
934
934
  "kobako-runtime",
935
935
  "kobako-wasmtime",
@@ -938,11 +938,11 @@ dependencies = [
938
938
 
939
939
  [[package]]
940
940
  name = "kobako-runtime"
941
- version = "0.7.0"
941
+ version = "0.8.0"
942
942
 
943
943
  [[package]]
944
944
  name = "kobako-wasmtime"
945
- version = "0.7.0"
945
+ version = "0.8.0"
946
946
  dependencies = [
947
947
  "kobako-runtime",
948
948
  "libc",
data/README.md CHANGED
@@ -302,10 +302,10 @@ sandbox.run(:Greeter, name: "world") # => "hello, world"
302
302
 
303
303
  | Form | Signature | Snippet name source | Validation timing |
304
304
  |----------|--------------------------------------|---------------------------------------|----------------------------------------------------------------------------|
305
- | Source | `preload(code: "...", name: :Const)` | The `name:` keyword | Trial-compiled at preload; compile errors raise immediately |
306
- | Bytecode | `preload(binary: bytes)` | Read from the bytecode's `debug_info` | Deferred to first invocation; failure raises `Kobako::BytecodeError` |
305
+ | Source | `preload(code: "...", name: :Const)` | The `name:` keyword | First invocation's replay; compile errors raise `Kobako::SandboxError` |
306
+ | Bytecode | `preload(binary: bytes)` | Read from the bytecode's `debug_info` | First invocation's replay; structural failure raises `Kobako::BytecodeError` |
307
307
 
308
- Use the source form for snippets authored in your repo (compile errors fail fast at `#preload`); use the bytecode form when snippets ship as build artifacts from a separate `mrbc` pipeline. Both replay through the same per-invocation path.
308
+ Use the source form for snippets authored in your repo; use the bytecode form when snippets ship as build artifacts from a separate `mrbc` pipeline. Both replay through the same per-invocation path, so no snippet content failure surfaces at `#preload` — force the first replay with a no-op invocation (e.g. `sandbox.eval("nil")`) when you want validation before real traffic.
309
309
 
310
310
  ## Security
311
311
 
data/ROADMAP.md ADDED
@@ -0,0 +1,26 @@
1
+ # Roadmap
2
+
3
+ kobako is a Ruby gem providing an in-process Wasm sandbox for untrusted mruby
4
+ code: a wasmtime host runs a precompiled `kobako.wasm` guest, with host↔guest
5
+ Transport over a MessagePack wire. Features cover one-shot `#eval`, preload +
6
+ `#run` dispatch, Service injection through Namespaces / Members, opaque
7
+ Capability Handles, block yield re-entry, three-class error attribution,
8
+ output capture, and a warm Sandbox pool.
9
+
10
+ | Feature | Entry Points | Notes |
11
+ |---------|-------------|-------|
12
+ | ✅ [F-01 Sandbox instantiation](docs/behavior/lifecycle.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | B-22 (per-Thread isolation) is exercised only indirectly through the pool contention tests; no test cites it |
13
+ | ✅ [F-02 Namespace declaration](docs/behavior/registration.md) | [lib/kobako/catalog/namespaces.rb](lib/kobako/catalog/namespaces.rb) | — |
14
+ | ✅ [F-03 Member binding](docs/behavior/registration.md) | [lib/kobako/namespace.rb](lib/kobako/namespace.rb) | — |
15
+ | ✅ [F-04 Synchronous mruby source execution (`#eval`)](docs/behavior/lifecycle.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
16
+ | ✅ [F-05 Guest-initiated Transport dispatch](docs/behavior/dispatch.md) | [lib/kobako/transport/dispatcher.rb](lib/kobako/transport/dispatcher.rb) | — |
17
+ | ✅ [F-06 Capability Handle encoding and referencing](docs/behavior/dispatch.md) | [lib/kobako/catalog/handles.rb](lib/kobako/catalog/handles.rb) | — |
18
+ | ✅ [F-07 Three-class error attribution and raising](docs/behavior/errors.md) | [lib/kobako/outcome.rb](lib/kobako/outcome.rb) | E-26 (guest-entry envelope decode failure) has no exercising test — not reachable through the public API |
19
+ | ✅ [F-08 Guest output capture](docs/behavior/lifecycle.md) | [lib/kobako/capture.rb](lib/kobako/capture.rb) | — |
20
+ | ✅ [F-09 Host–guest message codec](docs/wire-codec.md) | [lib/kobako/codec/](lib/kobako/codec/) | — |
21
+ | ✅ [F-10 Reproducible build pipeline](SPEC.md#code-organization) | [tasks/wasm/build.rake](tasks/wasm/build.rake) | Verified by build-time gates (`rake anchors`, double-bake byte-identity, gemspec whitelist), not `test/` |
22
+ | ✅ [F-11 Multi-layer test and benchmark suite](SPEC.md#testing-style) | [test/](test/) | Benchmarks live in [benchmark/](benchmark/) with the gate in `tasks/bench/`; the anchor baseline advances only by deliberate re-bless |
23
+ | ✅ [F-12 Guest block reception and yield re-entry](docs/behavior/yield.md) | [lib/kobako/transport/yielder.rb](lib/kobako/transport/yielder.rb) | — |
24
+ | ✅ [F-13 Snippet preloading (`#preload`)](docs/behavior/invocation.md) | [lib/kobako/catalog/snippets.rb](lib/kobako/catalog/snippets.rb) | — |
25
+ | ✅ [F-14 Synchronous entrypoint dispatch (`#run`)](docs/behavior/invocation.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
26
+ | ✅ [F-15 Warm Sandbox pool checkout (`Kobako::Pool`)](docs/behavior/runtime.md) | [lib/kobako/pool.rb](lib/kobako/pool.rb) | — |
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.7.0...kobako-runtime-v0.8.0) (2026-07-08)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **crates:** give SetupError a Display so Error::Setup reads cleanly ([bc8b128](https://github.com/elct9620/kobako/commit/bc8b128c9e296319c7fef47441b412d2ce345dff))
9
+
3
10
  ## [0.7.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.6.1...kobako-runtime-v0.7.0) (2026-07-03)
4
11
 
5
12
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  [package]
15
15
  name = "kobako-runtime"
16
- version = "0.7.0"
16
+ version = "0.8.0"
17
17
  edition = "2021"
18
18
  description = "Engine-neutral host runtime contract for embedding kobako Wasm guests."
19
19
  license = "Apache-2.0"
@@ -26,7 +26,7 @@ map the neutral types onto their own language surface.
26
26
 
27
27
  ```toml
28
28
  [dependencies]
29
- kobako-runtime = "0.7.0" # x-release-please-version
29
+ kobako-runtime = "0.8.0" # x-release-please-version
30
30
  ```
31
31
 
32
32
  ## License
@@ -62,3 +62,11 @@ impl fmt::Display for Trap {
62
62
  f.write_str(msg)
63
63
  }
64
64
  }
65
+
66
+ impl fmt::Display for SetupError {
67
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68
+ let (SetupError::ModuleNotBuilt(msg) | SetupError::Dead(msg) | SetupError::Intact(msg)) =
69
+ self;
70
+ f.write_str(msg)
71
+ }
72
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.7.0...kobako-wasmtime-v0.8.0) (2026-07-08)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **crates:** keep the no-timeout epoch deadline within range ([a3255df](https://github.com/elct9620/kobako/commit/a3255df98a77825bb39b571a60fe6ff83d269d19))
9
+
3
10
  ## [0.7.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.6.1...kobako-wasmtime-v0.7.0) (2026-07-03)
4
11
 
5
12
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  [package]
17
17
  name = "kobako-wasmtime"
18
- version = "0.7.0"
18
+ version = "0.8.0"
19
19
  edition = "2021"
20
20
  description = "wasmtime implementation of the kobako host runtime contract."
21
21
  license = "Apache-2.0"
@@ -28,7 +28,7 @@ categories = ["wasm", "virtualization"]
28
28
  # The engine-neutral contract this crate implements. The version pin
29
29
  # rides the linked release group; the path keeps in-tree builds (and
30
30
  # the Ruby gem, which ships both crates) resolving locally.
31
- kobako-runtime = { version = "0.7.0", path = "../kobako-runtime" }
31
+ kobako-runtime = { version = "0.8.0", path = "../kobako-runtime" }
32
32
  # wasmtime — host-side embedder for kobako.wasm. We disable default-features
33
33
  # and opt back in only what kobako needs: a Cranelift-backed runtime that can
34
34
  # compile a pre-built wasm32-wasip1 module on the host triple, plus the `wat`
@@ -24,7 +24,7 @@ SDK consumes the same surface.
24
24
 
25
25
  ```toml
26
26
  [dependencies]
27
- kobako-wasmtime = "0.7.0" # x-release-please-version
27
+ kobako-wasmtime = "0.8.0" # x-release-please-version
28
28
  ```
29
29
 
30
30
  ## License
@@ -183,7 +183,7 @@ impl Driver {
183
183
  }
184
184
  None => {
185
185
  store.data_mut().set_deadline(None);
186
- store.set_epoch_deadline(u64::MAX);
186
+ store.set_epoch_deadline(trap::NO_TIMEOUT_EPOCH_DELTA);
187
187
  }
188
188
  }
189
189
  let baseline = match exports.memory {
@@ -225,7 +225,7 @@ mod tests {
225
225
  profile,
226
226
  };
227
227
  let mut store = WtStore::new(engine, Invocation::new(None));
228
- store.set_epoch_deadline(u64::MAX);
228
+ store.set_epoch_deadline(crate::trap::NO_TIMEOUT_EPOCH_DELTA);
229
229
  install_wasi_frames(&mut store, &config, &[]).expect("WASI context must install");
230
230
 
231
231
  let mut linker: Linker<Invocation> = Linker::new(engine);
@@ -70,12 +70,13 @@ fn build_linker() -> Result<Linker<Invocation>, SetupError> {
70
70
  // `__kobako_dispatch` host import. Signature per docs/wire-codec.md
71
71
  // § ABI Signatures:
72
72
  // (req_ptr: i32, req_len: i32) -> i64
73
- // Decodes the Request bytes, dispatches via the Ruby-side
74
- // dispatch Proc (bound per-Sandbox through `Runtime#on_dispatch=`),
75
- // allocates a guest buffer through `__kobako_alloc`, writes
76
- // the Response bytes there, and returns the packed
77
- // `(ptr<<32)|len`. The dispatcher returns 0 on any wire-layer
78
- // fault (including no Proc bound); see `dispatch::handle`.
73
+ // Reads the Request bytes from guest memory and hands them —
74
+ // undecoded to the bound `DispatchHandler` (the frontend's
75
+ // dispatch bridge, e.g. a Ruby Proc), then allocates a guest
76
+ // buffer through `__kobako_alloc`, writes the handler's Response
77
+ // bytes there, and returns the packed `(ptr<<32)|len`. The
78
+ // dispatcher returns 0 on any wire-layer fault (including no
79
+ // handler bound); see `dispatch::handle`.
79
80
  linker
80
81
  .func_wrap(
81
82
  "env",
@@ -318,10 +318,9 @@ impl ResourceLimiter for MemoryLimiter {
318
318
 
319
319
  /// Marker error returned from `MemoryLimiter::memory_growing` when the
320
320
  /// per-invocation memory cap is exceeded. Downcast from the wasmtime
321
- /// trap error to surface as
322
- /// `Kobako::MemoryLimitError` on the Ruby side. Callers use the
323
- /// `Display` impl below — no field is read directly — so the inner
324
- /// state stays private.
321
+ /// trap error to classify the failure as a memory-limit `Trap`. Callers
322
+ /// use the `Display` impl below no field is read directly — so the
323
+ /// inner state stays private.
325
324
  #[derive(Debug)]
326
325
  pub(crate) struct MemoryLimitTrap {
327
326
  desired: usize,
@@ -354,7 +353,7 @@ impl std::error::Error for MemoryLimitTrap {}
354
353
 
355
354
  /// Marker error returned from the epoch-deadline callback when the
356
355
  /// wall-clock deadline is exceeded. Downcast from the wasmtime trap
357
- /// error to surface as `Kobako::TimeoutError` on the Ruby side.
356
+ /// error to classify the failure as a timeout `Trap`.
358
357
  #[derive(Debug)]
359
358
  pub(crate) struct TimeoutTrap;
360
359
 
@@ -1,12 +1,13 @@
1
1
  //! Trap classification for the run path.
2
2
  //!
3
- //! Maps a `wasmtime` run error to the right top-level `Kobako::*` Ruby
4
- //! exception (`TimeoutError` / `MemoryLimitError` / `TrapError`), and
5
- //! hosts the epoch-deadline callback that raises the wall-clock
6
- //! `TimeoutTrap`. The classification is a pure function over the error's
7
- //! downcast chain so it can be exercised from `cargo test` without the
8
- //! magnus surface; the trap marker types themselves live in
9
- //! `crate::invocation` (where the limiter / callback construct them).
3
+ //! Classifies a `wasmtime` run error into the engine-neutral `Trap`
4
+ //! kind (`Timeout` / `MemoryLimit` / `Other`) that each frontend maps
5
+ //! onto its own error surface, and hosts the epoch-deadline callback
6
+ //! that raises the wall-clock `TimeoutTrap`. The classification is a
7
+ //! pure function over the error's downcast chain so it can be exercised
8
+ //! from `cargo test` without any frontend; the trap marker types
9
+ //! themselves live in `crate::invocation` (where the limiter / callback
10
+ //! construct them).
10
11
 
11
12
  use std::time::Instant;
12
13
 
@@ -15,43 +16,48 @@ use wasmtime::{StoreContextMut, UpdateDeadline};
15
16
  use crate::invocation::{Invocation, MemoryLimitTrap, TimeoutTrap};
16
17
  use kobako_runtime::error::{SetupError, Trap};
17
18
 
19
+ /// Epoch delta that keeps the deadline effectively unreachable when no
20
+ /// wall-clock cap is configured. Half the epoch range rather than
21
+ /// `u64::MAX`: wasmtime adds the delta to the engine's current epoch,
22
+ /// which the process-wide ticker advances for the engine's whole
23
+ /// lifetime, so the full range overflows the sum (a panic under debug
24
+ /// overflow checks).
25
+ pub(crate) const NO_TIMEOUT_EPOCH_DELTA: u64 = u64::MAX / 2;
26
+
18
27
  /// Epoch-deadline callback installed on every Store. Read the per-run
19
28
  /// wall-clock deadline from `Invocation` and trap with
20
29
  /// `TimeoutTrap` once the deadline has passed; otherwise extend the
21
30
  /// next check by one tick of the process-wide epoch ticker. When the
22
31
  /// deadline is `None` the callback should not fire under the normal
23
32
  /// `Driver` invoke flow because
24
- /// `set_epoch_deadline(u64::MAX)` is used; returning a long extension
25
- /// keeps the callback inert as a defence in depth.
33
+ /// `NO_TIMEOUT_EPOCH_DELTA` is primed; returning the same long
34
+ /// extension keeps the callback inert as a defence in depth.
26
35
  pub(crate) fn epoch_deadline_callback(
27
36
  ctx: StoreContextMut<'_, Invocation>,
28
37
  ) -> wasmtime::Result<UpdateDeadline> {
29
38
  match ctx.data().deadline() {
30
39
  Some(deadline) if Instant::now() >= deadline => Err(wasmtime::Error::new(TimeoutTrap)),
31
40
  Some(_) => Ok(UpdateDeadline::Continue(1)),
32
- None => Ok(UpdateDeadline::Continue(u64::MAX / 2)),
41
+ None => Ok(UpdateDeadline::Continue(NO_TIMEOUT_EPOCH_DELTA)),
33
42
  }
34
43
  }
35
44
 
36
45
  /// Configured-cap path classification for a wasmtime error. The
37
- /// downcast logic stays in a pure helper so the
38
- /// `Kobako::TimeoutError` / `MemoryLimitError` /
39
- /// `Kobako::TrapError` mapping can be exercised from `cargo test`
40
- /// without the magnus surface.
46
+ /// downcast logic stays in a pure helper so the `Trap` kind routing
47
+ /// can be exercised from `cargo test` without any frontend.
41
48
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
42
49
  enum TrapClass {
43
50
  /// Wall-clock cap path.
44
51
  Timeout,
45
52
  /// Linear-memory cap path.
46
53
  MemoryLimit,
47
- /// Any other wasmtime error — surfaces as the base
48
- /// `Kobako::TrapError`.
54
+ /// Any other wasmtime error — surfaces as the base `Trap::Other`.
49
55
  Other,
50
56
  }
51
57
 
52
- /// Inspect a wasmtime error to decide which top-level `Kobako::*` trap
53
- /// class it should map to. Pure function — operates on the error's
54
- /// downcast chain only, no magnus / Ruby state required.
58
+ /// Inspect a wasmtime error to decide which neutral trap kind it
59
+ /// belongs to. Pure function — operates on the error's downcast chain
60
+ /// only, no frontend state required.
55
61
  fn classify_trap(err: &wasmtime::Error) -> TrapClass {
56
62
  if err.downcast_ref::<TimeoutTrap>().is_some() {
57
63
  TrapClass::Timeout
@@ -123,8 +129,23 @@ pub(crate) fn instantiate_err(err: wasmtime::Error) -> SetupError {
123
129
 
124
130
  #[cfg(test)]
125
131
  mod tests {
126
- use super::{classify_trap, other_trap_message, TrapClass};
127
- use crate::invocation::{MemoryLimitTrap, TimeoutTrap};
132
+ use super::{classify_trap, other_trap_message, TrapClass, NO_TIMEOUT_EPOCH_DELTA};
133
+ use crate::invocation::{Invocation, MemoryLimitTrap, TimeoutTrap};
134
+
135
+ // The no-timeout priming delta is added to the engine's current
136
+ // epoch inside wasmtime, and the process-wide ticker advances that
137
+ // epoch from the first `shared_engine` call on — so the sum must
138
+ // stay in range for a long-lived engine, not just a fresh one.
139
+ // `increment_epoch` stands in for the ticker to make the ticked
140
+ // state deterministic; under debug overflow checks an overflowing
141
+ // delta panics right here.
142
+ #[test]
143
+ fn no_timeout_delta_survives_a_ticked_engine_epoch() {
144
+ let engine = crate::cache::shared_engine().expect("shared engine must be constructible");
145
+ engine.increment_epoch();
146
+ let mut store = wasmtime::Store::new(engine, Invocation::new(None));
147
+ store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
148
+ }
128
149
 
129
150
  #[test]
130
151
  fn classify_trap_routes_timeout_trap_to_timeout() {
data/data/kobako.wasm CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "kobako"
3
- version = "0.13.0"
3
+ version = "0.14.0"
4
4
  edition = "2021"
5
5
  authors = ["Aotokitsuruya <contact@aotoki.me>"]
6
6
  license = "Apache-2.0"
@@ -95,7 +95,7 @@ module Kobako
95
95
  end
96
96
  end
97
97
 
98
- # Guard {#alloc} against issuing an ID past the cap. Returns +nil+
98
+ # Guard #alloc against issuing an ID past the cap. Returns +nil+
99
99
  # on success; raises +Kobako::HandleExhaustedError+ at exhaustion.
100
100
  def ensure_capacity!
101
101
  cap = Kobako::Handle::MAX_ID
@@ -107,7 +107,7 @@ module Kobako
107
107
  end
108
108
 
109
109
  # Single source of truth for the "unknown Handle id" raise used by
110
- # {#fetch}. Returns +nil+ on success; raises +Kobako::SandboxError+
110
+ # #fetch. Returns +nil+ on success; raises +Kobako::SandboxError+
111
111
  # when +id+ is not currently bound.
112
112
  def require_bound!(id)
113
113
  return if @entries.key?(id)
@@ -71,7 +71,7 @@ module Kobako
71
71
  end
72
72
 
73
73
  # Encode the preamble as msgpack bytes for stdin Frame 1 delivery.
74
- # Routes through {Kobako::Codec::Encoder} like every other host-side
74
+ # Routes through Kobako::Codec::Encoder like every other host-side
75
75
  # wire encode so there is a single codec path; the preamble carries
76
76
  # only Strings and Arrays, so none of the kobako ext types actually
77
77
  # fire. Structure: +[["Namespace", ["MemberA", "MemberB"]], ...]+.
@@ -106,7 +106,7 @@ module Kobako
106
106
  self
107
107
  end
108
108
 
109
- # Returns +true+ when {#seal!} has been called, +false+ otherwise.
109
+ # Returns +true+ when #seal! has been called, +false+ otherwise.
110
110
  def sealed?
111
111
  @sealed
112
112
  end
@@ -43,7 +43,7 @@ module Kobako
43
43
  #
44
44
  # The bytes are memoized — the table is replayed verbatim on every
45
45
  # invocation after sealing, so Frame 3 never changes between
46
- # encodes; {#register} drops the memo while the table is still open.
46
+ # encodes; #register drops the memo while the table is still open.
47
47
  # Unlike +Catalog::Namespaces#encode+, which gates its memo on the
48
48
  # seal, this one can fill eagerly and invalidate in +#register+
49
49
  # because every mutation funnels through that single method — there is
@@ -12,23 +12,23 @@ module Kobako
12
12
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Type Mapping).
13
13
  #
14
14
  # Translates msgpack gem exceptions into the kobako error taxonomy
15
- # ({Truncated}, {InvalidType}, {InvalidEncoding}, {UnsupportedType}) so
15
+ # (Truncated, InvalidType, InvalidEncoding, UnsupportedType) so
16
16
  # callers can pattern-match on the SPEC's wire-violation categories
17
17
  # without leaking the gem's internal exception classes.
18
18
  #
19
- # Public API is a single function — {.decode}. The decoder is
19
+ # Public API is a single function — +.decode+. The decoder is
20
20
  # stateless; the +MessagePack::Unpacker+ instance is built per call
21
21
  # because callers always decode exactly one wire value at a time.
22
22
  module Decoder
23
23
  # Decode +bytes+ into one Ruby value and validate transitively
24
- # against the SPEC type mapping. Raises {Truncated}, {InvalidType},
25
- # or {InvalidEncoding} on wire violations.
24
+ # against the SPEC type mapping. Raises Truncated, InvalidType,
25
+ # or InvalidEncoding on wire violations.
26
26
  #
27
27
  # When a block is given, the decoded value is yielded and the block's
28
28
  # result is returned — wire Value Objects use this to build themselves
29
29
  # from the decoded payload. The block runs inside this method's
30
30
  # rescue, so a Value Object's +ArgumentError+ invariant failure
31
- # surfaces as {InvalidType} without a separate {Utils.with_boundary}
31
+ # surfaces as InvalidType without a separate Utils.with_boundary
32
32
  # wrapper at the call site.
33
33
  def self.decode(bytes)
34
34
  value = Factory.load(bytes.b)
@@ -37,7 +37,7 @@ module Kobako
37
37
  # msgpack gem raises the format/type errors below; +ArgumentError+
38
38
  # comes from our ext-type validators (Handle id range, Exception type
39
39
  # whitelist) and from a yielded block's Value Object invariants — both
40
- # are wire violations, so both map to {InvalidType}.
40
+ # are wire violations, so both map to InvalidType.
41
41
  rescue ::MessagePack::UnknownExtTypeError, ::MessagePack::MalformedFormatError,
42
42
  ::MessagePack::StackError, ::ArgumentError => e
43
43
  raise InvalidType, e.message
@@ -53,7 +53,7 @@ module Kobako
53
53
  # Encoding Rules). The msgpack gem returns UTF-8-tagged Strings for
54
54
  # str family but does not validate the bytes; +bin+ family decodes
55
55
  # to ASCII-8BIT. Walk the tree once and reject invalid UTF-8 in any
56
- # str-typed leaf via {Utils.assert_utf8!}. {Kobako::Fault}
56
+ # str-typed leaf via Utils.assert_utf8!. Kobako::Fault
57
57
  # payloads are validated transitively: +Factory.unpack_fault+
58
58
  # feeds the inner ext-0x02 bytes back through this Decoder, so their
59
59
  # +str+ fields are already covered by the time control returns here.
@@ -14,9 +14,9 @@ module Kobako
14
14
  # strings, arrays, and maps go through the gem's narrowest-encoding
15
15
  # logic; the three kobako-specific ext types (0x00 Symbol, 0x01
16
16
  # Capability Handle, 0x02 Exception envelope) are registered on
17
- # the cached {Kobako::Codec::Factory} singleton.
17
+ # the cached Kobako::Codec::Factory singleton.
18
18
  #
19
- # Public API is a single function — {.encode}. The codec is stateless;
19
+ # Public API is a single function — +.encode+. The codec is stateless;
20
20
  # there is no buffer accumulator and no streaming write API. Callers
21
21
  # that need to concatenate multiple encodings build the bytes
22
22
  # themselves.
@@ -7,11 +7,11 @@ module Kobako
7
7
  # The wire codec implements the binary contract pinned in
8
8
  # {docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Type Mapping.
9
9
  # Every wire violation surfaces as a
10
- # subclass of {Error} so callers can pattern-match on the specific
10
+ # subclass of Error so callers can pattern-match on the specific
11
11
  # fault while still rescuing all codec faults via this base class.
12
12
  #
13
13
  # Higher layers (e.g. the Sandbox dispatch loop) translate these into
14
- # the public {Kobako::SandboxError} / {Kobako::TrapError} taxonomy.
14
+ # the public Kobako::SandboxError / Kobako::TrapError taxonomy.
15
15
  class Error < StandardError; end
16
16
 
17
17
  # Input ended before the type prefix or payload was fully consumed.
@@ -21,7 +21,7 @@ module Kobako
21
21
  # type mapping (e.g. an unknown ext code, or a reserved msgpack tag).
22
22
  class InvalidType < Error; end
23
23
 
24
- # A msgpack `str` payload was not valid UTF-8, or an ext 0x00 Symbol
24
+ # A msgpack +str+ payload was not valid UTF-8, or an ext 0x00 Symbol
25
25
  # payload was not valid UTF-8 — both are wire violations per SPEC.
26
26
  class InvalidEncoding < Error; end
27
27
 
@@ -16,7 +16,7 @@ module Kobako
16
16
  # § Ext Types).
17
17
  #
18
18
  # The factory is the single place in the host gem that touches the
19
- # msgpack API — both {Encoder} and {Decoder} delegate through it, so
19
+ # msgpack API — both Encoder and Decoder delegate through it, so
20
20
  # the three kobako ext codes (0x00 Symbol, 0x01 Capability Handle,
21
21
  # 0x02 Exception envelope) are configured exactly once at first use.
22
22
  #
@@ -93,11 +93,11 @@ module Kobako
93
93
  end
94
94
 
95
95
  # Validate the ext-0x00 payload as UTF-8 and intern. Raises
96
- # {InvalidEncoding} on invalid bytes — SPEC forbids the
96
+ # InvalidEncoding on invalid bytes — SPEC forbids the
97
97
  # binary-encoding fallback that msgpack-gem's default unpacker
98
98
  # would otherwise apply. The re-tag step lives here because the
99
99
  # msgpack ext-type unpacker hands us binary bytes; the assertion
100
- # itself is shared with {Decoder} via {Utils.assert_utf8!}. The
100
+ # itself is shared with Decoder via Utils.assert_utf8!. The
101
101
  # +"Symbol"+ label keeps the error message in Ruby vocabulary
102
102
  # rather than wire-ext-code vocabulary.
103
103
  def unpack_symbol(payload)
@@ -125,7 +125,7 @@ module Kobako
125
125
  # Peel off the fixext-4 frame, hand the bytes to the
126
126
  # Host-Gem-internal +Kobako::Handle.restore+ factory, and
127
127
  # translate the +ArgumentError+ raised by Handle's invariants
128
- # into a wire-layer +InvalidType+ via {Codec::Utils.with_boundary}.
128
+ # into a wire-layer +InvalidType+ via Codec::Utils.with_boundary.
129
129
  # The Value Object owns the id-range contract; this method only
130
130
  # owns the frame shape.
131
131
  def unpack_handle(payload)
@@ -136,11 +136,11 @@ module Kobako
136
136
  Codec::Utils.with_boundary { Kobako::Handle.restore(id) }
137
137
  end
138
138
 
139
- # Encode the inner ext-0x02 map via {Encoder} (not +factory.dump+) so
139
+ # Encode the inner ext-0x02 map via Encoder (not +factory.dump+) so
140
140
  # the embedded payload flows through the same boundary as a top-level
141
141
  # encode — nested kobako values (Handle, nested Fault) reach the
142
142
  # registered ext-type packers via the cached singleton. A +details+
143
- # chain nested past {MAX_EXT_DEPTH} has no wire representation and
143
+ # chain nested past MAX_EXT_DEPTH has no wire representation and
144
144
  # surfaces as +UnsupportedType+.
145
145
  def pack_fault(fault)
146
146
  within_ext_frame(UnsupportedType) do
@@ -149,12 +149,12 @@ module Kobako
149
149
  end
150
150
 
151
151
  # Peel the embedded msgpack map and hand it to +Kobako::Fault.new+
152
- # inside {Decoder.decode}'s block form, so the value-object's
152
+ # inside Decoder.decode's block form, so the value-object's
153
153
  # +ArgumentError+ invariants surface as +InvalidType+ through the
154
- # decoder boundary. Inner decode goes through {Decoder} (not
154
+ # decoder boundary. Inner decode goes through Decoder (not
155
155
  # +factory.load+) so the embedded +str+ payloads flow through the
156
156
  # same UTF-8 validation as a top-level decode. A nested ext 0x02 in
157
- # +details+ re-enters this method, so {#within_ext_frame} bounds the
157
+ # +details+ re-enters this method, so #within_ext_frame bounds the
158
158
  # chain depth to keep it from exhausting the native stack.
159
159
  def unpack_fault(payload)
160
160
  within_ext_frame(InvalidType) do
@@ -167,7 +167,7 @@ module Kobako
167
167
  end
168
168
 
169
169
  # Track ext-envelope re-entry depth and refuse a chain past
170
- # {MAX_EXT_DEPTH}, raising +over_limit+ so the failure lands in the
170
+ # MAX_EXT_DEPTH, raising +over_limit+ so the failure lands in the
171
171
  # caller's existing wire-error class. The counter is thread-scoped and
172
172
  # balanced by the +ensure+, so a raise mid-chain still unwinds it to
173
173
  # its entry value.