kobako 0.25.0-aarch64-linux → 0.27.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6b1af77d24fb13d4d331b5ee1ede3fbd2d981f3ad8df91796de97e9e320abd2
4
- data.tar.gz: e4305e4c7ad9845bf70d41b1d18f48465f82f37feff9fa85525a0443a622393b
3
+ metadata.gz: 60336f7cdf1fe0abadc8ade4e40a549de94ecc3233c01b4b5f3f9dc584cd0f45
4
+ data.tar.gz: 7e9a80c3838fdc079548e2dfb448eebca2e7497c7195cc80dc36f5315592e9e0
5
5
  SHA512:
6
- metadata.gz: b4feda43682dc0448fc1898ee173adad97664095bf3d175f9b9c6d525f272be82f91bd75baba54dc22f9a5ce7124209d2dc9a080be9ed29c5bade74176735d65
7
- data.tar.gz: 8e7775a4dd467b7f8d57f0158fa5667800f5296785fdcc0463380435d024ea3645b2abfd1c7499086f35009d287d2629a1b72059e2301278171491532df5d2a1
6
+ metadata.gz: 3a94964cb83b51c3e528d4aab9555710032405e700ad8ffcceb3f9f629d79527bb4ad456d5e2ef84e4c5b7140d7841e8406e7d861086bbe1cbbffb1b4f027c32
7
+ data.tar.gz: 8c0b5786a0ebc742f7667afe7ed109e837d8260a02316bc36389bf54124321e7441ef2d020e1b0c63668c34a49a480cb95d3a68197a4da4da6eeeca92f8cc519
@@ -1 +1 @@
1
- {".":"0.25.0","wasm/kobako-core":"0.15.0","wasm/kobako-mruby":"0.15.0","wasm/kobako-io":"0.15.0","wasm/kobako-json":"0.15.0","wasm/kobako-regexp":"0.15.0","wasm/kobako-baker":"0.15.0","crates/kobako-transport":"0.15.0","crates/kobako-codec":"0.15.0","crates/kobako-runtime":"0.15.0","crates/kobako-wasmtime":"0.15.0","crates/kobako":"0.15.0"}
1
+ {".":"0.27.0","wasm/kobako-core":"0.17.0","wasm/kobako-mruby":"0.17.0","wasm/kobako-io":"0.17.0","wasm/kobako-json":"0.17.0","wasm/kobako-regexp":"0.17.0","wasm/kobako-baker":"0.17.0","crates/kobako-transport":"0.17.0","crates/kobako-codec":"0.17.0","crates/kobako-runtime":"0.17.0","crates/kobako-wasmtime":"0.17.0","crates/kobako":"0.17.0"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.27.0](https://github.com/elct9620/kobako/compare/v0.26.0...v0.27.0) (2026-09-20)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * **guest:** a pattern's allocator is undefined from the moment the capability installs rather than from its first wrap, so allocating one is refused for the missing allocator throughout an invocation, where before the first statement could still reach an uninitialized carrier.
9
+ * **mruby:** `kobako_mruby::dispatch` takes `Option<Proc>` where it took a `Value` that could be nil. A gem passes the block its method read off its own frame; `None` says there is none.
10
+ * **json:** a JSON failure whose error class the guest replaced reaches the Host App as a guest exception rather than as a trap.
11
+ * **json:** JSON.parse refuses a second positional argument. Options passed as a positional Hash, the shape MRI also refuses, must be written as keywords.
12
+ * **mruby:** an object bearing a capability reference's class name without being one is refused as a value the wire cannot carry, where it previously crossed as a reference to whatever identifier it held.
13
+ * **mruby:** an object whose class carries a list's or a map's name without being one is refused rather than read as that container, both as the invocation's own answer and as a dispatch argument.
14
+ * **guest:** `Kobako::raise_transport_error`, `raise_service_error` and `reraise` are gone. A flow of its own builds `Kobako::transport_error` or `service_error` and hands the result back as `Err`, which beni raises at the guest call site.
15
+
16
+ ### Features
17
+
18
+ * **gate:** hold a crate README's install snippet to its manifest ([31f6926](https://github.com/elct9620/kobako/commit/31f692602de6e1a2d9e5f84872164e83c4008e50))
19
+ * **guest:** rebuild the guest crates on beni 0.17 ([b397701](https://github.com/elct9620/kobako/commit/b397701231c7d8718503d24f73b175676d0d1be3))
20
+ * **guest:** rebuild the guest crates on beni 0.18 ([ae232eb](https://github.com/elct9620/kobako/commit/ae232ebbf708419ff0672172fbf8627363fb02da))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **json:** read a parse's options from the keywords it declares ([6f0c83c](https://github.com/elct9620/kobako/commit/6f0c83cc0b6ae0fe0f327ad0b514d46a6f3432c0))
26
+ * **json:** report a replaced error class instead of ending the invocation ([5a75e42](https://github.com/elct9620/kobako/commit/5a75e42ba7990eb31d8487dd3979cca3c4a3d29b))
27
+ * **mruby:** prove a container's layout by its tag, not its class name ([5ee8c59](https://github.com/elct9620/kobako/commit/5ee8c59db90cee27f356028ff466860afe89e06e))
28
+ * **mruby:** read a reference's identity from the class the bridge registered ([0f24102](https://github.com/elct9620/kobako/commit/0f24102b75f771548cbc39256690e59d6df1f9e4))
29
+
30
+
31
+ ### Code Refactoring
32
+
33
+ * **mruby:** carry a dispatch's block as the block it is ([01d8831](https://github.com/elct9620/kobako/commit/01d8831dff173e80c10d965af8615acb4de2498b))
34
+
35
+ ## [0.26.0](https://github.com/elct9620/kobako/compare/v0.25.0...v0.26.0) (2026-09-15)
36
+
37
+
38
+ ### ⚠ BREAKING CHANGES
39
+
40
+ * **transport:** a #run argument or keyword value whose own nesting leaves the Run payload one level past the wire's 128-level bound used to reach the guest and fail there as Kobako::SandboxError "failed to decode the invocation arguments"; it is now refused by the host before the guest runs, still as Kobako::SandboxError, with a message naming the depth bound.
41
+ * **transport:** a Service answer nesting past 128 levels used to cross the host and fail in the guest's decoder, reaching the Host App as Kobako::Transport::Error; it is now refused at the host and reaches the Host App as Kobako::ServiceError, which guest code may rescue. A yield whose arguments nest past the bound — a single yielded value already at the bound does, since the arguments travel as one list — now raises Kobako::YieldValueError inside the Service at the yield instead of failing in the guest.
42
+
43
+ ### Bug Fixes
44
+
45
+ * **transport:** measure an entrypoint argument from the Run payload that carries it ([8f76912](https://github.com/elct9620/kobako/commit/8f76912f2290ec0d8484e8ac9a64cbb48409bf1d))
46
+ * **transport:** refuse a Service's over-deep value before the host writes it ([5594542](https://github.com/elct9620/kobako/commit/5594542c7160ae2f052025545a20bafabd7e51df))
47
+
48
+
49
+ ### Performance Improvements
50
+
51
+ * **codec:** walk into a value's members only where one could be a container ([a965136](https://github.com/elct9620/kobako/commit/a965136c989251b2ed991c2824214a017122430c))
52
+
3
53
  ## [0.25.0](https://github.com/elct9620/kobako/compare/v0.24.0...v0.25.0) (2026-09-14)
4
54
 
5
55
 
data/README.md CHANGED
@@ -24,11 +24,14 @@ The host (`wasmtime`) runs a precompiled `kobako.wasm` guest containing mruby an
24
24
 
25
25
  ## Requirements
26
26
 
27
- - **Ruby ≥ 3.3.0**
28
- - **Rust / Cargo** at install time — the native extension compiles from source via `rb_sys`
29
- - **Linux** or **macOS** — Windows is not supported
27
+ | What | Which versions | When you need it |
28
+ |---|---|---|
29
+ | Ruby | ≥ 3.3.0 | always |
30
+ | Platform | Linux or macOS | always — Windows is not supported |
31
+ | Rust / Cargo | current stable | only where no prebuilt gem matches your platform; otherwise the native extension installs ready-built |
32
+ | WASI toolchain | vendored by the build | only when building the gem from a source checkout — see [Development](#development) |
30
33
 
31
- The precompiled `kobako.wasm` Guest Binary ships inside the gem, so end users do **not** need a WASI toolchain. (The toolchain is only required if you build the gem from a source checkout — see [Development](#development).)
34
+ The precompiled `kobako.wasm` Guest Binary ships inside the gem, so end users do **not** need a WASI toolchain.
32
35
 
33
36
  ## Installation
34
37
 
@@ -63,9 +66,16 @@ The gem bundles its Guest Binary; a Rust host loads one explicitly — see [Fron
63
66
 
64
67
  ## Frontends & Guest Binaries
65
68
 
66
- Embedding kobako is two independent choices: the **host frontend** you build against, and the **Guest Binary** it runs. They compose freely — any frontend loads any Guest Binary, so a Ruby host can run a JSON-enabled guest and a Rust host can run the pure default.
69
+ Embedding kobako is two independent choices: the **host frontend** you build against, and the **Guest Binary** it runs.
67
70
 
68
- How far down those choices go — and which ones a given starting point quietly makes for you — is laid out in [`docs/architecture.md`](docs/architecture.md).
71
+ ```text
72
+ host frontend Guest Binary
73
+ Ruby gem ──┐ ┌── kobako.wasm (pure)
74
+ Rust SDK ──┼──┼── kobako+regexp.wasm
75
+ your own ──┘ └── kobako+json.wasm · your own
76
+ ```
77
+
78
+ They compose freely — any frontend loads any Guest Binary, so a Ruby host can run a JSON-enabled guest and a Rust host can run the pure default. How far down those choices go — and which ones a given starting point quietly makes for you — is laid out in [`docs/architecture.md`](docs/architecture.md).
69
79
 
70
80
  ### Host frontends
71
81
 
@@ -149,6 +159,16 @@ Build the crate as a `cdylib` for `wasm32-wasip1`, then bake the canonical boot
149
159
 
150
160
  ## Usage
151
161
 
162
+ Each section below stands on its own; this is the order they build in.
163
+
164
+ | If you want to | Read |
165
+ |---|---|
166
+ | let guest code call into your host | [Services](#services), [Per-Invocation Bindings](#per-invocation-bindings), [Service Blocks](#service-blocks) |
167
+ | see what a run produced or why it failed | [Output Capture](#output-capture), [Error Handling](#error-handling), [Invocation Lifecycle](#invocation-lifecycle) |
168
+ | bound what a run may consume | [Resource Limits](#resource-limits), [Concurrency](#concurrency), [Pooling](#pooling) |
169
+ | hand host objects across the boundary | [Handle Management](#handle-management) |
170
+ | ship guest code with the Sandbox | [Snippets & Entrypoints](#snippets--entrypoints), [Extensions](#extensions) |
171
+
152
172
  ### Services
153
173
 
154
174
  `bind` any Ruby object as a Service at a constant-path name; the guest reaches it as a `MyService::KV` (or top-level `File`) proxy and invokes the public methods its own class defines through the Transport wire. See [`SV-006`](docs/spec/behavior/services.md) and [`SV-007`](docs/spec/behavior/services.md).
@@ -256,7 +276,14 @@ Beyond the four caps, `profile:` requests the Sandbox's isolation posture on the
256
276
 
257
277
  ### Concurrency
258
278
 
259
- A Sandbox keeps no state from any run, so concurrent Threads may invoke distinct Sandboxes or share a single one; each invocation owns its Handles, captures, and usage either way ([`RT-001`](docs/spec/behavior/runtime.md), [`RT-002`](docs/spec/behavior/runtime.md)). One Thread still runs one invocation at a time. Sharing a Sandbox adds a single obligation: an object bound once at setup is reached by every Thread and must itself be thread-safe, while an object supplied per invocation — `ctx.bind`, or an Extension `provider:` — carries no such requirement.
279
+ A Sandbox keeps no state from any run, so concurrent Threads may invoke distinct Sandboxes or share a single one; each invocation owns its Handles, captures, and usage either way ([`RT-001`](docs/spec/behavior/runtime.md), [`RT-002`](docs/spec/behavior/runtime.md)). One Thread still runs one invocation at a time. Sharing a Sandbox adds a single obligation.
280
+
281
+ | What you bind | Who reaches it | What it owes |
282
+ |---|---|---|
283
+ | Bound once at setup | every Thread sharing the Sandbox | it must itself be thread-safe |
284
+ | Supplied per invocation — `ctx.bind`, or an Extension `provider:` | that invocation alone | nothing |
285
+
286
+ #### Choosing a GVL mode
260
287
 
261
288
  By default an invocation holds Ruby's GVL for its whole span, so guest execution across Threads serializes. `gvl: :release` drops the GVL for the guest span and re-acquires it for each guest→host dispatch, running guest code in parallel across Threads ([`RT-024`](docs/spec/behavior/runtime.md), [`RT-026`](docs/spec/behavior/runtime.md)).
262
289
 
@@ -321,8 +348,6 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
321
348
 
322
349
  For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`PL-003`](docs/spec/behavior/pool.md), [`PL-011`](docs/spec/behavior/pool.md)). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
323
350
 
324
- `Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. The build bakes the shared boot state into the artifact ([`mruby.md`](docs/spec/behavior/mruby.md)) and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the host-side `Sandbox.new` — now under 3 µs, an order of magnitude below the invocation that follows it. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a gain worth the coupling. What a Pool buys is warm setup and exclusive checkout, not isolation: a Sandbox holds no state from any run, so Threads sharing one are equally safe (see [Concurrency](#concurrency)).
325
-
326
351
  ```ruby
327
352
  pool = Kobako::Pool.new(slots: 4) do |sandbox|
328
353
  sandbox.bind("KV::Lookup", ->(key) { redis.get(key) })
@@ -331,6 +356,8 @@ end
331
356
  pool.with { |sandbox| sandbox.eval(%(KV::Lookup.call("user_42"))).value }
332
357
  ```
333
358
 
359
+ #### Options and lifetime
360
+
334
361
  | Option | Meaning | Default |
335
362
  |--------|---------|---------|
336
363
  | `slots:` | Upper bound on constructed Sandboxes | required |
@@ -338,6 +365,14 @@ pool.with { |sandbox| sandbox.eval(%(KV::Lookup.call("user_42"))).value }
338
365
 
339
366
  Sandboxes construct lazily on first demand. `#with` yields a Sandbox and returns the block's value; at block exit the Sandbox returns to the pool, except a block that raises `Kobako::TrapError` discards its Sandbox and the slot refills by a fresh construction on next demand. A checkout that waits past `checkout_timeout` raises `Kobako::PoolTimeoutError`. There is no teardown verb — a Pool releases everything with its own reachability.
340
367
 
368
+ #### What a Pool buys
369
+
370
+ | It gives you | It does not give you |
371
+ |---|---|
372
+ | warm, pre-configured Sandboxes and exclusive checkout | isolation — a Sandbox holds no state from any run, so Threads sharing one are equally safe (see [Concurrency](#concurrency)) |
373
+
374
+ `Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. The build bakes the shared boot state into the artifact ([`mruby.md`](docs/spec/behavior/mruby.md)) and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the host-side `Sandbox.new` — now about 3 µs, an order of magnitude below the invocation that follows it. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a gain worth the coupling.
375
+
341
376
  ### Service Blocks
342
377
 
343
378
  A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`T-085`](docs/spec/behavior/transport-yield.md) and [`T-089`](docs/spec/behavior/transport-yield.md).
@@ -367,6 +402,8 @@ sandbox.eval('Factory::Make.call("Bob")').value # => #<Greeter @name="Bob
367
402
 
368
403
  A `break` value from a guest block is the one exception: it unwinds back to the guest Service call rather than to host code, so a Handle in it stays a Handle — restoring would just re-wrap the same object into a new id on the return trip.
369
404
 
405
+ #### One Handle per dispatch
406
+
370
407
  Each dispatch that hands back a non-wire-representable object allocates a *new* Handle — kobako never deduplicates by object identity ([`T-007`](docs/spec/behavior/transport-dispatch.md), [`T-009`](docs/spec/behavior/transport-dispatch.md)). This is most visible with fluent / builder APIs. An `ActiveRecord::Relation` chain `spawn`s a fresh relation at each step, so every hop is an independent dispatch that binds its own Handle:
371
408
 
372
409
  ```
@@ -417,6 +454,8 @@ A target no snippet defined raises `Kobako::UndefinedEntrypointError`, whose `#a
417
454
  return the Execution, then discard the instance
418
455
  ```
419
456
 
457
+ #### Choosing a payload form
458
+
420
459
  `#preload` accepts two payload forms:
421
460
 
422
461
  | Form | Signature | Snippet name source | Validation timing |
@@ -481,11 +520,22 @@ sandbox.bind("Cfg::Settings", ThemeReader.new) # not: bind("Cfg::Settings", App
481
520
  sandbox.eval('Cfg::Settings.color').value # => "#3366ff" — every other method raises NoMethodError
482
521
  ```
483
522
 
523
+ #### Gating an object's own surface
524
+
484
525
  When a purpose-built wrapper is more than you need, an object can gate its own surface in
485
526
  place: a private `respond_to_guest?(name)` answers, per method, whether the guest may call
486
527
  it. Returning `false` for every name makes the object opaque — a credential the guest
487
528
  forwards to another Service but never reads — while permitting a named subset exposes exactly those.
488
529
 
530
+ ```ruby
531
+ class Credential
532
+ def initialize(token) = @token = token
533
+ def to_s = @token
534
+
535
+ private def respond_to_guest?(_name) = false # forwardable, never readable
536
+ end
537
+ ```
538
+
489
539
  Guest code can name any `MyService::KV` path, but a forged name only resolves to
490
540
  something you bound — the real authorization gate is this host-side allowlist. Give each
491
541
  trust context its own Sandbox, and see [`docs/security-model.md`](docs/security-model.md) for the rest
@@ -499,17 +549,25 @@ Order-of-magnitude figures on macOS arm64, Ruby 3.4.7, YJIT off. Absolute values
499
549
  | Phase | Cost |
500
550
  |--------------------------------------------------------------|-----------------------|
501
551
  | First `Sandbox.new` ever for a Guest Binary (Module JIT, then disk-cached) | ~500 ms once per machine |
502
- | First `Sandbox.new` in a fresh process (`.cwasm` cache warm) | ~1.3 ms one-time |
552
+ | First `Sandbox.new` in a fresh process (`.cwasm` cache warm) | ~3 ms one-time |
503
553
  | Subsequent `Sandbox.new` (caches warm) | ~3 µs |
504
- | Warm `#eval` with one Service call on a reused Sandbox | ~71 µs |
505
- | Warm `#run(:Entrypoint, ...)` dispatch | ~81 µs |
506
- | Service call amortized inside one invocation | ~5.7 µs |
507
- | Snippet replay per invocation | ~7.5 µs each |
554
+ | Warm `#eval` with one Service call on a reused Sandbox | ~70 µs |
555
+ | Warm `#run(:Entrypoint, ...)` dispatch | ~79 µs |
556
+ | Service call amortized inside one invocation | ~5.8 µs |
557
+ | Snippet replay per invocation | ~7.0 µs each |
508
558
  | Per additional idle Sandbox (RSS) | ~1 KB |
509
559
 
510
- The Cranelift JIT runs once per machine and gem version — the compiled artifact persists in a `.cwasm` disk cache, so later processes deserialize in milliseconds. An idle Sandbox holds no wasm instance (the canonical boot state is baked into the artifact and instantiated per invocation), which is why a thousand idle tenants cost ~33 MB total. Under the default `gvl: :hold`, wasm work is GVL-serialized: aggregate throughput stays around 16k `#eval`/s regardless of Thread count, though Ruby-side `#eval` setup still overlaps. Opting a Sandbox into `gvl: :release` lifts that ceiling for compute-bound scripts (see [Concurrency](#concurrency)). A +10% regression on any SPEC-mandated benchmark blocks release.
560
+ The Cranelift JIT runs once per machine and gem version — the compiled artifact persists in a `.cwasm` disk cache, so later processes deserialize in milliseconds. An idle Sandbox holds no wasm instance (the canonical boot state is baked into the artifact and instantiated per invocation), which is why a thousand idle tenants cost ~34 MB total. A +10% regression on any SPEC-mandated benchmark blocks release.
561
+
562
+ #### What moves the ceiling
511
563
 
512
- Regexp is an opt-in capability gem, excluded from the default binary and the gated set; its throughput is tracked in a separate non-gated characterization (`#11` in [`benchmark/README.md`](benchmark/README.md)). There `=~` (~5 µs/match) costs about 4.5× `match?` (~1.1 µs), because `=~` eagerly builds the `MatchData` and match globals — prefer `match?` for boolean tests.
564
+ | Choice | What it changes |
565
+ |---|---|
566
+ | `gvl: :hold` (default) | wasm work is GVL-serialized: aggregate throughput stays around 16k `#eval`/s regardless of Thread count, though Ruby-side `#eval` setup still overlaps |
567
+ | `gvl: :release` | lifts that ceiling for compute-bound scripts (see [Concurrency](#concurrency)) |
568
+ | `match?` over `=~` | `=~` (~5 µs/match) costs about 5× `match?` (~1.0 µs), because it eagerly builds the `MatchData` and match globals — prefer `match?` for boolean tests |
569
+
570
+ Regexp is an opt-in capability gem, excluded from the default binary and the gated set; its throughput is tracked in a separate non-gated characterization (`#11` in [`benchmark/README.md`](benchmark/README.md)).
513
571
 
514
572
  ```bash
515
573
  bundle exec rake bench # every gated regression benchmark (~5-8 min)
@@ -530,7 +588,12 @@ Building from source requires a WASI-capable Rust toolchain in addition to the s
530
588
 
531
589
  Bug reports and pull requests are welcome at <https://github.com/elct9620/kobako>. Please open an issue before starting on non-trivial changes so we can align on scope.
532
590
 
533
- Releases are automated with release-please across two tracks — the gem and the linked guest-crate group. The version rules, commit conventions, and how to cut each release live in [`docs/releasing.md`](docs/releasing.md).
591
+ Releases are automated with release-please across two tracks; the version rules, commit conventions, and how to cut each release live in [`docs/releasing.md`](docs/releasing.md).
592
+
593
+ | Track | What it ships | Tag |
594
+ |---|---|---|
595
+ | Gem | the Ruby gem, with the bundled `kobako.wasm` | `v*` |
596
+ | Crates | the linked guest-crate group | `<component>-v*` |
534
597
 
535
598
  ## License
536
599
 
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.16.0...kobako-runtime-v0.17.0) (2026-09-20)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **kobako-runtime:** Synchronize kobako crates versions
9
+
10
+ ## [0.16.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.15.0...kobako-runtime-v0.16.0) (2026-09-15)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **kobako-runtime:** Synchronize kobako crates versions
16
+
3
17
  ## [0.15.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.14.0...kobako-runtime-v0.15.0) (2026-09-13)
4
18
 
5
19
 
@@ -6,17 +6,14 @@ 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
- - `runtime` — the `Runtime` trait: one guest invocation on a fresh
12
- instance in, its observable `Snapshot` out
13
- - `snapshot` — the per-invocation observables: `Completion` (outcome
14
- or trap), the two output `Capture`s, and resource `Usage`, uniform
15
- across success and trap
16
- - `error` — the neutral failure channels: `Trap` (engine fault) and
17
- `SetupError` (the invocation never started)
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` | the per-invocation observables — `Completion` (outcome or trap), the two output `Capture`s, and resource `Usage`, uniform across success and trap |
15
+ | `error` | the neutral failure channels: `Trap` (engine fault) and `SetupError` (the invocation never started) |
16
+ | `dispatch` / `yielder` | the `DispatchHandler` and `Yielder` traits a frontend supplies for guest→host dispatch and block-yield re-entry |
20
17
 
21
18
  Engine implementations (such as `kobako-wasmtime`) implement
22
19
  `Runtime`; host frontends (such as the kobako Ruby gem's native ext)
@@ -26,7 +23,7 @@ map the neutral types onto their own language surface.
26
23
 
27
24
  ```toml
28
25
  [dependencies]
29
- kobako-runtime = "0.15.0" # x-release-please-version
26
+ kobako-runtime = "0.17.0" # x-release-please-version
30
27
  ```
31
28
 
32
29
  ## License
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.16.0...kobako-transport-v0.17.0) (2026-09-20)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **kobako-transport:** Synchronize kobako crates versions
9
+
10
+ ## [0.16.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.15.0...kobako-transport-v0.16.0) (2026-09-15)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **kobako-transport:** Synchronize kobako crates versions
16
+
3
17
  ## [0.15.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.14.0...kobako-transport-v0.15.0) (2026-09-13)
4
18
 
5
19
 
@@ -5,27 +5,34 @@ 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
- - `envelope` — the **core envelope**, the outer frame each message rides
11
- in: `Call` / `Reply` / `YieldReply` for a dispatch round-trip,
12
- `Outcome` / `Panic` for how an invocation ended, `Run` / `Preamble` /
13
- `Snippets` for what an invocation is handed, and the `ErrorRecord` a
14
- guest failure carries
15
- - `abi` — the values a host and a guest must already agree on to
16
- exchange a byte: the version, the packed `(ptr, len)` return layout,
17
- the invocation-channel frame prefix, and the message size cap
18
-
19
- The envelope reads a message's routing fields and its ok-versus-fault
20
- tag without decoding a payload byte; everything the resolved method
21
- consumes rides through as an opaque `payload` this layer never reads.
22
- A decoded envelope borrows the buffer it came from, so that payload
23
- reaches its reader as a view rather than a copy.
24
-
25
- This crate depends on no other, and every kobako tier depends on it:
26
- `kobako-runtime` and `kobako-wasmtime` on the host, `kobako-core` and
27
- `kobako-mruby` in the guest, and any third-party engine, codec, or
28
- guest that composes against them.
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: `Call` / `Reply` / `YieldReply` for a dispatch round-trip, `Outcome` / `Panic` for how an invocation ended, `Run` / `Preamble` / `Snippets` for what an invocation is handed, and the `ErrorRecord` a guest failure carries |
13
+ | `abi` | the values a host and a guest must already agree on to exchange a byte: the version, the packed `(ptr, len)` return layout, the invocation-channel frame prefix, and the message size cap |
14
+
15
+ ## What the envelope does not read
16
+
17
+ The envelope reads a message's routing fields and its ok-versus-fault tag
18
+ without decoding a payload byte. Everything the resolved method consumes
19
+ rides through opaque, and a decoded envelope borrows the buffer it came
20
+ from, so that payload reaches its reader as a view rather than a copy.
21
+
22
+ ```text
23
+ Call { method, payload }
24
+ └── opaque here; read by whoever owns the schema
25
+ ```
26
+
27
+ ## Where it sits
28
+
29
+ This crate depends on no other, and every kobako tier depends on it —
30
+ including any third-party engine, codec, or guest composing against them.
31
+
32
+ ```text
33
+ host kobako-runtime · kobako-wasmtime ──┐
34
+ guest kobako-core · kobako-mruby ────────┴──> kobako-transport
35
+ ```
29
36
 
30
37
  The byte layout is specified in
31
38
  [`docs/wire/envelope.md`](https://github.com/elct9620/kobako/blob/main/docs/wire/envelope.md);
@@ -35,7 +42,7 @@ the golden vectors in this crate are derived from that document.
35
42
 
36
43
  ```toml
37
44
  [dependencies]
38
- kobako-transport = "0.15.0" # x-release-please-version
45
+ kobako-transport = "0.17.0" # x-release-please-version
39
46
  ```
40
47
 
41
48
  ## License
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.16.0...kobako-wasmtime-v0.17.0) (2026-09-20)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **kobako-wasmtime:** Synchronize kobako crates versions
9
+
10
+ ## [0.16.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.15.0...kobako-wasmtime-v0.16.0) (2026-09-15)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **kobako-wasmtime:** Synchronize kobako crates versions
16
+
3
17
  ## [0.15.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.14.0...kobako-wasmtime-v0.15.0) (2026-09-13)
4
18
 
5
19
 
@@ -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
- - process-wide Engine and compiled-Module caches with an on-disk AOT
12
- (`.cwasm`) artifact cache keyed by Guest Binary content
13
- - a pre-linked `InstancePre` per guest path; every invocation runs on
14
- a fresh instance and discards its Store afterwards
15
- - the epoch-based wall-clock timeout and the per-invocation
16
- linear-memory cap
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 | frozen WASI clocks and a constant RNG, so a guest observes no real time and no real entropy |
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.15.0" # x-release-please-version
25
+ kobako-wasmtime = "0.17.0" # x-release-please-version
28
26
  ```
29
27
 
30
28
  ## License
data/data/kobako.wasm CHANGED
Binary file
Binary file
Binary file
Binary file
@@ -31,23 +31,14 @@ module Kobako
31
31
  # same error: an integer outside i64..u64 (+RangeError+) and any
32
32
  # packer-internal +NoMethodError+.
33
33
  #
34
- # A value that nests without bound — a reference cycle necessarily
35
- # does — exhausts the packer's own recursion instead, which Ruby
36
- # reports outside +StandardError+. Mapping it keeps an unwritable
37
- # value a wire violation the dispatch boundary can answer, rather than
38
- # one that escapes every caller's rescue and traps the invocation.
39
- #
40
- # The refusal is spent once per thread: a thread that has absorbed one
41
- # such overflow aborts on the next instead of raising, and a Hash cycle
42
- # never reaches Ruby at all — the packer walks a Hash through C frames
43
- # that carry no stack guard. Bounding the walk before the packer is
44
- # handed the value is what would make the refusal repeatable.
34
+ # The caller bounds +value+'s nesting first (Nesting): the packer takes
35
+ # no depth limit and walks a list or map in frames that carry no stack
36
+ # guard, so a value nesting without end — a reference cycle necessarily
37
+ # does — exhausts the machine stack instead of raising.
45
38
  def self.encode(value)
46
39
  FACTORY.dump(value)
47
40
  rescue ::RangeError, ::NoMethodError => e
48
41
  raise UnsupportedTypeError, e.message
49
- rescue ::SystemStackError
50
- raise InvalidTypeError, "value nests deeper than this host can write (a reference cycle necessarily does)"
51
42
  end
52
43
  end
53
44
  end
@@ -95,7 +95,7 @@ module Kobako
95
95
  return unless depth > MAX_NESTING_DEPTH
96
96
 
97
97
  raise Kobako::SandboxError,
98
- "a #run argument nests deeper than #{MAX_NESTING_DEPTH} levels and " \
98
+ "a #run argument nests deeper than #{MAX_NESTING_DEPTH} levels within the Run payload and " \
99
99
  "cannot cross the sandbox boundary (possible reference cycle)"
100
100
  end
101
101
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "error"
4
+
5
+ module Kobako
6
+ module Codec
7
+ # The structural nesting bound
8
+ # ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md]
9
+ # § Structural Nesting Depth), checked before a value reaches the
10
+ # packer. The packer takes no depth limit and walks a list or map in
11
+ # frames that carry no stack guard, so a value it cannot finish — a
12
+ # reference cycle necessarily is one — must be refused before it is
13
+ # handed over.
14
+ module Nesting
15
+ # Raise InvalidTypeError when +value+ nests past MAX_NESTING_DEPTH; a
16
+ # value nested exactly to the bound passes.
17
+ def self.assert_within_bound!(value, depth = 0)
18
+ case value
19
+ when ::Array then assert_members_within_bound!(value, depth)
20
+ when ::Hash
21
+ assert_members_within_bound!(value.keys, depth)
22
+ assert_members_within_bound!(value.values, depth)
23
+ end
24
+ end
25
+
26
+ # Measure the members of one container at +depth+, each sitting one
27
+ # level deeper. A member is matched by class rather than asked about
28
+ # itself — it may be a BasicObject — and only one that could be a
29
+ # container is walked into.
30
+ def self.assert_members_within_bound!(members, depth)
31
+ return if members.empty?
32
+ if depth >= MAX_NESTING_DEPTH
33
+ raise InvalidTypeError,
34
+ "value nests deeper than #{MAX_NESTING_DEPTH} levels (a reference cycle necessarily does)"
35
+ end
36
+ return unless members.any?(::Enumerable)
37
+
38
+ members.grep(::Enumerable) { |member| assert_within_bound!(member, depth + 1) }
39
+ end
40
+ private_class_method :assert_members_within_bound!
41
+ end
42
+ end
43
+ end
data/lib/kobako/codec.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "codec/error"
4
4
  require_relative "codec/utils"
5
5
  require_relative "codec/handle_walk"
6
+ require_relative "codec/nesting"
6
7
  require_relative "codec/state"
7
8
  require_relative "codec/ext_types"
8
9
  require_relative "codec/encoder"
@@ -29,8 +30,9 @@ module Kobako
29
30
  module Codec
30
31
  # The maximum structural nesting depth the wire represents (the
31
32
  # MessagePack ecosystem's bound), shared with the guest +kobako_codec+
32
- # so both sides cap identically. The host→guest wrap walk refuses a
33
- # +#run+ argument nesting past it — a reference cycle necessarily does.
33
+ # so both sides cap identically. The host refuses a value nesting past
34
+ # it — a reference cycle necessarily does — before handing it to the
35
+ # packer.
34
36
  MAX_NESTING_DEPTH = 128
35
37
 
36
38
  # Bracket a decode and return the block's result together with whether
@@ -236,6 +236,7 @@ module Kobako
236
236
  # known, instead of falling to the boundary's codec floor and reporting
237
237
  # as an exchange that produced no Service outcome.
238
238
  def encode_ok(value, handler)
239
+ Kobako::Codec::Nesting.assert_within_bound!(value)
239
240
  Kobako::Codec::Encoder.encode(value)
240
241
  rescue Kobako::Codec::UnsupportedTypeError
241
242
  encode_ok(wrap_as_handle(value, handler), handler)
@@ -54,10 +54,13 @@ module Kobako
54
54
  # invocation's table, sharing the same allocator the guest→host
55
55
  # return path uses. A wrapped leaf rides as ext 0x01 in its
56
56
  # original position (docs/wire/payload-msgpack.md § ext 0x01).
57
+ # The walk starts one level down, since +args+ and +kwargs+ ride
58
+ # inside the payload document the wire's nesting bound is counted
59
+ # from.
57
60
  def payload(handler)
58
61
  Payload::Arguments.new(
59
- args: Codec::HandleWalk.deep_wrap(args, handler),
60
- kwargs: Codec::HandleWalk.deep_wrap(kwargs, handler)
62
+ args: Codec::HandleWalk.deep_wrap(args, handler, 1),
63
+ kwargs: Codec::HandleWalk.deep_wrap(kwargs, handler, 1)
61
64
  ).encode
62
65
  end
63
66
 
@@ -94,11 +94,13 @@ module Kobako
94
94
 
95
95
  private
96
96
 
97
- # Encode what the Service is yielding. A value outside the wire type
98
- # set fails here, before the guest is re-entered, and is restated so
99
- # the Service reads a refusal of its own argument rather than a codec
100
- # class it never named.
97
+ # Encode what the Service is yielding. Arguments outside the wire
98
+ # type set, or nesting past the wire's bound as one list, fail here,
99
+ # before the guest is re-entered, and are restated so the Service
100
+ # reads a refusal of its own argument rather than a codec class it
101
+ # never named.
101
102
  def encode_args(args)
103
+ Kobako::Codec::Nesting.assert_within_bound!(args)
102
104
  Kobako::Codec::Encoder.encode(args)
103
105
  rescue Kobako::Codec::Error => e
104
106
  raise YieldValueError, "Service yielded a value the block cannot receive: #{e.message}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.25.0"
4
+ VERSION = "0.27.0"
5
5
  end
data/rust-toolchain.toml CHANGED
@@ -3,6 +3,10 @@
3
3
  # vendored wasi-sdk 33's libc.a supplies that symbol, so the two move together.
4
4
  # Bump this in lockstep with the wasi-sdk toolchain beni vendors
5
5
  # (`rake beni:build`, wired in the Rakefile's Beni::Tasks block).
6
+ # The floor is 1.98: rustc strips an Apple binary with its own Mach-O writer,
7
+ # and only from that release does the stripped string pool keep the alignment
8
+ # dyld requires from macOS 26 onward — below it, `strip = true` in the release
9
+ # profile yields an ext no dlopen accepts.
6
10
  # This file is the single source of the channel; the CI workflows read it.
7
11
  [toolchain]
8
12
  channel = "stable"
@@ -0,0 +1,9 @@
1
+ module Kobako
2
+ module Codec
3
+ module Nesting
4
+ def self.assert_within_bound!: (untyped value, ?Integer depth) -> void
5
+
6
+ def self.assert_members_within_bound!: (Array[untyped] members, Integer depth) -> void
7
+ end
8
+ end
9
+ 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.25.0
4
+ version: 0.27.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-09-14 00:00:00.000000000 Z
11
+ date: 2026-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -62,6 +62,7 @@ files:
62
62
  - lib/kobako/codec/error.rb
63
63
  - lib/kobako/codec/ext_types.rb
64
64
  - lib/kobako/codec/handle_walk.rb
65
+ - lib/kobako/codec/nesting.rb
65
66
  - lib/kobako/codec/state.rb
66
67
  - lib/kobako/codec/utils.rb
67
68
  - lib/kobako/context.rb
@@ -105,6 +106,7 @@ files:
105
106
  - sig/kobako/codec/error.rbs
106
107
  - sig/kobako/codec/ext_types.rbs
107
108
  - sig/kobako/codec/handle_walk.rbs
109
+ - sig/kobako/codec/nesting.rbs
108
110
  - sig/kobako/codec/state.rbs
109
111
  - sig/kobako/codec/utils.rbs
110
112
  - sig/kobako/context.rbs