kobako 0.22.0-aarch64-linux → 0.25.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 +96 -0
- data/README.md +19 -18
- data/ROADMAP.md +12 -12
- data/crates/kobako-runtime/CHANGELOG.md +7 -0
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-transport/CHANGELOG.md +7 -0
- data/crates/kobako-transport/README.md +1 -1
- data/crates/kobako-wasmtime/CHANGELOG.md +9 -0
- data/crates/kobako-wasmtime/README.md +1 -1
- 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 +8 -22
- data/lib/kobako/catalog/handles.rb +40 -13
- data/lib/kobako/catalog/services.rb +9 -7
- data/lib/kobako/context.rb +8 -8
- data/lib/kobako/handle.rb +10 -40
- data/lib/kobako/sandbox.rb +5 -0
- data/lib/kobako/transport/dispatcher.rb +28 -26
- data/lib/kobako/transport/exposure.rb +100 -0
- data/lib/kobako/transport/reflection.rb +40 -27
- data/lib/kobako/transport.rb +4 -2
- data/lib/kobako/usage.rb +11 -22
- data/lib/kobako/version.rb +1 -1
- data/release-please-config.json +1 -1
- data/sig/kobako/catalog/handles.rbs +2 -0
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +7 -7
- data/sig/kobako/transport/exposure.rbs +43 -0
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport.rbs +11 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6b1af77d24fb13d4d331b5ee1ede3fbd2d981f3ad8df91796de97e9e320abd2
|
|
4
|
+
data.tar.gz: e4305e4c7ad9845bf70d41b1d18f48465f82f37feff9fa85525a0443a622393b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b4feda43682dc0448fc1898ee173adad97664095bf3d175f9b9c6d525f272be82f91bd75baba54dc22f9a5ce7124209d2dc9a080be9ed29c5bade74176735d65
|
|
7
|
+
data.tar.gz: 8e7775a4dd467b7f8d57f0158fa5667800f5296785fdcc0463380435d024ea3645b2abfd1c7499086f35009d287d2629a1b72059e2301278171491532df5d2a1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
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"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.25.0](https://github.com/elct9620/kobako/compare/v0.24.0...v0.25.0) (2026-09-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **gem:** a bound object or Capability Handle now exposes to the guest only the public methods its own class and the object itself define in source. Methods inherited from a superclass, mixed in from a module, or built into Ruby (a StringIO's #read, a Struct's member writers) are refused as undefined targets, as are names a method_missing Service answers dynamically, and a class, module, or forwarder bound directly exposes nothing. To keep a method reachable, define it on the bound object's own class, or give the object a private respond_to_guest?(name) that answers true for the names the guest may call — it replaces the default rather than narrowing it.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **spec:** expose only what an object defines itself unless it narrows ([e199479](https://github.com/elct9620/kobako/commit/e1994798cbb06902a1073faa38acd60e15e28da7))
|
|
13
|
+
* **transport:** expose only what a bound object defines itself ([3086d5b](https://github.com/elct9620/kobako/commit/3086d5b88a7f3a984fb5e93571d1ff66a8e55c2d))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **release:** name the gem's version file so a release bumps it ([6df0467](https://github.com/elct9620/kobako/commit/6df0467e71332c24e30973c37a7e12e71929c07c))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
* **gem:** state what a bound object exposes to the guest ([d9c5de8](https://github.com/elct9620/kobako/commit/d9c5de813ce9a970fb9b14e4401d3db752ea39f4))
|
|
24
|
+
|
|
25
|
+
## [0.24.0](https://github.com/elct9620/kobako/compare/v0.23.0...v0.24.0) (2026-09-13)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* **mruby:** upgrade beni to 0.14 and locate a parse failure ([586c910](https://github.com/elct9620/kobako/commit/586c910873710169c0dfce1fe7edb21f9e91cdea))
|
|
31
|
+
* **spec:** account for every boundary and isolation behavior ([226a563](https://github.com/elct9620/kobako/commit/226a5637350f3561e93b56d2acc5571c75447fb6))
|
|
32
|
+
* **spec:** account for every dispatch behavior the old anchors stated ([524b0ba](https://github.com/elct9620/kobako/commit/524b0ba7024e3a5a05d883fd0529c7abd53649a4))
|
|
33
|
+
* **spec:** account for every error scenario the old anchors stated ([179de50](https://github.com/elct9620/kobako/commit/179de5040c54bf3800d3d6ba02b56cf4eaed685b))
|
|
34
|
+
* **spec:** account for every lifecycle and invocation behavior ([75dc57f](https://github.com/elct9620/kobako/commit/75dc57f833a9933bd42ad55f48a6ed25f579826d))
|
|
35
|
+
* **spec:** account for every regexp and JSON behavior the old anchors stated ([9f93e59](https://github.com/elct9620/kobako/commit/9f93e599b5bf8dd2aefedcc856ec1d634a8179c7))
|
|
36
|
+
* **spec:** account for every registration and Extension behavior ([8750cba](https://github.com/elct9620/kobako/commit/8750cbae0901d8f1e4b6a22b90a70cc5bc6fff79))
|
|
37
|
+
* **spec:** account for every yield, runtime, and pool behavior ([29a7824](https://github.com/elct9620/kobako/commit/29a7824abcaa539b451d4b38a0bb6a2876c32f75))
|
|
38
|
+
* **spec:** bring the Rust frontend under the behaviors it answers ([b62243f](https://github.com/elct9620/kobako/commit/b62243f68b338fd219136d60c9cf760d0be03fc0))
|
|
39
|
+
* **spec:** declare every behavior the parity harness compares ([94f044a](https://github.com/elct9620/kobako/commit/94f044afa5ef84f05b868587101b15faa486b039))
|
|
40
|
+
* **spec:** declare how a dispatch binds its arguments and reports what will not ([d5af2d8](https://github.com/elct9620/kobako/commit/d5af2d8c863f91c7916d9c5ed4ee8ac415c849b1))
|
|
41
|
+
* **spec:** declare that both frontends refuse a late registration ([3aac1d0](https://github.com/elct9620/kobako/commit/3aac1d094ee4f3b3017760892c62d4fcee49f34b))
|
|
42
|
+
* **spec:** declare the boundary's value fidelity and its outbound refusals ([505845c](https://github.com/elct9620/kobako/commit/505845cb02e6d5dd94886cc2256708a9058b4c7f))
|
|
43
|
+
* **spec:** declare the bytes both sides must already agree on ([3875755](https://github.com/elct9620/kobako/commit/3875755999550511837808dcd24a25086bedc926))
|
|
44
|
+
* **spec:** declare the form each value takes on the payload wire ([2ceee24](https://github.com/elct9620/kobako/commit/2ceee24df27b0fbe07c29d33830e8be49593f260))
|
|
45
|
+
* **spec:** declare the options a runtime will not build with ([1bbd018](https://github.com/elct9620/kobako/commit/1bbd018c05099b0501334292dd03e17ffb040c46))
|
|
46
|
+
* **spec:** declare the three ways a yield ends badly ([c1b9024](https://github.com/elct9620/kobako/commit/c1b9024416d5c3a2f52c6137621a2656cf95fd1d))
|
|
47
|
+
* **spec:** declare the walks a Host App takes end to end ([f5012a1](https://github.com/elct9620/kobako/commit/f5012a1a8a5ea7abe1db28a9a4039648c4bc4041))
|
|
48
|
+
* **spec:** declare what a capture holds and what mints a reference ([7cf8afe](https://github.com/elct9620/kobako/commit/7cf8afe1b643ebd7225998bf101219a0ab8421fd))
|
|
49
|
+
* **spec:** declare what a decode says about the references it read ([4cff987](https://github.com/elct9620/kobako/commit/4cff98751eb75e9316048d8b0820c5bb423d86a8))
|
|
50
|
+
* **spec:** declare what a Pool refuses and what it raises ([b5cec2d](https://github.com/elct9620/kobako/commit/b5cec2d2d1083d49cdfd8ab5dc46ef164ea30aa7))
|
|
51
|
+
* **spec:** declare what a preload and an entrypoint refuse ([299c177](https://github.com/elct9620/kobako/commit/299c1771cfaef57023b277bfd01a347f23908354))
|
|
52
|
+
* **spec:** declare what a run refuses before the guest is reached ([073f801](https://github.com/elct9620/kobako/commit/073f801f1b37a82d4b6bfd9f574f2c2cce49889c))
|
|
53
|
+
* **spec:** declare what an invocation settles into and who it is attributed to ([02a5b38](https://github.com/elct9620/kobako/commit/02a5b38eb256027db53aaa0c8dc5a97d4e2ae927))
|
|
54
|
+
* **spec:** declare what bind refuses ([1b5dce3](https://github.com/elct9620/kobako/commit/1b5dce30eb418a1a2deb6e690d43ba3a4d04a675))
|
|
55
|
+
* **spec:** declare what install refuses and when the check fires ([1032921](https://github.com/elct9620/kobako/commit/1032921f7e5d1e1c53fbf2d1c6ec46240d1ff768))
|
|
56
|
+
* **spec:** declare what the driver holds a run to ([4539cc1](https://github.com/elct9620/kobako/commit/4539cc16a7ddb6c1706c36282e64182bb480b684))
|
|
57
|
+
* **spec:** declare what the guest answers when a value will not cross ([886a16f](https://github.com/elct9620/kobako/commit/886a16f173efe44766dd9884ac8bfed61113728a))
|
|
58
|
+
* **spec:** declare what the payload wire carries and what it will not ([341fd40](https://github.com/elct9620/kobako/commit/341fd40ce60373858002205ad5a77618e6cbb520))
|
|
59
|
+
* **spec:** declare what the runtime keeps on disk and hands back ([e2f71de](https://github.com/elct9620/kobako/commit/e2f71de89a31dc968950d2c32815193090156d73))
|
|
60
|
+
* **spec:** let both payload implementations answer for one table ([5368ee7](https://github.com/elct9620/kobako/commit/5368ee790d0e8fc0c8e7d6b6222d55da11a7f05c))
|
|
61
|
+
* **spec:** locate a parse failure and name the class a bytecode load answers ([b9a04b7](https://github.com/elct9620/kobako/commit/b9a04b7b72cfc4a207d4540839e24475bbaa0313))
|
|
62
|
+
* **spec:** make Markdown the specification's source ([8d4e56b](https://github.com/elct9620/kobako/commit/8d4e56b18ae8118a2251f60fdbd8b917e5f60ebe))
|
|
63
|
+
* **spec:** register the calls each frontend keeps ([dc2285e](https://github.com/elct9620/kobako/commit/dc2285edd275185be35700cd3aec67abd253b8c7))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
### Bug Fixes
|
|
67
|
+
|
|
68
|
+
* **release:** let a gem-only release create its tag ([d65c451](https://github.com/elct9620/kobako/commit/d65c4513d30aacb11d0c008d28791355d8da13b7))
|
|
69
|
+
* **sdk:** refuse a Service path that is already a name ([8362f46](https://github.com/elct9620/kobako/commit/8362f46d80791baab23c86996d42bb80b9a2186a))
|
|
70
|
+
* **spec:** claim the scenario each test actually witnesses ([ab92e8b](https://github.com/elct9620/kobako/commit/ab92e8b9f5948f16fb86e517b9501598d76e4b89))
|
|
71
|
+
* **spec:** keep the two record-free arms cited on the anchor track ([9eec1b6](https://github.com/elct9620/kobako/commit/9eec1b6696c8ff0d5e1d63bb29c9637dd52bdb52))
|
|
72
|
+
* **transport:** keep a transparent forwarder off the guest boundary ([e789a4e](https://github.com/elct9620/kobako/commit/e789a4e618643209f289ee664d36f256970c4d62))
|
|
73
|
+
|
|
74
|
+
## [0.23.0](https://github.com/elct9620/kobako/compare/v0.22.0...v0.23.0) (2026-08-28)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Features
|
|
78
|
+
|
|
79
|
+
* **spec:** declare the boot state two invocations share, and what a callback raise costs ([d94fffc](https://github.com/elct9620/kobako/commit/d94fffc59a3142cb28e0025a0dc8c792f7282074))
|
|
80
|
+
* **spec:** declare the dispatch boundary one observation at a time ([f11b280](https://github.com/elct9620/kobako/commit/f11b280ef503c20d5b75d7b1c4187d261f60ef27))
|
|
81
|
+
* **spec:** declare the guest's pattern surface one observation at a time ([a477818](https://github.com/elct9620/kobako/commit/a477818f8fa505fdcd8ea1fd1051a74c0ba08155))
|
|
82
|
+
* **spec:** declare the Pool's behavior where its tests can claim it ([5ec5e4b](https://github.com/elct9620/kobako/commit/5ec5e4b41f44db05520a251ff6979e71efd27866))
|
|
83
|
+
* **spec:** declare the two descriptors a guest may write to, and how ([c15d09a](https://github.com/elct9620/kobako/commit/c15d09ab563947ab02bbe31129e88686fcff5c24))
|
|
84
|
+
* **spec:** declare the value shapes and the generated concurrency programs ([a06ae5b](https://github.com/elct9620/kobako/commit/a06ae5b24b4615c22c2abaae08c7029218da08bd))
|
|
85
|
+
* **spec:** declare what a Sandbox is built with and what each run leaves behind ([8cc6ac8](https://github.com/elct9620/kobako/commit/8cc6ac81713da7fc6ba6c89bc24da75406579aec))
|
|
86
|
+
* **spec:** declare what installing an Extension composes and resolves ([3cce5a6](https://github.com/elct9620/kobako/commit/3cce5a6551ae8710df71d7520a689c13630ee111))
|
|
87
|
+
* **spec:** declare what the guest's JSON surface reads, writes and refuses ([7e9e3b3](https://github.com/elct9620/kobako/commit/7e9e3b3897ae5f5dc7d6caadeb650302841aa0a6))
|
|
88
|
+
* **spec:** declare what the host checks before a guest runs ([28d5d4b](https://github.com/elct9620/kobako/commit/28d5d4bac0efba2aced1b4449926bd868787bd75))
|
|
89
|
+
* **spec:** declare where a host object becomes a name the guest can reach ([5d88647](https://github.com/elct9620/kobako/commit/5d88647aa922e3a7bb6915864aec700423060358))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
### Bug Fixes
|
|
93
|
+
|
|
94
|
+
* **gate:** keep the rendered specifications out of the anchor corpus ([1a24c7f](https://github.com/elct9620/kobako/commit/1a24c7ff1367228998b2f21beca885ab7096a893))
|
|
95
|
+
* **gate:** stop reading a sumi claim as an anchor citation ([b3c74d6](https://github.com/elct9620/kobako/commit/b3c74d6a296dfb7fa5e157ca9ba4678097bfab43))
|
|
96
|
+
* **spec:** let the dispatch feature reach the test that witnesses it ([430394f](https://github.com/elct9620/kobako/commit/430394fa906bf79b8d86d28de7b03e32748516a1))
|
|
97
|
+
* **transport:** keep a bound Class's class-level surface off the guest boundary ([dc00355](https://github.com/elct9620/kobako/commit/dc0035524f2a7b3eba3ec378f8f065c19a17660d))
|
|
98
|
+
|
|
3
99
|
## [0.22.0](https://github.com/elct9620/kobako/compare/v0.21.1...v0.22.0) (2026-08-06)
|
|
4
100
|
|
|
5
101
|
|
data/README.md
CHANGED
|
@@ -151,7 +151,7 @@ Build the crate as a `cdylib` for `wasm32-wasip1`, then bake the canonical boot
|
|
|
151
151
|
|
|
152
152
|
### Services
|
|
153
153
|
|
|
154
|
-
`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
|
|
154
|
+
`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).
|
|
155
155
|
|
|
156
156
|
```ruby
|
|
157
157
|
class User
|
|
@@ -175,7 +175,7 @@ Each `::`-separated path segment must match `/\A[A-Z]\w*\z/`. Symbol kwargs trav
|
|
|
175
175
|
|
|
176
176
|
### Per-Invocation Bindings
|
|
177
177
|
|
|
178
|
-
A setup-time `bind` fixes one object for the Sandbox's life. When the object belongs to a single run instead — the current request, the acting user, a per-tenant store — declare the path at setup and fill it per invocation. `bind(path)` with no object reserves the name as a *fillable*: the guest sees the constant, while an unfilled dispatch fails closed as `Kobako::ServiceError`. The optional `#eval` / `#run` block fills it ([`docs/behavior/
|
|
178
|
+
A setup-time `bind` fixes one object for the Sandbox's life. When the object belongs to a single run instead — the current request, the acting user, a per-tenant store — declare the path at setup and fill it per invocation. `bind(path)` with no object reserves the name as a *fillable*: the guest sees the constant, while an unfilled dispatch fails closed as `Kobako::ServiceError`. The optional `#eval` / `#run` block fills it ([`SV-017`](docs/spec/behavior/services.md), [`SV-024`](docs/spec/behavior/services.md)).
|
|
179
179
|
|
|
180
180
|
```ruby
|
|
181
181
|
sandbox.bind("Req::Current") # declared, unfilled — stands for Kobako::Unresolved
|
|
@@ -187,7 +187,7 @@ sandbox.eval("Req::Current.user_id") { |ctx| ctx.bind("Req::Current", request) }
|
|
|
187
187
|
|
|
188
188
|
### Output Capture
|
|
189
189
|
|
|
190
|
-
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and read off the run's Execution, independently of its `#value` ([`docs/behavior/
|
|
190
|
+
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and read off the run's Execution, independently of its `#value` ([`S-023`](docs/spec/behavior/sandbox.md), [`S-028`](docs/spec/behavior/sandbox.md)). Each invocation captures its own; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
191
191
|
|
|
192
192
|
```ruby
|
|
193
193
|
execution = sandbox.eval(<<~RUBY)
|
|
@@ -232,7 +232,7 @@ Each of these carries the failed run's Execution on `#execution`, so a rescue re
|
|
|
232
232
|
|
|
233
233
|
### Resource Limits
|
|
234
234
|
|
|
235
|
-
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 [`Execution#usage`](lib/kobako/execution.rb) for actual consumption — populated on every outcome, so a rescued trap reports it just as a completed run does ([`docs/behavior/
|
|
235
|
+
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 [`Execution#usage`](lib/kobako/execution.rb) for actual consumption — populated on every outcome, so a rescued trap reports it just as a completed run does ([`S-058`](docs/spec/behavior/sandbox.md), [`S-061`](docs/spec/behavior/sandbox.md)).
|
|
236
236
|
|
|
237
237
|
```ruby
|
|
238
238
|
sandbox = Kobako::Sandbox.new(
|
|
@@ -256,9 +256,9 @@ Beyond the four caps, `profile:` requests the Sandbox's isolation posture on the
|
|
|
256
256
|
|
|
257
257
|
### Concurrency
|
|
258
258
|
|
|
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 ([`docs/behavior/runtime.md`](docs/behavior/runtime.md)
|
|
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.
|
|
260
260
|
|
|
261
|
-
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 (
|
|
261
|
+
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
262
|
|
|
263
263
|
```ruby
|
|
264
264
|
sandbox = Kobako::Sandbox.new(gvl: :release)
|
|
@@ -319,9 +319,9 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
|
|
|
319
319
|
|
|
320
320
|
### Pooling
|
|
321
321
|
|
|
322
|
-
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/
|
|
322
|
+
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
323
|
|
|
324
|
-
`Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation.
|
|
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
325
|
|
|
326
326
|
```ruby
|
|
327
327
|
pool = Kobako::Pool.new(slots: 4) do |sandbox|
|
|
@@ -340,7 +340,7 @@ Sandboxes construct lazily on first demand. `#with` yields a Sandbox and returns
|
|
|
340
340
|
|
|
341
341
|
### Service Blocks
|
|
342
342
|
|
|
343
|
-
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)
|
|
343
|
+
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).
|
|
344
344
|
|
|
345
345
|
```ruby
|
|
346
346
|
sandbox.bind("Seq::Map", ->(items, &blk) { items.map(&blk) })
|
|
@@ -351,7 +351,7 @@ sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }').value
|
|
|
351
351
|
|
|
352
352
|
### Handle Management
|
|
353
353
|
|
|
354
|
-
A non-wire-representable host object — returned from a Service (
|
|
354
|
+
A non-wire-representable host object — returned from a Service ([`T-001`](docs/spec/behavior/transport-dispatch.md)), passed to `#run` ([`T-066`](docs/spec/behavior/transport-dispatch.md)), or handed back from the guest ([`T-054`](docs/spec/behavior/transport-dispatch.md)) — 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 ([`T-038`](docs/spec/behavior/transport-dispatch.md)).
|
|
355
355
|
|
|
356
356
|
```ruby
|
|
357
357
|
class Greeter
|
|
@@ -362,12 +362,12 @@ end
|
|
|
362
362
|
sandbox.bind("Factory::Make", ->(name) { Greeter.new(name) })
|
|
363
363
|
|
|
364
364
|
sandbox.eval('Factory::Make.call("Bob").greet').value # => "hi, Bob" (Handle round-trip inside guest)
|
|
365
|
-
sandbox.eval('Factory::Make.call("Bob")').value # => #<Greeter @name="Bob"> (
|
|
365
|
+
sandbox.eval('Factory::Make.call("Bob")').value # => #<Greeter @name="Bob"> (Handle restoration)
|
|
366
366
|
```
|
|
367
367
|
|
|
368
368
|
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
369
|
|
|
370
|
-
Each dispatch that hands back a non-wire-representable object allocates a *new* Handle — kobako never deduplicates by object identity (
|
|
370
|
+
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
371
|
|
|
372
372
|
```
|
|
373
373
|
guest chain host (Catalog::Handles, one invocation)
|
|
@@ -383,11 +383,11 @@ Each dispatch that hands back a non-wire-representable object allocates a *new*
|
|
|
383
383
|
all stay live until the invocation ends, then reset together
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
-
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 (
|
|
386
|
+
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 ([`transport-dispatch.md`](docs/spec/behavior/transport-dispatch.md)).
|
|
387
387
|
|
|
388
388
|
### Snippets & Entrypoints
|
|
389
389
|
|
|
390
|
-
`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/
|
|
390
|
+
`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 ([`S-053`](docs/spec/behavior/sandbox.md), [`S-045`](docs/spec/behavior/sandbox.md)).
|
|
391
391
|
|
|
392
392
|
```ruby
|
|
393
393
|
sandbox = Kobako::Sandbox.new
|
|
@@ -428,7 +428,7 @@ Use the source form for snippets authored in your repo; use the bytecode form wh
|
|
|
428
428
|
|
|
429
429
|
### Extensions
|
|
430
430
|
|
|
431
|
-
An Extension teaches the guest a native-style constant by pairing a guest idiom (`source`) with an optional host `backend`. `Sandbox#install` composes the two through the existing `#preload` and `#bind` verbs, adding no wire or Guest Binary surface: pure operations run in-guest with no round-trip, while the rest dispatch to the backend under the same isolation and reflection guarantees as any bound Service ([`docs/extensions.md`](docs/extensions.md), [`docs/behavior/extension.md`](docs/behavior/extension.md)
|
|
431
|
+
An Extension teaches the guest a native-style constant by pairing a guest idiom (`source`) with an optional host `backend`. `Sandbox#install` composes the two through the existing `#preload` and `#bind` verbs, adding no wire or Guest Binary surface: pure operations run in-guest with no round-trip, while the rest dispatch to the backend under the same isolation and reflection guarantees as any bound Service ([`docs/extensions.md`](docs/extensions.md), [`EX-005`](docs/spec/behavior/extension.md), [`EX-006`](docs/spec/behavior/extension.md)).
|
|
432
432
|
|
|
433
433
|
```ruby
|
|
434
434
|
FILE = <<~'MRUBY'
|
|
@@ -466,8 +466,9 @@ kobako ships no concrete Extension; the idiom and backend are yours. The [overla
|
|
|
466
466
|
## Security
|
|
467
467
|
|
|
468
468
|
kobako isolates the guest, but **what it may reach is whatever you `bind`** — and `bind`
|
|
469
|
-
exposes
|
|
470
|
-
|
|
469
|
+
exposes every public method the object's own class defines. Inherited, mixed-in, and built-in
|
|
470
|
+
methods stay out of reach, but the class's own surface does not, so bind a purpose-built
|
|
471
|
+
object scoped to the task, not a capable one whose other methods leak more than you intend.
|
|
471
472
|
|
|
472
473
|
```ruby
|
|
473
474
|
class ThemeReader # only #color is reachable; AppConfig.secret_key is not
|
|
@@ -483,7 +484,7 @@ sandbox.eval('Cfg::Settings.color').value # => "#3366ff" — every other metho
|
|
|
483
484
|
When a purpose-built wrapper is more than you need, an object can gate its own surface in
|
|
484
485
|
place: a private `respond_to_guest?(name)` answers, per method, whether the guest may call
|
|
485
486
|
it. Returning `false` for every name makes the object opaque — a credential the guest
|
|
486
|
-
forwards to another Service but never reads — while a named subset
|
|
487
|
+
forwards to another Service but never reads — while permitting a named subset exposes exactly those.
|
|
487
488
|
|
|
488
489
|
Guest code can name any `MyService::KV` path, but a forged name only resolves to
|
|
489
490
|
something you bound — the real authorization gate is this host-side allowlist. Give each
|
data/ROADMAP.md
CHANGED
|
@@ -9,17 +9,17 @@ output capture, and a warm Sandbox pool.
|
|
|
9
9
|
|
|
10
10
|
| Feature | Entry Points | Notes |
|
|
11
11
|
|---------|-------------|-------|
|
|
12
|
-
| ✅ [F-01 Sandbox instantiation](docs/behavior/
|
|
13
|
-
| ✅ [F-02 Service binding](docs/behavior/
|
|
14
|
-
| ✅ [F-04 Synchronous mruby source execution (`#eval`)](docs/behavior/
|
|
15
|
-
| ✅ [F-05 Guest-initiated Transport dispatch](docs/behavior/dispatch.md) | [lib/kobako/transport/dispatcher.rb](lib/kobako/transport/dispatcher.rb) | — |
|
|
16
|
-
| ✅ [F-06 Capability Handle encoding and referencing](docs/behavior/dispatch.md) | [lib/kobako/catalog/handles.rb](lib/kobako/catalog/handles.rb) | — |
|
|
17
|
-
| ✅ [F-07 Three-class error attribution and raising](docs/behavior/
|
|
18
|
-
| ✅ [F-08 Guest output capture](docs/behavior/
|
|
12
|
+
| ✅ [F-01 Sandbox instantiation](docs/spec/behavior/sandbox.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
|
|
13
|
+
| ✅ [F-02 Service binding](docs/spec/behavior/services.md) | [lib/kobako/catalog/services.rb](lib/kobako/catalog/services.rb) | — |
|
|
14
|
+
| ✅ [F-04 Synchronous mruby source execution (`#eval`)](docs/spec/behavior/sandbox.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
|
|
15
|
+
| ✅ [F-05 Guest-initiated Transport dispatch](docs/spec/behavior/transport-dispatch.md) | [lib/kobako/transport/dispatcher.rb](lib/kobako/transport/dispatcher.rb) | — |
|
|
16
|
+
| ✅ [F-06 Capability Handle encoding and referencing](docs/spec/behavior/transport-dispatch.md) | [lib/kobako/catalog/handles.rb](lib/kobako/catalog/handles.rb) | — |
|
|
17
|
+
| ✅ [F-07 Three-class error attribution and raising](docs/spec/behavior/outcome.md) | [lib/kobako/outcome.rb](lib/kobako/outcome.rb) | A guest-entry decode failure is exercised only through its payload half ([`CD-003`](docs/spec/behavior/codec.md)); a Run envelope that does not frame is not reachable through the public API |
|
|
18
|
+
| ✅ [F-08 Guest output capture](docs/spec/behavior/sandbox.md) | [lib/kobako/capture.rb](lib/kobako/capture.rb) | — |
|
|
19
19
|
| ✅ [F-09 Host–guest message codec](docs/wire-codec.md) | [crates/kobako-transport/](crates/kobako-transport/) (core envelope + ABI, one implementation), [lib/kobako/codec/](lib/kobako/codec/) (host payload codec) | The payload layer has a second implementation in `crates/kobako-codec`; the envelope layer is pinned by golden vectors instead |
|
|
20
|
-
| ✅ [F-10 Reproducible build pipeline](SPEC.md#code-organization) | [tasks/wasm/build.rake](tasks/wasm/build.rake) | Verified by build-time gates (
|
|
20
|
+
| ✅ [F-10 Reproducible build pipeline](SPEC.md#code-organization) | [tasks/wasm/build.rake](tasks/wasm/build.rake) | Verified by build-time gates (double-bake byte-identity, gemspec whitelist), not `test/` |
|
|
21
21
|
| ✅ [F-11 Multi-layer test and benchmark suite](SPEC.md#testing-style) | [test/](test/) | Benchmarks live in [benchmark/](benchmark/) with the gate in `tasks/bench/`; the anchor baseline advances only by deliberate re-bless |
|
|
22
|
-
| ✅ [F-12 Guest block reception and yield re-entry](docs/behavior/yield.md) | [lib/kobako/transport/yielder.rb](lib/kobako/transport/yielder.rb) | — |
|
|
23
|
-
| ✅ [F-13 Snippet preloading (`#preload`)](docs/behavior/
|
|
24
|
-
| ✅ [F-14 Synchronous entrypoint dispatch (`#run`)](docs/behavior/
|
|
25
|
-
| ✅ [F-15 Warm Sandbox pool checkout (`Kobako::Pool`)](docs/behavior/
|
|
22
|
+
| ✅ [F-12 Guest block reception and yield re-entry](docs/spec/behavior/transport-yield.md) | [lib/kobako/transport/yielder.rb](lib/kobako/transport/yielder.rb) | — |
|
|
23
|
+
| ✅ [F-13 Snippet preloading (`#preload`)](docs/spec/behavior/sandbox.md) | [lib/kobako/catalog/snippets.rb](lib/kobako/catalog/snippets.rb) | — |
|
|
24
|
+
| ✅ [F-14 Synchronous entrypoint dispatch (`#run`)](docs/spec/behavior/sandbox.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
|
|
25
|
+
| ✅ [F-15 Warm Sandbox pool checkout (`Kobako::Pool`)](docs/spec/behavior/pool.md) | [lib/kobako/pool.rb](lib/kobako/pool.rb) | — |
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.15.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.14.0...kobako-runtime-v0.15.0) (2026-09-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **spec:** declare what the guest answers when a value will not cross ([886a16f](https://github.com/elct9620/kobako/commit/886a16f173efe44766dd9884ac8bfed61113728a))
|
|
9
|
+
|
|
3
10
|
## [0.14.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.13.1...kobako-runtime-v0.14.0) (2026-08-06)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.15.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.14.0...kobako-transport-v0.15.0) (2026-09-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **spec:** declare the bytes both sides must already agree on ([3875755](https://github.com/elct9620/kobako/commit/3875755999550511837808dcd24a25086bedc926))
|
|
9
|
+
|
|
3
10
|
## [0.14.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.13.1...kobako-transport-v0.14.0) (2026-08-06)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.15.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.14.0...kobako-wasmtime-v0.15.0) (2026-09-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **spec:** bring the Rust frontend under the behaviors it answers ([b62243f](https://github.com/elct9620/kobako/commit/b62243f68b338fd219136d60c9cf760d0be03fc0))
|
|
9
|
+
* **spec:** declare the bytes both sides must already agree on ([3875755](https://github.com/elct9620/kobako/commit/3875755999550511837808dcd24a25086bedc926))
|
|
10
|
+
* **spec:** declare what the driver holds a run to ([4539cc1](https://github.com/elct9620/kobako/commit/4539cc16a7ddb6c1706c36282e64182bb480b684))
|
|
11
|
+
|
|
3
12
|
## [0.14.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.13.1...kobako-wasmtime-v0.14.0) (2026-08-06)
|
|
4
13
|
|
|
5
14
|
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/lib/kobako/3.3/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/3.4/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/4.0/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/capture.rb
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# cap.
|
|
8
|
-
#
|
|
9
|
-
# Immutable value object: the captured bytes and the truncation flag
|
|
10
|
-
# always travel together and the instance is frozen on construction.
|
|
11
|
-
# Construct via +Capture.new(bytes:, truncated:)+ for the ext-provided
|
|
12
|
-
# binary bytes (the constructor handles the UTF-8 / ASCII-8BIT fallback)
|
|
13
|
-
# or reach +Capture::EMPTY+ for the pre-invocation sentinel that
|
|
14
|
-
# +Sandbox+ uses before any invocation has executed.
|
|
4
|
+
# What one invocation wrote to stdout or stderr, up to that channel's
|
|
5
|
+
# cap, together with whether it wrote past the cap. Frozen, so the bytes
|
|
6
|
+
# and the flag always travel together.
|
|
15
7
|
class Capture
|
|
16
8
|
attr_reader :bytes
|
|
17
9
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# falls back to ASCII-8BIT so invalid sequences remain inspectable
|
|
22
|
-
# without raising; +bytes+ is duplicated, never mutated. Freezes the
|
|
23
|
-
# instance so callers cannot mutate the pair.
|
|
10
|
+
# The bytes read as UTF-8 when they are valid UTF-8, and as binary
|
|
11
|
+
# otherwise, so output that is not text stays inspectable instead of
|
|
12
|
+
# raising. The caller's String is copied, never changed.
|
|
24
13
|
def initialize(bytes:, truncated:)
|
|
25
14
|
copy = bytes.dup.force_encoding(Encoding::UTF_8)
|
|
26
15
|
copy.force_encoding(Encoding::ASCII_8BIT) unless copy.valid_encoding?
|
|
@@ -29,13 +18,10 @@ module Kobako
|
|
|
29
18
|
freeze
|
|
30
19
|
end
|
|
31
20
|
|
|
32
|
-
#
|
|
33
|
-
# configured cap during the originating +Sandbox+ invocation.
|
|
21
|
+
# Whether the invocation wrote past this channel's cap.
|
|
34
22
|
def truncated? = @truncated
|
|
35
23
|
|
|
36
|
-
#
|
|
37
|
-
# reused by every fresh +Sandbox+ and by +Sandbox+ between invocations
|
|
38
|
-
# to denote "no capture yet".
|
|
24
|
+
# The capture before any invocation has written anything.
|
|
39
25
|
EMPTY = new(bytes: "", truncated: false)
|
|
40
26
|
end
|
|
41
27
|
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
3
5
|
require_relative "../handle"
|
|
6
|
+
require_relative "../transport/exposure"
|
|
4
7
|
|
|
5
8
|
module Kobako
|
|
6
9
|
module Catalog
|
|
@@ -29,7 +32,9 @@ module Kobako
|
|
|
29
32
|
# tests pass a value near +Kobako::Handle::MAX_ID+ to exercise
|
|
30
33
|
# the cap-exhaustion path without 2³¹ allocations.
|
|
31
34
|
def initialize(next_id: 1)
|
|
32
|
-
@entries = {} # : Hash[Integer,
|
|
35
|
+
@entries = {} # : Hash[Integer, Kobako::Transport::Exposure]
|
|
36
|
+
surfaces = {} # : Hash[Module, Set[Symbol]]
|
|
37
|
+
@surfaces = surfaces.compare_by_identity
|
|
33
38
|
@next_id = next_id
|
|
34
39
|
end
|
|
35
40
|
|
|
@@ -45,19 +50,31 @@ module Kobako
|
|
|
45
50
|
# allocator's output a domain entity. An id is the Handle's only
|
|
46
51
|
# content, so the same internal +Kobako::Handle.restore+ constructor
|
|
47
52
|
# serves both this allocator and the codec's wire-decode path.
|
|
53
|
+
#
|
|
54
|
+
# The entry records the object's Exposure as it stands at mint, so a
|
|
55
|
+
# call made through this Handle is authorized against the reference
|
|
56
|
+
# the guest was given.
|
|
48
57
|
def alloc(object)
|
|
49
58
|
reject_unwrappable!(object)
|
|
50
59
|
ensure_capacity!
|
|
51
60
|
id = @next_id
|
|
52
|
-
@entries[id] = object
|
|
61
|
+
@entries[id] = Kobako::Transport::Exposure.of(object, @surfaces)
|
|
53
62
|
@next_id = id + 1
|
|
54
63
|
Kobako::Handle.restore(id)
|
|
55
64
|
end
|
|
56
65
|
|
|
57
|
-
# Resolve a Handle ID to its bound object
|
|
58
|
-
#
|
|
59
|
-
# +Kobako::SandboxError+ if
|
|
66
|
+
# Resolve a Handle ID to its bound object — the very object +#alloc+
|
|
67
|
+
# received, so a Handle crossing back restores to it. +id+ is a Handle
|
|
68
|
+
# ID previously returned by +#alloc+. Raises +Kobako::SandboxError+ if
|
|
69
|
+
# +id+ is not currently bound.
|
|
60
70
|
def fetch(id)
|
|
71
|
+
exposure(id).object
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The Exposure +id+ was minted with, which authorizes a call the guest
|
|
75
|
+
# makes through that Handle. Raises +Kobako::SandboxError+ if +id+ is
|
|
76
|
+
# not currently bound.
|
|
77
|
+
def exposure(id)
|
|
61
78
|
require_bound!(id)
|
|
62
79
|
@entries[id]
|
|
63
80
|
end
|
|
@@ -73,16 +90,26 @@ module Kobako
|
|
|
73
90
|
|
|
74
91
|
private
|
|
75
92
|
|
|
76
|
-
# Refuse to mint a Capability Handle for
|
|
77
|
-
# a +Binding+ / +Method+ /
|
|
78
|
-
#
|
|
79
|
-
# +Binding#eval+)
|
|
80
|
-
#
|
|
81
|
-
#
|
|
93
|
+
# Refuse to mint a Capability Handle for an object whose reachable
|
|
94
|
+
# surface is not Service behaviour: a +Binding+ / +Method+ /
|
|
95
|
+
# +UnboundMethod+ hands the guest a callable proxy onto host
|
|
96
|
+
# reflection (a returned +Binding+ reaches +Binding#eval+); a +Class+
|
|
97
|
+
# or +Module+ hands over its class-level API (+File.popen+ / +read+,
|
|
98
|
+
# +Kernel.system+), which the owner-based dispatch floor cannot see
|
|
99
|
+
# because a singleton-class owner matches no core-module list; a
|
|
100
|
+
# +Delegator+ (+SimpleDelegator+, +DelegateClass+, +WeakRef+,
|
|
101
|
+
# +Tempfile+) is a transparent forwarder whose public +method_missing+
|
|
102
|
+
# binds and calls the private method the guest names (+Kernel#system+),
|
|
103
|
+
# a surface the floor reads as ordinary Service behaviour. Raising here
|
|
104
|
+
# keeps the rule at the single mint point, so it holds on both the
|
|
105
|
+
# Service-return and the +#run+ host→guest auto-wrap paths.
|
|
82
106
|
def reject_unwrappable!(object)
|
|
83
107
|
case object
|
|
84
|
-
when Binding, Method, UnboundMethod
|
|
85
|
-
|
|
108
|
+
when Binding, Method, UnboundMethod, Module, Delegator
|
|
109
|
+
# Delegator < BasicObject exposes no static +#class+, so name the
|
|
110
|
+
# rejected object's class through Object's own.
|
|
111
|
+
kind = Object.instance_method(:class).bind_call(object)
|
|
112
|
+
raise SandboxError, "a #{kind} cannot cross as a Capability Handle"
|
|
86
113
|
end
|
|
87
114
|
end
|
|
88
115
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../errors"
|
|
4
|
+
require_relative "../transport/exposure"
|
|
4
5
|
|
|
5
6
|
module Kobako
|
|
6
7
|
module Catalog
|
|
7
8
|
# Kobako::Catalog::Services — per-Sandbox registry of Service
|
|
8
9
|
# bindings keyed by their constant-path name. Holds the flat
|
|
9
|
-
# path→
|
|
10
|
+
# path→Exposure table and the declared path set every invocation
|
|
10
11
|
# announces on Frame 1.
|
|
11
12
|
#
|
|
12
13
|
# Public API:
|
|
@@ -14,7 +15,7 @@ module Kobako
|
|
|
14
15
|
# services = Kobako::Catalog::Services.new
|
|
15
16
|
# services.bind("MyService::KV", kv_object) # => services (chainable)
|
|
16
17
|
# services.paths # => ["MyService::KV"]
|
|
17
|
-
# services.lookup("MyService::KV")
|
|
18
|
+
# services.lookup("MyService::KV").object # => kv_object
|
|
18
19
|
#
|
|
19
20
|
# Per-dispatch routing is +Kobako::Transport::Dispatcher+'s
|
|
20
21
|
# responsibility — the Dispatcher resolves a path against the invocation's
|
|
@@ -29,7 +30,7 @@ module Kobako
|
|
|
29
30
|
|
|
30
31
|
# Build a fresh registry.
|
|
31
32
|
def initialize
|
|
32
|
-
@bindings = {} # : Hash[String,
|
|
33
|
+
@bindings = {} # : Hash[String, Kobako::Transport::Exposure]
|
|
33
34
|
@sealed = false
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -39,14 +40,15 @@ module Kobako
|
|
|
39
40
|
# chaining. Raises +ArgumentError+ when a segment is malformed, when
|
|
40
41
|
# +path+ collides with an existing binding (a name is a bound Service
|
|
41
42
|
# or a grouping prefix, never both), or when the owning Sandbox has
|
|
42
|
-
# been sealed by its first invocation.
|
|
43
|
+
# been sealed by its first invocation. The binding records +object+'s
|
|
44
|
+
# Exposure as it stands now.
|
|
43
45
|
def bind(path, object)
|
|
44
46
|
raise ArgumentError, "cannot bind after first Sandbox invocation" if @sealed
|
|
45
47
|
|
|
46
48
|
path_str = validate_path!(path)
|
|
47
49
|
raise ArgumentError, "Service path #{path_str} conflicts with an existing binding" if collision?(path_str)
|
|
48
50
|
|
|
49
|
-
@bindings[path_str] = object
|
|
51
|
+
@bindings[path_str] = Kobako::Transport::Exposure.of(object)
|
|
50
52
|
self
|
|
51
53
|
end
|
|
52
54
|
|
|
@@ -56,8 +58,8 @@ module Kobako
|
|
|
56
58
|
@bindings.key?(path.to_s)
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
# Resolve a +target+ constant path to the bound Service.
|
|
60
|
-
# +KeyError+ when no Service is bound at +target+.
|
|
61
|
+
# Resolve a +target+ constant path to the bound Service's Exposure.
|
|
62
|
+
# Raises +KeyError+ when no Service is bound at +target+.
|
|
61
63
|
def lookup(target)
|
|
62
64
|
target_str = target.to_s
|
|
63
65
|
raise KeyError, "no service bound at #{target_str.inspect}" unless @bindings.key?(target_str)
|