kobako 0.13.0-aarch64-linux → 0.14.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +30 -0
  4. data/README.md +3 -3
  5. data/ROADMAP.md +26 -0
  6. data/crates/kobako-runtime/CHANGELOG.md +7 -0
  7. data/crates/kobako-runtime/README.md +1 -1
  8. data/crates/kobako-wasmtime/CHANGELOG.md +7 -0
  9. data/crates/kobako-wasmtime/README.md +1 -1
  10. data/data/kobako.wasm +0 -0
  11. data/lib/kobako/3.3/kobako.so +0 -0
  12. data/lib/kobako/3.4/kobako.so +0 -0
  13. data/lib/kobako/4.0/kobako.so +0 -0
  14. data/lib/kobako/catalog/handles.rb +2 -2
  15. data/lib/kobako/catalog/namespaces.rb +2 -2
  16. data/lib/kobako/catalog/snippets.rb +1 -1
  17. data/lib/kobako/codec/decoder.rb +7 -7
  18. data/lib/kobako/codec/encoder.rb +2 -2
  19. data/lib/kobako/codec/error.rb +3 -3
  20. data/lib/kobako/codec/factory.rb +10 -10
  21. data/lib/kobako/codec/handle_walk.rb +17 -21
  22. data/lib/kobako/codec/utils.rb +9 -9
  23. data/lib/kobako/codec.rb +3 -3
  24. data/lib/kobako/errors.rb +14 -39
  25. data/lib/kobako/handle.rb +5 -2
  26. data/lib/kobako/namespace.rb +3 -3
  27. data/lib/kobako/outcome.rb +1 -0
  28. data/lib/kobako/sandbox.rb +10 -12
  29. data/lib/kobako/transport/dispatcher.rb +17 -14
  30. data/lib/kobako/transport/request.rb +2 -2
  31. data/lib/kobako/transport/response.rb +2 -2
  32. data/lib/kobako/transport/run.rb +1 -1
  33. data/lib/kobako/transport/yield.rb +3 -3
  34. data/lib/kobako/transport/yielder.rb +6 -6
  35. data/lib/kobako/version.rb +1 -1
  36. data/release-please-config.json +37 -7
  37. data/sig/kobako/codec/handle_walk.rbs +2 -2
  38. data/sig/kobako/codec.rbs +11 -0
  39. data/sig/kobako/handle.rbs +0 -2
  40. data/sig/kobako/transport/dispatcher.rbs +8 -8
  41. data/sig/kobako/transport/run.rbs +1 -1
  42. data/sig/kobako/transport/yielder.rbs +2 -2
  43. data/sig/kobako/transport.rbs +8 -0
  44. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0a1b4ccd9f4e1debe32912b925847390d00fbf09e798b41eb54ac0756ef4ab3
4
- data.tar.gz: 4819002a95e4407ebacb91d1ae77cd8a7c52d80a1eb0511dcc01ac405d6b7459
3
+ metadata.gz: f52be5d778b482a65c810fb530ac716fd2232d2b54cc98aea60f906aa6959fe9
4
+ data.tar.gz: f537ac1f62dad56b63bbed4456b548495310c6809983765005d54f54ed195e4d
5
5
  SHA512:
6
- metadata.gz: 902954ca6a47611ab670ea52dbe5edc2268428128ffaac0dd9d55e309af6d5b4a70fc47d07eed4f31261bdcdc7158cbdd91fbfad32d56098c8a2c84077db2855
7
- data.tar.gz: 53174e7d4e921b558bca17b3683c0865954ff73219a0c07d86d11e18e6e812506aece5d5dc0626ce021fe9342a63d8d3b5ab612f513d7349e939e0e6e08ed063
6
+ metadata.gz: b0e38dd483517aa63e12d90e1936ee7f27ea0a3c2c1f7a998f7e475b4351313f170284d56c6b9e33f30d7925659adf5ed509b2c3b3f6eeaa8a8ae75bd25d620d
7
+ data.tar.gz: 2269f4eac755583383a491a9441cd2b30f54e174c7b16187ae0ef9e9520bd69dffd8950061aa59a387e9e91360d9c8bfdc21232f1da746cb3385b831ad723adb
@@ -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/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
 
@@ -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
@@ -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
 
@@ -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
data/data/kobako.wasm CHANGED
Binary file
Binary file
Binary file
Binary file
@@ -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.
@@ -5,16 +5,16 @@ require_relative "../handle"
5
5
  module Kobako
6
6
  module Codec
7
7
  # Substitutes Capability Handles into and out of a Ruby value tree at
8
- # the host↔guest boundary. {deep_wrap} allocates a +Kobako::Handle+ for
8
+ # the host↔guest boundary. #deep_wrap allocates a +Kobako::Handle+ for
9
9
  # each non-wire-representable leaf on the host→guest +#run+ argument
10
- # path; {deep_restore} resolves each wire-decoded Handle back to its
11
- # host object on every guest→host value path. {representable?} is the
12
- # by-value codec-type predicate that decides which leaves {deep_wrap}
10
+ # path; #deep_restore resolves each wire-decoded Handle back to its
11
+ # host object on every guest→host value path. #representable? is the
12
+ # by-value codec-type predicate that decides which leaves #deep_wrap
13
13
  # must wrap: the closed 12-entry wire type set
14
14
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Type
15
15
  # Mapping).
16
16
  #
17
- # All helpers are pure except {deep_wrap}, whose only side effect is
17
+ # All helpers are pure except #deep_wrap, whose only side effect is
18
18
  # allocating new Handle ids into the supplied table.
19
19
  module HandleWalk
20
20
  module_function
@@ -24,7 +24,7 @@ module Kobako
24
24
  # unsigned +uint 64+ maximum
25
25
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Type
26
26
  # Mapping #3, the +fixint+ / +int 8..64+ / +uint 8..64+ union).
27
- # Anchored as a +Range+ so {primitive_type?} stays a single
27
+ # Anchored as a +Range+ so #primitive_type? stays a single
28
28
  # dispatch line. This is the codec's encode domain — not to
29
29
  # be confused with the Handle id range, which lives on
30
30
  # +Kobako::Handle+ as +MIN_ID+ / +MAX_ID+ (1..2^31 − 1) and
@@ -41,13 +41,13 @@ module Kobako
41
41
  # representable. Integers outside the codec's signed-64 /
42
42
  # unsigned-64 union are rejected so the predicate agrees with the
43
43
  # msgpack gem's encode-time +RangeError+ behaviour the codec
44
- # already surfaces as {UnsupportedType}.
44
+ # already surfaces as UnsupportedType.
45
45
  def representable?(value)
46
46
  primitive_type?(value) || container_representable?(value)
47
47
  end
48
48
 
49
49
  # Deep-walk Array / Hash containers in +value+ and replace every
50
- # leaf that fails {representable?} with a +Kobako::Handle+
50
+ # leaf that fails #representable? with a +Kobako::Handle+
51
51
  # allocated from +handler+. The
52
52
  # walk only descends through representable container shapes
53
53
  # (Array, Hash) one structural level at a time; a non-representable
@@ -61,13 +61,9 @@ module Kobako
61
61
  # whose leaves are either representable or +Kobako::Handle+
62
62
  # tokens.
63
63
  #
64
- # The block bodies spell +HandleWalk.deep_wrap+ explicitly rather
65
- # than the unqualified +deep_wrap+ because +module_function+ makes
66
- # the instance copy of these helpers private; an implicit receiver
67
- # inside a block would resolve against the enclosing +self+
68
- # (still +HandleWalk+ at definition time, but the qualified form
69
- # keeps the dispatch readable when the recursive call sits inside a
70
- # Proc captured from elsewhere).
64
+ # Recursive calls spell the +HandleWalk.+ receiver so the dispatch
65
+ # stays valid even when a block is captured and run under a
66
+ # different +self+ (+module_function+ privatizes the instance copies).
71
67
  def deep_wrap(value, handler)
72
68
  case value
73
69
  when ::Array then value.map { |element| HandleWalk.deep_wrap(element, handler) }
@@ -79,7 +75,7 @@ module Kobako
79
75
 
80
76
  # Deep-walk Array / Hash containers in +value+ and replace every
81
77
  # +Kobako::Handle+ leaf with the host-side object +handler+ resolves
82
- # it to. The symmetric inverse of {deep_wrap}: that walk allocates objects
78
+ # it to. The symmetric inverse of #deep_wrap: that walk allocates objects
83
79
  # into Handles on the host→guest argument path; this walk resolves
84
80
  # Handles back to their objects wherever a guest→host payload carries
85
81
  # one — an invocation result, a yield-block result, or a dispatch
@@ -107,9 +103,9 @@ module Kobako
107
103
  end
108
104
  end
109
105
 
110
- # The non-container branch of {representable?}: returns +true+ for
106
+ # The non-container branch of #representable?: returns +true+ for
111
107
  # the scalar leaves and an existing Handle. Not part of the
112
- # public surface; reach for {representable?} instead.
108
+ # public surface; reach for #representable? instead.
113
109
  def primitive_type?(value)
114
110
  case value
115
111
  when ::NilClass, ::TrueClass, ::FalseClass, ::Float, ::String, ::Symbol, Kobako::Handle then true
@@ -118,10 +114,10 @@ module Kobako
118
114
  end
119
115
  end
120
116
 
121
- # The container branch of {representable?}: recurses into Array
117
+ # The container branch of #representable?: recurses into Array
122
118
  # elements and Hash key+value pairs through the public
123
- # {representable?}. Not part of the public surface; reach for
124
- # {representable?} instead.
119
+ # #representable?. Not part of the public surface; reach for
120
+ # #representable? instead.
125
121
  def container_representable?(value)
126
122
  case value
127
123
  when ::Array then value.all? { |element| HandleWalk.representable?(element) }
@@ -10,18 +10,18 @@ module Kobako
10
10
  # - UTF-8 assertion at the codec boundary
11
11
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md]
12
12
  # § str/bin Encoding Rules and § Ext Types → ext 0x00). Used by
13
- # {Decoder} when walking +str+ family payloads and by {Factory}
13
+ # Decoder when walking +str+ family payloads and by Factory
14
14
  # when validating the +ext 0x00+ Symbol payload.
15
15
  # - +ArgumentError+ translation at the codec boundary
16
- # ({with_boundary}) so the public taxonomy stays
17
- # {Kobako::Codec::Error}.
16
+ # (#with_boundary) so the public taxonomy stays
17
+ # Kobako::Codec::Error.
18
18
  #
19
19
  # Both helpers are pure — they only inspect inputs, never mutate them.
20
- # The host↔guest Handle substitution walk lives in {HandleWalk}.
20
+ # The host↔guest Handle substitution walk lives in HandleWalk.
21
21
  module Utils
22
22
  module_function
23
23
 
24
- # Raise {InvalidEncoding} unless +string+'s bytes are valid under
24
+ # Raise InvalidEncoding unless +string+'s bytes are valid under
25
25
  # its current encoding tag. +label+ is the caller-supplied prefix
26
26
  # for the error message (e.g. +"str payload"+, +"Symbol payload"+).
27
27
  def assert_utf8!(string, label)
@@ -32,13 +32,13 @@ module Kobako
32
32
 
33
33
  # Run +block+ at the codec boundary: a value object raises
34
34
  # +ArgumentError+ when an invariant is violated at construction, and
35
- # this helper surfaces that as {InvalidType} so the public taxonomy
36
- # stays {Kobako::Codec::Error} and never leaks +ArgumentError+ from
35
+ # this helper surfaces that as InvalidType so the public taxonomy
36
+ # stays Kobako::Codec::Error and never leaks +ArgumentError+ from
37
37
  # the Ruby standard library.
38
38
  #
39
39
  # Reach for this only where a value object is constructed outside a
40
- # {Decoder.decode} block, whose rescue already performs the same
41
- # mapping (worked example: {Factory#unpack_handle} building
40
+ # Decoder.decode block, whose rescue already performs the same
41
+ # mapping (worked example: Factory#unpack_handle building
42
42
  # +Handle.restore+ from a raw fixext payload). Do not use it for
43
43
  # general-purpose validation outside the codec boundary —
44
44
  # host-layer +ArgumentError+ values should propagate unchanged.
data/lib/kobako/codec.rb CHANGED
@@ -18,12 +18,12 @@ module Kobako
18
18
  # the kobako root so the codec can register them without depending
19
19
  # upward on Transport.
20
20
  #
21
- # Backed by the official +msgpack+ gem via {Factory}; {Encoder} and
22
- # {Decoder} are thin wrappers that register the three kobako-specific
21
+ # Backed by the official +msgpack+ gem via Factory; Encoder and
22
+ # Decoder are thin wrappers that register the three kobako-specific
23
23
  # ext types (0x00 Symbol, 0x01 Capability Handle, 0x02 Exception
24
24
  # envelope) on a single +MessagePack::Factory+ instance. The Rust side
25
25
  # mirrors this layer as the +codec+ module in the +kobako-codec+ crate;
26
- # the ext-code constants live as module-private values on {Factory}
26
+ # the ext-code constants live as module-private values on Factory
27
27
  # alongside +codec::EXT_SYMBOL+ / +codec::EXT_HANDLE+ /
28
28
  # +codec::EXT_ERRENV+ on that side.
29
29
  module Codec
data/lib/kobako/errors.rb CHANGED
@@ -4,38 +4,13 @@
4
4
  module Kobako
5
5
  # Error taxonomy.
6
6
  #
7
- # Every `Kobako::Sandbox` invocation (`#eval` or `#run`) either returns a value or raises
8
- # exactly one of three invocation-outcome classes. Attribution is decided after the
9
- # guest binary returns control to the host: first the Wasm-trap layer, then
10
- # the outcome-envelope tag.
11
- #
12
- # Three invocation-outcome branches:
13
- #
14
- # * {TrapError} — Wasm engine layer (trap, OOM, unreachable, or a
15
- # wire-violation fallback signalling a corrupted
16
- # guest runtime).
17
- # * {SandboxError} — sandbox / wire layer (mruby script error,
18
- # wire-decode failure on an otherwise valid tag,
19
- # Catalog::Handles exhaustion, output buffer overrun).
20
- # * {ServiceError} — service / capability layer (a Service capability
21
- # call that failed and was not rescued inside the
22
- # script).
23
- #
24
- # Two further branches sit outside the invocation taxonomy:
25
- #
26
- # * {SetupError} — construction layer. Raised by `Kobako::Sandbox.new`
27
- # when the wasm runtime cannot be built from the
28
- # configured +wasm_path+ before any invocation runs.
29
- # Not an invocation outcome, so it never passes
30
- # through the two-step attribution decision.
31
- # * {PoolTimeoutError} — pool checkout layer. Raised by `Kobako::Pool#with`
32
- # when the checkout wait exceeds +checkout_timeout+.
33
- #
34
- # Named subclasses:
35
- #
36
- # * {ModuleNotBuiltError} < {SetupError} — Guest Binary artifact absent
37
- # at +wasm_path+.
38
- # * {HandleExhaustedError} < {SandboxError} — Handle id cap hit.
7
+ # Every +Kobako::Sandbox+ invocation (+#eval+ or +#run+) either returns a
8
+ # value or raises exactly one of TrapError, SandboxError, or ServiceError.
9
+ # Attribution is decided after the guest binary returns control to the
10
+ # host: first the Wasm-trap layer, then the outcome-envelope tag.
11
+ # SetupError and PoolTimeoutError sit outside the invocation taxonomy and
12
+ # never pass through that attribution decision. Each class below
13
+ # documents its own layer.
39
14
 
40
15
  # Base for all kobako-raised errors so callers that want to ignore the
41
16
  # taxonomy can rescue a single class.
@@ -48,8 +23,8 @@ module Kobako
48
23
  #
49
24
  # Two named subclasses cover the configured per-invocation caps:
50
25
  #
51
- # * {TimeoutError} — wall-clock +timeout+ exceeded.
52
- # * {MemoryLimitError} — guest +memory.grow+ would exceed
26
+ # * TimeoutError — wall-clock +timeout+ exceeded.
27
+ # * MemoryLimitError — guest +memory.grow+ would exceed
53
28
  # +memory_limit+.
54
29
  #
55
30
  # Host Apps that only care about "guest is unrecoverable, discard the
@@ -128,14 +103,14 @@ module Kobako
128
103
  class HandleExhaustedError < SandboxError; end
129
104
 
130
105
  # BytecodeError is the SandboxError subclass raised when a
131
- # `#preload(binary:)` snippet fails structural validation during the
132
- # first invocation's snippet replay against a fresh `mrb_state` (RITE
106
+ # +#preload(binary:)+ snippet fails structural validation during the
107
+ # first invocation's snippet replay against a fresh +mrb_state+ (RITE
133
108
  # version mismatch or corrupt body). Bytecode that loads cleanly and
134
- # then raises at top level surfaces as plain `SandboxError` with the
109
+ # then raises at top level surfaces as plain +SandboxError+ with the
135
110
  # natural mruby class preserved. Inherits from SandboxError so a single
136
- # `rescue Kobako::SandboxError` covers both source and bytecode
111
+ # +rescue Kobako::SandboxError+ covers both source and bytecode
137
112
  # snippet failures while callers wanting bytecode-specific handling
138
- # can `rescue Kobako::BytecodeError` directly.
113
+ # can +rescue Kobako::BytecodeError+ directly.
139
114
  class BytecodeError < SandboxError; end
140
115
 
141
116
  # Pool checkout layer. Raised by +Kobako::Pool#with+ when the checkout
data/lib/kobako/handle.rb CHANGED
@@ -14,8 +14,10 @@ module Kobako
14
14
  # The constructor is internal to the Host Gem. +Kobako::Handle.new+ is
15
15
  # privatised so Host App code cannot fabricate a Handle from a bare
16
16
  # integer; legitimate Handle instances enter Host App code only as
17
- # fields on raised error objects. The Host Gem itself constructs
18
- # Handles through {.restore}, which exists at exactly two call
17
+ # fields on raised error objects. +#with+ Data's copy-with-changes
18
+ # constructor is removed for the same reason: a legitimate Handle
19
+ # must not derive a sibling with a caller-chosen id. The Host Gem itself constructs
20
+ # Handles through +.restore+, which exists at exactly two call
19
21
  # sites: +Kobako::Codec::Factory#unpack_handle+ (wire decode) and
20
22
  # +Kobako::Codec::HandleWalk.deep_wrap+ / +Kobako::Transport::Dispatcher#wrap_as_handle+
21
23
  # (allocator paths). Both live inside +lib/kobako/+ and are not part
@@ -41,6 +43,7 @@ module Kobako
41
43
  end
42
44
 
43
45
  private_class_method :new
46
+ undef_method :with
44
47
 
45
48
  # Host Gem–internal factory. Allocates the Data instance through
46
49
  # +Class#allocate+ and dispatches +#initialize+ explicitly so the
@@ -3,7 +3,7 @@
3
3
  module Kobako
4
4
  # A named grouping of Members for one Sandbox.
5
5
  # Returned by +Sandbox#define+. Each instance owns a flat name→object
6
- # table of Members; member binding is validated against {NAME_PATTERN}.
6
+ # table of Members; member binding is validated against NAME_PATTERN.
7
7
  class Namespace
8
8
  # Ruby constant-name pattern shared by Namespace and Member names.
9
9
  NAME_PATTERN = /\A[A-Z]\w*\z/
@@ -11,7 +11,7 @@ module Kobako
11
11
  attr_reader :name
12
12
 
13
13
  # Build a new Namespace. +name+ is an already-validated Namespace
14
- # name (must satisfy {NAME_PATTERN}; validation is the caller's
14
+ # name (must satisfy NAME_PATTERN; validation is the caller's
15
15
  # responsibility).
16
16
  def initialize(name)
17
17
  @name = name
@@ -38,7 +38,7 @@ module Kobako
38
38
 
39
39
  # Mark this Namespace as sealed. Called by
40
40
  # +Kobako::Catalog::Namespaces#seal!+ on the owning Sandbox's first
41
- # invocation; afterwards {#bind} raises +ArgumentError+. Idempotent;
41
+ # invocation; afterwards #bind raises +ArgumentError+. Idempotent;
42
42
  # returns +self+.
43
43
  def seal!
44
44
  @sealed = true
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "codec"
3
4
  require_relative "outcome/panic"
4
5
  require_relative "transport/error"
5
6
 
@@ -120,7 +120,9 @@ module Kobako
120
120
  # Register a snippet on this Sandbox in one of two forms:
121
121
  #
122
122
  # * +preload(code: source, name: Name)+ — +source+ is mruby source
123
- # as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. The +name+
123
+ # as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. Compile
124
+ # failures surface as +Kobako::SandboxError+ on the first
125
+ # invocation's replay. The +name+
124
126
  # becomes the snippet's +(snippet:Name)+ backtrace filename and
125
127
  # is the dedupe key that rejects a duplicate +code:+ snippet.
126
128
  # * +preload(binary: bytes)+ — +bytes+ is precompiled RITE
@@ -216,7 +218,7 @@ module Kobako
216
218
  # runtime that cannot honor the request never runs guest code.
217
219
  def build_runtime!
218
220
  runtime = Kobako::Runtime.from_path(@wasm_path, @options.timeout, @options.memory_limit,
219
- @options.stdout_limit, @options.stderr_limit, profile)
221
+ @options.stdout_limit, @options.stderr_limit, @options.profile)
220
222
  @options.enforce_floor!(runtime.profile)
221
223
  runtime
222
224
  end
@@ -257,10 +259,8 @@ module Kobako
257
259
  # the readout here also covers the trap path, where +Runtime#eval+ /
258
260
  # +#run+ raise instead of returning outcome bytes.
259
261
  #
260
- # The ext returns a positional 2-tuple +[wall_time, memory_peak]+
261
- # whose order matches the +Kobako::Usage+ field order; the
262
- # destructure-then-kwargs handoff below is the explicit
263
- # positional→keyword conversion point.
262
+ # The ext-side contract is positional: +Runtime#usage+ yields
263
+ # +[wall_time, memory_peak]+ in +Kobako::Usage+ field order.
264
264
  def read_usage!
265
265
  wall_time, memory_peak = @runtime.usage
266
266
  @usage = Usage.new(wall_time: wall_time, memory_peak: memory_peak)
@@ -283,16 +283,14 @@ module Kobako
283
283
 
284
284
  # Read the per-last-invocation output captures from the ext and wrap
285
285
  # them as +Kobako::Capture+ value objects. Runs in the +invoke!+
286
- # +ensure+ block next to {#read_usage!} for the same reason: the ext
286
+ # +ensure+ block next to #read_usage! for the same reason: the ext
287
287
  # stashes the captures on every outcome, so the readout also covers
288
288
  # the trap path, where +Runtime#eval+ / +#run+ raise instead of
289
289
  # returning outcome bytes — +#stdout+ / +#stderr+ keep the guest's
290
290
  # partial output readable after a rescue.
291
291
  #
292
- # The ext returns a positional 4-tuple
293
- # +[stdout_bytes, stdout_truncated, stderr_bytes, stderr_truncated]+;
294
- # the destructure-then-kwargs handoff below is the explicit
295
- # positional→keyword conversion point.
292
+ # The ext-side contract is positional: +Runtime#captures+ yields
293
+ # +[stdout_bytes, stdout_truncated, stderr_bytes, stderr_truncated]+.
296
294
  def read_captures!
297
295
  stdout_bytes, stdout_truncated, stderr_bytes, stderr_truncated = @runtime.captures
298
296
  @stdout_capture = Capture.new(bytes: stdout_bytes, truncated: stdout_truncated)
@@ -306,7 +304,7 @@ module Kobako
306
304
  # The yielded block must return the invocation's raw outcome bytes —
307
305
  # i.e. the value of +Runtime#eval+ / +#run+ — which the success path
308
306
  # feeds to +Outcome.decode+. Captures and usage are populated by the
309
- # +ensure+ readouts ({#read_usage!} / {#read_captures!}) on every
307
+ # +ensure+ readouts (#read_usage! / #read_captures!) on every
310
308
  # outcome, so +#stdout+ / +#stderr+ / +#usage+ stay readable after a
311
309
  # rescued trap.
312
310
  # The rescue chain is the single trap-translation boundary —
@@ -27,7 +27,7 @@ module Kobako
27
27
  # Kobako::Transport::Dispatcher.dispatch(request_bytes, namespaces, handler, yield_to_guest)
28
28
  # # => msgpack-encoded Response bytes (never raises)
29
29
  module Dispatcher
30
- # Throw tag for the {Yielder}'s break unwind back to the
30
+ # Throw tag for the Yielder's break unwind back to the
31
31
  # dispatcher's +catch+ frame. +private_constant+ is a
32
32
  # convention boundary — not a defence.
33
33
  BREAK_THROW = :__kobako_break__
@@ -54,12 +54,12 @@ module Kobako
54
54
  # Callable gadget types whose own public methods are reflection surface
55
55
  # (+Proc#binding+ reaches +Binding#eval+, +Method#receiver+ / +#unbind+
56
56
  # hand back the underlying object) rather than Service behaviour. Only
57
- # {CALLABLE_ALLOW} is reachable on a target of these types; a bound
57
+ # CALLABLE_ALLOW is reachable on a target of these types; a bound
58
58
  # lambda stays invocable, its reflective surface does not.
59
59
  GADGET_OWNERS = [Proc, Method, UnboundMethod, Binding].freeze
60
60
  private_constant :GADGET_OWNERS
61
61
 
62
- # The sole methods reachable on a {GADGET_OWNERS} target: invoking it
62
+ # The sole methods reachable on a GADGET_OWNERS target: invoking it
63
63
  # (+call+ / +[]+ / +yield+) and the harmless +arity+ / +lambda?+
64
64
  # describers that aid guest-side debugging.
65
65
  CALLABLE_ALLOW = %i[call [] yield arity lambda?].freeze
@@ -91,7 +91,7 @@ module Kobako
91
91
  end
92
92
 
93
93
  # Resolve positional and keyword arguments off +request+ in one
94
- # step. Both pass through {#resolve_arg} so Capability Handles
94
+ # step. Both pass through #resolve_arg so Capability Handles
95
95
  # round-trip back to the host-side Ruby object before the call
96
96
  # reaches +public_send+.
97
97
  def resolve_call_args(request, handler)
@@ -101,7 +101,7 @@ module Kobako
101
101
 
102
102
  # Map an error caught at the dispatch boundary to a +Response.error+
103
103
  # envelope (binary msgpack). +error+ is the +StandardError+ caught by
104
- # {#dispatch}'s rescue; the +type+ field tells the guest which kind
104
+ # #dispatch's rescue; the +type+ field tells the guest which kind
105
105
  # of failure it was so it can raise the matching proxy-side error.
106
106
  def encode_caught_error(error)
107
107
  case error
@@ -119,8 +119,8 @@ module Kobako
119
119
  # reject calls to no-kwarg methods when the wire carries the
120
120
  # uniform empty-map shape.
121
121
  #
122
- # +yielder+ is the host-side {Yielder} materialised when the guest
123
- # call site supplied a block; its {Yielder#to_proc}
122
+ # +yielder+ is the host-side Yielder materialised when the guest
123
+ # call site supplied a block; its Yielder#to_proc
124
124
  # rides the +&block+ slot. +&nil+ is a no-op block argument in Ruby,
125
125
  # so the same call site handles both cases without an explicit
126
126
  # conditional.
@@ -137,8 +137,8 @@ module Kobako
137
137
  end
138
138
 
139
139
  # Guard the +public_send+ below against ambient reflection methods.
140
- # A public method whose owner is a {META_OWNERS} or {GADGET_OWNERS} module is
141
- # rejected, except {CALLABLE_ALLOW} on a gadget target (a bound lambda
140
+ # A public method whose owner is a META_OWNERS or GADGET_OWNERS module is
141
+ # rejected, except CALLABLE_ALLOW on a gadget target (a bound lambda
142
142
  # stays invocable). A name with no concrete public method is allowed
143
143
  # only when the target opts into it via +respond_to?+ (dynamic
144
144
  # +method_missing+ Services), since the dangerous methods are all
@@ -159,7 +159,7 @@ module Kobako
159
159
  # Consult the target's opt-in narrowing predicate. A bound object
160
160
  # may define a private +respond_to_guest?(name)+ to restrict which of its
161
161
  # methods the guest reaches; a falsy answer rejects the dispatch.
162
- # The predicate composes beneath {#reject_meta_method!} — it only narrows,
162
+ # The predicate composes beneath #reject_meta_method! — it only narrows,
163
163
  # never re-opening the reflection surface the floor rejects — and is
164
164
  # consulted with the private surface included so the guest's +public_send+
165
165
  # dispatch can never reach +respond_to_guest?+ itself.
@@ -213,7 +213,7 @@ module Kobako
213
213
  # Encode +value+ as a +Response.ok+ envelope. When the value is not
214
214
  # wire-representable per the codec's type mapping, the
215
215
  # +UnsupportedType+ rescue routes it through the
216
- # Catalog::Handles via {#wrap_as_handle} and re-encodes with the Capability
216
+ # Catalog::Handles via #wrap_as_handle and re-encodes with the Capability
217
217
  # Handle in place. The happy path encodes exactly once.
218
218
  def encode_ok(value, handler)
219
219
  response = Kobako::Transport::Response.ok(value)
@@ -224,15 +224,18 @@ module Kobako
224
224
 
225
225
  # Allocate +value+ in the Sandbox's Catalog::Handles and return a +Handle+
226
226
  # that the wire codec can carry. Used as the fallback path of
227
- # {#encode_ok} when +value+ has no wire representation.
227
+ # #encode_ok when +value+ has no wire representation.
228
228
  def wrap_as_handle(value, handler)
229
229
  handler.alloc(value)
230
230
  end
231
231
 
232
+ # +message+ folds to UTF-8 first: Ruby core builds some exception
233
+ # messages as ASCII-8BIT (the arity ArgumentError, for one), and
234
+ # the codec would ride those as bin — a shape the guest refuses.
232
235
  def encode_error(type, message)
236
+ message = message.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
233
237
  fault = Kobako::Fault.new(type: type, message: message)
234
- response = Kobako::Transport::Response.error(fault)
235
- response.encode
238
+ Kobako::Transport::Response.error(fault).encode
236
239
  end
237
240
  end
238
241
  end
@@ -13,7 +13,7 @@ module Kobako
13
13
  # 5-element msgpack array:
14
14
  # +[target, method_name, args, kwargs, block_given]+. +target+ is
15
15
  # either a +String+ (+"<Namespace>::<Member>"+, e.g. +"MyService::KV"+)
16
- # or a {Handle}. SPEC pins +kwargs+ map keys to ext 0x00 Symbol;
16
+ # or a Handle. SPEC pins +kwargs+ map keys to ext 0x00 Symbol;
17
17
  # enforced at construction so the Value Object is the single source of
18
18
  # truth. +block_given+ is a Boolean signalling whether the guest call
19
19
  # site supplied a block; the block body itself never crosses the
@@ -43,7 +43,7 @@ module Kobako
43
43
  Codec::Encoder.encode([target, method_name, args, kwargs, block_given])
44
44
  end
45
45
 
46
- # Decode +bytes+ into a {Request}. Raises +Codec::InvalidType+ when the
46
+ # Decode +bytes+ into a Request. Raises +Codec::InvalidType+ when the
47
47
  # envelope is not the expected 5-element msgpack array, or when the
48
48
  # Value Object's construction invariants reject the decoded fields.
49
49
  def self.decode(bytes)
@@ -20,7 +20,7 @@ module Kobako
20
20
  #
21
21
  # 2-element msgpack array: +[status, value-or-fault]+. +status+ is 0
22
22
  # (success) or 1 (fault). For success the second element is the return
23
- # value; for fault it is a {Fault} (ext 0x02 envelope).
23
+ # value; for fault it is a Fault (ext 0x02 envelope).
24
24
  #
25
25
  # Built on the +class X < Data.define(...)+ subclass form so the
26
26
  # class body is fully Steep-visible; see +lib/kobako/outcome/panic.rb+
@@ -58,7 +58,7 @@ module Kobako
58
58
  Codec::Encoder.encode([status, payload])
59
59
  end
60
60
 
61
- # Decode +bytes+ into a {Response}. Raises +Codec::InvalidType+ when the
61
+ # Decode +bytes+ into a Response. Raises +Codec::InvalidType+ when the
62
62
  # envelope is not the expected 2-element msgpack array, or when the
63
63
  # Value Object's construction invariants reject the decoded fields.
64
64
  def self.decode(bytes)
@@ -51,7 +51,7 @@ module Kobako
51
51
  # Encode this Run to the msgpack bytes the guest's +__kobako_run+
52
52
  # entry point consumes as its command-buffer payload
53
53
  # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
54
- # Walks +args+ / +kwargs+ through {Codec::HandleWalk.deep_wrap} so
54
+ # Walks +args+ / +kwargs+ through Codec::HandleWalk.deep_wrap so
55
55
  # any non-wire-representable leaf is allocated into +handler+ and
56
56
  # replaced with a +Kobako::Handle+; the
57
57
  # +handler+ argument is the Sandbox's table, sharing the same
@@ -10,9 +10,9 @@ module Kobako
10
10
  # First byte of the YieldResponse for the success branch — body is
11
11
  # the block's return value encoded as a single msgpack value.
12
12
  TAG_OK = 0x01
13
- # First byte for `break val` — body is the break value.
13
+ # First byte for +break val+ — body is the break value.
14
14
  TAG_BREAK = 0x02
15
- # Reserved for future `return val` support; both sides reject this
15
+ # Reserved for future +return val+ support; both sides reject this
16
16
  # tag as a wire violation (YieldResponse envelope contract).
17
17
  TAG_RESERVED = 0x03
18
18
  # First byte for an error / fault outcome — body is a
@@ -58,7 +58,7 @@ module Kobako
58
58
  [tag].pack("C") + Codec::Encoder.encode(value)
59
59
  end
60
60
 
61
- # Decode +bytes+ into a {Yield}. Rejects empty input, the reserved
61
+ # Decode +bytes+ into a Yield. Rejects empty input, the reserved
62
62
  # tag 0x03, and any tag outside +LIVE_TAGS+ by raising
63
63
  # +Kobako::Codec::InvalidType+ — these are wire violations per the
64
64
  # SPEC's YieldResponse envelope contract.
@@ -13,7 +13,7 @@ module Kobako
13
13
  # Each guest call that carries +block_given: true+ gets a Yielder
14
14
  # that the Dispatcher hands to the Service method as +&block+. The
15
15
  # Service method observes it as an ordinary Ruby Proc through
16
- # {#to_proc}; +yield val+ / +block.call(val)+ invokes {#yield}, which
16
+ # #to_proc; +yield val+ / +block.call(val)+ invokes #yield, which
17
17
  # serialises the positional args, re-enters the guest via the injected
18
18
  # +yield_to_guest+ lambda, and reifies the +YieldResponse+ into Ruby
19
19
  # control flow:
@@ -24,13 +24,13 @@ module Kobako
24
24
  # * +tag 0x04+ error — raise the +{class, message}+ payload at the
25
25
  # Service's yield site
26
26
  #
27
- # The Dispatcher calls {#invalidate!} from its +ensure+ block once
27
+ # The Dispatcher calls #invalidate! from its +ensure+ block once
28
28
  # dispatch completes; any later call to a stashed Yielder then raises
29
29
  # +LocalJumpError+ — the observable shape of an escaped Yielder.
30
30
  class Yielder
31
31
  # +yield_to_guest+ is a +String → String+ callable (the ext's
32
32
  # per-dispatch +Kobako::Runtime::GuestYielder+) that
33
- # {#yield} invokes to re-enter the guest; +break_tag+ is the +catch+
33
+ # #yield invokes to re-enter the guest; +break_tag+ is the +catch+
34
34
  # throw tag the Dispatcher matches against to unwind the Service on
35
35
  # +tag 0x02+. +handler+ is the Sandbox's +Kobako::Catalog::Handles+,
36
36
  # used to restore a Capability Handle in the block's ok value back to
@@ -44,7 +44,7 @@ module Kobako
44
44
 
45
45
  # Re-enter the guest with +args+ and reify the YieldResponse into
46
46
  # Ruby control flow. Raises +LocalJumpError+ if called after
47
- # {#invalidate!}. The ok value is consumed by the host Service
47
+ # #invalidate!. The ok value is consumed by the host Service
48
48
  # method, so a Capability Handle in it is restored to its host object.
49
49
  # The break value unwinds past the Service back to the guest
50
50
  # Member call, so it passes through verbatim — a Handle stays a
@@ -60,14 +60,14 @@ module Kobako
60
60
  raise yield_failure(response.value, default: "yield error")
61
61
  end
62
62
 
63
- # The Proc the Dispatcher passes as +&block+, binding {#yield} so a
63
+ # The Proc the Dispatcher passes as +&block+, binding #yield so a
64
64
  # Service method's +yield+ / +block.call+ drives the round-trip.
65
65
  def to_proc
66
66
  method(:yield).to_proc
67
67
  end
68
68
 
69
69
  # Mark this Yielder dead. Called by the Dispatcher's +ensure+ block
70
- # when the originating dispatch frame returns; any later {#yield}
70
+ # when the originating dispatch frame returns; any later #yield
71
71
  # call then raises +LocalJumpError+.
72
72
  def invalidate!
73
73
  @active = false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.0"
5
5
  end
@@ -29,28 +29,28 @@
29
29
  }
30
30
  ]
31
31
  },
32
- "wasm/kobako": {
33
- "component": "kobako-rs",
32
+ "wasm/kobako-mruby": {
33
+ "component": "kobako-mruby",
34
34
  "release-type": "rust",
35
35
  "extra-files": [
36
36
  {
37
37
  "type": "toml",
38
38
  "path": "/wasm/Cargo.lock",
39
- "jsonpath": "$.package[?(@.name=='kobako')].version"
39
+ "jsonpath": "$.package[?(@.name=='kobako-mruby')].version"
40
40
  },
41
41
  {
42
42
  "type": "toml",
43
- "path": "/wasm/kobako/Cargo.toml",
43
+ "path": "/wasm/kobako-mruby/Cargo.toml",
44
44
  "jsonpath": "$.dependencies['kobako-core'].version"
45
45
  },
46
46
  {
47
47
  "type": "toml",
48
- "path": "/wasm/kobako/Cargo.toml",
48
+ "path": "/wasm/kobako-mruby/Cargo.toml",
49
49
  "jsonpath": "$.dependencies['kobako-codec'].version"
50
50
  },
51
51
  {
52
52
  "type": "generic",
53
- "path": "/wasm/kobako/README.md"
53
+ "path": "/wasm/kobako-mruby/README.md"
54
54
  }
55
55
  ]
56
56
  },
@@ -178,13 +178,43 @@
178
178
  "path": "/crates/kobako-wasmtime/README.md"
179
179
  }
180
180
  ]
181
+ },
182
+ "crates/kobako": {
183
+ "component": "kobako-sdk",
184
+ "release-type": "rust",
185
+ "extra-files": [
186
+ {
187
+ "type": "toml",
188
+ "path": "/crates/Cargo.lock",
189
+ "jsonpath": "$.package[?(@.name=='kobako')].version"
190
+ },
191
+ {
192
+ "type": "toml",
193
+ "path": "/crates/kobako/Cargo.toml",
194
+ "jsonpath": "$.dependencies['kobako-codec'].version"
195
+ },
196
+ {
197
+ "type": "toml",
198
+ "path": "/crates/kobako/Cargo.toml",
199
+ "jsonpath": "$.dependencies['kobako-runtime'].version"
200
+ },
201
+ {
202
+ "type": "toml",
203
+ "path": "/crates/kobako/Cargo.toml",
204
+ "jsonpath": "$.dependencies['kobako-wasmtime'].version"
205
+ },
206
+ {
207
+ "type": "generic",
208
+ "path": "/crates/kobako/README.md"
209
+ }
210
+ ]
181
211
  }
182
212
  },
183
213
  "plugins": [
184
214
  {
185
215
  "type": "linked-versions",
186
216
  "groupName": "kobako crates",
187
- "components": ["kobako-codec", "kobako-core", "kobako-rs", "kobako-io", "kobako-json", "kobako-regexp", "kobako-baker", "kobako-runtime", "kobako-wasmtime"]
217
+ "components": ["kobako-codec", "kobako-core", "kobako-mruby", "kobako-io", "kobako-json", "kobako-regexp", "kobako-baker", "kobako-runtime", "kobako-wasmtime", "kobako-sdk"]
188
218
  }
189
219
  ],
190
220
  "extra-files": [
@@ -5,9 +5,9 @@ module Kobako
5
5
 
6
6
  def self?.representable?: (untyped value) -> bool
7
7
 
8
- def self?.deep_wrap: (untyped value, Kobako::Catalog::Handles handler) -> untyped
8
+ def self?.deep_wrap: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
9
9
 
10
- def self?.deep_restore: (untyped value, Kobako::Catalog::Handles handler) -> untyped
10
+ def self?.deep_restore: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
11
11
 
12
12
  def self?.primitive_type?: (untyped value) -> bool
13
13
 
data/sig/kobako/codec.rbs CHANGED
@@ -1,4 +1,15 @@
1
1
  module Kobako
2
2
  module Codec
3
+ # The Handle allocator/resolver a wire walk works against: alloc
4
+ # registers a non-wire-representable object and returns its Handle,
5
+ # fetch resolves a wire Handle id back to the live object. The
6
+ # Sandbox's Kobako::Catalog::Handles is the production conformer;
7
+ # modelled structurally so the Codec and Transport tiers need no
8
+ # upward dependency on Catalog.
9
+ interface _HandleTable
10
+ def alloc: (untyped object) -> Kobako::Handle
11
+
12
+ def fetch: (Integer id) -> untyped
13
+ end
3
14
  end
4
15
  end
@@ -9,8 +9,6 @@ module Kobako
9
9
 
10
10
  def self.restore: (Integer id) -> Handle
11
11
 
12
- def with: (?id: Integer) -> Handle
13
-
14
12
  def ==: (untyped other) -> bool
15
13
 
16
14
  def hash: () -> Integer
@@ -12,9 +12,9 @@ module Kobako
12
12
 
13
13
  CALLABLE_ALLOW: Array[Symbol]
14
14
 
15
- def self?.dispatch: (String request_bytes, Kobako::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
15
+ def self?.dispatch: (String request_bytes, Kobako::Transport::_NamespaceRegistry namespaces, Kobako::Codec::_HandleTable handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
16
16
 
17
- def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Catalog::Handles handler) -> [Array[untyped], Hash[Symbol, untyped]]
17
+ def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Codec::_HandleTable handler) -> [Array[untyped], Hash[Symbol, untyped]]
18
18
 
19
19
  def self?.encode_caught_error: (StandardError error) -> String
20
20
 
@@ -24,17 +24,17 @@ module Kobako
24
24
 
25
25
  def self?.reject_unexposed!: (untyped target, Symbol name) -> void
26
26
 
27
- def self?.resolve_arg: (untyped value, Kobako::Catalog::Handles handler) -> untyped
27
+ def self?.resolve_arg: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
28
28
 
29
- def self?.resolve_target: (String | Kobako::Handle target, Kobako::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler) -> untyped
29
+ def self?.resolve_target: (String | Kobako::Handle target, Kobako::Transport::_NamespaceRegistry namespaces, Kobako::Codec::_HandleTable handler) -> untyped
30
30
 
31
- def self?.resolve_path: (String path, Kobako::Catalog::Namespaces namespaces) -> untyped
31
+ def self?.resolve_path: (String path, Kobako::Transport::_NamespaceRegistry namespaces) -> untyped
32
32
 
33
- def self?.require_live_object!: (Integer id, Kobako::Catalog::Handles handler) -> untyped
33
+ def self?.require_live_object!: (Integer id, Kobako::Codec::_HandleTable handler) -> untyped
34
34
 
35
- def self?.encode_ok: (untyped value, Kobako::Catalog::Handles handler) -> String
35
+ def self?.encode_ok: (untyped value, Kobako::Codec::_HandleTable handler) -> String
36
36
 
37
- def self?.wrap_as_handle: (untyped value, Kobako::Catalog::Handles handler) -> Kobako::Handle
37
+ def self?.wrap_as_handle: (untyped value, Kobako::Codec::_HandleTable handler) -> Kobako::Handle
38
38
 
39
39
  def self?.encode_error: (String type, String message) -> String
40
40
  end
@@ -11,7 +11,7 @@ module Kobako
11
11
 
12
12
  def initialize: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> void
13
13
 
14
- def encode: (Kobako::Catalog::Handles handler) -> String
14
+ def encode: (Kobako::Codec::_HandleTable handler) -> String
15
15
 
16
16
  private
17
17
 
@@ -3,10 +3,10 @@ module Kobako
3
3
  class Yielder
4
4
  @yield_to_guest: Kobako::Transport::_GuestYielder
5
5
  @break_tag: Symbol
6
- @handler: Kobako::Catalog::Handles
6
+ @handler: Kobako::Codec::_HandleTable
7
7
  @active: bool
8
8
 
9
- def initialize: (Kobako::Transport::_GuestYielder yield_to_guest, Symbol break_tag, Kobako::Catalog::Handles handler) -> void
9
+ def initialize: (Kobako::Transport::_GuestYielder yield_to_guest, Symbol break_tag, Kobako::Codec::_HandleTable handler) -> void
10
10
 
11
11
  def yield: (*untyped args) -> untyped
12
12
 
@@ -8,5 +8,13 @@ module Kobako
8
8
  interface _GuestYielder
9
9
  def call: (String) -> String
10
10
  end
11
+
12
+ # The Service registry a dispatch resolves constant-path targets
13
+ # against. The Sandbox's Kobako::Catalog::Namespaces is the
14
+ # production conformer; modelled structurally so the Transport
15
+ # layer needs no upward dependency on Catalog.
16
+ interface _NamespaceRegistry
17
+ def lookup: (String target) -> untyped
18
+ end
11
19
  end
12
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kobako
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Aotokitsuruya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-03 00:00:00.000000000 Z
11
+ date: 2026-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -37,6 +37,7 @@ files:
37
37
  - CHANGELOG.md
38
38
  - LICENSE
39
39
  - README.md
40
+ - ROADMAP.md
40
41
  - SECURITY.md
41
42
  - crates/kobako-runtime/CHANGELOG.md
42
43
  - crates/kobako-runtime/README.md