kobako 0.20.0 → 0.21.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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +107 -0
- data/Cargo.lock +12 -3
- data/README.md +96 -44
- data/ROADMAP.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +37 -0
- data/crates/kobako-runtime/Cargo.toml +8 -1
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-runtime/src/dispatch.rs +17 -11
- data/crates/kobako-runtime/src/error.rs +38 -6
- data/crates/kobako-runtime/src/lib.rs +13 -2
- data/crates/kobako-runtime/src/profile.rs +8 -0
- data/crates/kobako-runtime/src/runtime.rs +7 -2
- data/crates/kobako-runtime/src/snapshot.rs +13 -4
- data/crates/kobako-runtime/src/yielder.rs +4 -4
- data/crates/kobako-transport/CHANGELOG.md +31 -0
- data/crates/kobako-transport/Cargo.toml +27 -0
- data/crates/kobako-transport/README.md +43 -0
- data/crates/kobako-transport/src/abi.rs +89 -0
- data/crates/kobako-transport/src/envelope/bytes.rs +234 -0
- data/crates/kobako-transport/src/envelope/call.rs +222 -0
- data/crates/kobako-transport/src/envelope/error_record.rs +101 -0
- data/crates/kobako-transport/src/envelope/fault.rs +171 -0
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +232 -0
- data/crates/kobako-transport/src/envelope/outcome.rs +261 -0
- data/crates/kobako-transport/src/envelope/reply.rs +240 -0
- data/crates/kobako-transport/src/envelope/run.rs +85 -0
- data/crates/kobako-transport/src/envelope.rs +73 -0
- data/crates/kobako-transport/src/lib.rs +21 -0
- data/crates/kobako-wasmtime/CHANGELOG.md +23 -0
- data/crates/kobako-wasmtime/Cargo.toml +5 -2
- data/crates/kobako-wasmtime/README.md +1 -1
- data/crates/kobako-wasmtime/src/config.rs +19 -12
- data/crates/kobako-wasmtime/src/dispatch.rs +15 -8
- data/crates/kobako-wasmtime/src/driver.rs +8 -27
- data/crates/kobako-wasmtime/src/frames.rs +11 -9
- data/crates/kobako-wasmtime/src/guest_mem.rs +7 -15
- data/crates/kobako-wasmtime/src/instance_pre.rs +2 -2
- data/crates/kobako-wasmtime/src/invocation.rs +1 -1
- data/crates/kobako-wasmtime/src/lib.rs +1 -1
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +2 -1
- data/ext/kobako/src/runtime/bridge.rs +78 -21
- data/ext/kobako/src/runtime/errors.rs +13 -5
- data/ext/kobako/src/runtime.rs +114 -35
- data/lib/kobako/catalog/services.rb +13 -21
- data/lib/kobako/catalog/snippets.rb +16 -34
- data/lib/kobako/codec/decoder.rb +10 -13
- data/lib/kobako/codec/encoder.rb +7 -7
- data/lib/kobako/codec/error.rb +6 -6
- data/lib/kobako/codec/ext_types.rb +12 -72
- data/lib/kobako/codec/handle_walk.rb +6 -6
- data/lib/kobako/codec/state.rb +4 -56
- data/lib/kobako/codec/utils.rb +4 -4
- data/lib/kobako/codec.rb +11 -21
- data/lib/kobako/context.rb +23 -17
- data/lib/kobako/errors.rb +32 -11
- data/lib/kobako/outcome.rb +71 -128
- data/lib/kobako/payload/arguments.rb +59 -0
- data/lib/kobako/payload.rb +19 -0
- data/lib/kobako/sandbox.rb +12 -13
- data/lib/kobako/sandbox_options.rb +1 -1
- data/lib/kobako/snippet/binary.rb +3 -4
- data/lib/kobako/snippet/source.rb +5 -6
- data/lib/kobako/snippet.rb +2 -2
- data/lib/kobako/transport/call.rb +21 -0
- data/lib/kobako/transport/dispatcher.rb +86 -135
- data/lib/kobako/transport/error.rb +21 -5
- data/lib/kobako/transport/reflection.rb +82 -0
- data/lib/kobako/transport/run.rb +25 -33
- data/lib/kobako/transport/yielder.rb +27 -36
- data/lib/kobako/transport.rb +11 -8
- data/lib/kobako/usage.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/release-please-config.json +47 -2
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/codec/error.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +0 -7
- data/sig/kobako/codec/state.rbs +0 -4
- data/sig/kobako/codec.rbs +4 -5
- data/sig/kobako/context.rbs +4 -4
- data/sig/kobako/errors.rbs +15 -3
- data/sig/kobako/outcome.rbs +12 -13
- data/sig/kobako/payload/arguments.rbs +26 -0
- data/sig/kobako/payload.rbs +4 -0
- data/sig/kobako/pool.rbs +0 -2
- data/sig/kobako/runtime.rbs +15 -13
- data/sig/kobako/snippet/binary.rbs +1 -1
- data/sig/kobako/snippet/source.rbs +1 -1
- data/sig/kobako/transport/call.rbs +21 -0
- data/sig/kobako/transport/dispatcher.rbs +8 -10
- data/sig/kobako/transport/error.rbs +11 -0
- data/sig/kobako/transport/reflection.rbs +15 -0
- data/sig/kobako/transport/run.rbs +1 -1
- data/sig/kobako/transport/yielder.rbs +2 -2
- data/sig/kobako/transport.rbs +8 -2
- metadata +23 -11
- data/lib/kobako/fault.rb +0 -38
- data/lib/kobako/outcome/panic.rb +0 -42
- data/lib/kobako/transport/request.rb +0 -76
- data/lib/kobako/transport/response.rb +0 -83
- data/lib/kobako/transport/yield.rb +0 -94
- data/sig/kobako/fault.rbs +0 -19
- data/sig/kobako/outcome/panic.rbs +0 -34
- data/sig/kobako/transport/request.rbs +0 -29
- data/sig/kobako/transport/response.rbs +0 -33
- data/sig/kobako/transport/yield.rbs +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa120ba374ca1b1ca531a99218645f73bca5bdf157fa40c053f4ee13297ac38e
|
|
4
|
+
data.tar.gz: 244a2791399621ba863fd23796c2b93bbd29a81606e48e700d51cdad140f968a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58093ae0a28a1018af3479465647ea5ef5eaf61aadfdbc42cbcd7904d310015363d7aa414bf6d4073b06967d878dcce139327329cb7e9c9d21e4ce9abe2ab22b
|
|
7
|
+
data.tar.gz: 22bd8b84e48e2d49f27202353998cf80d207b2f3e0c4ae748cd95676f4a444c6d69623a87412c94cbe9658b0b08741b8f11c85d10a13371639c1d669075a53de
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.21.0","wasm/kobako-core":"0.13.0","wasm/kobako-mruby":"0.13.0","wasm/kobako-io":"0.13.0","wasm/kobako-json":"0.13.0","wasm/kobako-regexp":"0.13.0","wasm/kobako-baker":"0.13.0","crates/kobako-transport":"0.13.0","crates/kobako-codec":"0.13.0","crates/kobako-runtime":"0.13.0","crates/kobako-wasmtime":"0.13.0","crates/kobako":"0.13.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,112 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.21.0](https://github.com/elct9620/kobako/compare/v0.20.0...v0.21.0) (2026-07-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **spec:** speak one word for the arm every envelope answers success on
|
|
9
|
+
* **transport:** let every envelope spell its success arm the same way
|
|
10
|
+
* **spec:** separate what a name promises from what the wire promises
|
|
11
|
+
* **sdk:** keep the root to what every build of this crate has
|
|
12
|
+
* **sdk:** make the wasm engine something a host can actually take out
|
|
13
|
+
* **mruby:** name each codec method after the position it serves
|
|
14
|
+
* **wasmtime:** put every cap in one struct, named the way the field is read
|
|
15
|
+
* **runtime:** give the engine contract names an implementer can write
|
|
16
|
+
* **transport:** name the core envelope's types after what they carry
|
|
17
|
+
* **guest:** make a parked block and its wire bit one statement
|
|
18
|
+
* **sdk:** reach a result's host object without a schema
|
|
19
|
+
* **guest:** let the shell ask for the codec instead of inheriting it
|
|
20
|
+
* **customization:** the harness carries no codec until a shell asks
|
|
21
|
+
* **sdk:** make the payload codec an optional dependency
|
|
22
|
+
* **wire:** carry a Reply's fault arm on the envelope
|
|
23
|
+
* **wire:** move the Reply's fault arm onto the envelope
|
|
24
|
+
* **transport:** split the invocation module along the line its doc draws
|
|
25
|
+
* **wire:** route every tier through the one envelope
|
|
26
|
+
* **gem:** follow Ruby's own convention for what an error is called
|
|
27
|
+
* **guest:** name the payload seam for what it is — a codec
|
|
28
|
+
* **wire:** keep a Fault to what its author can bound
|
|
29
|
+
* **wire:** give the fault concept one name on both sides of the boundary
|
|
30
|
+
* **guest:** let the shell name the schema its guest speaks
|
|
31
|
+
* **guest:** route a dispatch through kobako-core without reading its payload
|
|
32
|
+
* **outcome:** raise an unresolved entrypoint as its own error carrying the names it could have been
|
|
33
|
+
* **outcome:** keep a Panic's attribution when its diagnostics are unreadable
|
|
34
|
+
* **wire:** carry the Outcome over the core envelope
|
|
35
|
+
* **wire:** carry the Yield Reply over the core envelope
|
|
36
|
+
* **wire:** carry the Run envelope over the core envelope
|
|
37
|
+
* **wire:** carry the invocation frames over the core envelope
|
|
38
|
+
* **codec:** make the MessagePack adapter an optional feature
|
|
39
|
+
* **wire:** carry dispatch over the core envelope with an opaque payload
|
|
40
|
+
|
|
41
|
+
### Features
|
|
42
|
+
|
|
43
|
+
* **bench:** measure the host's per-invocation cost against a null guest ([c60abdf](https://github.com/elct9620/kobako/commit/c60abdfc98a38b03a4a598913df9a1120ed6302b))
|
|
44
|
+
* **bench:** move the two host-side benchmarks into the gated set ([31a6f7e](https://github.com/elct9620/kobako/commit/31a6f7ebb40c038b39c38ee2441216a412be48d0))
|
|
45
|
+
* **codec:** make the MessagePack adapter an optional feature ([effff27](https://github.com/elct9620/kobako/commit/effff278fd220a4d3c8d4e857828d610fe01f48e))
|
|
46
|
+
* **guest:** let the shell name the schema its guest speaks ([5ad2e0d](https://github.com/elct9620/kobako/commit/5ad2e0db4c645ab56153a81d1623e37e8cd8f5c6))
|
|
47
|
+
* **guest:** open the block seam to a capability gem ([c6d6e49](https://github.com/elct9620/kobako/commit/c6d6e49013bd0f850dd4f2c17273103f166294c2))
|
|
48
|
+
* **outcome:** keep a Panic's attribution when its diagnostics are unreadable ([8accd40](https://github.com/elct9620/kobako/commit/8accd404c9d9155f19fa0458d7e0650b5d587eb1))
|
|
49
|
+
* **outcome:** raise an unresolved entrypoint as its own error carrying the names it could have been ([05b4125](https://github.com/elct9620/kobako/commit/05b41257ca8d4bcb90d6759c6cc7b20582af0661))
|
|
50
|
+
* **sdk:** let a host bring its own wasm engine ([70c2475](https://github.com/elct9620/kobako/commit/70c24759ef4cd5785bd1418a6f8d199620f59e2b))
|
|
51
|
+
* **sdk:** make the payload codec an optional dependency ([0c7da71](https://github.com/elct9620/kobako/commit/0c7da71bfc0b138a316c9612b23b2944684d652e))
|
|
52
|
+
* **sdk:** make the wasm engine something a host can actually take out ([c9f8de6](https://github.com/elct9620/kobako/commit/c9f8de662e6dcd31c8444d9424b1a87ca144f1a6))
|
|
53
|
+
* **sdk:** reach a result's host object without a schema ([a0a2c56](https://github.com/elct9620/kobako/commit/a0a2c56ca5efe8daded876cdcfb8cf0ec8dd4e77))
|
|
54
|
+
* **spec:** anchor what a guest does at a position its codec does not serve ([3a084ea](https://github.com/elct9620/kobako/commit/3a084ea3c60c193038b6dd06d191b14d2ccde4f1))
|
|
55
|
+
* **tasks:** gate signatures against declarations the code dropped ([8b1b834](https://github.com/elct9620/kobako/commit/8b1b834664a51ed6330ba77e9cc3fb7cc4490faf))
|
|
56
|
+
* **tasks:** gate the benchmark probes on still running ([31c3455](https://github.com/elct9620/kobako/commit/31c345534c36f49670075fbfdecff645314d6d24))
|
|
57
|
+
* **wire:** carry a Reply's fault arm on the envelope ([0bce850](https://github.com/elct9620/kobako/commit/0bce850b3416696e92ae6ee12c353c4c21c8e583))
|
|
58
|
+
* **wire:** carry dispatch over the core envelope with an opaque payload ([556104b](https://github.com/elct9620/kobako/commit/556104bf86fdf481b5368b70af83eb0add4b2708))
|
|
59
|
+
* **wire:** carry the invocation frames over the core envelope ([b6f266a](https://github.com/elct9620/kobako/commit/b6f266a310ff723a1868cff745763d91c0603a5e))
|
|
60
|
+
* **wire:** carry the Outcome over the core envelope ([3fa338e](https://github.com/elct9620/kobako/commit/3fa338e4e03b16c4f25f903f1d45f672ab1d015d))
|
|
61
|
+
* **wire:** carry the Run envelope over the core envelope ([d7c46ab](https://github.com/elct9620/kobako/commit/d7c46ab64e382f5226c780b39c8bb66ee12213ac))
|
|
62
|
+
* **wire:** carry the Yield Reply over the core envelope ([0db7261](https://github.com/elct9620/kobako/commit/0db726189adbc369e32b569cd191861dbba17302))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### Bug Fixes
|
|
66
|
+
|
|
67
|
+
* **bench:** bound and surface the band the archive widens ([cc2ddd2](https://github.com/elct9620/kobako/commit/cc2ddd213d8f9382c9b7ff20581abd2b171fe486))
|
|
68
|
+
* **bench:** let probe discovery reach any depth ([2a164ca](https://github.com/elct9620/kobako/commit/2a164cab782a51e9d15b522f8a2c340b5f234b9a))
|
|
69
|
+
* **bench:** name only the rows the archive actually sets the bar for ([68e167f](https://github.com/elct9620/kobako/commit/68e167f56c801ad4b9caf7f290824d28f216aec9))
|
|
70
|
+
* **bench:** sample the cold-start row's guest budget instead of observing it once ([95bdadd](https://github.com/elct9620/kobako/commit/95bdadde1f81fb169293c89cbccd460fb0c6e502))
|
|
71
|
+
* **bench:** widen the gate's noise band by what the archive shows ([183407c](https://github.com/elct9620/kobako/commit/183407cfc3854c1179f7c5b7da338d2295526bfc))
|
|
72
|
+
* **build:** rebuild the guest when the fixed tier changes ([037cc2e](https://github.com/elct9620/kobako/commit/037cc2e9451fbf34bd2138abfbc8b9b59fd613cc))
|
|
73
|
+
* **ext:** name the Panic attribution tuple and drop two lint carries ([ab24625](https://github.com/elct9620/kobako/commit/ab24625233c3559c9b3c737182d6614a4e03926b))
|
|
74
|
+
* **guest:** make a parked block and its wire bit one statement ([dba5625](https://github.com/elct9620/kobako/commit/dba5625c541c9b54caa89f5937419ec416ec05a8))
|
|
75
|
+
* **guest:** read a String's bytes instead of rendering it ([de8c238](https://github.com/elct9620/kobako/commit/de8c238e900830f72d374f83d8c8e5b63cef4384))
|
|
76
|
+
* **guest:** refuse text the capability gems cannot read as text ([539045a](https://github.com/elct9620/kobako/commit/539045a80192cc28c23a4bcbebc311c55eb138fb))
|
|
77
|
+
* **payload:** stop claiming a zero-copy decode the args position cannot have ([c56cb9c](https://github.com/elct9620/kobako/commit/c56cb9c30dc727d9ad0510e3f1572c1ab53463d9))
|
|
78
|
+
* **release:** drop the version from a dev-dependency nothing rewrites ([524e536](https://github.com/elct9620/kobako/commit/524e5365448fedb101571a11de546dddebbfbcb6))
|
|
79
|
+
* **sig:** declare the dispatch seam the shape the ext actually calls ([f0de465](https://github.com/elct9620/kobako/commit/f0de46500eda366d3edd7869edcc39d843f4b687))
|
|
80
|
+
* **sig:** drop the Pool method the signature outlived ([35a5b4f](https://github.com/elct9620/kobako/commit/35a5b4ff3e834b157d695819272c2b445f0bb5bf))
|
|
81
|
+
* **tasks:** let a retired anchor hold the ceiling it was assigned ([a9cd51d](https://github.com/elct9620/kobako/commit/a9cd51d2bd454698833024c89c3693b0d73ef505))
|
|
82
|
+
* **wire:** keep the ABI at 3, which no release has shipped ([2b301b7](https://github.com/elct9620/kobako/commit/2b301b777384c0edf79426805cfb1cc08688aa95))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
### Documentation
|
|
86
|
+
|
|
87
|
+
* **customization:** the harness carries no codec until a shell asks ([a9bf10c](https://github.com/elct9620/kobako/commit/a9bf10c20f27f52183932dd149768626d70f31bf))
|
|
88
|
+
* **spec:** separate what a name promises from what the wire promises ([545bfbd](https://github.com/elct9620/kobako/commit/545bfbd59834bab6d564850d9adafa892cbae005))
|
|
89
|
+
* **spec:** speak one word for the arm every envelope answers success on ([f4ef5f5](https://github.com/elct9620/kobako/commit/f4ef5f5c096cb2c5b0153f7a28f7feaf15a405d0))
|
|
90
|
+
* **wire:** move the Reply's fault arm onto the envelope ([3a56e89](https://github.com/elct9620/kobako/commit/3a56e892caea5b2024baeeeff0bcb8b0169e9b16))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
### Code Refactoring
|
|
94
|
+
|
|
95
|
+
* **gem:** follow Ruby's own convention for what an error is called ([b1ce1fe](https://github.com/elct9620/kobako/commit/b1ce1fe994e5123a950ba9b9f64810387822bb46))
|
|
96
|
+
* **guest:** let the shell ask for the codec instead of inheriting it ([abc24d3](https://github.com/elct9620/kobako/commit/abc24d3321c614f36f8c01c932169083796a0915))
|
|
97
|
+
* **guest:** name the payload seam for what it is — a codec ([b5d90e2](https://github.com/elct9620/kobako/commit/b5d90e26cdea1e0048cb44e48c6543a6bad4a592))
|
|
98
|
+
* **guest:** route a dispatch through kobako-core without reading its payload ([62c5791](https://github.com/elct9620/kobako/commit/62c5791eb88b9021b3d60c8a3dd45d5907011214))
|
|
99
|
+
* **mruby:** name each codec method after the position it serves ([e2d4b53](https://github.com/elct9620/kobako/commit/e2d4b53dd2d165d4f6312c8e87a5dd936d2f7a2f))
|
|
100
|
+
* **runtime:** give the engine contract names an implementer can write ([0a41491](https://github.com/elct9620/kobako/commit/0a41491360cf17def8245c0758679e92572b990f))
|
|
101
|
+
* **sdk:** keep the root to what every build of this crate has ([7f93096](https://github.com/elct9620/kobako/commit/7f93096d4371e7e7f70f3b31301fad3a3198184a))
|
|
102
|
+
* **transport:** let every envelope spell its success arm the same way ([df595e0](https://github.com/elct9620/kobako/commit/df595e08097af9e3e0a0c5ed52a4a6f854eb21dc))
|
|
103
|
+
* **transport:** name the core envelope's types after what they carry ([b44de65](https://github.com/elct9620/kobako/commit/b44de6502953826c567d4ef8a594561479f50c1d))
|
|
104
|
+
* **transport:** split the invocation module along the line its doc draws ([4d3a2d3](https://github.com/elct9620/kobako/commit/4d3a2d3060167d71a807e203688186740fd1485b))
|
|
105
|
+
* **wasmtime:** put every cap in one struct, named the way the field is read ([3f57860](https://github.com/elct9620/kobako/commit/3f578608b044d1ade0ae35231266cf3c9e517c02))
|
|
106
|
+
* **wire:** give the fault concept one name on both sides of the boundary ([564798a](https://github.com/elct9620/kobako/commit/564798a9662f547f665530a75d99c73051a79d86))
|
|
107
|
+
* **wire:** keep a Fault to what its author can bound ([141209d](https://github.com/elct9620/kobako/commit/141209df0f5525853462fddcfaf3584ea530038b))
|
|
108
|
+
* **wire:** route every tier through the one envelope ([c5cd33a](https://github.com/elct9620/kobako/commit/c5cd33a5346f49857fa6e1f45c9cf9b9bea0ff77))
|
|
109
|
+
|
|
3
110
|
## [0.20.0](https://github.com/elct9620/kobako/compare/v0.19.0...v0.20.0) (2026-07-24)
|
|
4
111
|
|
|
5
112
|
|
data/Cargo.lock
CHANGED
|
@@ -929,9 +929,10 @@ dependencies = [
|
|
|
929
929
|
|
|
930
930
|
[[package]]
|
|
931
931
|
name = "kobako"
|
|
932
|
-
version = "0.
|
|
932
|
+
version = "0.21.0"
|
|
933
933
|
dependencies = [
|
|
934
934
|
"kobako-runtime",
|
|
935
|
+
"kobako-transport",
|
|
935
936
|
"kobako-wasmtime",
|
|
936
937
|
"magnus",
|
|
937
938
|
"rb-sys",
|
|
@@ -939,13 +940,21 @@ dependencies = [
|
|
|
939
940
|
|
|
940
941
|
[[package]]
|
|
941
942
|
name = "kobako-runtime"
|
|
942
|
-
version = "0.
|
|
943
|
+
version = "0.13.0"
|
|
944
|
+
dependencies = [
|
|
945
|
+
"kobako-transport",
|
|
946
|
+
]
|
|
947
|
+
|
|
948
|
+
[[package]]
|
|
949
|
+
name = "kobako-transport"
|
|
950
|
+
version = "0.13.0"
|
|
943
951
|
|
|
944
952
|
[[package]]
|
|
945
953
|
name = "kobako-wasmtime"
|
|
946
|
-
version = "0.
|
|
954
|
+
version = "0.13.0"
|
|
947
955
|
dependencies = [
|
|
948
956
|
"kobako-runtime",
|
|
957
|
+
"kobako-transport",
|
|
949
958
|
"libc",
|
|
950
959
|
"sha2 0.11.0",
|
|
951
960
|
"wasmtime",
|
data/README.md
CHANGED
|
@@ -14,9 +14,10 @@ The host (`wasmtime`) runs a precompiled `kobako.wasm` guest containing mruby an
|
|
|
14
14
|
│ Services │ ◀─call─ │ KV::Lookup.call(k) │
|
|
15
15
|
│ KV::Lookup │ ─resp─▶ │ │
|
|
16
16
|
│ │ │ │
|
|
17
|
-
│
|
|
18
|
-
│
|
|
19
|
-
│
|
|
17
|
+
│ Execution │ ◀─pipe─ │ puts / warn │
|
|
18
|
+
│ value │ ◀─last─ │ last expression │
|
|
19
|
+
│ stdout / stderr │ │ │
|
|
20
|
+
│ usage │ │ │
|
|
20
21
|
└──────────────────────┘ └──────────────────────┘
|
|
21
22
|
trusted untrusted
|
|
22
23
|
```
|
|
@@ -45,23 +46,27 @@ The same untrusted script runs identically from either host frontend. It execute
|
|
|
45
46
|
require "kobako"
|
|
46
47
|
|
|
47
48
|
sandbox = Kobako::Sandbox.new
|
|
48
|
-
sandbox.eval("1 + 2") # => 3
|
|
49
|
+
sandbox.eval("1 + 2").value # => 3
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
```rust
|
|
52
53
|
use kobako::{Options, Sandbox};
|
|
53
54
|
|
|
54
55
|
// Options::default() is secure by default: no caps, hermetic isolation.
|
|
55
|
-
let
|
|
56
|
-
sandbox.eval("1 + 2")?; // => Value::Int(3)
|
|
56
|
+
let sandbox = Sandbox::new("kobako.wasm", Options::default())?;
|
|
57
|
+
sandbox.eval("1 + 2")?.value()?; // => Value::Int(3)
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
Each invocation returns the record of that one run — `Kobako::Execution` in Ruby, `Execution` in Rust — carrying the guest value alongside the run's output and resource usage. Nothing a run observes is stored on the Sandbox, so the same Sandbox serves concurrent invocations without them seeing each other.
|
|
61
|
+
|
|
59
62
|
The gem bundles its Guest Binary; a Rust host loads one explicitly — see [Frontends & Guest Binaries](#frontends--guest-binaries) for the packages and the Guest Binary variants.
|
|
60
63
|
|
|
61
64
|
## Frontends & Guest Binaries
|
|
62
65
|
|
|
63
66
|
Embedding kobako is two independent choices: the **host frontend** you build against, and the **Guest Binary** it runs. They compose freely — any frontend loads any Guest Binary, so a Ruby host can run a JSON-enabled guest and a Rust host can run the pure default.
|
|
64
67
|
|
|
68
|
+
How far down those choices go — and which ones a given starting point quietly makes for you — is laid out in [`docs/architecture.md`](docs/architecture.md).
|
|
69
|
+
|
|
65
70
|
### Host frontends
|
|
66
71
|
|
|
67
72
|
The host embeds the sandbox and owns the SPEC wire codec. Choose by your host language and how much of the contract you want pre-assembled.
|
|
@@ -69,8 +74,8 @@ The host embeds the sandbox and owns the SPEC wire codec. Choose by your host la
|
|
|
69
74
|
| Frontend | Package | Add it | Best for |
|
|
70
75
|
|----------|---------|--------|----------|
|
|
71
76
|
| Ruby gem | `kobako` (RubyGems) | `gem install kobako` | A Ruby host — Services, Handles, snippets, and pooling out of the box |
|
|
72
|
-
| Rust SDK | `kobako` (crates.io) | `kobako = "0.
|
|
73
|
-
| Low-level crates | `kobako-wasmtime` + `kobako-runtime` + `kobako-codec` | Cargo deps | A custom host, or driving the wire directly in another language |
|
|
77
|
+
| Rust SDK | `kobako` (crates.io) | `kobako = "0.12"` | A Rust host — the same behavior contract behind an idiomatic Rust API |
|
|
78
|
+
| Low-level crates | `kobako-wasmtime` + `kobako-runtime` + `kobako-transport` + `kobako-codec` | Cargo deps | A custom host, or driving the wire directly in another language |
|
|
74
79
|
|
|
75
80
|
The Rust crates are documented on [crates.io](https://crates.io/crates/kobako); the Ruby gem is this README. Two runnable Rust hosts show the choice: [`plugin-rs`](examples/plugin-rs) builds on the SDK, and [`wire-rs`](examples/wire-rs) assembles a host by hand on the low-level crates.
|
|
76
81
|
|
|
@@ -88,7 +93,7 @@ The gem bundles the pure `kobako.wasm`. Regexp and JSON are opt-in capabilities
|
|
|
88
93
|
|
|
89
94
|
```ruby
|
|
90
95
|
sandbox = Kobako::Sandbox.new(wasm_path: "kobako+full.wasm")
|
|
91
|
-
sandbox.eval('JSON.generate({ n: "42".to_i })') # => "{\"n\":42}"
|
|
96
|
+
sandbox.eval('JSON.generate({ n: "42".to_i })').value # => "{\"n\":42}"
|
|
92
97
|
```
|
|
93
98
|
|
|
94
99
|
### Custom Guest Binaries
|
|
@@ -133,9 +138,11 @@ Build the crate as a `cdylib` for `wasm32-wasip1`, then bake the canonical boot
|
|
|
133
138
|
|
|
134
139
|
| Term | Meaning |
|
|
135
140
|
|------|---------|
|
|
136
|
-
| Sandbox | The
|
|
141
|
+
| Sandbox | The reusable unit (`Kobako::Sandbox`) that runs guest code and returns a result or raises a typed error. It holds configuration only — no state from any run. |
|
|
137
142
|
| Service | A host object bound at a constant-path name (`MyService::KV`) — the guest's only path to host resources. |
|
|
138
|
-
| Invocation | One `#eval` or `#run`; capability state
|
|
143
|
+
| Invocation | One `#eval` or `#run`; capability state is scoped to it and ends with it. |
|
|
144
|
+
| Execution | The frozen record one invocation returns (`Kobako::Execution`): its `#value`, output captures, and `#usage`. A failed run raises, carrying the same record on the error's `#execution`. |
|
|
145
|
+
| Context | The per-invocation object the optional `#eval` / `#run` block receives; its `ctx.bind` supplies a Service object for that one run. |
|
|
139
146
|
| Snippet | Named mruby code (source or bytecode) replayed into a fresh state before every invocation. |
|
|
140
147
|
| Handle | An opaque token the guest holds for a host object the wire cannot transmit directly. |
|
|
141
148
|
| Block | A guest mruby block passed to a Service; each `yield` is a synchronous round-trip into the guest. |
|
|
@@ -166,25 +173,37 @@ RUBY
|
|
|
166
173
|
|
|
167
174
|
Each `::`-separated path segment must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the host method's keyword arguments. The registry seals at the first invocation (see [Invocation Lifecycle](#invocation-lifecycle)); later `#bind` raises `ArgumentError`.
|
|
168
175
|
|
|
176
|
+
### Per-Invocation Bindings
|
|
177
|
+
|
|
178
|
+
A setup-time `bind` fixes one object for the Sandbox's life. When the object belongs to a single run instead — the current request, the acting user, a per-tenant store — declare the path at setup and fill it per invocation. `bind(path)` with no object reserves the name as a *fillable*: the guest sees the constant, while an unfilled dispatch fails closed as `Kobako::ServiceError`. The optional `#eval` / `#run` block fills it ([`docs/behavior/registration.md`](docs/behavior/registration.md) B-62..B-63).
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
sandbox.bind("Req::Current") # declared, unfilled — stands for Kobako::Unresolved
|
|
182
|
+
|
|
183
|
+
sandbox.eval("Req::Current.user_id") { |ctx| ctx.bind("Req::Current", request) }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`ctx.bind` also shadows an already-bound path for that one run. The Context is spent once the block returns, so a `ctx` captured out of it raises `ArgumentError`. Choosing the object per invocation instead of sharing one is what lets concurrent Threads invoke a single Sandbox and still keep their identities apart — the [multi-tenant example](examples/multi-tenant/) runs that shape end to end.
|
|
187
|
+
|
|
169
188
|
### Output Capture
|
|
170
189
|
|
|
171
|
-
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and
|
|
190
|
+
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and read off the run's Execution, independently of its `#value` ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-04). Each invocation captures its own; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
172
191
|
|
|
173
192
|
```ruby
|
|
174
|
-
|
|
193
|
+
execution = sandbox.eval(<<~RUBY)
|
|
175
194
|
puts "hello"
|
|
176
195
|
warn "be careful"
|
|
177
196
|
42
|
|
178
197
|
RUBY
|
|
179
198
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
199
|
+
execution.value # => 42
|
|
200
|
+
execution.stdout # => "hello\n"
|
|
201
|
+
execution.stderr # => "be careful\n"
|
|
183
202
|
```
|
|
184
203
|
|
|
185
204
|
### Error Handling
|
|
186
205
|
|
|
187
|
-
Every invocation either returns
|
|
206
|
+
Every invocation either returns an Execution or raises exactly one of three classes, so you can route faults without inspecting messages. The full taxonomy lives in [`lib/kobako/errors.rb`](lib/kobako/errors.rb).
|
|
188
207
|
|
|
189
208
|
```ruby
|
|
190
209
|
begin
|
|
@@ -193,23 +212,27 @@ rescue Kobako::TrapError
|
|
|
193
212
|
# Wasm engine fault or cap exhaustion. Discard the Sandbox.
|
|
194
213
|
rescue Kobako::ServiceError
|
|
195
214
|
# A host Service call failed and the script did not rescue it.
|
|
196
|
-
rescue Kobako::SandboxError
|
|
215
|
+
rescue Kobako::SandboxError => e
|
|
197
216
|
# The script raised, failed to compile, or returned an unrepresentable value.
|
|
217
|
+
logger.warn(e.execution.stderr) # the failed run's own captures and usage
|
|
198
218
|
end
|
|
199
219
|
```
|
|
200
220
|
|
|
221
|
+
Each of these carries the failed run's Execution on `#execution`, so a rescue reads that run's output and usage exactly as a successful caller reads them off the returned one. `#failed?` keeps the two apart when both `#value` are `nil` — a script whose last expression was `nil` versus one that never produced a value. A failure caught before the guest ran at all — a host pre-flight refusal — leaves `#execution` `nil`, since there is no run to report.
|
|
222
|
+
|
|
201
223
|
| Class | Parent | Trigger |
|
|
202
224
|
|---------------------------------|----------------|------------------------------------------------------|
|
|
203
225
|
| `Kobako::TimeoutError` | `TrapError` | Per-invocation `timeout` exhausted |
|
|
204
226
|
| `Kobako::MemoryLimitError` | `TrapError` | Per-invocation `memory_limit` exhausted |
|
|
205
227
|
| `Kobako::HandleExhaustedError` | `SandboxError` | Handle counter reached its 2³¹ − 1 cap |
|
|
206
228
|
| `Kobako::BytecodeError` | `SandboxError` | `#preload(binary:)` failed RITE validation at replay |
|
|
229
|
+
| `Kobako::UndefinedEntrypointError` | `SandboxError` | `#run` named a constant no snippet defined; carries `#name` and `#available` |
|
|
207
230
|
|
|
208
|
-
`SandboxError` and `ServiceError` carry structured `origin` / `klass` / `backtrace_lines`
|
|
231
|
+
`SandboxError` and `ServiceError` carry structured `origin` / `klass` / `backtrace_lines` fields when the guest produced a panic envelope.
|
|
209
232
|
|
|
210
233
|
### Resource Limits
|
|
211
234
|
|
|
212
|
-
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`
|
|
235
|
+
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Execution#usage`](lib/kobako/execution.rb) for actual consumption — populated on every outcome, so a rescued trap reports it just as a completed run does ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-35).
|
|
213
236
|
|
|
214
237
|
```ruby
|
|
215
238
|
sandbox = Kobako::Sandbox.new(
|
|
@@ -229,17 +252,30 @@ sandbox = Kobako::Sandbox.new(
|
|
|
229
252
|
|
|
230
253
|
`memory_limit` covers the per-invocation `memory.grow` delta from the entry baseline, so a Sandbox reused across invocations does not silently accumulate against a global budget.
|
|
231
254
|
|
|
232
|
-
|
|
255
|
+
Beyond the four caps, `profile:` requests the Sandbox's isolation posture on the `:permissive` < `:hermetic` ladder (default `:hermetic`). `:hermetic` denies the guest ambient time and entropy; `:permissive` lets the guest's `wasi:clocks` / `wasi:random` read live host sources — an explicit trade of reproducibility, with filesystem, environment, and network still unreachable. The request is also a floor: construction fails with `Kobako::SetupError` on a runtime that declares a weaker posture than requested. See [`docs/security-model.md`](docs/security-model.md) § Isolation profiles.
|
|
256
|
+
|
|
257
|
+
### Concurrency
|
|
258
|
+
|
|
259
|
+
A Sandbox keeps no state from any run, so concurrent Threads may invoke distinct Sandboxes or share a single one; each invocation owns its Handles, captures, and usage either way ([`docs/behavior/runtime.md`](docs/behavior/runtime.md) B-22). One Thread still runs one invocation at a time. Sharing a Sandbox adds a single obligation: an object bound once at setup is reached by every Thread and must itself be thread-safe, while an object supplied per invocation — `ctx.bind`, or an Extension `provider:` — carries no such requirement.
|
|
260
|
+
|
|
261
|
+
By default an invocation holds Ruby's GVL for its whole span, so guest execution across Threads serializes. `gvl: :release` drops the GVL for the guest span and re-acquires it for each guest→host dispatch, running guest code in parallel across Threads (B-64).
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
sandbox = Kobako::Sandbox.new(gvl: :release)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The mode is per-Sandbox and fixed at construction; it changes scheduling only, leaving isolation, Handle lifetimes, captures, and outcomes identical. `:hold` remains the default because releasing pays a handoff cost at every dispatch: compute-bound scripts scale with Thread count, while dispatch-heavy ones match or trail `:hold`. `rake bench:gvl_scheduling` measures both ends on your own hardware.
|
|
233
268
|
|
|
234
269
|
### Invocation Lifecycle
|
|
235
270
|
|
|
236
|
-
One Sandbox serves many invocations. Service bindings and preloaded snippets persist across calls;
|
|
271
|
+
One Sandbox serves many invocations. Service bindings and preloaded snippets persist across calls; everything a run produces — Handles, captured output, memory delta — belongs to that run alone.
|
|
237
272
|
|
|
238
273
|
```
|
|
239
274
|
───────────── setup phase (mutable) ─────────────
|
|
240
275
|
|
|
241
276
|
sandbox = Kobako::Sandbox.new
|
|
242
277
|
sandbox.bind("KV::Lookup", ...)
|
|
278
|
+
sandbox.bind("Req::Current") # fillable: object comes later
|
|
243
279
|
sandbox.preload(code: ..., name: :Adder)
|
|
244
280
|
sandbox.preload(code: ..., name: :Greeter)
|
|
245
281
|
|
|
@@ -256,20 +292,24 @@ One Sandbox serves many invocations. Service bindings and preloaded snippets per
|
|
|
256
292
|
|
|
257
293
|
──────────────── invocation N ───────────────────
|
|
258
294
|
|
|
259
|
-
1.
|
|
295
|
+
1. resolve this run's bindings, before the guest starts:
|
|
296
|
+
ctx.bind("Req::Current", ...) fills the fillable
|
|
297
|
+
provider: backends yield this run's object
|
|
298
|
+
|
|
299
|
+
2. start from the canonical boot state
|
|
260
300
|
(mruby pre-initialized into the artifact at build time)
|
|
261
301
|
|
|
262
|
-
|
|
302
|
+
3. replay snippets (in insertion order):
|
|
263
303
|
:Adder → defines Adder
|
|
264
304
|
:Greeter → defines Greeter
|
|
265
305
|
|
|
266
|
-
|
|
306
|
+
4. dispatch: eval(source) or run(:Target, *args, **kwargs)
|
|
267
307
|
|
|
268
|
-
|
|
308
|
+
5. return the run's Execution to host
|
|
269
309
|
|
|
270
|
-
|
|
310
|
+
6. discard the instance; per-invocation state ends with it:
|
|
271
311
|
· Handles invalidated
|
|
272
|
-
·
|
|
312
|
+
· captures frozen into the Execution
|
|
273
313
|
· memory delta zeroed
|
|
274
314
|
|
|
275
315
|
Services + snippets persist; invocation N+1 repeats.
|
|
@@ -281,14 +321,14 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
|
|
|
281
321
|
|
|
282
322
|
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior/runtime.md`](docs/behavior/runtime.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
283
323
|
|
|
284
|
-
`Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. B-49 bakes the shared boot state into the artifact and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the ~28 µs host-side `Sandbox.new`. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a significant gain (~4-5% in the [serverless example](examples/serverless/README.md), and proportionally less once the script itself does real work).
|
|
324
|
+
`Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. B-49 bakes the shared boot state into the artifact and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the ~28 µs host-side `Sandbox.new`. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a significant gain (~4-5% in the [serverless example](examples/serverless/README.md), and proportionally less once the script itself does real work). What a Pool buys is warm setup and exclusive checkout, not isolation: a Sandbox holds no state from any run, so Threads sharing one are equally safe (see [Concurrency](#concurrency)).
|
|
285
325
|
|
|
286
326
|
```ruby
|
|
287
327
|
pool = Kobako::Pool.new(slots: 4) do |sandbox|
|
|
288
328
|
sandbox.bind("KV::Lookup", ->(key) { redis.get(key) })
|
|
289
329
|
end
|
|
290
330
|
|
|
291
|
-
pool.with { |sandbox| sandbox.eval(%(KV::Lookup.call("user_42"))) }
|
|
331
|
+
pool.with { |sandbox| sandbox.eval(%(KV::Lookup.call("user_42"))).value }
|
|
292
332
|
```
|
|
293
333
|
|
|
294
334
|
| Option | Meaning | Default |
|
|
@@ -296,7 +336,7 @@ pool.with { |sandbox| sandbox.eval(%(KV::Lookup.call("user_42"))) }
|
|
|
296
336
|
| `slots:` | Upper bound on constructed Sandboxes | required |
|
|
297
337
|
| `checkout_timeout:` | Seconds `#with` waits for a free Sandbox; `nil` waits indefinitely | 5.0 |
|
|
298
338
|
|
|
299
|
-
Sandboxes construct lazily on first demand. `#with` yields a Sandbox
|
|
339
|
+
Sandboxes construct lazily on first demand. `#with` yields a Sandbox and returns the block's value; at block exit the Sandbox returns to the pool, except a block that raises `Kobako::TrapError` discards its Sandbox and the slot refills by a fresh construction on next demand. A checkout that waits past `checkout_timeout` raises `Kobako::PoolTimeoutError`. There is no teardown verb — a Pool releases everything with its own reachability.
|
|
300
340
|
|
|
301
341
|
### Service Blocks
|
|
302
342
|
|
|
@@ -305,7 +345,7 @@ A Service method can accept a guest-supplied block via `&blk` and `yield` into i
|
|
|
305
345
|
```ruby
|
|
306
346
|
sandbox.bind("Seq::Map", ->(items, &blk) { items.map(&blk) })
|
|
307
347
|
|
|
308
|
-
sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
|
|
348
|
+
sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }').value
|
|
309
349
|
# => [2, 4, 6]
|
|
310
350
|
```
|
|
311
351
|
|
|
@@ -321,8 +361,8 @@ end
|
|
|
321
361
|
|
|
322
362
|
sandbox.bind("Factory::Make", ->(name) { Greeter.new(name) })
|
|
323
363
|
|
|
324
|
-
sandbox.eval('Factory::Make.call("Bob").greet') # => "hi, Bob" (Handle round-trip inside guest)
|
|
325
|
-
sandbox.eval('Factory::Make.call("Bob")') # => #<Greeter @name="Bob"> (B-37 restoration)
|
|
364
|
+
sandbox.eval('Factory::Make.call("Bob").greet').value # => "hi, Bob" (Handle round-trip inside guest)
|
|
365
|
+
sandbox.eval('Factory::Make.call("Bob")').value # => #<Greeter @name="Bob"> (B-37 restoration)
|
|
326
366
|
```
|
|
327
367
|
|
|
328
368
|
A `break` value from a guest block is the one exception: it unwinds back to the guest Service call rather than to host code, so a Handle in it stays a Handle — restoring would just re-wrap the same object into a new id on the return trip.
|
|
@@ -351,13 +391,17 @@ This is deliberate, not a leak. Handle IDs run to 2³¹ − 1 per invocation and
|
|
|
351
391
|
|
|
352
392
|
```ruby
|
|
353
393
|
sandbox = Kobako::Sandbox.new
|
|
354
|
-
sandbox.preload(code: "Adder = ->(a, b)
|
|
355
|
-
sandbox.preload(code: 'Greeter = ->(
|
|
394
|
+
sandbox.preload(code: "Adder = ->(a, b) { a + b }", name: :Adder)
|
|
395
|
+
sandbox.preload(code: 'Greeter = ->(opts) { "hello, #{opts[:name]}" }', name: :Greeter)
|
|
356
396
|
|
|
357
|
-
sandbox.run(:Adder, 2, 3) # => 5
|
|
358
|
-
sandbox.run(:Greeter, name: "world") # => "hello, world"
|
|
397
|
+
sandbox.run(:Adder, 2, 3).value # => 5
|
|
398
|
+
sandbox.run(:Greeter, name: "world").value # => "hello, world"
|
|
359
399
|
```
|
|
360
400
|
|
|
401
|
+
An entrypoint's `kwargs` arrive as a trailing positional Hash — mruby's C-side call path carries no keyword arguments — so declare a Hash parameter and unpack it yourself.
|
|
402
|
+
|
|
403
|
+
A target no snippet defined raises `Kobako::UndefinedEntrypointError`, whose `#available` lists the top-level constants the snippets did contribute — so the name is corrected from the error rather than by reading the guest source.
|
|
404
|
+
|
|
361
405
|
```
|
|
362
406
|
per-invocation replay (every #eval / #run, snippets in insertion order):
|
|
363
407
|
|
|
@@ -370,7 +414,7 @@ sandbox.run(:Greeter, name: "world") # => "hello, world"
|
|
|
370
414
|
└──▶ eval(source) -or- run(:Target, *args, **kwargs)
|
|
371
415
|
│
|
|
372
416
|
▼
|
|
373
|
-
|
|
417
|
+
return the Execution, then discard the instance
|
|
374
418
|
```
|
|
375
419
|
|
|
376
420
|
`#preload` accepts two payload forms:
|
|
@@ -401,7 +445,7 @@ sandbox.install(
|
|
|
401
445
|
source: FILE,
|
|
402
446
|
backend: Kobako::Extension::Backend.new(
|
|
403
447
|
path: "File",
|
|
404
|
-
provider: -> { OverlayFileSystem.new(root) } #
|
|
448
|
+
provider: -> { OverlayFileSystem.new(root) } # invoked once per invocation
|
|
405
449
|
)
|
|
406
450
|
)
|
|
407
451
|
)
|
|
@@ -409,7 +453,15 @@ sandbox.install(
|
|
|
409
453
|
sandbox.eval('File.read("sample.txt")') # dispatches to the backend's #read
|
|
410
454
|
```
|
|
411
455
|
|
|
412
|
-
|
|
456
|
+
A backend declares the bound object's lifetime by keyword, never by inference — so a static object that happens to be callable stays unambiguous.
|
|
457
|
+
|
|
458
|
+
| Keyword | Lifetime |
|
|
459
|
+
|-------------|-----------------------------------------------------------------------------|
|
|
460
|
+
| `object:` | One object, shared by every invocation |
|
|
461
|
+
| `provider:` | A no-argument callable invoked per invocation — what a writable backend needs, so its state cannot leak across runs |
|
|
462
|
+
| neither | A fillable, standing for `Kobako::Unresolved` until `ctx.bind` supplies the run's object |
|
|
463
|
+
|
|
464
|
+
kobako ships no concrete Extension; the idiom and backend are yours. The [overlay VFS example](examples/vfs/) is a worked `File` that reads through to disk while protecting it from guest writes.
|
|
413
465
|
|
|
414
466
|
## Security
|
|
415
467
|
|
|
@@ -425,7 +477,7 @@ end
|
|
|
425
477
|
sandbox = Kobako::Sandbox.new
|
|
426
478
|
sandbox.bind("Cfg::Settings", ThemeReader.new) # not: bind("Cfg::Settings", AppConfig)
|
|
427
479
|
|
|
428
|
-
sandbox.eval('Cfg::Settings.color') # => "#3366ff" — every other method raises NoMethodError
|
|
480
|
+
sandbox.eval('Cfg::Settings.color').value # => "#3366ff" — every other method raises NoMethodError
|
|
429
481
|
```
|
|
430
482
|
|
|
431
483
|
When a purpose-built wrapper is more than you need, an object can gate its own surface in
|
|
@@ -454,12 +506,12 @@ Order-of-magnitude figures on macOS arm64, Ruby 3.4.7, YJIT off. Absolute values
|
|
|
454
506
|
| Snippet replay per invocation | ~7.6 µs each |
|
|
455
507
|
| Per additional idle Sandbox (RSS) | ~1 KB |
|
|
456
508
|
|
|
457
|
-
The Cranelift JIT runs once per machine and gem version — the compiled artifact persists in a `.cwasm` disk cache, so later processes deserialize in milliseconds. An idle Sandbox holds no wasm instance (the canonical boot state is baked into the artifact and instantiated per invocation), which is why a thousand idle tenants cost ~33 MB total.
|
|
509
|
+
The Cranelift JIT runs once per machine and gem version — the compiled artifact persists in a `.cwasm` disk cache, so later processes deserialize in milliseconds. An idle Sandbox holds no wasm instance (the canonical boot state is baked into the artifact and instantiated per invocation), which is why a thousand idle tenants cost ~33 MB total. Under the default `gvl: :hold`, wasm work is GVL-serialized: aggregate throughput stays around 17k `#eval`/s regardless of Thread count, though Ruby-side `#eval` setup still overlaps. Opting a Sandbox into `gvl: :release` lifts that ceiling for compute-bound scripts (see [Concurrency](#concurrency)). A +10% regression on any SPEC-mandated benchmark blocks release.
|
|
458
510
|
|
|
459
511
|
Regexp is an opt-in capability gem, excluded from the default binary and the gated set; its throughput is tracked in a separate non-gated characterization (`#11` in [`benchmark/README.md`](benchmark/README.md)). There `=~` (~5 µs/match) costs about 4× `match?` (~1.2 µs), because `=~` eagerly builds the `MatchData` and match globals — prefer `match?` for boolean tests.
|
|
460
512
|
|
|
461
513
|
```bash
|
|
462
|
-
bundle exec rake bench #
|
|
514
|
+
bundle exec rake bench # every gated regression benchmark (~5-8 min)
|
|
463
515
|
```
|
|
464
516
|
|
|
465
517
|
## Development
|
data/ROADMAP.md
CHANGED
|
@@ -16,7 +16,7 @@ output capture, and a warm Sandbox pool.
|
|
|
16
16
|
| ✅ [F-06 Capability Handle encoding and referencing](docs/behavior/dispatch.md) | [lib/kobako/catalog/handles.rb](lib/kobako/catalog/handles.rb) | — |
|
|
17
17
|
| ✅ [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 |
|
|
18
18
|
| ✅ [F-08 Guest output capture](docs/behavior/lifecycle.md) | [lib/kobako/capture.rb](lib/kobako/capture.rb) | — |
|
|
19
|
-
| ✅ [F-09 Host–guest message codec](docs/wire-codec.md) | [lib/kobako/codec/](lib/kobako/codec/) |
|
|
19
|
+
| ✅ [F-09 Host–guest message codec](docs/wire-codec.md) | [crates/kobako-transport/](crates/kobako-transport/) (core envelope + ABI, one implementation), [lib/kobako/codec/](lib/kobako/codec/) (host payload codec) | The payload layer has a second implementation in `crates/kobako-codec`; the envelope layer is pinned by golden vectors instead |
|
|
20
20
|
| ✅ [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/` |
|
|
21
21
|
| ✅ [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 |
|
|
22
22
|
| ✅ [F-12 Guest block reception and yield re-entry](docs/behavior/yield.md) | [lib/kobako/transport/yielder.rb](lib/kobako/transport/yielder.rb) | — |
|
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.12.0...kobako-runtime-v0.13.0) (2026-07-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **runtime:** mark the sets that grow, and say why the closed ones do not
|
|
9
|
+
* **spec:** separate what a name promises from what the wire promises
|
|
10
|
+
* **runtime:** give the engine contract names an implementer can write
|
|
11
|
+
* **sdk:** attribute an invocation without reading its payload
|
|
12
|
+
* **wire:** route every tier through the one envelope
|
|
13
|
+
* **outcome:** raise an unresolved entrypoint as its own error carrying the names it could have been
|
|
14
|
+
* **wire:** carry dispatch over the core envelope with an opaque payload
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **outcome:** raise an unresolved entrypoint as its own error carrying the names it could have been ([05b4125](https://github.com/elct9620/kobako/commit/05b41257ca8d4bcb90d6759c6cc7b20582af0661))
|
|
19
|
+
* **runtime:** mark the sets that grow, and say why the closed ones do not ([1be2449](https://github.com/elct9620/kobako/commit/1be24492961e2c2a3a317f784cc2f20ae584dcbf))
|
|
20
|
+
* **wire:** add the core envelope, implemented independently on both peers ([e6c41d1](https://github.com/elct9620/kobako/commit/e6c41d179e5bf619d43d94160dd597c8ae3bc9bf))
|
|
21
|
+
* **wire:** carry dispatch over the core envelope with an opaque payload ([556104b](https://github.com/elct9620/kobako/commit/556104bf86fdf481b5368b70af83eb0add4b2708))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
|
|
26
|
+
* **sig:** declare the dispatch seam the shape the ext actually calls ([f0de465](https://github.com/elct9620/kobako/commit/f0de46500eda366d3edd7869edcc39d843f4b687))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
|
|
31
|
+
* **spec:** separate what a name promises from what the wire promises ([545bfbd](https://github.com/elct9620/kobako/commit/545bfbd59834bab6d564850d9adafa892cbae005))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Code Refactoring
|
|
35
|
+
|
|
36
|
+
* **runtime:** give the engine contract names an implementer can write ([0a41491](https://github.com/elct9620/kobako/commit/0a41491360cf17def8245c0758679e92572b990f))
|
|
37
|
+
* **sdk:** attribute an invocation without reading its payload ([70d22d0](https://github.com/elct9620/kobako/commit/70d22d01c79034d3d3011e95af5839d6eea654c8))
|
|
38
|
+
* **wire:** route every tier through the one envelope ([c5cd33a](https://github.com/elct9620/kobako/commit/c5cd33a5346f49857fa6e1f45c9cf9b9bea0ff77))
|
|
39
|
+
|
|
3
40
|
## [0.12.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.11.0...kobako-runtime-v0.12.0) (2026-07-24)
|
|
4
41
|
|
|
5
42
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
[package]
|
|
15
15
|
name = "kobako-runtime"
|
|
16
|
-
version = "0.
|
|
16
|
+
version = "0.13.0"
|
|
17
17
|
edition = "2021"
|
|
18
18
|
description = "Engine-neutral host runtime contract for embedding kobako Wasm guests."
|
|
19
19
|
license = "Apache-2.0"
|
|
@@ -21,3 +21,10 @@ repository = "https://github.com/elct9620/kobako"
|
|
|
21
21
|
readme = "README.md"
|
|
22
22
|
keywords = ["wasm", "mruby", "sandbox", "wasi"]
|
|
23
23
|
categories = ["wasm", "virtualization"]
|
|
24
|
+
|
|
25
|
+
# The fixed tier: the core envelope this contract routes and attributes
|
|
26
|
+
# through. The version pin rides the linked release group; the path keeps
|
|
27
|
+
# in-tree builds (and the Ruby gem, which ships both crates) resolving
|
|
28
|
+
# locally.
|
|
29
|
+
[dependencies]
|
|
30
|
+
kobako-transport = { version = "0.13.0", path = "../kobako-transport" }
|