kobako 0.10.0-aarch64-linux → 0.11.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/README.md +14 -7
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/capture.rb +5 -7
- data/lib/kobako/catalog/handles.rb +24 -31
- data/lib/kobako/catalog/namespaces.rb +19 -27
- data/lib/kobako/catalog/snippets.rb +10 -16
- data/lib/kobako/codec/utils.rb +6 -9
- data/lib/kobako/errors.rb +33 -39
- data/lib/kobako/handle.rb +2 -3
- data/lib/kobako/namespace.rb +8 -11
- data/lib/kobako/outcome.rb +12 -14
- data/lib/kobako/pool.rb +18 -24
- data/lib/kobako/sandbox.rb +61 -83
- data/lib/kobako/sandbox_options.rb +5 -9
- data/lib/kobako/snapshot.rb +2 -4
- data/lib/kobako/snippet/binary.rb +1 -3
- data/lib/kobako/snippet/source.rb +1 -2
- data/lib/kobako/snippet.rb +1 -2
- data/lib/kobako/transport/dispatcher.rb +39 -38
- data/lib/kobako/transport/request.rb +1 -1
- data/lib/kobako/transport/run.rb +23 -28
- data/lib/kobako/transport/yielder.rb +11 -17
- data/lib/kobako/transport.rb +2 -3
- data/lib/kobako/usage.rb +10 -13
- data/lib/kobako/version.rb +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a44a6f91fb5e57a8cc4cfca97e501fab889eef17961c8088b736625e0a06983
|
|
4
|
+
data.tar.gz: 5ce894137c6df04ddf48b4bbd821bf76764a06663d7552624539e6a6ffa88242
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d28583de8802ed0dcab2cb1138d671674cf9ca11823a766ec9a46a075c73aa24d40da121273da0c506fddebea1a6fa1cd75e1041aff4a2929391b7b97d5e064
|
|
7
|
+
data.tar.gz: c6aea42ae6d840cee738ee3306e2963d4ad4e60f6e640835e9f38ef98a323a267f04986d27afb08ee6bb21665a245bee97e05eaba13596220dbbd914f2325b56
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.11.0","wasm/kobako-core":"0.5.0","wasm/kobako":"0.5.0","wasm/kobako-io":"0.5.0","wasm/kobako-regexp":"0.5.0","wasm/kobako-baker":"0.5.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0](https://github.com/elct9620/kobako/compare/v0.10.0...v0.11.0) (2026-06-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **transport:** narrow guest-reachable methods via respond_to_guest? ([7a25fe3](https://github.com/elct9620/kobako/commit/7a25fe3b440b523b8a692b7601d08877b1305b0d))
|
|
9
|
+
|
|
3
10
|
## [0.10.0](https://github.com/elct9620/kobako/compare/v0.9.2...v0.10.0) (2026-06-12)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -69,7 +69,7 @@ The script executes inside the Wasm guest. It cannot read your filesystem, open
|
|
|
69
69
|
|
|
70
70
|
### Services
|
|
71
71
|
|
|
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.
|
|
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/registration.md`](docs/behavior/registration.md) B-07..B-12.
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
75
|
class User
|
|
@@ -93,7 +93,7 @@ Names must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the hos
|
|
|
93
93
|
|
|
94
94
|
### Output Capture
|
|
95
95
|
|
|
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?`.
|
|
96
|
+
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
97
97
|
|
|
98
98
|
```ruby
|
|
99
99
|
result = sandbox.eval(<<~RUBY)
|
|
@@ -134,7 +134,7 @@ end
|
|
|
134
134
|
|
|
135
135
|
### Resource Limits
|
|
136
136
|
|
|
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).
|
|
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/lifecycle.md`](docs/behavior/lifecycle.md) B-35).
|
|
138
138
|
|
|
139
139
|
```ruby
|
|
140
140
|
sandbox = Kobako::Sandbox.new(
|
|
@@ -202,7 +202,9 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
|
|
|
202
202
|
|
|
203
203
|
### Pooling
|
|
204
204
|
|
|
205
|
-
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior.md`](docs/behavior.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
205
|
+
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior/runtime.md`](docs/behavior/runtime.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
206
|
+
|
|
207
|
+
`Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. B-49 bakes the shared boot state into the artifact and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the ~30 µs host-side `Sandbox.new`. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a significant gain (~4-5% in the [serverless example](examples/serverless/README.md), and proportionally less once the script itself does real work).
|
|
206
208
|
|
|
207
209
|
```ruby
|
|
208
210
|
pool = Kobako::Pool.new(slots: 4) do |sandbox|
|
|
@@ -221,7 +223,7 @@ Sandboxes construct lazily on first demand. `#with` yields a Sandbox with empty
|
|
|
221
223
|
|
|
222
224
|
### Service Blocks
|
|
223
225
|
|
|
224
|
-
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.
|
|
226
|
+
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/yield.md`](docs/behavior/yield.md) B-23..B-30.
|
|
225
227
|
|
|
226
228
|
```ruby
|
|
227
229
|
sandbox.define(:Seq).bind(:Map, ->(items, &blk) { items.map(&blk) })
|
|
@@ -232,7 +234,7 @@ sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
|
|
|
232
234
|
|
|
233
235
|
### Handle Management
|
|
234
236
|
|
|
235
|
-
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).
|
|
237
|
+
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/dispatch.md`](docs/behavior/dispatch.md) B-13..B-21, B-34, B-37).
|
|
236
238
|
|
|
237
239
|
```ruby
|
|
238
240
|
class Greeter
|
|
@@ -268,7 +270,7 @@ This is deliberate, not a leak. Handle IDs run to 2³¹ − 1 per invocation and
|
|
|
268
270
|
|
|
269
271
|
### Snippets & Entrypoints
|
|
270
272
|
|
|
271
|
-
`Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `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).
|
|
273
|
+
`Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior/invocation.md`](docs/behavior/invocation.md) B-31..B-33).
|
|
272
274
|
|
|
273
275
|
```ruby
|
|
274
276
|
sandbox = Kobako::Sandbox.new
|
|
@@ -320,6 +322,11 @@ sandbox.define(:Cfg).bind(:Settings, ThemeReader.new) # not: bind(:Settings, Ap
|
|
|
320
322
|
sandbox.eval('Cfg::Settings.color') # => "#3366ff" — every other method raises NoMethodError
|
|
321
323
|
```
|
|
322
324
|
|
|
325
|
+
When a purpose-built wrapper is more than you need, an object can gate its own surface in
|
|
326
|
+
place: a private `respond_to_guest?(name)` answers, per method, whether the guest may call
|
|
327
|
+
it. Returning `false` for every name makes the object opaque — a credential the guest
|
|
328
|
+
forwards to another Service but never reads — while a named subset becomes an allow-list.
|
|
329
|
+
|
|
323
330
|
Guest code can name any `<Namespace>::<Member>` path, but a forged name only resolves to
|
|
324
331
|
something you bound — the real authorization gate is this host-side allowlist. Give each
|
|
325
332
|
trust context its own Sandbox, and see [`docs/security.md`](docs/security.md) for the rest
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/lib/kobako/3.3/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/3.4/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/4.0/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/capture.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Kobako
|
|
|
4
4
|
# Host-side captured prefix of guest stdout / stderr produced during a
|
|
5
5
|
# single +Kobako::Sandbox+ invocation, paired with the truncation flag
|
|
6
6
|
# the WASI pipe sets when the guest wrote past the configured per-channel
|
|
7
|
-
# cap
|
|
7
|
+
# cap.
|
|
8
8
|
#
|
|
9
9
|
# Immutable value object: the captured bytes and the truncation flag
|
|
10
10
|
# always travel together and the instance is frozen on construction.
|
|
@@ -30,14 +30,12 @@ module Kobako
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Returns +true+ iff the underlying capture channel exceeded its
|
|
33
|
-
# configured cap during the originating +Sandbox+ invocation
|
|
34
|
-
# ({docs/behavior.md B-04}[link:../../docs/behavior.md]).
|
|
33
|
+
# configured cap during the originating +Sandbox+ invocation.
|
|
35
34
|
def truncated? = @truncated
|
|
36
35
|
|
|
37
|
-
# Pre-invocation sentinel
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# yet".
|
|
36
|
+
# Pre-invocation sentinel. Empty UTF-8 bytes and +truncated? == false+;
|
|
37
|
+
# reused by every fresh +Sandbox+ and by +Sandbox+ between invocations
|
|
38
|
+
# to denote "no capture yet".
|
|
41
39
|
EMPTY = new(bytes: "", truncated: false)
|
|
42
40
|
end
|
|
43
41
|
end
|
|
@@ -5,34 +5,29 @@ require_relative "../handle"
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Catalog
|
|
7
7
|
# Host-side mapping from opaque integer Handle IDs to Ruby objects.
|
|
8
|
-
# The table is owned by +Kobako::Sandbox+
|
|
9
|
-
# ({docs/behavior.md B-19}[link:../../../docs/behavior.md]) and injected
|
|
8
|
+
# The table is owned by +Kobako::Sandbox+ and injected
|
|
10
9
|
# into the per-Sandbox +Kobako::Catalog::Namespaces+ so guest→host dispatch
|
|
11
10
|
# resolves Handle targets and arguments against the same table that
|
|
12
|
-
# host→guest wire encoding allocates into
|
|
13
|
-
# ({docs/behavior.md B-14, B-34}[link:../../../docs/behavior.md]).
|
|
11
|
+
# host→guest wire encoding allocates into.
|
|
14
12
|
#
|
|
15
|
-
# Lifecycle invariants
|
|
13
|
+
# Lifecycle invariants:
|
|
16
14
|
#
|
|
17
|
-
# -
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# +#alloc+.
|
|
15
|
+
# - Handle IDs are allocated by a monotonically increasing counter
|
|
16
|
+
# scoped to a single invocation. The first ID issued in an
|
|
17
|
+
# invocation is 1; ID 0 is reserved as the invalid sentinel and is
|
|
18
|
+
# never returned by +#alloc+.
|
|
22
19
|
#
|
|
23
|
-
# -
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# allocation source (B-14 Service return or B-34 host-injected
|
|
20
|
+
# - At every invocation boundary (via +#reset!+), every Handle issued
|
|
21
|
+
# under the old state becomes invalid. Reset applies uniformly
|
|
22
|
+
# regardless of allocation source (Service return or host-injected
|
|
27
23
|
# argument).
|
|
28
24
|
#
|
|
29
|
-
# -
|
|
30
|
-
#
|
|
31
|
-
# immediately — no silent truncation, no wrap, no ID reuse.
|
|
25
|
+
# - The cap is +0x7fff_ffff+ (2³¹ − 1). Allocation beyond the cap
|
|
26
|
+
# raises immediately — no silent truncation, no wrap, no ID reuse.
|
|
32
27
|
class Handles
|
|
33
28
|
# Build a fresh, empty table. +next_id+ is an internal seam that
|
|
34
|
-
# sets the starting value of the monotonic counter (defaults to 1
|
|
35
|
-
#
|
|
29
|
+
# sets the starting value of the monotonic counter (defaults to 1);
|
|
30
|
+
# tests pass a value near +Kobako::Handle::MAX_ID+ to exercise
|
|
36
31
|
# the cap-exhaustion path without 2³¹ allocations.
|
|
37
32
|
def initialize(next_id: 1)
|
|
38
33
|
@entries = {} # : Hash[Integer, untyped]
|
|
@@ -45,8 +40,7 @@ module Kobako
|
|
|
45
40
|
# +[Kobako::Handle::MIN_ID, Kobako::Handle::MAX_ID]+. Raises
|
|
46
41
|
# +Kobako::HandlerExhaustedError+ if the next ID would exceed the
|
|
47
42
|
# cap. The cap is anchored on +Kobako::Handle+ — the wire codec
|
|
48
|
-
# and the allocator share the same invariant
|
|
49
|
-
# ({docs/behavior.md B-21}[link:../../../docs/behavior.md]).
|
|
43
|
+
# and the allocator share the same invariant.
|
|
50
44
|
#
|
|
51
45
|
# Returning a Handle (rather than a bare Integer id) keeps the
|
|
52
46
|
# allocator's output a domain entity; +Kobako::Handle.restore+
|
|
@@ -69,9 +63,8 @@ module Kobako
|
|
|
69
63
|
@entries[id]
|
|
70
64
|
end
|
|
71
65
|
|
|
72
|
-
# Clear all entries AND reset the counter to 1. Called at the
|
|
73
|
-
# boundary by +Kobako::Sandbox
|
|
74
|
-
# {docs/behavior.md B-19}[link:../../../docs/behavior.md]. Returns +self+.
|
|
66
|
+
# Clear all entries AND reset the counter to 1. Called at the
|
|
67
|
+
# per-invocation boundary by +Kobako::Sandbox+. Returns +self+.
|
|
75
68
|
def reset!
|
|
76
69
|
@entries.clear
|
|
77
70
|
@next_id = 1
|
|
@@ -89,12 +82,12 @@ module Kobako
|
|
|
89
82
|
|
|
90
83
|
private
|
|
91
84
|
|
|
92
|
-
# Refuse to mint a Capability Handle for a reflective gadget
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
85
|
+
# Refuse to mint a Capability Handle for a reflective gadget:
|
|
86
|
+
# a +Binding+ / +Method+ / +UnboundMethod+ would hand the guest a
|
|
87
|
+
# callable proxy onto host reflection (a returned +Binding+ reaches
|
|
88
|
+
# +Binding#eval+). Raising here keeps the rule at the single mint
|
|
89
|
+
# point, so it holds on both the Service-return and the +#run+
|
|
90
|
+
# host→guest auto-wrap paths.
|
|
98
91
|
def reject_unwrappable!(object)
|
|
99
92
|
case object
|
|
100
93
|
when Binding, Method, UnboundMethod
|
|
@@ -102,7 +95,7 @@ module Kobako
|
|
|
102
95
|
end
|
|
103
96
|
end
|
|
104
97
|
|
|
105
|
-
# Guard {#alloc} against issuing an ID past the
|
|
98
|
+
# Guard {#alloc} against issuing an ID past the cap. Returns +nil+
|
|
106
99
|
# on success; raises +Kobako::HandlerExhaustedError+ at exhaustion.
|
|
107
100
|
def ensure_capacity!
|
|
108
101
|
cap = Kobako::Handle::MAX_ID
|
|
@@ -9,8 +9,7 @@ module Kobako
|
|
|
9
9
|
module Catalog
|
|
10
10
|
# Kobako::Catalog::Namespaces — per-Sandbox registry of
|
|
11
11
|
# +Kobako::Namespace+ entities. Holds the Namespace / Member bindings
|
|
12
|
-
# and the preamble emitted on Frame 1
|
|
13
|
-
# ({docs/behavior.md B-07..B-11}[link:../../../docs/behavior.md]).
|
|
12
|
+
# and the preamble emitted on Frame 1.
|
|
14
13
|
#
|
|
15
14
|
# Public API:
|
|
16
15
|
#
|
|
@@ -24,14 +23,13 @@ module Kobako
|
|
|
24
23
|
# +Kobako::Transport::Dispatcher+'s responsibility — the Dispatcher
|
|
25
24
|
# receives this registry and the +Catalog::Handles+ as arguments from
|
|
26
25
|
# the +Runtime#on_dispatch+ Proc that +Kobako::Sandbox#initialize+
|
|
27
|
-
# installs
|
|
28
|
-
# The registry holds an injected +Catalog::Handles+ reference so
|
|
26
|
+
# installs. The registry holds an injected +Catalog::Handles+ reference so
|
|
29
27
|
# dispatch target resolution and host→guest auto-wrap share the same
|
|
30
|
-
# Sandbox-owned allocator
|
|
28
|
+
# Sandbox-owned allocator.
|
|
31
29
|
class Namespaces
|
|
32
30
|
# Build a fresh registry. +handler+ is an internal seam that injects
|
|
33
31
|
# a pre-configured +Catalog::Handles+; tests pass one whose +next_id+
|
|
34
|
-
# is pinned near +MAX_ID+ to exercise the
|
|
32
|
+
# is pinned near +MAX_ID+ to exercise the cap-exhaustion path
|
|
35
33
|
# without 2³¹ allocations. Production callers leave it at the default.
|
|
36
34
|
def initialize(handler: Catalog::Handles.new)
|
|
37
35
|
@namespaces = {} # : Hash[String, Kobako::Namespace]
|
|
@@ -40,14 +38,12 @@ module Kobako
|
|
|
40
38
|
@encoded = nil # : String?
|
|
41
39
|
end
|
|
42
40
|
|
|
43
|
-
# Declare or retrieve the Namespace named +name+ (idempotent
|
|
44
|
-
# {docs/behavior.md B-10}[link:../../../docs/behavior.md]).
|
|
41
|
+
# Declare or retrieve the Namespace named +name+ (idempotent).
|
|
45
42
|
# +name+ is a constant-form name as a +Symbol+ or +String+ (must satisfy
|
|
46
43
|
# +Namespace::NAME_PATTERN+). Returns the +Kobako::Namespace+ for that
|
|
47
44
|
# name, creating it if it does not exist. Raises +ArgumentError+ when
|
|
48
45
|
# +name+ is malformed, or when called after the owning Sandbox has been
|
|
49
|
-
# sealed by its first invocation
|
|
50
|
-
# ({docs/behavior.md B-07}[link:../../../docs/behavior.md]).
|
|
46
|
+
# sealed by its first invocation.
|
|
51
47
|
def define(name)
|
|
52
48
|
raise ArgumentError, "cannot define after first Sandbox invocation" if @sealed
|
|
53
49
|
|
|
@@ -74,20 +70,18 @@ module Kobako
|
|
|
74
70
|
namespace.fetch(member_name)
|
|
75
71
|
end
|
|
76
72
|
|
|
77
|
-
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# +String+ of msgpack bytes.
|
|
73
|
+
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery.
|
|
74
|
+
# Routes through {Kobako::Codec::Encoder} like every other host-side
|
|
75
|
+
# wire encode so there is a single codec path; the preamble carries
|
|
76
|
+
# only Strings and Arrays, so none of the kobako ext types actually
|
|
77
|
+
# fire. Structure: +[["Namespace", ["MemberA", "MemberB"]], ...]+.
|
|
78
|
+
# Returns a binary +String+ of msgpack bytes.
|
|
84
79
|
#
|
|
85
80
|
# Once sealed, the bytes are computed once and reused for every
|
|
86
|
-
# subsequent invocation:
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# and never alters Frame 1.
|
|
81
|
+
# subsequent invocation: sealing freezes Service registration at the
|
|
82
|
+
# first invocation, so the preamble is exactly the bindings that
|
|
83
|
+
# existed at that moment — a bind reaching a +Kobako::Namespace+
|
|
84
|
+
# after the seal raises +ArgumentError+ and never alters Frame 1.
|
|
91
85
|
def encode
|
|
92
86
|
return @encoded if @encoded
|
|
93
87
|
|
|
@@ -97,11 +91,9 @@ module Kobako
|
|
|
97
91
|
end
|
|
98
92
|
|
|
99
93
|
# Mark the registry as sealed and propagate the seal to every
|
|
100
|
-
# declared +Kobako::Namespace+
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
# raises ArgumentError (E-18) and +Namespace#bind+ raises
|
|
104
|
-
# ArgumentError (E-45). Idempotent.
|
|
94
|
+
# declared +Kobako::Namespace+. Called by +Sandbox+ on the first
|
|
95
|
+
# invocation. After sealing, both #define and +Namespace#bind+
|
|
96
|
+
# raise ArgumentError. Idempotent.
|
|
105
97
|
def seal!
|
|
106
98
|
return self if @sealed
|
|
107
99
|
|
|
@@ -6,8 +6,7 @@ require_relative "../snippet"
|
|
|
6
6
|
module Kobako
|
|
7
7
|
module Catalog
|
|
8
8
|
# Kobako::Catalog::Snippets — per-Sandbox insertion-ordered registry
|
|
9
|
-
# of preloaded snippets
|
|
10
|
-
# ({docs/behavior.md B-32 / B-33}[link:../../../docs/behavior.md]).
|
|
9
|
+
# of preloaded snippets.
|
|
11
10
|
#
|
|
12
11
|
# Entries replay against the fresh +mrb_state+ before per-invocation
|
|
13
12
|
# source / entrypoint resolution. Each +Snippet::Source+ entry's +name+
|
|
@@ -15,18 +14,16 @@ module Kobako
|
|
|
15
14
|
# +debug_info+ that surfaces in every backtrace frame originating from
|
|
16
15
|
# the snippet as +(snippet:Name):line+. Duplicate names within the
|
|
17
16
|
# +code:+ form would produce ambiguous attribution and are rejected at
|
|
18
|
-
# registration time
|
|
19
|
-
# ({docs/behavior.md E-33}[link:../../../docs/behavior.md]).
|
|
17
|
+
# registration time.
|
|
20
18
|
# +Snippet::Binary+ entries carry no host-side name — their canonical
|
|
21
19
|
# name lives in the bytecode's +debug_info+ and is read by the guest at
|
|
22
20
|
# load time; the host does not extract it.
|
|
23
21
|
#
|
|
24
|
-
# Sealing
|
|
22
|
+
# Sealing is governed by the owning Sandbox — the registry itself
|
|
25
23
|
# is append-only and exposes no mutation API beyond +#register+; the
|
|
26
24
|
# Sandbox guards +#register+ behind the seal check before delegating.
|
|
27
25
|
class Snippets
|
|
28
|
-
# Ruby constant-name pattern enforced on snippet names
|
|
29
|
-
# ({docs/behavior.md E-34}[link:../../../docs/behavior.md]).
|
|
26
|
+
# Ruby constant-name pattern enforced on snippet names.
|
|
30
27
|
NAME_PATTERN = /\A[A-Z]\w*\z/
|
|
31
28
|
|
|
32
29
|
def initialize
|
|
@@ -45,7 +42,7 @@ module Kobako
|
|
|
45
42
|
# self-encode.
|
|
46
43
|
#
|
|
47
44
|
# The bytes are memoized — the table is replayed verbatim on every
|
|
48
|
-
# invocation after
|
|
45
|
+
# invocation after sealing, so Frame 3 never changes between
|
|
49
46
|
# encodes; {#register} drops the memo while the table is still open.
|
|
50
47
|
def encode
|
|
51
48
|
return @encoded if @encoded
|
|
@@ -53,8 +50,7 @@ module Kobako
|
|
|
53
50
|
@encoded = Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze
|
|
54
51
|
end
|
|
55
52
|
|
|
56
|
-
# Register one preloaded snippet in either of two forms
|
|
57
|
-
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
53
|
+
# Register one preloaded snippet in either of two forms.
|
|
58
54
|
#
|
|
59
55
|
# * Source form +register(code: src, name: Name)+ — +src+ is the
|
|
60
56
|
# mruby source as a String; the bytes are re-encoded as UTF-8
|
|
@@ -65,15 +61,13 @@ module Kobako
|
|
|
65
61
|
# precompiled RITE bytecode as a String, duplicated and forced
|
|
66
62
|
# to ASCII-8BIT so msgpack-ruby ships it as +bin+. Returns
|
|
67
63
|
# +nil+ — bytecode entries are anonymous on the host side; any
|
|
68
|
-
# structural validation
|
|
69
|
-
# ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
|
|
70
|
-
# is deferred to the guest at first replay.
|
|
64
|
+
# structural validation is deferred to the guest at first replay.
|
|
71
65
|
#
|
|
72
66
|
# The two forms are mutually exclusive: shape validation lives
|
|
73
67
|
# here so callers (chiefly +Kobako::Sandbox#preload+) collapse to
|
|
74
68
|
# a single delegation. Raises +ArgumentError+ on mixed forms,
|
|
75
|
-
# missing keywords, wrong types, malformed +name
|
|
76
|
-
# duplicate +code:+ +name
|
|
69
|
+
# missing keywords, wrong types, malformed +name+, or
|
|
70
|
+
# duplicate +code:+ +name+.
|
|
77
71
|
def register(code: nil, name: nil, binary: nil)
|
|
78
72
|
@encoded = nil
|
|
79
73
|
if binary
|
|
@@ -89,7 +83,7 @@ module Kobako
|
|
|
89
83
|
|
|
90
84
|
# Source-form register path. Delegates argument-shape checks to
|
|
91
85
|
# +ensure_source_args!+ (which returns the narrowed +[code, name]+
|
|
92
|
-
# pair), normalises +name+ to a Symbol, rejects duplicates
|
|
86
|
+
# pair), normalises +name+ to a Symbol, rejects duplicates,
|
|
93
87
|
# and appends the Source entry.
|
|
94
88
|
def register_source!(code, name)
|
|
95
89
|
code, name = ensure_source_args!(code, name)
|
data/lib/kobako/codec/utils.rb
CHANGED
|
@@ -19,8 +19,7 @@ module Kobako
|
|
|
19
19
|
# - Representability predicate ({representable?}) and the symmetric
|
|
20
20
|
# host→guest +#run+ argument walk ({deep_wrap}) used by
|
|
21
21
|
# +Kobako::Transport::Run#encode+ to route non-representable leaves
|
|
22
|
-
# through the Sandbox's +Kobako::Catalog::Handles
|
|
23
|
-
# ({docs/behavior.md B-34}[link:../../../docs/behavior.md]).
|
|
22
|
+
# through the Sandbox's +Kobako::Catalog::Handles+.
|
|
24
23
|
#
|
|
25
24
|
# All helpers are pure — they only inspect inputs, never mutate
|
|
26
25
|
# them — except {deep_wrap}, whose only side effect is allocating
|
|
@@ -84,8 +83,7 @@ module Kobako
|
|
|
84
83
|
|
|
85
84
|
# Deep-walk Array / Hash containers in +value+ and replace every
|
|
86
85
|
# leaf that fails {representable?} with a +Kobako::Handle+
|
|
87
|
-
# allocated from +handler
|
|
88
|
-
# ({docs/behavior.md B-34}[link:../../../docs/behavior.md]). The
|
|
86
|
+
# allocated from +handler+. The
|
|
89
87
|
# walk only descends through representable container shapes
|
|
90
88
|
# (Array, Hash) one structural level at a time; a non-representable
|
|
91
89
|
# leaf is wrapped as-is without inspecting its internal structure.
|
|
@@ -116,8 +114,7 @@ module Kobako
|
|
|
116
114
|
|
|
117
115
|
# Deep-walk Array / Hash containers in +value+ and replace every
|
|
118
116
|
# +Kobako::Handle+ leaf with the host-side object +handler+ resolves
|
|
119
|
-
# it to
|
|
120
|
-
# The symmetric inverse of {deep_wrap}: that walk allocates objects
|
|
117
|
+
# it to. The symmetric inverse of {deep_wrap}: that walk allocates objects
|
|
121
118
|
# into Handles on the host→guest argument path; this walk resolves
|
|
122
119
|
# Handles back to their objects on every guest→host value path — the
|
|
123
120
|
# +#eval+ / +#run+ result and the yield-block result alike. The walk
|
|
@@ -126,11 +123,11 @@ module Kobako
|
|
|
126
123
|
# unchanged.
|
|
127
124
|
#
|
|
128
125
|
# +value+ is a decoded Ruby value (a Handle here is a wire-decoded
|
|
129
|
-
# +Kobako::Handle+, never a guest-forged one
|
|
126
|
+
# +Kobako::Handle+, never a guest-forged one); +handler+ must
|
|
130
127
|
# respond to +#fetch(id) -> object+ (a host-side
|
|
131
128
|
# +Kobako::Catalog::Handles+). +handler.fetch+ raises
|
|
132
|
-
# +Kobako::SandboxError+ for an id with no live binding,
|
|
133
|
-
# corrupted-runtime fallback
|
|
129
|
+
# +Kobako::SandboxError+ for an id with no live binding, the
|
|
130
|
+
# corrupted-runtime fallback.
|
|
134
131
|
def deep_restore(value, handler)
|
|
135
132
|
case value
|
|
136
133
|
when ::Array then value.map { |element| Utils.deep_restore(element, handler) }
|
data/lib/kobako/errors.rb
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# Top-level Kobako namespace.
|
|
4
4
|
module Kobako
|
|
5
|
-
# Error taxonomy
|
|
5
|
+
# Error taxonomy.
|
|
6
6
|
#
|
|
7
7
|
# Every `Kobako::Sandbox` invocation (`#eval` or `#run`) either returns a value or raises
|
|
8
8
|
# exactly one of three invocation-outcome classes. Attribution is decided after the
|
|
9
|
-
# guest binary returns control to the host
|
|
10
|
-
#
|
|
9
|
+
# guest binary returns control to the host: first the Wasm-trap layer, then
|
|
10
|
+
# the outcome-envelope tag.
|
|
11
11
|
#
|
|
12
12
|
# Three invocation-outcome branches:
|
|
13
13
|
#
|
|
@@ -25,19 +25,17 @@ module Kobako
|
|
|
25
25
|
#
|
|
26
26
|
# * {SetupError} — construction layer. Raised by `Kobako::Sandbox.new`
|
|
27
27
|
# when the wasm runtime cannot be built from the
|
|
28
|
-
# configured +wasm_path+ before any invocation runs
|
|
29
|
-
# ({docs/behavior.md E-40 / E-41}[link:../../docs/behavior.md]).
|
|
28
|
+
# configured +wasm_path+ before any invocation runs.
|
|
30
29
|
# Not an invocation outcome, so it never passes
|
|
31
30
|
# through the two-step attribution decision.
|
|
32
31
|
# * {PoolTimeoutError} — pool checkout layer. Raised by `Kobako::Pool#with`
|
|
33
|
-
# when the checkout wait exceeds +checkout_timeout
|
|
34
|
-
# ({docs/behavior.md E-46}[link:../../docs/behavior.md]).
|
|
32
|
+
# when the checkout wait exceeds +checkout_timeout+.
|
|
35
33
|
#
|
|
36
|
-
#
|
|
34
|
+
# Named subclasses:
|
|
37
35
|
#
|
|
38
36
|
# * {ModuleNotBuiltError} < {SetupError} — Guest Binary artifact absent
|
|
39
|
-
# at +wasm_path
|
|
40
|
-
# * {HandlerExhaustedError} < {SandboxError} — Handle id cap hit
|
|
37
|
+
# at +wasm_path+.
|
|
38
|
+
# * {HandlerExhaustedError} < {SandboxError} — Handle id cap hit.
|
|
41
39
|
|
|
42
40
|
# Base for all kobako-raised errors so callers that want to ignore the
|
|
43
41
|
# taxonomy can rescue a single class.
|
|
@@ -46,13 +44,13 @@ module Kobako
|
|
|
46
44
|
# Wasm engine layer. Raised when the Wasm execution engine crashed
|
|
47
45
|
# (trap, OOM, unreachable) or when the wire layer detected a structural
|
|
48
46
|
# violation that signals a corrupted guest execution environment
|
|
49
|
-
# (zero-length OUTCOME_BUFFER, unknown outcome tag
|
|
47
|
+
# (zero-length OUTCOME_BUFFER, unknown outcome tag).
|
|
50
48
|
#
|
|
51
|
-
# Two named subclasses cover the configured per-invocation caps
|
|
49
|
+
# Two named subclasses cover the configured per-invocation caps:
|
|
52
50
|
#
|
|
53
|
-
# * {TimeoutError} — wall-clock +timeout+ exceeded
|
|
51
|
+
# * {TimeoutError} — wall-clock +timeout+ exceeded.
|
|
54
52
|
# * {MemoryLimitError} — guest +memory.grow+ would exceed
|
|
55
|
-
# +memory_limit
|
|
53
|
+
# +memory_limit+.
|
|
56
54
|
#
|
|
57
55
|
# Host Apps that only care about "guest is unrecoverable, discard the
|
|
58
56
|
# Sandbox" can rescue +TrapError+ and ignore the subclass; Host Apps that
|
|
@@ -60,24 +58,23 @@ module Kobako
|
|
|
60
58
|
# first.
|
|
61
59
|
class TrapError < Error; end
|
|
62
60
|
|
|
63
|
-
# Wall-clock timeout cap exhausted
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
61
|
+
# Wall-clock timeout cap exhausted: the absolute deadline
|
|
62
|
+
# +entry_time + timeout+ passed and the next guest wasm safepoint
|
|
63
|
+
# trapped. The Sandbox is unrecoverable after this point; discard and
|
|
64
|
+
# recreate before another execution.
|
|
67
65
|
class TimeoutError < TrapError; end
|
|
68
66
|
|
|
69
|
-
# Linear-memory cap exhausted
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
67
|
+
# Linear-memory cap exhausted: a guest +memory.grow+ would have pushed
|
|
68
|
+
# linear memory past the configured +memory_limit+. The Sandbox is
|
|
69
|
+
# unrecoverable after this point; discard and recreate before another
|
|
70
|
+
# execution.
|
|
73
71
|
class MemoryLimitError < TrapError; end
|
|
74
72
|
|
|
75
73
|
# Construction-layer error raised by +Kobako::Sandbox.new+ /
|
|
76
74
|
# +Kobako::Runtime.from_path+ when the wasm runtime cannot be built
|
|
77
75
|
# from the configured +wasm_path+ before any invocation runs —
|
|
78
76
|
# an unreadable artifact, bytes that are not a valid Wasm module, or
|
|
79
|
-
# engine / linker / instantiation setup failure
|
|
80
|
-
# ({docs/behavior.md E-41}[link:../../docs/behavior.md]). Construction
|
|
77
|
+
# engine / linker / instantiation setup failure. Construction
|
|
81
78
|
# is not an invocation, so +SetupError+ sits beside the invocation
|
|
82
79
|
# taxonomy under +Kobako::Error+ rather than under +TrapError+: no
|
|
83
80
|
# Sandbox is produced, so the +TrapError+ "discard and recreate"
|
|
@@ -86,8 +83,7 @@ module Kobako
|
|
|
86
83
|
|
|
87
84
|
# The named +SetupError+ subclass for the common, actionable case:
|
|
88
85
|
# the Guest Binary artifact is absent at +wasm_path+ — the pre-build
|
|
89
|
-
# state on a fresh clone before +bundle exec rake compile
|
|
90
|
-
# ({docs/behavior.md E-40}[link:../../docs/behavior.md]). Host Apps
|
|
86
|
+
# state on a fresh clone before +bundle exec rake compile+. Host Apps
|
|
91
87
|
# that only need "the Sandbox could not be set up" rescue +SetupError+;
|
|
92
88
|
# those wanting to special-case the unbuilt-artifact state rescue
|
|
93
89
|
# +ModuleNotBuiltError+ first.
|
|
@@ -123,19 +119,18 @@ module Kobako
|
|
|
123
119
|
end
|
|
124
120
|
end
|
|
125
121
|
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
122
|
+
# HandlerExhaustedError is the canonical SandboxError subclass for the
|
|
123
|
+
# id-cap-hit path. Raised when the per-invocation Handle ID counter in
|
|
124
|
+
# Catalog::Handles reaches +0x7fff_ffff+ (2³¹ − 1) and further
|
|
125
|
+
# allocation would exceed the cap.
|
|
130
126
|
class HandlerExhaustedError < SandboxError; end
|
|
131
127
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
# preserved. Inherits from SandboxError so a single
|
|
128
|
+
# BytecodeError is the SandboxError subclass raised when a
|
|
129
|
+
# `#preload(binary:)` snippet fails structural validation during the
|
|
130
|
+
# first invocation's snippet replay against a fresh `mrb_state` (RITE
|
|
131
|
+
# version mismatch or corrupt body). Bytecode that loads cleanly and
|
|
132
|
+
# then raises at top level surfaces as plain `SandboxError` with the
|
|
133
|
+
# natural mruby class preserved. Inherits from SandboxError so a single
|
|
139
134
|
# `rescue Kobako::SandboxError` covers both source and bytecode
|
|
140
135
|
# snippet failures while callers wanting bytecode-specific handling
|
|
141
136
|
# can `rescue Kobako::BytecodeError` directly.
|
|
@@ -143,8 +138,7 @@ module Kobako
|
|
|
143
138
|
|
|
144
139
|
# Pool checkout layer. Raised by +Kobako::Pool#with+ when the checkout
|
|
145
140
|
# wait exceeded the configured +checkout_timeout+ while every slot was
|
|
146
|
-
# held
|
|
147
|
-
# Sandbox state is touched — retrying succeeds as soon as a holder
|
|
141
|
+
# held. No Sandbox state is touched — retrying succeeds as soon as a holder
|
|
148
142
|
# returns its Sandbox.
|
|
149
143
|
class PoolTimeoutError < Error; end
|
|
150
144
|
end
|
data/lib/kobako/handle.rb
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
module Kobako
|
|
4
4
|
# Wire-level value object for an ext-0x01 Capability Handle, used in both
|
|
5
5
|
# directions across the Sandbox boundary: as a Service method's return
|
|
6
|
-
# value (guest→host return path
|
|
7
|
-
#
|
|
8
|
-
# ({docs/behavior.md B-34}[link:../../docs/behavior.md]).
|
|
6
|
+
# value (guest→host return path) and as a +#run+ argument auto-wrapped
|
|
7
|
+
# by the host.
|
|
9
8
|
#
|
|
10
9
|
# SPEC pins the binary layout to fixext 4 with a 4-byte big-endian u32
|
|
11
10
|
# payload ({docs/wire-codec.md}[link:../../docs/wire-codec.md]
|