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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31ada84079df29e6403f69a5d3a5dcf6dd8780430a4063cbc3576399272904f8
4
- data.tar.gz: 3f66558a59dde0642cc6fa8b4871c009e2aaf6efb9efb973e74a45e5158fa9c2
3
+ metadata.gz: e6b1af77d24fb13d4d331b5ee1ede3fbd2d981f3ad8df91796de97e9e320abd2
4
+ data.tar.gz: e4305e4c7ad9845bf70d41b1d18f48465f82f37feff9fa85525a0443a622393b
5
5
  SHA512:
6
- metadata.gz: e839baee0544f72426b9887a745a564bf8350370beca72f4dd3e230fb71b568682bb6e7794acad502e5e27c984cafe9fe7ffb14f0d5ab8ea6cbbc2e863a73439
7
- data.tar.gz: c54343f8b35e7e340ff90155c23e185f1be3888226675aa0f5a1fb6ac6912a77f9bff5b69f8db8583c622eee5b3a39666dca6c3d780fd5f8cc977571c6a2cc35
6
+ metadata.gz: b4feda43682dc0448fc1898ee173adad97664095bf3d175f9b9c6d525f272be82f91bd75baba54dc22f9a5ce7124209d2dc9a080be9ed29c5bade74176735d65
7
+ data.tar.gz: 8e7775a4dd467b7f8d57f0158fa5667800f5296785fdcc0463380435d024ea3645b2abfd1c7499086f35009d287d2629a1b72059e2301278171491532df5d2a1
@@ -1 +1 @@
1
- {".":"0.22.0","wasm/kobako-core":"0.14.0","wasm/kobako-mruby":"0.14.0","wasm/kobako-io":"0.14.0","wasm/kobako-json":"0.14.0","wasm/kobako-regexp":"0.14.0","wasm/kobako-baker":"0.14.0","crates/kobako-transport":"0.14.0","crates/kobako-codec":"0.14.0","crates/kobako-runtime":"0.14.0","crates/kobako-wasmtime":"0.14.0","crates/kobako":"0.14.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 its public methods through the Transport wire. See [`docs/behavior/registration.md`](docs/behavior/registration.md) B-08..B-12.
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/registration.md`](docs/behavior/registration.md) B-62..B-63).
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/lifecycle.md`](docs/behavior/lifecycle.md) B-04). Each invocation captures its own; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
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/lifecycle.md`](docs/behavior/lifecycle.md) B-35).
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) B-22). 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.
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 (B-64).
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/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.
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. 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 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)).
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) B-23..B-30.
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 (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).
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"> (B-37 restoration)
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 (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:
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 (B-19).
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/invocation.md`](docs/behavior/invocation.md) B-31..B-33).
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) B-55..B-57).
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 *every* public method of the object. So bind a purpose-built object scoped to the
470
- task, not a capable one whose other methods leak more than you intend.
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 becomes an allow-list.
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/lifecycle.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | B-22 (per-Thread isolation) is exercised only indirectly through the pool contention tests; no test cites it |
13
- | ✅ [F-02 Service binding](docs/behavior/registration.md) | [lib/kobako/catalog/services.rb](lib/kobako/catalog/services.rb) | — |
14
- | ✅ [F-04 Synchronous mruby source execution (`#eval`)](docs/behavior/lifecycle.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
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/errors.md) | [lib/kobako/outcome.rb](lib/kobako/outcome.rb) | E-26 (guest-entry envelope decode failure) has no exercising test not reachable through the public API |
18
- | ✅ [F-08 Guest output capture](docs/behavior/lifecycle.md) | [lib/kobako/capture.rb](lib/kobako/capture.rb) | — |
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 (`rake anchors`, double-bake byte-identity, gemspec whitelist), not `test/` |
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/invocation.md) | [lib/kobako/catalog/snippets.rb](lib/kobako/catalog/snippets.rb) | — |
24
- | ✅ [F-14 Synchronous entrypoint dispatch (`#run`)](docs/behavior/invocation.md) | [lib/kobako/sandbox.rb](lib/kobako/sandbox.rb) | — |
25
- | ✅ [F-15 Warm Sandbox pool checkout (`Kobako::Pool`)](docs/behavior/runtime.md) | [lib/kobako/pool.rb](lib/kobako/pool.rb) | — |
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
 
@@ -26,7 +26,7 @@ map the neutral types onto their own language surface.
26
26
 
27
27
  ```toml
28
28
  [dependencies]
29
- kobako-runtime = "0.14.0" # x-release-please-version
29
+ kobako-runtime = "0.15.0" # x-release-please-version
30
30
  ```
31
31
 
32
32
  ## License
@@ -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
 
@@ -35,7 +35,7 @@ the golden vectors in this crate are derived from that document.
35
35
 
36
36
  ```toml
37
37
  [dependencies]
38
- kobako-transport = "0.14.0" # x-release-please-version
38
+ kobako-transport = "0.15.0" # x-release-please-version
39
39
  ```
40
40
 
41
41
  ## License
@@ -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
 
@@ -24,7 +24,7 @@ SDK consumes the same surface.
24
24
 
25
25
  ```toml
26
26
  [dependencies]
27
- kobako-wasmtime = "0.14.0" # x-release-please-version
27
+ kobako-wasmtime = "0.15.0" # x-release-please-version
28
28
  ```
29
29
 
30
30
  ## License
data/data/kobako.wasm CHANGED
Binary file
Binary file
Binary file
Binary file
@@ -1,26 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- # Host-side captured prefix of guest stdout / stderr produced during a
5
- # single +Kobako::Sandbox+ invocation, paired with the truncation flag
6
- # the WASI pipe sets when the guest wrote past the configured per-channel
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
- # Build a Capture wrapping +bytes+ (the captured prefix as a String) and
19
- # +truncated+ (whether the originating WASI pipe reported the cap was
20
- # hit). Coerces +bytes+ to UTF-8 when they are valid UTF-8, otherwise
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
- # Returns +true+ iff the underlying capture channel exceeded its
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
- # 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".
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, untyped]
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. +id+ is a Handle ID previously
58
- # returned by +#alloc+. Returns the bound object. Raises
59
- # +Kobako::SandboxError+ if +id+ is not currently bound.
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 a reflective gadget:
77
- # a +Binding+ / +Method+ / +UnboundMethod+ would hand the guest a
78
- # callable proxy onto host reflection (a returned +Binding+ reaches
79
- # +Binding#eval+). Raising here keeps the rule at the single mint
80
- # point, so it holds on both the Service-return and the +#run+
81
- # host→guest auto-wrap paths.
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
- raise SandboxError, "a #{object.class} cannot cross as a Capability Handle"
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→object table and the declared path set every invocation
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") # => kv_object
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, untyped]
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. Raises
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)