kobako 0.26.0-aarch64-linux → 0.28.0-aarch64-linux
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 +103 -0
- data/README.md +156 -118
- data/ROADMAP.md +23 -22
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +38 -0
- data/crates/kobako-runtime/README.md +10 -12
- data/crates/kobako-transport/CHANGELOG.md +35 -0
- data/crates/kobako-transport/README.md +43 -27
- data/crates/kobako-wasmtime/CHANGELOG.md +51 -0
- data/crates/kobako-wasmtime/README.md +8 -10
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +41 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/services.rb +7 -61
- data/lib/kobako/catalog/snippets.rb +42 -66
- data/lib/kobako/catalog.rb +2 -10
- data/lib/kobako/codec/decoder.rb +9 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +4 -10
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec.rb +3 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +51 -112
- data/lib/kobako/errors.rb +51 -127
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +23 -53
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +96 -133
- data/lib/kobako/sandbox_options.rb +38 -84
- data/lib/kobako/snippet/binary.rb +1 -16
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +1 -9
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +112 -195
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +5 -10
- data/lib/kobako/transport/reflection.rb +39 -50
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +21 -73
- data/lib/kobako/transport.rb +3 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +17 -0
- data/sig/kobako/catalog/services.rbs +0 -6
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec.rbs +2 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +2 -3
- data/sig/kobako/errors.rbs +6 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/runtime.rbs +12 -8
- data/sig/kobako/sandbox.rbs +4 -0
- data/sig/kobako/sandbox_options.rbs +2 -6
- data/sig/kobako/transport/dispatcher.rbs +14 -18
- data/sig/kobako/transport/reflection.rbs +5 -3
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +3 -3
- data/sig/kobako/transport.rbs +7 -6
- metadata +6 -3
- data/rust-toolchain.toml +0 -11
data/SECURITY.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
kobako runs untrusted guest code inside an in-process Wasm sandbox, so a break in
|
|
4
4
|
its isolation boundary is treated as a security issue. This file is about **reporting
|
|
5
5
|
such an issue**; for how the boundary is meant to work and where your
|
|
6
|
-
responsibilities as a host begin, see [`docs/security
|
|
6
|
+
responsibilities as a host begin, see [`docs/guides/security.md`](docs/guides/security.md).
|
|
7
7
|
|
|
8
8
|
## Supported versions
|
|
9
9
|
|
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.17.0...kobako-runtime-v0.18.0) (2026-10-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **transport:** the stdin layout a Guest Binary reads changed within ABI 4. kobako_transport::envelope::Frames is replaced by Preamble, which is #[non_exhaustive] and built from Default; Bindings and Snippets no longer have their own decode and encode. Guest::eval and Guest::run, MrbGuest::eval and MrbGuest::run, and Runtime::invoke take a Preamble in place of Frames. kobako_core re-exports Preamble and Flag in place of Frames.
|
|
9
|
+
* **transport:** the ABI version is 4, so a Guest Binary built for ABI 3 is refused at Sandbox construction. kobako_core::export_guest! emits __kobako_downcall in place of __kobako_eval, __kobako_run, __kobako_yield_to_block and __kobako_take_outcome, and the host import is __kobako_upcall. kobako_core::abi::{take_outcome, write_outcome, write_reply, decode_run} are replaced by serve and answer, and the frames module is no longer public. Reply, YieldReply and Outcome gain an Unsupported arm and take their tags from the shared registry, and kobako_core::DispatchError and kobako::YieldError gain an Unsupported arm. A yield to a guest without block support raises Kobako::UnsupportedError at the Service's yield instead of failing the invocation as a trap.
|
|
10
|
+
* **runtime:** Runtime::invoke takes &kobako_transport::envelope::Frames in place of kobako_runtime::Frames, whose byte fields are gone. Guest::eval and Guest::run (and their MrbGuest flows) receive the decoded Frames, eval also its source; kobako_core::frames::read_frame is replaced by read_eval_frames and read_frames.
|
|
11
|
+
* **runtime:** kobako_runtime::snapshot::Completion::Outcome holds a decoded kobako_transport::envelope::Outcome instead of Vec<u8>; an engine decodes the result itself and reports an unframed one as
|
|
12
|
+
* **runtime:** kobako_runtime::runtime::Entry::Run holds { entrypoint: &str, payload: Vec<u8> } instead of { envelope: &[u8] }; a frontend passes the fields, and an engine encodes the Run envelope itself.
|
|
13
|
+
* **runtime:** kobako_runtime::yielder::Yielder::yield_to_block returns Result<YieldReply, Trap> instead of the encoded bytes; an engine implementing it decodes the reply itself.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **runtime:** compile a Preamble's snippets on an instance of their own ([303432d](https://github.com/elct9620/kobako/commit/303432dd90ed62dc378391515e68e784e1b473a4)), closes [#75](https://github.com/elct9620/kobako/issues/75)
|
|
18
|
+
* **transport:** carry each operation as a tag on one channel per direction ([c7d574b](https://github.com/elct9620/kobako/commit/c7d574b59a42e58632f2bdc46cc9a8e4c7ac6121)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
19
|
+
* **transport:** set an invocation up from a Preamble of tagged records ([84d4498](https://github.com/elct9620/kobako/commit/84d4498464f94d7312f26931ddd4ec43894a6654))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* keep a compile's bytecode only when the Preamble still fits it ([765db08](https://github.com/elct9620/kobako/commit/765db08ad31c8f5163794c7e144355f3237af93b))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Code Refactoring
|
|
28
|
+
|
|
29
|
+
* **runtime:** hand a frontend the decoded Outcome ([0da6aac](https://github.com/elct9620/kobako/commit/0da6aac0f7943d694a5a4ab9439b2d6c527ae1b0)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
30
|
+
* **runtime:** hand a frontend the decoded Yield Reply ([e80ab54](https://github.com/elct9620/kobako/commit/e80ab54e36c2a80b0f746c675f85bd1238fd4331)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
31
|
+
* **runtime:** let the ABI endpoints frame and read the Frames ([dea069a](https://github.com/elct9620/kobako/commit/dea069ac49a59c12a6f7102ed3cf2925f316cbff)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
32
|
+
* **runtime:** let the engine frame a run's entrypoint and payload ([62f9c94](https://github.com/elct9620/kobako/commit/62f9c9407ff3ce690723625bea075fa49d2dda47)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
33
|
+
|
|
34
|
+
## [0.17.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.16.0...kobako-runtime-v0.17.0) (2026-09-20)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Miscellaneous Chores
|
|
38
|
+
|
|
39
|
+
* **kobako-runtime:** Synchronize kobako crates versions
|
|
40
|
+
|
|
3
41
|
## [0.16.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.15.0...kobako-runtime-v0.16.0) (2026-09-15)
|
|
4
42
|
|
|
5
43
|
|
|
@@ -6,17 +6,15 @@ sandbox for running untrusted mruby scripts.
|
|
|
6
6
|
|
|
7
7
|
A kobako host drives a Guest Binary through a wasm engine; this crate
|
|
8
8
|
is the surface where the two meet, free of any engine or frontend
|
|
9
|
-
type, so the engine stays swappable
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- `dispatch` / `yielder` — the `DispatchHandler` and `Yielder` traits
|
|
19
|
-
a frontend supplies for guest→host dispatch and block-yield re-entry
|
|
9
|
+
type, so the engine stays swappable.
|
|
10
|
+
|
|
11
|
+
| Module | What it declares |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `runtime` | the `Runtime` trait: one guest invocation on a fresh instance in, its observable `Snapshot` out |
|
|
14
|
+
| `snapshot` | each invocation's `Completion` (outcome or trap), both output `Capture`s, and `Usage` |
|
|
15
|
+
| `error` | the neutral failure types: `Trap`, `SetupError`, and `InvokeError` over both |
|
|
16
|
+
| `profile` | the isolation `Profile` a runtime declares |
|
|
17
|
+
| `dispatch` / `yielder` | the `DispatchHandler` and `Yielder` traits a frontend supplies for guest→host dispatch and block-yield re-entry |
|
|
20
18
|
|
|
21
19
|
Engine implementations (such as `kobako-wasmtime`) implement
|
|
22
20
|
`Runtime`; host frontends (such as the kobako Ruby gem's native ext)
|
|
@@ -26,7 +24,7 @@ map the neutral types onto their own language surface.
|
|
|
26
24
|
|
|
27
25
|
```toml
|
|
28
26
|
[dependencies]
|
|
29
|
-
kobako-runtime = "0.
|
|
27
|
+
kobako-runtime = "0.18.0" # x-release-please-version
|
|
30
28
|
```
|
|
31
29
|
|
|
32
30
|
## License
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.17.0...kobako-transport-v0.18.0) (2026-10-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **transport:** the stdin layout a Guest Binary reads changed within ABI 4. kobako_transport::envelope::Frames is replaced by Preamble, which is #[non_exhaustive] and built from Default; Bindings and Snippets no longer have their own decode and encode. Guest::eval and Guest::run, MrbGuest::eval and MrbGuest::run, and Runtime::invoke take a Preamble in place of Frames. kobako_core re-exports Preamble and Flag in place of Frames.
|
|
9
|
+
* **mruby:** CodecError::Unsupported is renamed to CodecError::Unserved; a codec of one's own that refuses a position returns the new name.
|
|
10
|
+
* **transport:** the ABI version is 4, so a Guest Binary built for ABI 3 is refused at Sandbox construction. kobako_core::export_guest! emits __kobako_downcall in place of __kobako_eval, __kobako_run, __kobako_yield_to_block and __kobako_take_outcome, and the host import is __kobako_upcall. kobako_core::abi::{take_outcome, write_outcome, write_reply, decode_run} are replaced by serve and answer, and the frames module is no longer public. Reply, YieldReply and Outcome gain an Unsupported arm and take their tags from the shared registry, and kobako_core::DispatchError and kobako::YieldError gain an Unsupported arm. A yield to a guest without block support raises Kobako::UnsupportedError at the Service's yield instead of failing the invocation as a trap.
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **transport:** carry a compile request and its answer ([f30958e](https://github.com/elct9620/kobako/commit/f30958e7d61c95667c32cfee7d9b2d2f74550a8e)), closes [#75](https://github.com/elct9620/kobako/issues/75)
|
|
15
|
+
* **transport:** carry each operation as a tag on one channel per direction ([c7d574b](https://github.com/elct9620/kobako/commit/c7d574b59a42e58632f2bdc46cc9a8e4c7ac6121)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
16
|
+
* **transport:** keep an unknown request's body, and refuse tag 0x00 ([edb64df](https://github.com/elct9620/kobako/commit/edb64df8f87694d17c5b6fd71ac63329023306ec)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
17
|
+
* **transport:** name the Frames a Sandbox is set up with ([52b25aa](https://github.com/elct9620/kobako/commit/52b25aa48d321ce8ea7dc9532cfade062118c64f)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
18
|
+
* **transport:** set an invocation up from a Preamble of tagged records ([84d4498](https://github.com/elct9620/kobako/commit/84d4498464f94d7312f26931ddd4ec43894a6654))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **transport:** read only an unassigned tag as an unknown request ([fcb81b5](https://github.com/elct9620/kobako/commit/fcb81b5c38d001b729a10287443c5012281b76b6)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
24
|
+
* **transport:** size a counted list by what its bytes can hold ([cfcc9fb](https://github.com/elct9620/kobako/commit/cfcc9fb6e79ec0bd1e6bf3a15659220aab0f6914))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Code Refactoring
|
|
28
|
+
|
|
29
|
+
* **mruby:** name a codec's refused position Unserved ([91979f9](https://github.com/elct9620/kobako/commit/91979f9546a3fa2d2dec8c136ed6affc521ba6fb)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
30
|
+
|
|
31
|
+
## [0.17.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.16.0...kobako-transport-v0.17.0) (2026-09-20)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Miscellaneous Chores
|
|
35
|
+
|
|
36
|
+
* **kobako-transport:** Synchronize kobako crates versions
|
|
37
|
+
|
|
3
38
|
## [0.16.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.15.0...kobako-transport-v0.16.0) (2026-09-15)
|
|
4
39
|
|
|
5
40
|
|
|
@@ -5,39 +5,55 @@ an in-process Wasm sandbox for running untrusted mruby scripts.
|
|
|
5
5
|
|
|
6
6
|
kobako is assembled from three parts chosen independently: a host, a
|
|
7
7
|
payload codec, and a guest. That works only because two things are the
|
|
8
|
-
same in every assembly. This crate is both of them, and nothing else
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
8
|
+
same in every assembly. This crate is both of them, and nothing else.
|
|
9
|
+
|
|
10
|
+
| Module | What it fixes |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `envelope` | the **core envelope**, the outer frame each message rides in |
|
|
13
|
+
| `abi` | the values both sides agree on before exchanging a byte |
|
|
14
|
+
|
|
15
|
+
The envelope covers the downcall's and the upcall's requests under one
|
|
16
|
+
tag registry. It also covers a dispatch round-trip, how an invocation
|
|
17
|
+
ended, and what an invocation is handed, and re-exports every message
|
|
18
|
+
type. The ABI values are the version, the packed `(ptr, len)` return
|
|
19
|
+
layout, the length prefix the Preamble rides stdin behind, and the
|
|
20
|
+
message size cap.
|
|
21
|
+
|
|
22
|
+
## Opaque Payload
|
|
23
|
+
|
|
24
|
+
The envelope reads a message's routing fields and its answer's tag
|
|
25
|
+
without decoding a payload byte. Everything the resolved method consumes
|
|
26
|
+
rides through opaque. A decoded envelope borrows the buffer it came from,
|
|
27
|
+
so that payload reaches its reader as a view rather than a copy.
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Call { method, payload }
|
|
31
|
+
└── opaque here; read by whoever owns the schema
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Dependency Position
|
|
35
|
+
|
|
36
|
+
This crate depends on no other, and every kobako tier depends on it —
|
|
37
|
+
including any third-party engine, codec, or guest composing against them.
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
host kobako-runtime · kobako-wasmtime ──┐
|
|
41
|
+
guest kobako-core · kobako-mruby ────────┴──> kobako-transport
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The golden vectors in this crate are derived from the kobako repository's
|
|
45
|
+
byte-layout document, not from this code.
|
|
33
46
|
|
|
34
47
|
## Usage
|
|
35
48
|
|
|
49
|
+
Depend on it directly only to build a tier of your own; a guest shell
|
|
50
|
+
reaches the same types through `kobako-core`.
|
|
51
|
+
|
|
36
52
|
```toml
|
|
37
53
|
[dependencies]
|
|
38
|
-
kobako-transport = "0.
|
|
54
|
+
kobako-transport = "0.18.0" # x-release-please-version
|
|
39
55
|
```
|
|
40
56
|
|
|
41
57
|
## License
|
|
42
58
|
|
|
43
|
-
Apache-2.0
|
|
59
|
+
Licensed under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.17.0...kobako-wasmtime-v0.18.0) (2026-10-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* the kobako and kobako-wasmtime crates declare a minimum supported Rust version of 1.96.
|
|
9
|
+
* **transport:** the stdin layout a Guest Binary reads changed within ABI 4. kobako_transport::envelope::Frames is replaced by Preamble, which is #[non_exhaustive] and built from Default; Bindings and Snippets no longer have their own decode and encode. Guest::eval and Guest::run, MrbGuest::eval and MrbGuest::run, and Runtime::invoke take a Preamble in place of Frames. kobako_core re-exports Preamble and Flag in place of Frames.
|
|
10
|
+
* **transport:** the ABI version is 4, so a Guest Binary built for ABI 3 is refused at Sandbox construction. kobako_core::export_guest! emits __kobako_downcall in place of __kobako_eval, __kobako_run, __kobako_yield_to_block and __kobako_take_outcome, and the host import is __kobako_upcall. kobako_core::abi::{take_outcome, write_outcome, write_reply, decode_run} are replaced by serve and answer, and the frames module is no longer public. Reply, YieldReply and Outcome gain an Unsupported arm and take their tags from the shared registry, and kobako_core::DispatchError and kobako::YieldError gain an Unsupported arm. A yield to a guest without block support raises Kobako::UnsupportedError at the Service's yield instead of failing the invocation as a trap.
|
|
11
|
+
* **runtime:** Runtime::invoke takes &kobako_transport::envelope::Frames in place of kobako_runtime::Frames, whose byte fields are gone. Guest::eval and Guest::run (and their MrbGuest flows) receive the decoded Frames, eval also its source; kobako_core::frames::read_frame is replaced by read_eval_frames and read_frames.
|
|
12
|
+
* **runtime:** kobako_runtime::snapshot::Completion::Outcome holds a decoded kobako_transport::envelope::Outcome instead of Vec<u8>; an engine decodes the result itself and reports an unframed one as
|
|
13
|
+
* **runtime:** kobako_runtime::runtime::Entry::Run holds { entrypoint: &str, payload: Vec<u8> } instead of { envelope: &[u8] }; a frontend passes the fields, and an engine encodes the Run envelope itself.
|
|
14
|
+
* **runtime:** kobako_runtime::yielder::Yielder::yield_to_block returns Result<YieldReply, Trap> instead of the encoded bytes; an engine implementing it decodes the reply itself.
|
|
15
|
+
* a memory budget or deadline reached during a Service answer or a yielded block now raises Kobako::MemoryLimitError or
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **guest:** raise Kobako::UnsupportedError when a host does not serve a call ([db003af](https://github.com/elct9620/kobako/commit/db003af75cf2d11b5f644986aa77c0408f5f59ba)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
20
|
+
* **runtime:** compile a Preamble's snippets on an instance of their own ([303432d](https://github.com/elct9620/kobako/commit/303432dd90ed62dc378391515e68e784e1b473a4)), closes [#75](https://github.com/elct9620/kobako/issues/75)
|
|
21
|
+
* **transport:** carry each operation as a tag on one channel per direction ([c7d574b](https://github.com/elct9620/kobako/commit/c7d574b59a42e58632f2bdc46cc9a8e4c7ac6121)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
22
|
+
* **transport:** keep an unknown request's body, and refuse tag 0x00 ([edb64df](https://github.com/elct9620/kobako/commit/edb64df8f87694d17c5b6fd71ac63329023306ec)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
23
|
+
* **transport:** set an invocation up from a Preamble of tagged records ([84d4498](https://github.com/elct9620/kobako/commit/84d4498464f94d7312f26931ddd4ec43894a6654))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* end the invocation as the trap a host callback into the guest hits ([b8f47d3](https://github.com/elct9620/kobako/commit/b8f47d3699a19adcfd5596af93136401a286c761))
|
|
29
|
+
* keep a compile's bytecode only when the Preamble still fits it ([765db08](https://github.com/elct9620/kobako/commit/765db08ad31c8f5163794c7e144355f3237af93b))
|
|
30
|
+
* **wasmtime:** keep a compile answer only when it covers every source snippet ([93e9ddd](https://github.com/elct9620/kobako/commit/93e9ddd79f714446ae713a3145c59d3d2f4145dc))
|
|
31
|
+
* **wasmtime:** tell every frontend how to recover, not only the gem ([cab805a](https://github.com/elct9620/kobako/commit/cab805ab62441a0ca2c78775b7a0d7ce9cc05c43))
|
|
32
|
+
* **wasmtime:** word a missing or mistyped export one way ([8ab0aee](https://github.com/elct9620/kobako/commit/8ab0aeec8bda2960b6cab0502ef51860ea9093b8))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Code Refactoring
|
|
36
|
+
|
|
37
|
+
* **runtime:** hand a frontend the decoded Outcome ([0da6aac](https://github.com/elct9620/kobako/commit/0da6aac0f7943d694a5a4ab9439b2d6c527ae1b0)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
38
|
+
* **runtime:** hand a frontend the decoded Yield Reply ([e80ab54](https://github.com/elct9620/kobako/commit/e80ab54e36c2a80b0f746c675f85bd1238fd4331)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
39
|
+
* **runtime:** let the ABI endpoints frame and read the Frames ([dea069a](https://github.com/elct9620/kobako/commit/dea069ac49a59c12a6f7102ed3cf2925f316cbff)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
40
|
+
* **runtime:** let the engine frame a run's entrypoint and payload ([62f9c94](https://github.com/elct9620/kobako/commit/62f9c9407ff3ce690723625bea075fa49d2dda47)), closes [#73](https://github.com/elct9620/kobako/issues/73)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Build System
|
|
44
|
+
|
|
45
|
+
* declare the Rust 1.96 floor wasmtime 49 sets ([f49a2df](https://github.com/elct9620/kobako/commit/f49a2df88fa51d3b0eecb619cab1e98d79bb4245))
|
|
46
|
+
|
|
47
|
+
## [0.17.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.16.0...kobako-wasmtime-v0.17.0) (2026-09-20)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Miscellaneous Chores
|
|
51
|
+
|
|
52
|
+
* **kobako-wasmtime:** Synchronize kobako crates versions
|
|
53
|
+
|
|
3
54
|
## [0.16.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.15.0...kobako-wasmtime-v0.16.0) (2026-09-15)
|
|
4
55
|
|
|
5
56
|
|
|
@@ -6,16 +6,14 @@ The [wasmtime](https://wasmtime.dev) implementation of the
|
|
|
6
6
|
|
|
7
7
|
`Driver` implements the contract's `Runtime` trait over wasmtime and
|
|
8
8
|
owns every engine-bound mechanic, so frontends see only the neutral
|
|
9
|
-
contract surface
|
|
9
|
+
contract surface.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- ambient denial: frozen WASI clocks and a constant RNG, so a guest
|
|
18
|
-
observes no real time and no real entropy
|
|
11
|
+
| Mechanic | What the driver holds |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Compilation | process-wide Engine and compiled-Module caches, with an on-disk AOT (`.cwasm`) artifact cache keyed by Guest Binary content |
|
|
14
|
+
| Instantiation | a pre-linked `InstancePre` per guest path; every invocation runs on a fresh instance and discards its Store afterwards |
|
|
15
|
+
| Bounds | the epoch-based wall-clock timeout and the per-invocation linear-memory cap |
|
|
16
|
+
| Ambient denial | under the default hermetic profile, frozen clocks and a constant RNG |
|
|
19
17
|
|
|
20
18
|
The kobako Ruby gem's native ext is the first frontend; a Rust host
|
|
21
19
|
SDK consumes the same surface.
|
|
@@ -24,7 +22,7 @@ SDK consumes the same surface.
|
|
|
24
22
|
|
|
25
23
|
```toml
|
|
26
24
|
[dependencies]
|
|
27
|
-
kobako-wasmtime = "0.
|
|
25
|
+
kobako-wasmtime = "0.18.0" # x-release-please-version
|
|
28
26
|
```
|
|
29
27
|
|
|
30
28
|
## License
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/lib/kobako/3.3/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/3.4/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/4.0/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/capture.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
|
-
|
|
5
|
-
# cap, together with whether it wrote past the cap. Frozen, so the bytes
|
|
6
|
-
# and the flag always travel together.
|
|
7
|
-
class Capture
|
|
4
|
+
class Capture # :nodoc:
|
|
8
5
|
attr_reader :bytes
|
|
9
6
|
|
|
10
7
|
# The bytes read as UTF-8 when they are valid UTF-8, and as binary
|
|
@@ -4,37 +4,17 @@ require_relative "../unresolved"
|
|
|
4
4
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Catalog
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
# present when the Sandbox seals, and resolves each callable-backed
|
|
12
|
-
# path to a fresh object for every invocation — returning that map to the
|
|
13
|
-
# driving +Context+ rather than mutating the shared registry, so
|
|
14
|
-
# concurrent invocations stay shared-nothing.
|
|
15
|
-
#
|
|
16
|
-
# Sealing and the reject-after-install guard are governed by the owning
|
|
17
|
-
# Sandbox through +Catalog::Services#sealed?+, the shared seal signal;
|
|
18
|
-
# this registry only records entries and enforces the Extension-shape
|
|
19
|
-
# and dependency rules.
|
|
20
|
-
class Extensions
|
|
7
|
+
# The installed Extensions, composed onto the Service and snippet
|
|
8
|
+
# registries. Providers resolve afresh for each invocation and are handed
|
|
9
|
+
# back rather than stored, so concurrent invocations share nothing.
|
|
10
|
+
class Extensions # :nodoc:
|
|
21
11
|
def initialize
|
|
22
12
|
@entries = [] # : Array[untyped]
|
|
23
13
|
@asserted = false
|
|
24
14
|
end
|
|
25
15
|
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# backend path in +services+. A static +object:+ binds directly; a
|
|
29
|
-
# per-invocation +provider:+ or a fillable backend reserves the path with
|
|
30
|
-
# +Kobako::Unresolved+ (the per-invocation result is layered onto the
|
|
31
|
-
# Context, a fillable stays Unresolved until filled). Raises
|
|
32
|
-
# +ArgumentError+ for the two shapes +#validate!+ owns — +source+ not a
|
|
33
|
-
# String, or a present +backend+ that omits +path+ / +object+ /
|
|
34
|
-
# +provider+. The Extension readers are duck-typed, so an object missing
|
|
35
|
-
# them surfaces the underlying +NoMethodError+, and a malformed +name+ or
|
|
36
|
-
# +backend.path+ surfaces through the +snippets+ / +services+
|
|
37
|
-
# registration it routes to.
|
|
16
|
+
# The Extension readers are duck-typed, so a malformed +name+ or
|
|
17
|
+
# +backend.path+ surfaces through the registry it routes to.
|
|
38
18
|
def install(extension, snippets:, services:)
|
|
39
19
|
validate!(extension)
|
|
40
20
|
snippets.register(code: extension.source, name: extension.name)
|
|
@@ -44,27 +24,24 @@ module Kobako
|
|
|
44
24
|
self
|
|
45
25
|
end
|
|
46
26
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
# so a seal that failed re-checks on the next attempt rather than
|
|
52
|
-
# silently passing a broken Sandbox. Idempotent across later invocations.
|
|
53
|
-
def seal!
|
|
27
|
+
# The asserted flag flips only on success, so a check that failed
|
|
28
|
+
# re-runs on the next invocation rather than silently passing a broken
|
|
29
|
+
# Sandbox.
|
|
30
|
+
def assert_dependencies!
|
|
54
31
|
return self if @asserted
|
|
55
32
|
|
|
56
|
-
|
|
33
|
+
extension, dependency = unmet_dependency
|
|
34
|
+
if extension
|
|
35
|
+
raise ArgumentError,
|
|
36
|
+
"Extension #{extension.name.inspect} depends on #{dependency.inspect}, which is not installed"
|
|
37
|
+
end
|
|
38
|
+
|
|
57
39
|
@asserted = true
|
|
58
40
|
self
|
|
59
41
|
end
|
|
60
42
|
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
# static base bindings. Distinct providers yield distinct objects; one
|
|
64
|
-
# provider shared by several Extensions is invoked once and its result
|
|
65
|
-
# shared, so provider identity is resource identity. Static and fillable
|
|
66
|
-
# backends are omitted — they stay the object bound at install in the
|
|
67
|
-
# base registry (+Kobako::Unresolved+ for a fillable).
|
|
43
|
+
# One provider shared by several Extensions is invoked once and its
|
|
44
|
+
# result shared, so provider identity is resource identity.
|
|
68
45
|
def resolve
|
|
69
46
|
resolved = {} # : Hash[String, untyped]
|
|
70
47
|
by_provider = {} # : Hash[untyped, untyped]
|
|
@@ -75,15 +52,8 @@ module Kobako
|
|
|
75
52
|
|
|
76
53
|
private
|
|
77
54
|
|
|
78
|
-
# The object a backend binds at install: a static +object:+ directly, or
|
|
79
|
-
# +Kobako::Unresolved+ as the placeholder for a per-invocation +provider:+
|
|
80
|
-
# (layered onto the Context) or a fillable (neither keyword).
|
|
81
55
|
def install_object(backend) = backend.object.nil? ? Kobako::Unresolved : backend.object
|
|
82
56
|
|
|
83
|
-
# Resolve one Extension's +provider:+-backed path against the shared
|
|
84
|
-
# per-invocation identity cache (keyed by provider identity) and record
|
|
85
|
-
# it in +resolved+. A static or fillable backend is skipped — its object
|
|
86
|
-
# stays as bound at install in the base registry.
|
|
87
57
|
def resolve_backend(extension, resolved, by_provider)
|
|
88
58
|
backend = extension.backend
|
|
89
59
|
return unless backend
|
|
@@ -94,9 +64,6 @@ module Kobako
|
|
|
94
64
|
resolved[backend.path.to_s] = by_provider.fetch(provider) { by_provider[provider] = provider.call }
|
|
95
65
|
end
|
|
96
66
|
|
|
97
|
-
# Enforce the Extension-shape checks +#preload+ / +#bind+ do not: a
|
|
98
|
-
# mandatory String +source+ (the install/bind boundary) and a
|
|
99
|
-
# +backend+ that, when present, exposes +path+ / +object+ / +provider+.
|
|
100
67
|
def validate!(extension)
|
|
101
68
|
source = extension.source
|
|
102
69
|
raise ArgumentError, "Extension #source must be a String, got #{source.class}" unless source.is_a?(String)
|
|
@@ -108,21 +75,16 @@ module Kobako
|
|
|
108
75
|
raise ArgumentError, "Extension #backend must expose #path, #object, and #provider"
|
|
109
76
|
end
|
|
110
77
|
|
|
111
|
-
def
|
|
78
|
+
def unmet_dependency
|
|
112
79
|
names = @entries.map { |extension| symbolize(extension.name) }
|
|
113
80
|
@entries.each do |extension|
|
|
114
81
|
(extension.depends_on || []).each do |dependency|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
raise ArgumentError,
|
|
118
|
-
"Extension #{extension.name.inspect} depends on #{dependency.inspect}, which is not installed"
|
|
82
|
+
return [extension, dependency] unless names.include?(symbolize(dependency))
|
|
119
83
|
end
|
|
120
84
|
end
|
|
85
|
+
nil
|
|
121
86
|
end
|
|
122
87
|
|
|
123
|
-
# Match names and dependencies by Symbol so the Symbol-or-String forms
|
|
124
|
-
# of a constant token are interchangeable; a value that is neither
|
|
125
|
-
# falls through unchanged to the not-installed path.
|
|
126
88
|
def symbolize(name) = name.is_a?(String) ? name.to_sym : name
|
|
127
89
|
end
|
|
128
90
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Catalog
|
|
5
|
+
class Flags # :nodoc:
|
|
6
|
+
def initialize
|
|
7
|
+
@entries = {} # : Hash[String, [String, bool]]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Each entry is the name, the value, and whether the flag is critical.
|
|
11
|
+
def entries
|
|
12
|
+
@entries.map { |name, (value, critical)| [name, value, critical] }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register(name, value, advisory:)
|
|
16
|
+
unless name.is_a?(Symbol) || name.is_a?(String)
|
|
17
|
+
raise ArgumentError, "flag name must be a Symbol or String, got #{name.class}"
|
|
18
|
+
end
|
|
19
|
+
raise ArgumentError, "flag value must be a String, got #{value.class}" unless value.is_a?(String)
|
|
20
|
+
|
|
21
|
+
key = utf8!(name.to_s, "flag name")
|
|
22
|
+
raise ArgumentError, "flag #{key.inspect} already set" if @entries.key?(key)
|
|
23
|
+
|
|
24
|
+
@entries[key] = [utf8!(value, "flag value"), !advisory]
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# The native side carries a flag only as UTF-8 text, so text read as
|
|
31
|
+
# bytes is re-tagged, and bytes that are not UTF-8 are refused here
|
|
32
|
+
# rather than failing every invocation.
|
|
33
|
+
def utf8!(text, what)
|
|
34
|
+
utf8 = text.dup.force_encoding(Encoding::UTF_8)
|
|
35
|
+
raise ArgumentError, "#{what} must be UTF-8 text" unless utf8.valid_encoding?
|
|
36
|
+
|
|
37
|
+
utf8
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -7,30 +7,11 @@ require_relative "../transport/exposure"
|
|
|
7
7
|
|
|
8
8
|
module Kobako
|
|
9
9
|
module Catalog
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
# Lifecycle invariants:
|
|
17
|
-
#
|
|
18
|
-
# - Handle IDs are allocated by a monotonically increasing counter
|
|
19
|
-
# scoped to a single invocation. The first ID issued in an
|
|
20
|
-
# invocation is 1; ID 0 is reserved as the invalid sentinel and is
|
|
21
|
-
# never returned by +#alloc+.
|
|
22
|
-
#
|
|
23
|
-
# - A fresh table backs each invocation, so a Handle issued in one
|
|
24
|
-
# invocation resolves in no other — uniformly regardless of
|
|
25
|
-
# allocation source (Service return or host-injected argument).
|
|
26
|
-
#
|
|
27
|
-
# - The cap is +0x7fff_ffff+ (2³¹ − 1). Allocation beyond the cap
|
|
28
|
-
# raises immediately — no silent truncation, no wrap, no ID reuse.
|
|
29
|
-
class Handles
|
|
30
|
-
# Build a fresh, empty table. +next_id+ is an internal seam that
|
|
31
|
-
# sets the starting value of the monotonic counter (defaults to 1);
|
|
32
|
-
# tests pass a value near +Kobako::Handle::MAX_ID+ to exercise
|
|
33
|
-
# the cap-exhaustion path without 2³¹ allocations.
|
|
10
|
+
# One invocation's Handle table, mapping opaque ids to host objects. Each
|
|
11
|
+
# invocation mints its own, so a Handle resolves only where it was issued.
|
|
12
|
+
class Handles # :nodoc:
|
|
13
|
+
# +next_id+ lets tests start near +Kobako::Handle::MAX_ID+ to reach
|
|
14
|
+
# exhaustion without 2³¹ allocations.
|
|
34
15
|
def initialize(next_id: 1)
|
|
35
16
|
@entries = {} # : Hash[Integer, Kobako::Transport::Exposure]
|
|
36
17
|
surfaces = {} # : Hash[Module, Set[Symbol]]
|
|
@@ -38,19 +19,6 @@ module Kobako
|
|
|
38
19
|
@next_id = next_id
|
|
39
20
|
end
|
|
40
21
|
|
|
41
|
-
# Bind +object+ in the table and return a +Kobako::Handle+ token
|
|
42
|
-
# for it. +object+ is any host-side Ruby object to bind. Returns a
|
|
43
|
-
# freshly-allocated +Kobako::Handle+ whose +#id+ falls in
|
|
44
|
-
# +[Kobako::Handle::MIN_ID, Kobako::Handle::MAX_ID]+. Raises
|
|
45
|
-
# +Kobako::HandleExhaustedError+ if the next ID would exceed the
|
|
46
|
-
# cap. The cap is anchored on +Kobako::Handle+ — the wire codec
|
|
47
|
-
# and the allocator share the same invariant.
|
|
48
|
-
#
|
|
49
|
-
# Returning a Handle (rather than a bare Integer id) keeps the
|
|
50
|
-
# allocator's output a domain entity. An id is the Handle's only
|
|
51
|
-
# content, so the same internal +Kobako::Handle.restore+ constructor
|
|
52
|
-
# serves both this allocator and the codec's wire-decode path.
|
|
53
|
-
#
|
|
54
22
|
# The entry records the object's Exposure as it stands at mint, so a
|
|
55
23
|
# call made through this Handle is authorized against the reference
|
|
56
24
|
# the guest was given.
|
|
@@ -63,27 +31,17 @@ module Kobako
|
|
|
63
31
|
Kobako::Handle.restore(id)
|
|
64
32
|
end
|
|
65
33
|
|
|
66
|
-
# Resolve a Handle ID to its bound object — the very object +#alloc+
|
|
67
|
-
# received, so a Handle crossing back restores to it. +id+ is a Handle
|
|
68
|
-
# ID previously returned by +#alloc+. Raises +Kobako::SandboxError+ if
|
|
69
|
-
# +id+ is not currently bound.
|
|
70
34
|
def fetch(id)
|
|
71
35
|
exposure(id).object
|
|
72
36
|
end
|
|
73
37
|
|
|
74
|
-
# The Exposure +id+ was minted with, which authorizes a call the guest
|
|
75
|
-
# makes through that Handle. Raises +Kobako::SandboxError+ if +id+ is
|
|
76
|
-
# not currently bound.
|
|
77
38
|
def exposure(id)
|
|
78
39
|
require_bound!(id)
|
|
79
40
|
@entries[id]
|
|
80
41
|
end
|
|
81
42
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# exactly the Handle entries it should — the +Handles+ table itself never
|
|
85
|
-
# consults its own size, but the surrounding code's allocation
|
|
86
|
-
# contract is part of the observable boundary.
|
|
43
|
+
# The table never consults its own size; tests read it to pin how many
|
|
44
|
+
# Handles each path allocates.
|
|
87
45
|
def size
|
|
88
46
|
@entries.size
|
|
89
47
|
end
|
|
@@ -113,8 +71,6 @@ module Kobako
|
|
|
113
71
|
end
|
|
114
72
|
end
|
|
115
73
|
|
|
116
|
-
# Guard #alloc against issuing an ID past the cap. Returns +nil+
|
|
117
|
-
# on success; raises +Kobako::HandleExhaustedError+ at exhaustion.
|
|
118
74
|
def ensure_capacity!
|
|
119
75
|
cap = Kobako::Handle::MAX_ID
|
|
120
76
|
return unless @next_id > cap
|
|
@@ -124,9 +80,6 @@ module Kobako
|
|
|
124
80
|
"in a single invocation (limit #{cap})"
|
|
125
81
|
end
|
|
126
82
|
|
|
127
|
-
# Single source of truth for the "unknown Handle id" raise used by
|
|
128
|
-
# #fetch. Returns +nil+ on success; raises +Kobako::SandboxError+
|
|
129
|
-
# when +id+ is not currently bound.
|
|
130
83
|
def require_bound!(id)
|
|
131
84
|
return if @entries.key?(id)
|
|
132
85
|
|