kobako 0.15.0-aarch64-linux → 0.17.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: 00df75af1fd85b3c4d3729d456148a282ab904f26162e6930ecd8ad003bcd9df
4
- data.tar.gz: 34533660556ce44eddbc76831b973ed10217e5a3801affbb5b7a991e15069c3a
3
+ metadata.gz: a6a999cf44d11a6c9456e2eb64469cad4f3e2171a0b36fe50f6a521af92f054d
4
+ data.tar.gz: e9815939407b3c49b4bdca283d9389e6aeeca3394e4028a2e30032f9bd299d8c
5
5
  SHA512:
6
- metadata.gz: 34250f5db8642efbab2654993df69316be3cdb581bafacc07ac6993e072ea5addf3c04bae86d41ad5412c182b3f22e9aa158b9e9cb27d86571a7cf01ad38a674
7
- data.tar.gz: ac5d4d68b447714f78412acd554c842cbd52f8bfab46794ffa7fabe1dcc6a169bb4d04a3caf10e88bf16ff41b4312ab30626687ee46b31530514b5fa4ac93242
6
+ metadata.gz: 4464cad4dd28d5b6db8eed691ce623a28a0fb18eb1d6486ed5e8fc918f6ef0be125cc9e089475557aa5f298b488d5436b3f5f716e9d92b9d4e74cf2013d91117
7
+ data.tar.gz: 7d582dc78e56ec880cda51f76561371a72b7a44a63f11699436e12f3baadd4d8f756e376d31a1826bace9534b79741ddedea5090da8e1848d6c2db157c7c4a61
@@ -1 +1 @@
1
- {".":"0.15.0","wasm/kobako-core":"0.9.0","wasm/kobako-mruby":"0.9.0","wasm/kobako-io":"0.9.0","wasm/kobako-json":"0.9.0","wasm/kobako-regexp":"0.9.0","wasm/kobako-baker":"0.9.0","crates/kobako-codec":"0.9.0","crates/kobako-runtime":"0.9.0","crates/kobako-wasmtime":"0.9.0","crates/kobako":"0.9.0"}
1
+ {".":"0.17.0","wasm/kobako-core":"0.10.1","wasm/kobako-mruby":"0.10.1","wasm/kobako-io":"0.10.1","wasm/kobako-json":"0.10.1","wasm/kobako-regexp":"0.10.1","wasm/kobako-baker":"0.10.1","crates/kobako-codec":"0.10.1","crates/kobako-runtime":"0.10.1","crates/kobako-wasmtime":"0.10.1","crates/kobako":"0.10.1"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/elct9620/kobako/compare/v0.16.0...v0.17.0) (2026-07-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * **examples:** add vfs, an overlay filesystem that protects the disk ([1c83d44](https://github.com/elct9620/kobako/commit/1c83d44de971bf76d3db84cc3021b23bfc363842))
9
+ * **tasks:** add a rake gate aggregate over every gate:* check ([c02e069](https://github.com/elct9620/kobako/commit/c02e06996134f124025538ba5c23f923606bc3d2))
10
+ * **tasks:** add KobakoReport, the shared static-analysis output template ([7854a61](https://github.com/elct9620/kobako/commit/7854a61d30b823534bcad869a4e4970beab80340))
11
+ * **tasks:** disclose the Ruby-only scope of the coverage report ([51cb291](https://github.com/elct9620/kobako/commit/51cb291bbc45671240a63e00a1461958694e4ee1))
12
+ * **tasks:** disclose the tail below the hotspots top-N cut ([d7584ee](https://github.com/elct9620/kobako/commit/d7584ee610e347dcef9cb2515680c97c911320e5))
13
+ * **tasks:** gate the pub-surface acknowledgement ledger for staleness ([5f97c77](https://github.com/elct9620/kobako/commit/5f97c773f300363e387ff8020523f141cdaaf88f))
14
+ * **tasks:** gate the RBS collection lock against Gemfile.lock drift ([8201ce0](https://github.com/elct9620/kobako/commit/8201ce03848d9710239c586321a6ffb75de1950d))
15
+ * **tasks:** measure Rust line coverage with cargo llvm-cov ([1f6bd9f](https://github.com/elct9620/kobako/commit/1f6bd9f2876f3ef6d8e60880c40198eb70327bf4))
16
+ * **tasks:** open the stats reports with a self-describing banner ([914d0cf](https://github.com/elct9620/kobako/commit/914d0cf74e338a0a7405cc6f981135defac534f1))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **catalog:** match Extension dependencies by Symbol form ([57f17b8](https://github.com/elct9620/kobako/commit/57f17b872e506f3935ebd4ca1f9d81c4bc34455f))
22
+ * **codec:** cap the host wrap-walk nesting depth (E-54) ([be67f37](https://github.com/elct9620/kobako/commit/be67f37108f24699354950f4e72293169da66917))
23
+ * **codec:** keep the guard id out of the wire-symmetry inventory ([e51998b](https://github.com/elct9620/kobako/commit/e51998bc7d54715aecb58f3b7817c337cd882c8b))
24
+ * **codec:** reject a non-representable #run argument Hash key cleanly ([dbabace](https://github.com/elct9620/kobako/commit/dbabaceecb6f32f37a26839b8162c0e94fe881bb))
25
+ * **codec:** reject non-wire values via a factory guard, not to_msgpack ([8e5c6b8](https://github.com/elct9620/kobako/commit/8e5c6b8cb67faa2eb7b82d6e63e3f8464b1126a0))
26
+ * **codec:** tighten map-decode pre-allocation to the true pair bound ([7094f91](https://github.com/elct9620/kobako/commit/7094f91898dae4c91c4e6863b09d25a6f34d096e))
27
+ * **examples:** contain overlay writes and prove the vfs traversal guard ([2fe7965](https://github.com/elct9620/kobako/commit/2fe7965b8f588b160a907771a14e51f80cd8d3a7))
28
+ * **examples:** contain vfs reads against symlink escape ([43317c8](https://github.com/elct9620/kobako/commit/43317c82c4a4171c08fdbf0c9fe7014cc249b926))
29
+ * **guest:** reject non-representable dispatch args instead of to_s (E-55) ([6b9ab56](https://github.com/elct9620/kobako/commit/6b9ab562753407cf6168a5baad34140afd75a86a))
30
+ * **tasks:** land the gate move for parity and wire:symmetry ([a0dddbc](https://github.com/elct9620/kobako/commit/a0dddbca9638418dbf3cd08e29806e05c5198cd3))
31
+ * **tasks:** resync the pub-surface ledger to the renamed install cluster ([c6a6c33](https://github.com/elct9620/kobako/commit/c6a6c33f5802f4536a4ebcc2df36acc130059d89))
32
+
33
+ ## [0.16.0](https://github.com/elct9620/kobako/compare/v0.15.0...v0.16.0) (2026-07-12)
34
+
35
+
36
+ ### Features
37
+
38
+ * **sandbox:** add the Extension install mechanism ([ad8e4da](https://github.com/elct9620/kobako/commit/ad8e4da703e4ceee85959663d77bb1e26c26791a))
39
+ * **sdk:** add the Extension install mechanism to the Rust host SDK ([4043f76](https://github.com/elct9620/kobako/commit/4043f764b7038619a30c16542ed38d566e4a72a9))
40
+ * **tasks:** break each module down by language in stats:<slug> ([b496e3c](https://github.com/elct9620/kobako/commit/b496e3cb23150f8f073cc1b972978cfbebfbb1e6))
41
+ * **tasks:** report code sizes per publishable module in stats:all ([f554930](https://github.com/elct9620/kobako/commit/f554930e669a82bed0c7305c5cbbab96c3ae79da))
42
+ * **tasks:** split impl vs inline test per module in stats:all ([cefa38d](https://github.com/elct9620/kobako/commit/cefa38d96ff79509b947269ea5c361ead0610bfa))
43
+
44
+
45
+ ### Bug Fixes
46
+
47
+ * **tasks:** count Rust inline tests in the stats code-to-test ratio ([134c970](https://github.com/elct9620/kobako/commit/134c9704a5a9bb6fb01c1aa7968f907d1436e5f2))
48
+
3
49
  ## [0.15.0](https://github.com/elct9620/kobako/compare/v0.14.0...v0.15.0) (2026-07-11)
4
50
 
5
51
 
data/README.md CHANGED
@@ -69,7 +69,7 @@ The host embeds the sandbox and owns the SPEC wire codec. Choose by your host la
69
69
  | Frontend | Package | Add it | Best for |
70
70
  |----------|---------|--------|----------|
71
71
  | Ruby gem | `kobako` (RubyGems) | `gem install kobako` | A Ruby host — Services, Handles, snippets, and pooling out of the box |
72
- | Rust SDK | `kobako` (crates.io) | `kobako = "0.8"` | A Rust host — the same behavior contract behind an idiomatic Rust API |
72
+ | Rust SDK | `kobako` (crates.io) | `kobako = "0.9"` | A Rust host — the same behavior contract behind an idiomatic Rust API |
73
73
  | Low-level crates | `kobako-wasmtime` + `kobako-runtime` + `kobako-codec` | Cargo deps | A custom host, or driving the wire directly in another language |
74
74
 
75
75
  The Rust crates are documented on [crates.io](https://crates.io/crates/kobako); the Ruby gem is this README. Two runnable Rust hosts show the choice: [`plugin-rs`](examples/plugin-rs) builds on the SDK, and [`wire-rs`](examples/wire-rs) assembles a host by hand on the low-level crates.
@@ -382,6 +382,34 @@ sandbox.run(:Greeter, name: "world") # => "hello, world"
382
382
 
383
383
  Use the source form for snippets authored in your repo; use the bytecode form when snippets ship as build artifacts from a separate `mrbc` pipeline. Both replay through the same per-invocation path, so no snippet content failure surfaces at `#preload` — force the first replay with a no-op invocation (e.g. `sandbox.eval("nil")`) when you want validation before real traffic.
384
384
 
385
+ ### Extensions
386
+
387
+ 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).
388
+
389
+ ```ruby
390
+ FILE = <<~'MRUBY'
391
+ class File < Kobako::Member
392
+ def self.basename(path) = path.split("/").last || "" # pure: runs in-guest
393
+ # read / write are undefined here, so they dispatch to the backend
394
+ end
395
+ MRUBY
396
+
397
+ sandbox.install(
398
+ Kobako::Extension.new(
399
+ name: :File,
400
+ source: FILE,
401
+ backend: Kobako::Extension::Backend.new(
402
+ path: "File",
403
+ provider: -> { OverlayFileSystem.new(root) } # callable → fresh per invocation
404
+ )
405
+ )
406
+ )
407
+
408
+ sandbox.eval('File.read("sample.txt")') # dispatches to the backend's #read
409
+ ```
410
+
411
+ The `backend.provider` sets the bound object's lifetime: a fixed object is shared across every invocation, while a callable is invoked once per invocation to yield a fresh object — the form a writable backend needs, so its state cannot leak across calls. kobako ships no concrete Extension; the idiom and backend are yours. The [overlay VFS example](examples/vfs/) is a worked `File` that reads through to disk while protecting it from guest writes.
412
+
385
413
  ## Security
386
414
 
387
415
  kobako isolates the guest, but **what it may reach is whatever you `bind`** — and `bind`
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.10.0...kobako-runtime-v0.10.1) (2026-07-17)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **kobako-runtime:** Synchronize kobako crates versions
9
+
10
+ ## [0.10.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.9.0...kobako-runtime-v0.10.0) (2026-07-12)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **kobako-runtime:** Synchronize kobako crates versions
16
+
3
17
  ## [0.9.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.8.0...kobako-runtime-v0.9.0) (2026-07-11)
4
18
 
5
19
 
@@ -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.9.0" # x-release-please-version
29
+ kobako-runtime = "0.10.1" # x-release-please-version
30
30
  ```
31
31
 
32
32
  ## License
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.10.0...kobako-wasmtime-v0.10.1) (2026-07-17)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **kobako-wasmtime:** Synchronize kobako crates versions
9
+
10
+ ## [0.10.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.9.0...kobako-wasmtime-v0.10.0) (2026-07-12)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **kobako-wasmtime:** Synchronize kobako crates versions
16
+
3
17
  ## [0.9.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.8.0...kobako-wasmtime-v0.9.0) (2026-07-11)
4
18
 
5
19
 
@@ -24,7 +24,7 @@ SDK consumes the same surface.
24
24
 
25
25
  ```toml
26
26
  [dependencies]
27
- kobako-wasmtime = "0.9.0" # x-release-please-version
27
+ kobako-wasmtime = "0.10.1" # x-release-please-version
28
28
  ```
29
29
 
30
30
  ## License
data/data/kobako.wasm CHANGED
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ module Catalog
5
+ # Kobako::Catalog::Extensions — per-Sandbox registry of installed
6
+ # Extensions. Composes each Extension onto the sibling registries at
7
+ # install time (its +source+ into +Catalog::Snippets+, its +backend+
8
+ # path into +Catalog::Services+), asserts declared dependencies are
9
+ # present when the Sandbox seals, and resolves each callable-backed
10
+ # path to a fresh object at the start of every invocation.
11
+ #
12
+ # Sealing and the reject-after-install guard are governed by the owning
13
+ # Sandbox through +Catalog::Services#sealed?+, the shared seal signal;
14
+ # this registry only records entries and enforces the Extension-shape
15
+ # and dependency rules.
16
+ class Extensions
17
+ def initialize
18
+ @entries = [] # : Array[untyped]
19
+ @asserted = false
20
+ end
21
+
22
+ # Install +extension+: validate its shape, register its +source+ as a
23
+ # preloaded snippet, and — when it carries a +backend+ — reserve the
24
+ # backend path in +services+. A callable provider reserves the path
25
+ # with a placeholder the per-invocation refresh replaces; a fixed
26
+ # provider binds its object directly. Raises +ArgumentError+ for the two
27
+ # shapes +#validate!+ owns — +source+ not a String, or a present
28
+ # +backend+ that omits +path+ / +provider+. The Extension readers are
29
+ # duck-typed, so an object missing them surfaces the underlying
30
+ # +NoMethodError+, and a malformed +name+ or +backend.path+ surfaces
31
+ # through the +snippets+ / +services+ registration it routes to.
32
+ def install(extension, snippets:, services:)
33
+ validate!(extension)
34
+ snippets.register(code: extension.source, name: extension.name)
35
+ backend = extension.backend
36
+ services.bind(backend.path, initial_object(backend.provider)) if backend
37
+ @entries << extension
38
+ self
39
+ end
40
+
41
+ # Assert every installed Extension's +depends_on+ names a fellow
42
+ # installed Extension. Runs once, when the Sandbox first seals; an
43
+ # unmet dependency raises +ArgumentError+ naming the gap, before the
44
+ # guest runs. Idempotent across later invocations.
45
+ def seal!
46
+ return self if @asserted
47
+
48
+ @asserted = true
49
+ assert_dependencies!
50
+ self
51
+ end
52
+
53
+ # Resolve each callable-backed path to this invocation's object and
54
+ # refresh it behind its already-sealed path in +services+. Distinct
55
+ # providers yield distinct objects; one provider shared by several
56
+ # Extensions is invoked once and its result shared, so provider
57
+ # identity is resource identity. Fixed providers are left untouched —
58
+ # they stay the object bound at install.
59
+ def refresh_backends!(services)
60
+ resolved = {} # : Hash[untyped, untyped]
61
+ resolved.compare_by_identity
62
+ @entries.each { |extension| refresh_backend(extension, services, resolved) }
63
+ self
64
+ end
65
+
66
+ private
67
+
68
+ # Resolve one Extension's callable-backed path against the shared
69
+ # per-invocation +resolved+ cache (keyed by provider identity) and
70
+ # refresh it in +services+. A fixed provider is skipped — its object
71
+ # stays as bound at install.
72
+ def refresh_backend(extension, services, resolved)
73
+ backend = extension.backend
74
+ return unless backend
75
+
76
+ provider = backend.provider
77
+ return unless callable?(provider)
78
+
79
+ object = resolved.fetch(provider) { resolved[provider] = provider.call }
80
+ services.refresh(backend.path, object)
81
+ end
82
+
83
+ # Enforce the Extension-shape checks +#preload+ / +#bind+ do not: a
84
+ # mandatory String +source+ (the install/bind boundary) and a
85
+ # +backend+ that, when present, exposes +path+ and +provider+.
86
+ def validate!(extension)
87
+ source = extension.source
88
+ raise ArgumentError, "Extension #source must be a String, got #{source.class}" unless source.is_a?(String)
89
+
90
+ backend = extension.backend
91
+ return if backend.nil?
92
+ return if backend.respond_to?(:path) && backend.respond_to?(:provider)
93
+
94
+ raise ArgumentError, "Extension #backend must expose #path and #provider"
95
+ end
96
+
97
+ def assert_dependencies!
98
+ names = @entries.map { |extension| symbolize(extension.name) }
99
+ @entries.each do |extension|
100
+ (extension.depends_on || []).each do |dependency|
101
+ next if names.include?(symbolize(dependency))
102
+
103
+ raise ArgumentError,
104
+ "Extension #{extension.name.inspect} depends on #{dependency.inspect}, which is not installed"
105
+ end
106
+ end
107
+ end
108
+
109
+ # Match names and dependencies by Symbol so the Symbol-or-String forms
110
+ # of a constant token are interchangeable; a value that is neither
111
+ # falls through unchanged to the not-installed path.
112
+ def symbolize(name) = name.is_a?(String) ? name.to_sym : name
113
+
114
+ def callable?(provider) = provider.respond_to?(:call)
115
+
116
+ # A callable provider reserves its path with a placeholder that the
117
+ # per-invocation refresh replaces before any dispatch; a fixed
118
+ # provider is the bound object itself.
119
+ def initial_object(provider) = callable?(provider) ? nil : provider
120
+ end
121
+ end
122
+ end
@@ -65,6 +65,18 @@ module Kobako
65
65
  @bindings[target_str]
66
66
  end
67
67
 
68
+ # Replace the object bound at an already-registered +path+ without the
69
+ # collision or seal checks +#bind+ applies. +Catalog::Extensions+ calls
70
+ # this during +begin_invocation!+ to swap a callable backend provider's
71
+ # per-invocation result behind a path whose Frame 1 membership was
72
+ # fixed at the seal. A path that was never registered is left untouched,
73
+ # so the sealed key set can never grow here.
74
+ def refresh(path, object)
75
+ key = path.to_s
76
+ @bindings[key] = object if @bindings.key?(key)
77
+ self
78
+ end
79
+
68
80
  # Encode the preamble as msgpack bytes for stdin Frame 1 delivery —
69
81
  # a flat array of the bound constant paths, in bind order:
70
82
  # +["MyService::KV", "File"]+. Routes through Kobako::Codec::Encoder
@@ -3,13 +3,15 @@
3
3
  require_relative "catalog/handles"
4
4
  require_relative "catalog/services"
5
5
  require_relative "catalog/snippets"
6
+ require_relative "catalog/extensions"
6
7
 
7
8
  module Kobako
8
9
  # Kobako::Catalog — Sandbox-level configuration and per-invocation
9
- # allocation tables. Houses the three host-side registries the Sandbox
10
- # owns: +Catalog::Services+ (path→Service binding registry),
11
- # +Catalog::Snippets+ (preloaded source / bytecode entries), and
12
- # +Catalog::Handles+ (per-invocation Handle ID allocator).
10
+ # allocation tables. Houses the host-side registries the Sandbox owns:
11
+ # +Catalog::Services+ (path→Service binding registry),
12
+ # +Catalog::Snippets+ (preloaded source / bytecode entries),
13
+ # +Catalog::Extensions+ (installed Extensions, composed onto Services /
14
+ # Snippets), and +Catalog::Handles+ (per-invocation Handle ID allocator).
13
15
  #
14
16
  # See {SPEC.md Refinement → Internal Concepts}[link:../../SPEC.md] for
15
17
  # how Catalog fits alongside Transport and Runtime.
@@ -22,15 +22,14 @@ module Kobako
22
22
  # themselves.
23
23
  module Encoder
24
24
  # Encode +value+ to wire bytes (binary-encoded String).
25
- # Wire violations surface as +UnsupportedType+: SPEC's 12-entry type
26
- # mapping is a closed set, and anything outside it is rejected by
27
- # the msgpack gem itself (arbitrary objects raise +NoMethodError+
28
- # from missing +to_msgpack+, integers outside i64..u64 raise
29
- # +RangeError+). The +NoMethodError+ catch is deliberately broad:
30
- # MessagePack signals "no wire representation" only through that error,
31
- # so there is no narrower discriminator a packer-internal
32
- # +NoMethodError+ is likewise reported as +UnsupportedType+ rather than
33
- # propagating.
25
+ # SPEC's 12-entry type mapping is a closed set: a value outside it is
26
+ # rejected as +UnsupportedType+ by the factory's +BasicObject+ guard
27
+ # (ExtTypes#register_unrepresentable), which raises before the msgpack
28
+ # gem can route the value through +to_msgpack+ so a permissive
29
+ # +method_missing+ object cannot answer that probe and mis-encode. The
30
+ # rescue below maps the two violations the guard does not reach onto the
31
+ # same error: an integer outside i64..u64 (+RangeError+) and any
32
+ # packer-internal +NoMethodError+.
34
33
  def self.encode(value)
35
34
  FACTORY.dump(value)
36
35
  rescue ::RangeError, ::NoMethodError => e
@@ -33,18 +33,28 @@ module Kobako
33
33
  EXT_ERRENV = 0x02
34
34
  private_constant :EXT_SYMBOL, :EXT_HANDLE, :EXT_ERRENV
35
35
 
36
+ # Inert ext id the unrepresentable-value guard registers under. It is
37
+ # never emitted (the guard's packer always raises) and never decoded
38
+ # (no unpacker is registered, so the id stays an UnknownExtTypeError on
39
+ # the wire), so it is not a wire ext type: deliberately not named
40
+ # +EXT_*+ like the three real ext codes, since it has no Rust-side mirror
41
+ # and must stay outside the wire-symmetry inventory.
42
+ UNREPRESENTABLE_GUARD_ID = 0x7F
43
+ private_constant :UNREPRESENTABLE_GUARD_ID
44
+
36
45
  module_function
37
46
 
38
- # Assemble a +MessagePack::Factory+ with the three kobako ext types
39
- # registered, frozen because registration is its only mutation and
40
- # happens exactly once. The stateful conversions resolve their
41
- # per-operation state at call time, so one registered factory serves
42
- # every thread.
47
+ # Assemble a +MessagePack::Factory+ with the three kobako ext types plus
48
+ # the unrepresentable-value guard registered, frozen because
49
+ # registration is its only mutation and happens exactly once. The
50
+ # stateful conversions resolve their per-operation state at call time,
51
+ # so one registered factory serves every thread.
43
52
  def build_factory
44
53
  factory = MessagePack::Factory.new
45
54
  register_symbol(factory)
46
55
  register_handle(factory)
47
56
  register_fault(factory)
57
+ register_unrepresentable(factory)
48
58
  factory.freeze
49
59
  end
50
60
 
@@ -157,6 +167,26 @@ module Kobako
157
167
  unpacker: ->(payload) { ExtTypes.unpack_fault(payload, State.current) }
158
168
  )
159
169
  end
170
+
171
+ # A catch-all packer that rejects any value with no wire representation
172
+ # as +UnsupportedType+. Registered on +BasicObject+ so it also covers
173
+ # BasicObject-based proxies; the narrower Symbol / Handle / Fault
174
+ # registrations still win by most-specific match, and native types never
175
+ # reach it. Packer-only: the guard never writes bytes, so its id is inert
176
+ # and the decode surface stays fail-closed.
177
+ #
178
+ # This makes the host's non-wire detection a positive allowlist — a value
179
+ # outside the type set is rejected here rather than routed to +to_msgpack+
180
+ # — matching the guest's classname allowlist and the Rust codec's closed
181
+ # +Value+ enum. Without it, a value with a permissive +method_missing+
182
+ # answers the codec's +to_msgpack+ probe and mis-encodes as +nil+ instead
183
+ # of crossing as a Capability Handle.
184
+ def register_unrepresentable(factory)
185
+ factory.register_type(
186
+ UNREPRESENTABLE_GUARD_ID, BasicObject,
187
+ packer: ->(_value) { raise UnsupportedType, "value has no wire representation" }
188
+ )
189
+ end
160
190
  end
161
191
 
162
192
  # The process-wide registered factory: ext registration is paid once at
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../handle"
4
+ require_relative "../errors"
4
5
 
5
6
  module Kobako
6
7
  module Codec
@@ -42,8 +43,16 @@ module Kobako
42
43
  # unsigned-64 union are rejected so the predicate agrees with the
43
44
  # msgpack gem's encode-time +RangeError+ behaviour the codec
44
45
  # already surfaces as UnsupportedType.
45
- def representable?(value)
46
- primitive_type?(value) || container_representable?(value)
46
+ #
47
+ # The optional +depth+ bounds the recursive descent at the wire's
48
+ # structural nesting cap so a self-referential container — reachable
49
+ # as a cyclic Hash key on the +#run+ argument path — reads as
50
+ # non-representable rather than looping, letting #deep_wrap_hash
51
+ # refuse it instead of overflowing this walk.
52
+ def representable?(value, depth = 0)
53
+ return false if depth > MAX_NESTING_DEPTH
54
+
55
+ primitive_type?(value) || container_representable?(value, depth)
47
56
  end
48
57
 
49
58
  # Deep-walk Array / Hash containers in +value+ and replace every
@@ -53,7 +62,11 @@ module Kobako
53
62
  # (Array, Hash) one structural level at a time; a non-representable
54
63
  # leaf is wrapped as-is without inspecting its internal structure.
55
64
  # An existing +Kobako::Handle+ is representable and passes through
56
- # unchanged — auto-wrap never re-wraps a Handle.
65
+ # unchanged — auto-wrap never re-wraps a Handle. Only Hash *values*
66
+ # are wrapped: a Hash *key* must already be wire-representable, since a
67
+ # key is not auto-wrapped the way a value is — a non-representable key
68
+ # raises +Kobako::SandboxError+ rather than crossing as an opaque token
69
+ # the guest→host restore walk could not round-trip.
57
70
  #
58
71
  # +value+ may be any Ruby value; +handler+ must respond to
59
72
  # +#alloc(object) -> Kobako::Handle+ (a host-side
@@ -64,15 +77,46 @@ module Kobako
64
77
  # Recursive calls spell the +HandleWalk.+ receiver so the dispatch
65
78
  # stays valid even when a block is captured and run under a
66
79
  # different +self+ (+module_function+ privatizes the instance copies).
67
- def deep_wrap(value, handler)
80
+ def deep_wrap(value, handler, depth = 0)
81
+ guard_nesting!(depth)
68
82
  case value
69
- when ::Array then value.map { |element| HandleWalk.deep_wrap(element, handler) }
70
- when ::Hash then value.transform_values { |val| HandleWalk.deep_wrap(val, handler) }
83
+ when ::Array then value.map { |element| HandleWalk.deep_wrap(element, handler, depth + 1) }
84
+ when ::Hash then HandleWalk.deep_wrap_hash(value, handler, depth)
71
85
  else
72
86
  representable?(value) ? value : handler.alloc(value)
73
87
  end
74
88
  end
75
89
 
90
+ # Refuse a wrap walk that has descended past the wire's structural
91
+ # nesting bound — a reference cycle necessarily trips it — so a +#run+
92
+ # argument fails as a clean +Kobako::SandboxError+ rather than an
93
+ # unbounded host recursion.
94
+ def guard_nesting!(depth)
95
+ return unless depth > MAX_NESTING_DEPTH
96
+
97
+ raise Kobako::SandboxError,
98
+ "a #run argument nests deeper than #{MAX_NESTING_DEPTH} levels and " \
99
+ "cannot cross the sandbox boundary (possible reference cycle)"
100
+ end
101
+
102
+ # Wrap a Hash on the host→guest argument path: each value's
103
+ # non-representable leaves become Handles, while each key must be
104
+ # wire-representable as-is. A stateful object may cross the boundary as
105
+ # a Hash value but not as a key — the one deliberate asymmetry with the
106
+ # guest→host restore walk, which resolves Handle keys the guest built.
107
+ def deep_wrap_hash(hash, handler, depth)
108
+ wrapped = {} # : Hash[untyped, untyped]
109
+ hash.each do |key, val|
110
+ unless HandleWalk.representable?(key, depth + 1)
111
+ raise Kobako::SandboxError,
112
+ "a Hash passed to #run has a key that cannot cross the sandbox boundary " \
113
+ "(#{key.class}); only wire-representable values may be Hash keys"
114
+ end
115
+ wrapped[key] = HandleWalk.deep_wrap(val, handler, depth + 1)
116
+ end
117
+ wrapped
118
+ end
119
+
76
120
  # Deep-walk Array / Hash containers in +value+ and replace every
77
121
  # +Kobako::Handle+ leaf with the host-side object +handler+ resolves
78
122
  # it to. The symmetric inverse of #deep_wrap: that walk allocates objects
@@ -118,10 +162,10 @@ module Kobako
118
162
  # elements and Hash key+value pairs through the public
119
163
  # #representable?. Not part of the public surface; reach for
120
164
  # #representable? instead.
121
- def container_representable?(value)
165
+ def container_representable?(value, depth)
122
166
  case value
123
- when ::Array then value.all? { |element| HandleWalk.representable?(element) }
124
- when ::Hash then value.all? { |key, val| HandleWalk.representable?(key) && HandleWalk.representable?(val) }
167
+ when ::Array then value.all? { |element| HandleWalk.representable?(element, depth + 1) }
168
+ when ::Hash then value.all? { |pair| pair.all? { |member| HandleWalk.representable?(member, depth + 1) } }
125
169
  else false
126
170
  end
127
171
  end
data/lib/kobako/codec.rb CHANGED
@@ -28,6 +28,12 @@ module Kobako
28
28
  # alongside +codec::EXT_SYMBOL+ / +codec::EXT_HANDLE+ /
29
29
  # +codec::EXT_ERRENV+ on that side.
30
30
  module Codec
31
+ # The maximum structural nesting depth the wire represents (the
32
+ # MessagePack ecosystem's bound), shared with the guest +kobako_codec+
33
+ # so both sides cap identically. The host→guest wrap walk refuses a
34
+ # +#run+ argument nesting past it — a reference cycle necessarily does.
35
+ MAX_NESTING_DEPTH = 128
36
+
31
37
  # Bracket a decode and return the block's result together with whether
32
38
  # the decoded tree carried an ext 0x01 Capability Handle — the signal a
33
39
  # dispatch path uses to skip an all-identity Handle-resolution walk.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ # Kobako::Extension — a guest idiom paired with an optional host backend,
5
+ # installed on a Sandbox via +Sandbox#install+. It composes the existing
6
+ # +#preload+ (the guest +source+) and +#bind+ (the +backend+) verbs into
7
+ # one setup unit, so guest code sees a native-style constant whose pure
8
+ # methods run in-guest and whose privileged methods dispatch to the
9
+ # backend.
10
+ #
11
+ # The four readers form the contract +#install+ duck-types on:
12
+ #
13
+ # * +name+ — a Symbol matching +/\A[A-Z]\w*\z/+, the preloaded snippet's
14
+ # canonical backtrace name and the +depends_on+ match key. Independent
15
+ # of any bound path.
16
+ # * +source+ — the mruby idiom as a String; always present, since an
17
+ # Extension always carries a guest idiom. A host object with no idiom
18
+ # is bound with +#bind+ directly.
19
+ # * +backend+ — an +Extension::Backend+ or +nil+ for a pure-guest
20
+ # Extension.
21
+ # * +depends_on+ — Symbol names of Extensions that must also be
22
+ # installed, checked for presence at the first invocation.
23
+ #
24
+ # +Kobako::Extension+ is the bundled value type; any object exposing the
25
+ # four readers is equally valid, so a Host App or gem may supply its own.
26
+ class Extension < Data.define(:name, :source, :backend, :depends_on)
27
+ # Kobako::Extension::Backend — the host attachment of an Extension,
28
+ # pairing +path+ (the constant path the backend binds at, single-segment
29
+ # +"File"+ or nested +"MyApp::Store"+, spelling the guest constant the
30
+ # idiom routes to) with +provider+ (the source of the bound object).
31
+ #
32
+ # A +provider+ that is not itself callable is the bound object, resolved
33
+ # once for the Sandbox's life; a callable provider is invoked once per
34
+ # invocation to yield that invocation's object, so a fresh object backs
35
+ # the path every invocation. Callability is the sole discriminator — a
36
+ # fixed backend that is itself callable is supplied through a
37
+ # non-callable wrapper. A callable provider that raises propagates its
38
+ # exception to the invocation caller and leaves the guest unrun; the
39
+ # next invocation resolves it afresh.
40
+ class Backend < Data.define(:path, :provider)
41
+ end
42
+
43
+ # +backend+ and +depends_on+ default to absent so the common
44
+ # pure-idiom and single-backend shapes stay terse.
45
+ def initialize(name:, source:, backend: nil, depends_on: [])
46
+ super
47
+ end
48
+ end
49
+ end
@@ -48,29 +48,21 @@ module Kobako
48
48
  # so use +#stdout_truncated?+ to observe overflow. Populated on every
49
49
  # outcome — including a rescued +TrapError+, after which it holds the
50
50
  # bytes written before the trap fired — mirroring +#usage+.
51
- def stdout
52
- @stdout_capture.bytes
53
- end
51
+ def stdout = @stdout_capture.bytes
54
52
 
55
53
  # Returns the bytes the guest wrote to stderr during the most recent
56
54
  # invocation as a UTF-8 String, clipped at +stderr_limit+. Empty before
57
55
  # any invocation. Mirror of +#stdout+.
58
- def stderr
59
- @stderr_capture.bytes
60
- end
56
+ def stderr = @stderr_capture.bytes
61
57
 
62
58
  # Returns +true+ iff stdout capture during the most recent invocation
63
59
  # exceeded +stdout_limit+. Resets to +false+ at the start of the next
64
60
  # invocation.
65
- def stdout_truncated?
66
- @stdout_capture.truncated?
67
- end
61
+ def stdout_truncated? = @stdout_capture.truncated?
68
62
 
69
63
  # Returns +true+ iff stderr capture during the most recent invocation
70
64
  # exceeded +stderr_limit+. Mirror of +#stdout_truncated?+.
71
- def stderr_truncated?
72
- @stderr_capture.truncated?
73
- end
65
+ def stderr_truncated? = @stderr_capture.truncated?
74
66
 
75
67
  # Returns the +Kobako::Usage+ value object for the most recent
76
68
  # invocation. Carries +wall_time+ (Float seconds the guest export call spent
@@ -102,6 +94,7 @@ module Kobako
102
94
  @handler = Catalog::Handles.new
103
95
  @services = Kobako::Catalog::Services.new(handler: @handler)
104
96
  @snippets = Catalog::Snippets.new
97
+ @extensions = Catalog::Extensions.new
105
98
  @runtime = build_runtime!
106
99
  install_dispatch_proc!
107
100
  reset_invocation_state!
@@ -121,6 +114,22 @@ module Kobako
121
114
  self
122
115
  end
123
116
 
117
+ # Install one or more Extensions — each a guest idiom (+source+) paired
118
+ # with an optional host +backend+, composed onto the Sandbox through
119
+ # +#preload+ and +#bind+. An Extension is any object exposing
120
+ # +name+ / +source+ / +backend+ / +depends_on+; +Kobako::Extension+ is
121
+ # the bundled value type. Returns +self+.
122
+ #
123
+ # Raises +ArgumentError+ for a malformed Extension, a call after the
124
+ # first invocation seals registration, or — at that first invocation —
125
+ # an unmet +depends_on+.
126
+ def install(*extensions)
127
+ raise ArgumentError, "cannot install after first Sandbox invocation" if @services.sealed?
128
+
129
+ extensions.each { |extension| @extensions.install(extension, snippets: @snippets, services: @services) }
130
+ self
131
+ end
132
+
124
133
  # Register a snippet on this Sandbox in one of two forms:
125
134
  #
126
135
  # * +preload(code: source, name: Name)+ — +source+ is mruby source
@@ -241,14 +250,18 @@ module Kobako
241
250
  end
242
251
  end
243
252
 
244
- # Per-invocation prologue. Seals the Service / snippet registries on
245
- # first call (idempotent) and zeros the per-invocation capability
253
+ # Per-invocation prologue. Seals the Service / snippet / Extension
254
+ # registries on first call (idempotent asserting Extension
255
+ # dependencies then), refreshes each callable Extension backend to this
256
+ # invocation's fresh object, and zeros the per-invocation capability
246
257
  # state — capture buffers, truncation predicates, and the
247
258
  # +Catalog::Handles+ counter — before the guest runs. The
248
259
  # +Catalog::Handles+ itself is held as +@handler+ and never exposed
249
260
  # beyond this class — it is not part of the Host App's surface.
250
261
  def begin_invocation!
251
262
  @services.seal!
263
+ @extensions.seal!
264
+ @extensions.refresh_backends!(@services)
252
265
  @handler.reset!
253
266
  reset_invocation_state!
254
267
  end
@@ -87,6 +87,11 @@ module Kobako
87
87
  args, kwargs = resolve_call_args(request, handler, carried_handle)
88
88
  yielder = Yielder.new(yield_to_guest, BREAK_THROW, handler) if request.block_given
89
89
  encode_ok(catch(BREAK_THROW) { invoke(target, request.method_name, args, kwargs, yielder) }, handler)
90
+ # StandardError is the boundary by intent: a Service method's
91
+ # application fault folds into a guest-rescuable Response.error, while a
92
+ # host-process failure (NoMemoryError, SignalException, a bare Exception)
93
+ # stays uncaught and traps the invocation rather than being masked as a
94
+ # rescuable fault.
90
95
  rescue StandardError => e
91
96
  encode_caught_error(e)
92
97
  ensure
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.15.0"
4
+ VERSION = "0.17.0"
5
5
  end
data/lib/kobako.rb CHANGED
@@ -10,6 +10,7 @@ rescue LoadError
10
10
  end
11
11
 
12
12
  require_relative "kobako/errors"
13
+ require_relative "kobako/extension"
13
14
  require_relative "kobako/transport"
14
15
  require_relative "kobako/catalog"
15
16
  require_relative "kobako/runtime"
@@ -0,0 +1,27 @@
1
+ module Kobako
2
+ module Catalog
3
+ class Extensions
4
+ def initialize: () -> void
5
+
6
+ def install: (untyped extension, snippets: Kobako::Catalog::Snippets, services: Kobako::Catalog::Services) -> self
7
+
8
+ def seal!: () -> self
9
+
10
+ def refresh_backends!: (Kobako::Catalog::Services services) -> self
11
+
12
+ private
13
+
14
+ def refresh_backend: (untyped extension, Kobako::Catalog::Services services, Hash[untyped, untyped] resolved) -> void
15
+
16
+ def validate!: (untyped extension) -> void
17
+
18
+ def assert_dependencies!: () -> void
19
+
20
+ def symbolize: (untyped name) -> untyped
21
+
22
+ def callable?: (untyped provider) -> bool
23
+
24
+ def initial_object: (untyped provider) -> untyped
25
+ end
26
+ end
27
+ end
@@ -9,6 +9,8 @@ module Kobako
9
9
 
10
10
  def lookup: (Symbol | String target) -> untyped
11
11
 
12
+ def refresh: (Symbol | String path, untyped object) -> self
13
+
12
14
  def encode: () -> String
13
15
 
14
16
  def seal!: () -> self
@@ -4,6 +4,7 @@ module Kobako
4
4
  EXT_SYMBOL: Integer
5
5
  EXT_HANDLE: Integer
6
6
  EXT_ERRENV: Integer
7
+ UNREPRESENTABLE_GUARD_ID: Integer
7
8
 
8
9
  def self?.build_factory: () -> MessagePack::Factory
9
10
 
@@ -24,6 +25,8 @@ module Kobako
24
25
  def self?.register_handle: (MessagePack::Factory factory) -> void
25
26
 
26
27
  def self?.register_fault: (MessagePack::Factory factory) -> void
28
+
29
+ def self?.register_unrepresentable: (MessagePack::Factory factory) -> void
27
30
  end
28
31
 
29
32
  FACTORY: MessagePack::Factory
@@ -3,15 +3,19 @@ module Kobako
3
3
  module HandleWalk
4
4
  MSGPACK_INT_RANGE: Range[Integer]
5
5
 
6
- def self?.representable?: (untyped value) -> bool
6
+ def self?.representable?: (untyped value, ?Integer depth) -> bool
7
7
 
8
- def self?.deep_wrap: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
8
+ def self?.deep_wrap: (untyped value, Kobako::Codec::_HandleTable handler, ?Integer depth) -> untyped
9
+
10
+ def self?.guard_nesting!: (Integer depth) -> void
11
+
12
+ def self?.deep_wrap_hash: (Hash[untyped, untyped] hash, Kobako::Codec::_HandleTable handler, Integer depth) -> Hash[untyped, untyped]
9
13
 
10
14
  def self?.deep_restore: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
11
15
 
12
16
  def self?.primitive_type?: (untyped value) -> bool
13
17
 
14
- def self?.container_representable?: (untyped value) -> bool
18
+ def self?.container_representable?: (untyped value, Integer depth) -> bool
15
19
  end
16
20
  end
17
21
  end
data/sig/kobako/codec.rbs CHANGED
@@ -1,5 +1,17 @@
1
1
  module Kobako
2
2
  module Codec
3
+ MAX_NESTING_DEPTH: Integer
4
+
5
+ # A value in the closed wire type set (docs/wire-codec.md § Type
6
+ # Mapping): the scalar leaves, a Capability Handle, and Array / Hash
7
+ # containers of the same. The by-value payloads that cross the wire —
8
+ # dispatch arguments and success values — inhabit this type; a Fault
9
+ # (ext 0x02) is an envelope-position value, not a wire_value.
10
+ type wire_value = nil | bool | Integer | Float | String | Symbol
11
+ | Kobako::Handle
12
+ | Array[wire_value]
13
+ | Hash[wire_value, wire_value]
14
+
3
15
  def self.track_handles: [T] { () -> T } -> [T, bool]
4
16
  def self.forbid_faults: [T] { () -> T } -> T
5
17
 
@@ -0,0 +1,20 @@
1
+ module Kobako
2
+ class Extension < Data
3
+ attr_reader name: Symbol
4
+ attr_reader source: String
5
+ attr_reader backend: Kobako::Extension::Backend?
6
+ attr_reader depends_on: Array[Symbol]
7
+
8
+ def self.new: (name: Symbol, source: String, ?backend: Kobako::Extension::Backend?, ?depends_on: Array[Symbol]) -> Kobako::Extension
9
+
10
+ def initialize: (name: Symbol, source: String, ?backend: Kobako::Extension::Backend?, ?depends_on: Array[Symbol]) -> void
11
+
12
+ class Backend < Data
13
+ attr_reader path: String
14
+ attr_reader provider: untyped
15
+
16
+ def self.new: (path: String, provider: untyped) -> Kobako::Extension::Backend
17
+ | (String path, untyped provider) -> Kobako::Extension::Backend
18
+ end
19
+ end
20
+ end
@@ -9,7 +9,7 @@ module Kobako
9
9
 
10
10
  def self?.split_tag: (String bytes) -> [Integer?, String]
11
11
 
12
- def self?.decode_value: (String body) -> untyped
12
+ def self?.decode_value: (String body) -> Kobako::Codec::wire_value
13
13
 
14
14
  def self?.decode_panic: (String body) -> void
15
15
 
@@ -31,6 +31,8 @@ module Kobako
31
31
 
32
32
  def bind: (Symbol | String path, untyped object) -> Kobako::Sandbox
33
33
 
34
+ def install: (*untyped extensions) -> Kobako::Sandbox
35
+
34
36
  def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
35
37
  | (binary: String) -> Kobako::Sandbox
36
38
 
@@ -3,19 +3,19 @@ module Kobako
3
3
  class Request < Data
4
4
  attr_reader target: String | Kobako::Handle
5
5
  attr_reader method_name: String
6
- attr_reader args: Array[untyped]
7
- attr_reader kwargs: Hash[Symbol, untyped]
6
+ attr_reader args: Array[Kobako::Codec::wire_value]
7
+ attr_reader kwargs: Hash[Symbol, Kobako::Codec::wire_value]
8
8
  attr_reader block_given: bool
9
9
 
10
- def self.new: (target: String | Kobako::Handle, method_name: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped], ?block_given: bool) -> Request
10
+ def self.new: (target: String | Kobako::Handle, method_name: String, ?args: Array[Kobako::Codec::wire_value], ?kwargs: Hash[Symbol, Kobako::Codec::wire_value], ?block_given: bool) -> Request
11
11
 
12
12
  def self.decode: (String bytes) -> Request
13
13
 
14
- def initialize: (target: String | Kobako::Handle, method_name: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped], ?block_given: bool) -> void
14
+ def initialize: (target: String | Kobako::Handle, method_name: String, ?args: Array[Kobako::Codec::wire_value], ?kwargs: Hash[Symbol, Kobako::Codec::wire_value], ?block_given: bool) -> void
15
15
 
16
16
  def encode: () -> String
17
17
 
18
- def with: (?target: String | Kobako::Handle, ?method_name: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped], ?block_given: bool) -> Request
18
+ def with: (?target: String | Kobako::Handle, ?method_name: String, ?args: Array[Kobako::Codec::wire_value], ?kwargs: Hash[Symbol, Kobako::Codec::wire_value], ?block_given: bool) -> Request
19
19
 
20
20
  def ==: (untyped other) -> bool
21
21
 
@@ -5,7 +5,7 @@ module Kobako
5
5
 
6
6
  class Response < Data
7
7
  attr_reader status: Integer
8
- attr_reader payload: untyped
8
+ attr_reader payload: Kobako::Codec::wire_value | Kobako::Fault
9
9
 
10
10
  def self.ok: (untyped value) -> Response
11
11
 
@@ -4,8 +4,8 @@ module Kobako
4
4
  NAME_PATTERN: Regexp
5
5
 
6
6
  attr_reader entrypoint: Symbol
7
- attr_reader args: Array[untyped]
8
- attr_reader kwargs: Hash[Symbol, untyped]
7
+ attr_reader args: Array[Kobako::Codec::wire_value]
8
+ attr_reader kwargs: Hash[Symbol, Kobako::Codec::wire_value]
9
9
 
10
10
  def self.new: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> Run
11
11
 
@@ -8,7 +8,7 @@ module Kobako
8
8
 
9
9
  class Yield
10
10
  attr_reader tag: Integer
11
- attr_reader value: untyped
11
+ attr_reader value: Kobako::Codec::wire_value | Kobako::Fault
12
12
 
13
13
  def self.decode: (String bytes) -> Yield
14
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kobako
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.17.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Aotokitsuruya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-11 00:00:00.000000000 Z
11
+ date: 2026-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -50,6 +50,7 @@ files:
50
50
  - lib/kobako/4.0/kobako.so
51
51
  - lib/kobako/capture.rb
52
52
  - lib/kobako/catalog.rb
53
+ - lib/kobako/catalog/extensions.rb
53
54
  - lib/kobako/catalog/handles.rb
54
55
  - lib/kobako/catalog/services.rb
55
56
  - lib/kobako/catalog/snippets.rb
@@ -62,6 +63,7 @@ files:
62
63
  - lib/kobako/codec/state.rb
63
64
  - lib/kobako/codec/utils.rb
64
65
  - lib/kobako/errors.rb
66
+ - lib/kobako/extension.rb
65
67
  - lib/kobako/fault.rb
66
68
  - lib/kobako/handle.rb
67
69
  - lib/kobako/outcome.rb
@@ -88,6 +90,7 @@ files:
88
90
  - sig/kobako.rbs
89
91
  - sig/kobako/capture.rbs
90
92
  - sig/kobako/catalog.rbs
93
+ - sig/kobako/catalog/extensions.rbs
91
94
  - sig/kobako/catalog/handles.rbs
92
95
  - sig/kobako/catalog/services.rbs
93
96
  - sig/kobako/catalog/snippets.rbs
@@ -100,6 +103,7 @@ files:
100
103
  - sig/kobako/codec/state.rbs
101
104
  - sig/kobako/codec/utils.rbs
102
105
  - sig/kobako/errors.rbs
106
+ - sig/kobako/extension.rbs
103
107
  - sig/kobako/fault.rbs
104
108
  - sig/kobako/handle.rbs
105
109
  - sig/kobako/outcome.rbs