kobako 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +14 -0
- data/Cargo.lock +1 -1
- data/README.md +81 -51
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +8 -1
- data/rust-toolchain.toml +10 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd12a974208b6d2ef80dd443bf6a57c66295db8ee79624691a7d2ffe3dd1ec3d
|
|
4
|
+
data.tar.gz: b54a381dd914fabc424a6a9784800fcc5277dbd2e1dc4a2c278c11306b12ba46
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fe7c845ce6a0570904454600fc90e1c149562422dd71c654c1653b7bbbd90962437e28461c5f02a2c7639ab567d924e4f45da4c3d0f4485c9a943d3af630b9b
|
|
7
|
+
data.tar.gz: be6770e7a751d1243b7b719da70d7d0b4cef850f46d0018eb1b0f339502911376aa5ba793513fcf8fd79806f1b61aae34ff4abfa03ad9a5e6eab0fbdf93179b3
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.6.
|
|
1
|
+
{".":"0.6.2"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.2](https://github.com/elct9620/kobako/compare/v0.6.1...v0.6.2) (2026-05-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **guest:** enable mruby-sprintf for printf and String#% ([1179227](https://github.com/elct9620/kobako/commit/1179227b85b861bccc82d2a258481769a13ed4d5))
|
|
9
|
+
|
|
10
|
+
## [0.6.1](https://github.com/elct9620/kobako/compare/v0.6.0...v0.6.1) (2026-05-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **loader:** try Ruby-ABI subdir before bare path ([51017eb](https://github.com/elct9620/kobako/commit/51017eb5ee40fa722ec962ce3b9a5d016b128d41))
|
|
16
|
+
|
|
3
17
|
## [0.6.0](https://github.com/elct9620/kobako/compare/v0.5.0...v0.6.0) (2026-05-28)
|
|
4
18
|
|
|
5
19
|
|
data/Cargo.lock
CHANGED
data/README.md
CHANGED
|
@@ -53,9 +53,21 @@ result # => 3
|
|
|
53
53
|
|
|
54
54
|
The script executes inside the Wasm guest. It cannot read your filesystem, open sockets, or touch your `ENV`.
|
|
55
55
|
|
|
56
|
+
## Glossary
|
|
57
|
+
|
|
58
|
+
| Term | Meaning |
|
|
59
|
+
|------|---------|
|
|
60
|
+
| Sandbox | The runtime unit (`Kobako::Sandbox`) that runs guest code and returns a result or raises a typed error. |
|
|
61
|
+
| Service | A host Ruby object injected under `<Namespace>::<Member>` — the guest's only path to host resources. |
|
|
62
|
+
| Namespace / Member | A guest-visible Ruby module, and a named binding (a module constant) within it. |
|
|
63
|
+
| Invocation | One `#eval` or `#run`; capability state resets between invocations. |
|
|
64
|
+
| Snippet | Named mruby code (source or bytecode) replayed into a fresh state before every invocation. |
|
|
65
|
+
| Handle | An opaque token the guest holds for a host object the wire cannot transmit directly. |
|
|
66
|
+
| Block | A guest mruby block passed to a Service; each `yield` is a synchronous round-trip into the guest. |
|
|
67
|
+
|
|
56
68
|
## Usage
|
|
57
69
|
|
|
58
|
-
###
|
|
70
|
+
### Services
|
|
59
71
|
|
|
60
72
|
Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior.md`](docs/behavior.md) B-07..B-12.
|
|
61
73
|
|
|
@@ -77,42 +89,9 @@ sandbox.eval(<<~RUBY)
|
|
|
77
89
|
RUBY
|
|
78
90
|
```
|
|
79
91
|
|
|
80
|
-
Names must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the host method's keyword arguments. The registry seals at the first invocation; later `#define` raises `ArgumentError`.
|
|
81
|
-
|
|
82
|
-
### Yielding to guest blocks
|
|
83
|
-
|
|
84
|
-
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 [`docs/behavior.md`](docs/behavior.md) B-23..B-30.
|
|
85
|
-
|
|
86
|
-
```ruby
|
|
87
|
-
sandbox.define(:Seq).bind(:Map, ->(items, &blk) { items.map(&blk) })
|
|
88
|
-
|
|
89
|
-
sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
|
|
90
|
-
# => [2, 4, 6]
|
|
91
|
-
```
|
|
92
|
+
Names must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the host method's keyword arguments. The registry seals at the first invocation (see [Invocation Lifecycle](#invocation-lifecycle)); later `#define` raises `ArgumentError`.
|
|
92
93
|
|
|
93
|
-
###
|
|
94
|
-
|
|
95
|
-
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior.md`](docs/behavior.md) B-35).
|
|
96
|
-
|
|
97
|
-
```ruby
|
|
98
|
-
sandbox = Kobako::Sandbox.new(
|
|
99
|
-
timeout: 5.0, # seconds, default 60.0
|
|
100
|
-
memory_limit: 10 * 1024 * 1024, # bytes, default 1 MiB
|
|
101
|
-
stdout_limit: 64 * 1024, # bytes, default 1 MiB
|
|
102
|
-
stderr_limit: 64 * 1024
|
|
103
|
-
)
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
| Cap | Raises | Default |
|
|
107
|
-
|----------------|----------------------------|---------|
|
|
108
|
-
| `timeout` | `Kobako::TimeoutError` | 60.0 s |
|
|
109
|
-
| `memory_limit` | `Kobako::MemoryLimitError` | 1 MiB |
|
|
110
|
-
| `stdout_limit` | output clipped (no raise) | 1 MiB |
|
|
111
|
-
| `stderr_limit` | output clipped (no raise) | 1 MiB |
|
|
112
|
-
|
|
113
|
-
`memory_limit` covers the per-invocation `memory.grow` delta from the entry baseline, so a Sandbox reused across invocations does not silently accumulate against a global budget.
|
|
114
|
-
|
|
115
|
-
### Capturing stdout / stderr
|
|
94
|
+
### Output Capture
|
|
116
95
|
|
|
117
96
|
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior.md`](docs/behavior.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
118
97
|
|
|
@@ -128,7 +107,7 @@ sandbox.stdout # => "hello\n"
|
|
|
128
107
|
sandbox.stderr # => "be careful\n"
|
|
129
108
|
```
|
|
130
109
|
|
|
131
|
-
### Error
|
|
110
|
+
### Error Handling
|
|
132
111
|
|
|
133
112
|
Every invocation either returns a value or raises exactly one of three classes, so you can route faults without inspecting messages. The full taxonomy lives in [`lib/kobako/errors.rb`](lib/kobako/errors.rb).
|
|
134
113
|
|
|
@@ -153,25 +132,29 @@ end
|
|
|
153
132
|
|
|
154
133
|
`SandboxError` and `ServiceError` carry structured `origin` / `klass` / `backtrace_lines` / `details` fields when the guest produced a panic envelope.
|
|
155
134
|
|
|
156
|
-
###
|
|
135
|
+
### Resource Limits
|
|
157
136
|
|
|
158
|
-
|
|
137
|
+
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior.md`](docs/behavior.md) B-35).
|
|
159
138
|
|
|
160
139
|
```ruby
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
sandbox.eval('Factory::Make.call("Bob").greet') # => "hi, Bob" (Handle round-trip inside guest)
|
|
169
|
-
sandbox.eval('Factory::Make.call("Bob")') # => #<Greeter @name="Bob"> (B-37 restoration)
|
|
140
|
+
sandbox = Kobako::Sandbox.new(
|
|
141
|
+
timeout: 5.0, # seconds, default 60.0
|
|
142
|
+
memory_limit: 10 * 1024 * 1024, # bytes, default 1 MiB
|
|
143
|
+
stdout_limit: 64 * 1024, # bytes, default 1 MiB
|
|
144
|
+
stderr_limit: 64 * 1024
|
|
145
|
+
)
|
|
170
146
|
```
|
|
171
147
|
|
|
172
|
-
|
|
148
|
+
| Cap | Raises | Default |
|
|
149
|
+
|----------------|----------------------------|---------|
|
|
150
|
+
| `timeout` | `Kobako::TimeoutError` | 60.0 s |
|
|
151
|
+
| `memory_limit` | `Kobako::MemoryLimitError` | 1 MiB |
|
|
152
|
+
| `stdout_limit` | output clipped (no raise) | 1 MiB |
|
|
153
|
+
| `stderr_limit` | output clipped (no raise) | 1 MiB |
|
|
173
154
|
|
|
174
|
-
|
|
155
|
+
`memory_limit` covers the per-invocation `memory.grow` delta from the entry baseline, so a Sandbox reused across invocations does not silently accumulate against a global budget.
|
|
156
|
+
|
|
157
|
+
### Invocation Lifecycle
|
|
175
158
|
|
|
176
159
|
One Sandbox serves many invocations. Service bindings and preloaded snippets persist across calls; capability state (Handles, stdout, stderr, memory delta) resets between them.
|
|
177
160
|
|
|
@@ -216,7 +199,54 @@ One Sandbox serves many invocations. Service bindings and preloaded snippets per
|
|
|
216
199
|
|
|
217
200
|
For workloads that must be isolated from each other (one Sandbox per tenant, per student submission, per agent session), construct a fresh `Kobako::Sandbox` per scope — wasmtime's Engine and the compiled Module are cached at process scope, so additional Sandboxes amortize cold-start cost automatically.
|
|
218
201
|
|
|
219
|
-
###
|
|
202
|
+
### Service Blocks
|
|
203
|
+
|
|
204
|
+
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 [`docs/behavior.md`](docs/behavior.md) B-23..B-30.
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
sandbox.define(:Seq).bind(:Map, ->(items, &blk) { items.map(&blk) })
|
|
208
|
+
|
|
209
|
+
sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
|
|
210
|
+
# => [2, 4, 6]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Handle Management
|
|
214
|
+
|
|
215
|
+
A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior.md`](docs/behavior.md) B-13..B-21, B-34, B-37).
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
class Greeter
|
|
219
|
+
def initialize(name) = @name = name
|
|
220
|
+
def greet = "hi, #{@name}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
sandbox.define(:Factory).bind(:Make, ->(name) { Greeter.new(name) })
|
|
224
|
+
|
|
225
|
+
sandbox.eval('Factory::Make.call("Bob").greet') # => "hi, Bob" (Handle round-trip inside guest)
|
|
226
|
+
sandbox.eval('Factory::Make.call("Bob")') # => #<Greeter @name="Bob"> (B-37 restoration)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A `break` value from a guest block is the one exception: it unwinds back to the guest Member 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.
|
|
230
|
+
|
|
231
|
+
Each dispatch that hands back a non-wire-representable object allocates a *new* Handle — kobako never deduplicates by object identity (B-15, B-17). 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:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
guest chain host (Catalog::Handles, one invocation)
|
|
235
|
+
─────────── ─────────────────────────────────────────
|
|
236
|
+
User.where(active: true) ─call──▶ Relation #1 (fresh clone) bound ▶ Handle 1
|
|
237
|
+
◀─Handle 1
|
|
238
|
+
.order(:created_at) ─call──▶ Relation #2 (fresh clone) bound ▶ Handle 2
|
|
239
|
+
◀─Handle 2
|
|
240
|
+
.limit(10) ─call──▶ Relation #3 (fresh clone) bound ▶ Handle 3
|
|
241
|
+
◀─Handle 3
|
|
242
|
+
|
|
243
|
+
3 hops ─▶ 3 dispatches ─▶ 3 distinct relations ─▶ 3 Handles
|
|
244
|
+
all stay live until the invocation ends, then reset together
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
This is deliberate, not a leak. Handle IDs run to 2³¹ − 1 per invocation and reset between invocations, so even deep chains stay far inside the range. Two consequences are worth keeping in mind: the same host object handed back twice yields two *different* Handles — the guest cannot tell they alias — and every intermediate Handle stays live until the invocation ends, since there is no per-Handle release (B-19).
|
|
248
|
+
|
|
249
|
+
### Snippets & Entrypoints
|
|
220
250
|
|
|
221
251
|
`Sandbox#preload` registers named mruby snippets that replay against the fresh `mrb_state` before every invocation; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior.md`](docs/behavior.md) B-31..B-33).
|
|
222
252
|
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/ext/kobako/Cargo.toml
CHANGED
data/lib/kobako/version.rb
CHANGED
data/lib/kobako.rb
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "kobako/version"
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
7
|
+
require "kobako/#{Regexp.last_match(1)}/kobako"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
require "kobako/kobako"
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
require_relative "kobako/errors"
|
|
6
13
|
require_relative "kobako/transport"
|
|
7
14
|
require_relative "kobako/catalog"
|
data/rust-toolchain.toml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Pin the Rust toolchain so local builds and CI stay byte-identical.
|
|
2
|
+
# The wasm32-wasip1 crt1-command.o references __wasi_init_tp from 1.96 onward;
|
|
3
|
+
# vendored wasi-sdk 33's libc.a supplies that symbol, so the two move together.
|
|
4
|
+
# Bump this in lockstep with WASI_SDK_VERSION in tasks/support/kobako_vendor.rb.
|
|
5
|
+
# This file is the single source of the channel; the CI workflows read it.
|
|
6
|
+
[toolchain]
|
|
7
|
+
channel = "1.96.0"
|
|
8
|
+
components = ["clippy", "rustfmt"]
|
|
9
|
+
targets = ["wasm32-wasip1"]
|
|
10
|
+
profile = "minimal"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kobako
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aotokitsuruya
|
|
@@ -103,6 +103,7 @@ files:
|
|
|
103
103
|
- lib/kobako/usage.rb
|
|
104
104
|
- lib/kobako/version.rb
|
|
105
105
|
- release-please-config.json
|
|
106
|
+
- rust-toolchain.toml
|
|
106
107
|
- sig/kobako.rbs
|
|
107
108
|
- sig/kobako/capture.rbs
|
|
108
109
|
- sig/kobako/catalog.rbs
|