kobako 0.14.0-aarch64-linux → 0.16.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +67 -0
  4. data/README.md +104 -29
  5. data/ROADMAP.md +2 -3
  6. data/SECURITY.md +1 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +14 -0
  8. data/crates/kobako-runtime/README.md +1 -1
  9. data/crates/kobako-wasmtime/CHANGELOG.md +14 -0
  10. data/crates/kobako-wasmtime/README.md +1 -1
  11. data/data/kobako.wasm +0 -0
  12. data/lib/kobako/3.3/kobako.so +0 -0
  13. data/lib/kobako/3.4/kobako.so +0 -0
  14. data/lib/kobako/4.0/kobako.so +0 -0
  15. data/lib/kobako/catalog/extensions.rb +114 -0
  16. data/lib/kobako/catalog/handles.rb +1 -1
  17. data/lib/kobako/catalog/services.rb +135 -0
  18. data/lib/kobako/catalog/snippets.rb +1 -1
  19. data/lib/kobako/catalog.rb +7 -5
  20. data/lib/kobako/codec/decoder.rb +3 -3
  21. data/lib/kobako/codec/encoder.rb +4 -4
  22. data/lib/kobako/codec/ext_types.rb +169 -0
  23. data/lib/kobako/codec/state.rb +98 -0
  24. data/lib/kobako/codec/utils.rb +2 -2
  25. data/lib/kobako/codec.rb +23 -6
  26. data/lib/kobako/extension.rb +47 -0
  27. data/lib/kobako/fault.rb +1 -1
  28. data/lib/kobako/handle.rb +1 -1
  29. data/lib/kobako/outcome.rb +13 -7
  30. data/lib/kobako/pool.rb +1 -1
  31. data/lib/kobako/sandbox.rb +47 -27
  32. data/lib/kobako/transport/dispatcher.rb +25 -18
  33. data/lib/kobako/transport/request.rb +14 -9
  34. data/lib/kobako/transport/response.rb +9 -2
  35. data/lib/kobako/transport/yield.rb +4 -1
  36. data/lib/kobako/transport/yielder.rb +14 -6
  37. data/lib/kobako/version.rb +1 -1
  38. data/lib/kobako.rb +1 -0
  39. data/sig/kobako/catalog/extensions.rbs +25 -0
  40. data/sig/kobako/catalog/services.rbs +27 -0
  41. data/sig/kobako/codec/ext_types.rbs +31 -0
  42. data/sig/kobako/codec/state.rbs +20 -0
  43. data/sig/kobako/codec.rbs +3 -0
  44. data/sig/kobako/extension.rbs +20 -0
  45. data/sig/kobako/sandbox.rbs +3 -1
  46. data/sig/kobako/transport/dispatcher.rbs +4 -4
  47. data/sig/kobako/transport/yielder.rbs +1 -1
  48. data/sig/kobako/transport.rbs +2 -2
  49. metadata +12 -8
  50. data/lib/kobako/catalog/namespaces.rb +0 -115
  51. data/lib/kobako/codec/factory.rb +0 -187
  52. data/lib/kobako/namespace.rb +0 -78
  53. data/sig/kobako/catalog/namespaces.rbs +0 -17
  54. data/sig/kobako/codec/factory.rbs +0 -34
  55. data/sig/kobako/namespace.rbs +0 -21
data/lib/kobako/pool.rb CHANGED
@@ -24,7 +24,7 @@ module Kobako
24
24
  # seconds (+nil+ waits indefinitely); every other keyword is
25
25
  # forwarded verbatim to +Kobako::Sandbox.new+. The optional block
26
26
  # runs exactly once per constructed Sandbox — it is the setup window
27
- # for +#define+ / +#preload+ before that Sandbox's first checkout.
27
+ # for +#bind+ / +#preload+ before that Sandbox's first checkout.
28
28
  # No Sandbox is constructed here. Raises +ArgumentError+ for an
29
29
  # invalid +slots+ / +checkout_timeout+.
30
30
  def initialize(slots:, checkout_timeout: DEFAULT_CHECKOUT_TIMEOUT_SECONDS, **sandbox_options, &setup)
@@ -17,7 +17,7 @@ module Kobako
17
17
  #
18
18
  # The Sandbox owns the +Kobako::Runtime+, the per-Sandbox
19
19
  # +Kobako::Catalog::Handles+, the per-instance
20
- # +Kobako::Catalog::Namespaces+ (which receives the +Catalog::Handles+ by
20
+ # +Kobako::Catalog::Services+ (which receives the +Catalog::Handles+ by
21
21
  # injection so guest→host dispatch and host→guest auto-wrap share one
22
22
  # allocator), and the dispatch +Proc+ / +yield_to_guest+ lambda installed
23
23
  # on the Runtime via +Runtime#on_dispatch=+. The underlying wasmtime Engine
@@ -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
@@ -100,21 +92,42 @@ module Kobako
100
92
  @wasm_path = wasm_path || Kobako::Runtime.default_path
101
93
  @options = SandboxOptions.new(**)
102
94
  @handler = Catalog::Handles.new
103
- @services = Kobako::Catalog::Namespaces.new(handler: @handler)
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!
108
101
  end
109
102
 
110
- # Declare or retrieve the Namespace named +name+ on this Sandbox. +name+
111
- # must be a Symbol or String in constant form. Returns the
112
- # +Kobako::Namespace+.
103
+ # Bind +object+ as the Service reachable at +path+ a Symbol or
104
+ # String of one or more +::+-separated constant-form segments
105
+ # (+"MyService::KV"+ or a top-level +"File"+). Returns +self+ for
106
+ # chaining.
107
+ #
108
+ # Raises +ArgumentError+ when a segment is malformed, when +path+
109
+ # collides with an existing binding (a name is a bound Service or a
110
+ # grouping prefix, never both), or when called after the first
111
+ # invocation has sealed Service registration.
112
+ def bind(path, object)
113
+ @services.bind(path, object)
114
+ self
115
+ end
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+.
113
122
  #
114
- # Raises +ArgumentError+ when called after the first invocation, or
115
- # when +name+ does not match the constant-name pattern.
116
- def define(name)
117
- @services.define(name)
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
118
131
  end
119
132
 
120
133
  # Register a snippet on this Sandbox in one of two forms:
@@ -175,15 +188,15 @@ module Kobako
175
188
  #
176
189
  # Source delivery uses the WASI stdin three-frame protocol
177
190
  # ({docs/wire-codec.md Invocation channels}[link:../../docs/wire-codec.md]):
178
- # Frame 1 carries the msgpack-encoded preamble (Namespace / Member
179
- # registry snapshot), Frame 2 carries the user source UTF-8 bytes, and
191
+ # Frame 1 carries the msgpack-encoded preamble (Service registry
192
+ # snapshot), Frame 2 carries the user source UTF-8 bytes, and
180
193
  # Frame 3 carries the snippet table registered via +#preload+.
181
194
  # Each frame is prefixed by a 4-byte big-endian u32 length; Frame 3 is
182
195
  # mandatory-presence — an empty snippet table sends an empty msgpack
183
196
  # array, never an absent frame.
184
197
  #
185
198
  # The first invocation seals the Service registry and snippet table;
186
- # subsequent +#define+ / +#preload+ calls raise +ArgumentError+.
199
+ # subsequent +#bind+ / +#preload+ calls raise +ArgumentError+.
187
200
  #
188
201
  # Raises +Kobako::TrapError+ on a Wasm trap or wire-violation fallback;
189
202
  # +Kobako::SandboxError+ when the guest ran to completion but failed
@@ -237,14 +250,18 @@ module Kobako
237
250
  end
238
251
  end
239
252
 
240
- # Per-invocation prologue. Seals the Service / snippet registries on
241
- # 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
242
257
  # state — capture buffers, truncation predicates, and the
243
258
  # +Catalog::Handles+ counter — before the guest runs. The
244
259
  # +Catalog::Handles+ itself is held as +@handler+ and never exposed
245
260
  # beyond this class — it is not part of the Host App's surface.
246
261
  def begin_invocation!
247
262
  @services.seal!
263
+ @extensions.seal!
264
+ @extensions.refresh_backends!(@services)
248
265
  @handler.reset!
249
266
  reset_invocation_state!
250
267
  end
@@ -318,7 +335,10 @@ module Kobako
318
335
  # token; restore it to the host object the guest referenced before
319
336
  # handing the value to the Host App. @handler still holds this
320
337
  # invocation's table — reset only happens at the next #begin_invocation!.
321
- Codec::HandleWalk.deep_restore(Outcome.decode(return_bytes), @handler)
338
+ # A Handle-free result resolves to itself, so the restoration walk is
339
+ # skipped when the decode carried none.
340
+ value, carried_handle = Codec.track_handles { Outcome.decode(return_bytes) }
341
+ carried_handle ? Codec::HandleWalk.deep_restore(value, @handler) : value
322
342
  rescue Kobako::TrapError => e
323
343
  raise trap_class_for(e), "Sandbox##{verb} failed: #{e.message}"
324
344
  ensure
@@ -13,7 +13,7 @@ module Kobako
13
13
  module Transport
14
14
  # Pure-function dispatcher for guest-initiated transport calls.
15
15
  # Decodes a msgpack-encoded Request envelope, resolves the target
16
- # object through the Catalog::Namespaces (path lookup) or
16
+ # object through the Catalog::Services (path lookup) or
17
17
  # Catalog::Handles (Handle lookup), invokes the method, and returns
18
18
  # a msgpack-encoded Response envelope.
19
19
  #
@@ -24,7 +24,7 @@ module Kobako
24
24
  #
25
25
  # Entry point:
26
26
  #
27
- # Kobako::Transport::Dispatcher.dispatch(request_bytes, namespaces, handler, yield_to_guest)
27
+ # Kobako::Transport::Dispatcher.dispatch(request_bytes, services, handler, yield_to_guest)
28
28
  # # => msgpack-encoded Response bytes (never raises)
29
29
  module Dispatcher
30
30
  # Throw tag for the Yielder's break unwind back to the
@@ -67,7 +67,7 @@ module Kobako
67
67
 
68
68
  # Dispatch a single transport request and return the encoded
69
69
  # Response bytes. Invoked from the +Runtime#on_dispatch+ Proc that
70
- # +Kobako::Sandbox#initialize+ installs on the ext side; +namespaces+,
70
+ # +Kobako::Sandbox#initialize+ installs on the ext side; +services+,
71
71
  # +handler+, and +yield_to_guest+ are captured in that Proc's
72
72
  # closure so the Dispatcher stays stateless and the registry doesn't
73
73
  # need to publish accessors for the Sandbox-owned +Catalog::Handles+
@@ -77,24 +77,31 @@ module Kobako
77
77
  # returns a binary String — every failure path is reified as a
78
78
  # Response.error envelope so the guest sees a transport error rather
79
79
  # than a wasm trap.
80
- def dispatch(request_bytes, namespaces, handler, yield_to_guest)
81
- request = Kobako::Transport::Request.decode(request_bytes)
82
- target = resolve_target(request.target, namespaces, handler)
83
- args, kwargs = resolve_call_args(request, handler)
80
+ #
81
+ # The decode runs inside +Codec.track_handles+ so #resolve_call_args
82
+ # can skip the argument walk when no Capability Handle crossed the
83
+ # wire.
84
+ def dispatch(request_bytes, services, handler, yield_to_guest)
85
+ request, carried_handle = Kobako::Codec.track_handles { Kobako::Transport::Request.decode(request_bytes) }
86
+ target = resolve_target(request.target, services, handler)
87
+ args, kwargs = resolve_call_args(request, handler, carried_handle)
84
88
  yielder = Yielder.new(yield_to_guest, BREAK_THROW, handler) if request.block_given
85
- value = catch(BREAK_THROW) { invoke(target, request.method_name, args, kwargs, yielder) }
86
- encode_ok(value, handler)
89
+ encode_ok(catch(BREAK_THROW) { invoke(target, request.method_name, args, kwargs, yielder) }, handler)
87
90
  rescue StandardError => e
88
91
  encode_caught_error(e)
89
92
  ensure
90
93
  yielder&.invalidate!
91
94
  end
92
95
 
93
- # Resolve positional and keyword arguments off +request+ in one
94
- # step. Both pass through #resolve_arg so Capability Handles
95
- # round-trip back to the host-side Ruby object before the call
96
- # reaches +public_send+.
97
- def resolve_call_args(request, handler)
96
+ # Resolve positional and keyword arguments off +request+ in one step.
97
+ # +carried_handle+ reports whether the decode carried any Capability
98
+ # Handle; when it did not, every argument resolves to itself, so the
99
+ # decoded values pass straight through and the walk is skipped entirely.
100
+ # Otherwise both go through #resolve_arg so Handles round-trip back to
101
+ # the host-side Ruby object before the call reaches +public_send+.
102
+ def resolve_call_args(request, handler, carried_handle)
103
+ return [request.args, request.kwargs] unless carried_handle
104
+
98
105
  [request.args.map { |v| resolve_arg(v, handler) },
99
106
  request.kwargs.transform_values { |v| resolve_arg(v, handler) }]
100
107
  end
@@ -187,17 +194,17 @@ module Kobako
187
194
  # Target type is already validated by +Transport::Request.decode+
188
195
  # before this method is reached, so no else-branch is needed here —
189
196
  # the wire layer is the system boundary that enforces the invariant.
190
- def resolve_target(target, namespaces, handler)
197
+ def resolve_target(target, services, handler)
191
198
  case target
192
199
  when String
193
- resolve_path(target, namespaces)
200
+ resolve_path(target, services)
194
201
  when Kobako::Handle
195
202
  require_live_object!(target.id, handler)
196
203
  end
197
204
  end
198
205
 
199
- def resolve_path(path, namespaces)
200
- namespaces.lookup(path)
206
+ def resolve_path(path, services)
207
+ services.lookup(path)
201
208
  rescue KeyError => e
202
209
  raise UndefinedTargetError, e.message
203
210
  end
@@ -12,7 +12,7 @@ module Kobako
12
12
  #
13
13
  # 5-element msgpack array:
14
14
  # +[target, method_name, args, kwargs, block_given]+. +target+ is
15
- # either a +String+ (+"<Namespace>::<Member>"+, e.g. +"MyService::KV"+)
15
+ # either a +String+ (+"MyService::KV"+, e.g. +"MyService::KV"+)
16
16
  # or a Handle. SPEC pins +kwargs+ map keys to ext 0x00 Symbol;
17
17
  # enforced at construction so the Value Object is the single source of
18
18
  # truth. +block_given+ is a Boolean signalling whether the guest call
@@ -44,16 +44,21 @@ module Kobako
44
44
  end
45
45
 
46
46
  # Decode +bytes+ into a Request. Raises +Codec::InvalidType+ when the
47
- # envelope is not the expected 5-element msgpack array, or when the
48
- # Value Object's construction invariants reject the decoded fields.
47
+ # envelope is not the expected 5-element msgpack array, when any
48
+ # position carries an ext 0x02 Fault envelope (a Request is a payload
49
+ # position; the Response fault field is the envelope's only home), or
50
+ # when the Value Object's construction invariants reject the decoded
51
+ # fields.
49
52
  def self.decode(bytes)
50
- Codec::Decoder.decode(bytes) do |arr|
51
- unless arr.is_a?(Array) && arr.length == 5
52
- raise Codec::InvalidType, "Request envelope is malformed (expected a 5-element array)"
53
- end
53
+ Codec.forbid_faults do
54
+ Codec::Decoder.decode(bytes) do |arr|
55
+ unless arr.is_a?(Array) && arr.length == 5
56
+ raise Codec::InvalidType, "Request envelope is malformed (expected a 5-element array)"
57
+ end
54
58
 
55
- target, method_name, args, kwargs, block_given = arr
56
- new(target: target, method_name: method_name, args: args, kwargs: kwargs, block_given: block_given)
59
+ target, method_name, args, kwargs, block_given = arr
60
+ new(target: target, method_name: method_name, args: args, kwargs: kwargs, block_given: block_given)
61
+ end
57
62
  end
58
63
  end
59
64
 
@@ -53,9 +53,16 @@ module Kobako
53
53
  def error? = status == STATUS_ERROR
54
54
 
55
55
  # Encode this Response to msgpack bytes as the 2-element
56
- # +[status, payload]+ array.
56
+ # +[status, payload]+ array. The ok variant's value is a payload
57
+ # position, so a +Kobako::Fault+ inside it has no wire
58
+ # representation and surfaces as +UnsupportedType+ — the
59
+ # Dispatcher's auto-wrap rescue turns it into a Capability Handle.
60
+ # The error variant carries the Fault envelope in its one legal
61
+ # position and encodes it plainly.
57
62
  def encode
58
- Codec::Encoder.encode([status, payload])
63
+ return Codec::Encoder.encode([status, payload]) if error?
64
+
65
+ Codec.forbid_faults { Codec::Encoder.encode([status, payload]) }
59
66
  end
60
67
 
61
68
  # Decode +bytes+ into a Response. Raises +Codec::InvalidType+ when the
@@ -70,7 +70,10 @@ module Kobako
70
70
  body = bytes.byteslice(1, bytes.bytesize - 1) # : String
71
71
 
72
72
  reject_dead_tag!(tag)
73
- new(tag: tag, value: Codec::Decoder.decode(body))
73
+ # A YieldResponse is a payload position: an ext 0x02 Fault in its
74
+ # value is a wire violation (the Response fault field is the
75
+ # envelope's only home).
76
+ new(tag: tag, value: Codec.forbid_faults { Codec::Decoder.decode(body) })
74
77
  end
75
78
 
76
79
  def self.reject_dead_tag!(tag)
@@ -52,8 +52,14 @@ module Kobako
52
52
  def yield(*args)
53
53
  raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active
54
54
 
55
- response = Kobako::Transport::Yield.decode(@yield_to_guest.call(Kobako::Codec::Encoder.encode(args)))
56
- return restore(response.value) if response.ok?
55
+ # Yield arguments are a payload position: a +Kobako::Fault+ among
56
+ # them has no wire representation, so the encode refuses it at
57
+ # this call site. The tracking bracket below opens only around the
58
+ # decode: the guest re-entry may run nested dispatches whose own
59
+ # brackets would otherwise pollute the signal.
60
+ bytes = @yield_to_guest.call(Kobako::Codec.forbid_faults { Kobako::Codec::Encoder.encode(args) })
61
+ response, carried_handle = Kobako::Codec.track_handles { Kobako::Transport::Yield.decode(bytes) }
62
+ return restore(response.value, carried_handle) if response.ok?
57
63
 
58
64
  throw @break_tag, response.value if response.break?
59
65
 
@@ -78,10 +84,12 @@ module Kobako
78
84
  # Restore any Capability Handle in a block's ok value to its host
79
85
  # object via the injected +Catalog::Handles+. Only the
80
86
  # ok path calls this — host code consumes the ok value, whereas a
81
- # break value returns to the guest and stays a Handle. Walks nested
82
- # Array / Hash one level at a time; a plain value passes through
83
- # unchanged.
84
- def restore(value)
87
+ # break value returns to the guest and stays a Handle. A response
88
+ # whose decode carried no Handle resolves to itself, so the walk is
89
+ # skipped entirely.
90
+ def restore(value, carried_handle)
91
+ return value unless carried_handle
92
+
85
93
  Kobako::Codec::HandleWalk.deep_restore(value, @handler)
86
94
  end
87
95
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.14.0"
4
+ VERSION = "0.16.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,25 @@
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 callable?: (untyped provider) -> bool
21
+
22
+ def initial_object: (untyped provider) -> untyped
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ module Kobako
2
+ module Catalog
3
+ class Services
4
+ NAME_PATTERN: Regexp
5
+
6
+ def initialize: (?handler: Kobako::Catalog::Handles) -> void
7
+
8
+ def bind: (Symbol | String path, untyped object) -> self
9
+
10
+ def lookup: (Symbol | String target) -> untyped
11
+
12
+ def refresh: (Symbol | String path, untyped object) -> self
13
+
14
+ def encode: () -> String
15
+
16
+ def seal!: () -> self
17
+
18
+ def sealed?: () -> bool
19
+
20
+ private
21
+
22
+ def validate_path!: (Symbol | String path) -> String
23
+
24
+ def collision?: (String path) -> bool
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Kobako
2
+ module Codec
3
+ module ExtTypes
4
+ EXT_SYMBOL: Integer
5
+ EXT_HANDLE: Integer
6
+ EXT_ERRENV: Integer
7
+
8
+ def self?.build_factory: () -> MessagePack::Factory
9
+
10
+ def self?.pack_symbol: (Symbol symbol) -> String
11
+
12
+ def self?.unpack_symbol: (String payload) -> Symbol
13
+
14
+ def self?.pack_handle: (Kobako::Handle handle) -> String
15
+
16
+ def self?.unpack_handle: (String payload, State state) -> Kobako::Handle
17
+
18
+ def self?.pack_fault: (Kobako::Fault fault, State state) -> String
19
+
20
+ def self?.unpack_fault: (String payload, State state) -> Kobako::Fault
21
+
22
+ def self?.register_symbol: (MessagePack::Factory factory) -> void
23
+
24
+ def self?.register_handle: (MessagePack::Factory factory) -> void
25
+
26
+ def self?.register_fault: (MessagePack::Factory factory) -> void
27
+ end
28
+
29
+ FACTORY: MessagePack::Factory
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ module Kobako
2
+ module Codec
3
+ class State
4
+ MAX_EXT_DEPTH: Integer
5
+ STATE_KEY: Symbol
6
+
7
+ def self.current: () -> State
8
+
9
+ def track_handles: [T] { () -> T } -> [T, bool]
10
+ def record_handle!: () -> void
11
+ def forbid_faults: [T] { () -> T } -> T
12
+ def faults_forbidden?: () -> bool
13
+ def within_ext_frame: [T] (singleton(Kobako::Codec::Error) over_limit) { () -> T } -> T
14
+
15
+ private
16
+
17
+ def initialize: () -> void
18
+ end
19
+ end
20
+ end
data/sig/kobako/codec.rbs CHANGED
@@ -1,5 +1,8 @@
1
1
  module Kobako
2
2
  module Codec
3
+ def self.track_handles: [T] { () -> T } -> [T, bool]
4
+ def self.forbid_faults: [T] { () -> T } -> T
5
+
3
6
  # The Handle allocator/resolver a wire walk works against: alloc
4
7
  # registers a non-wire-representable object and returns its Handle,
5
8
  # fetch resolves a wire Handle id back to the live object. The
@@ -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
@@ -29,7 +29,9 @@ module Kobako
29
29
 
30
30
  attr_reader usage: Kobako::Usage
31
31
 
32
- def define: (Symbol | String name) -> Kobako::Namespace
32
+ def bind: (Symbol | String path, untyped object) -> Kobako::Sandbox
33
+
34
+ def install: (*untyped extensions) -> Kobako::Sandbox
33
35
 
34
36
  def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
35
37
  | (binary: String) -> Kobako::Sandbox
@@ -12,9 +12,9 @@ module Kobako
12
12
 
13
13
  CALLABLE_ALLOW: Array[Symbol]
14
14
 
15
- def self?.dispatch: (String request_bytes, Kobako::Transport::_NamespaceRegistry namespaces, Kobako::Codec::_HandleTable handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
15
+ def self?.dispatch: (String request_bytes, Kobako::Transport::_ServiceRegistry services, Kobako::Codec::_HandleTable handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
16
16
 
17
- def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Codec::_HandleTable handler) -> [Array[untyped], Hash[Symbol, untyped]]
17
+ def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Codec::_HandleTable handler, bool carried_handle) -> [Array[untyped], Hash[Symbol, untyped]]
18
18
 
19
19
  def self?.encode_caught_error: (StandardError error) -> String
20
20
 
@@ -26,9 +26,9 @@ module Kobako
26
26
 
27
27
  def self?.resolve_arg: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
28
28
 
29
- def self?.resolve_target: (String | Kobako::Handle target, Kobako::Transport::_NamespaceRegistry namespaces, Kobako::Codec::_HandleTable handler) -> untyped
29
+ def self?.resolve_target: (String | Kobako::Handle target, Kobako::Transport::_ServiceRegistry services, Kobako::Codec::_HandleTable handler) -> untyped
30
30
 
31
- def self?.resolve_path: (String path, Kobako::Transport::_NamespaceRegistry namespaces) -> untyped
31
+ def self?.resolve_path: (String path, Kobako::Transport::_ServiceRegistry services) -> untyped
32
32
 
33
33
  def self?.require_live_object!: (Integer id, Kobako::Codec::_HandleTable handler) -> untyped
34
34
 
@@ -16,7 +16,7 @@ module Kobako
16
16
 
17
17
  private
18
18
 
19
- def restore: (untyped value) -> untyped
19
+ def restore: (untyped value, bool carried_handle) -> untyped
20
20
 
21
21
  def yield_failure: (untyped payload, default: String) -> RuntimeError
22
22
  end
@@ -10,10 +10,10 @@ module Kobako
10
10
  end
11
11
 
12
12
  # The Service registry a dispatch resolves constant-path targets
13
- # against. The Sandbox's Kobako::Catalog::Namespaces is the
13
+ # against. The Sandbox's Kobako::Catalog::Services is the
14
14
  # production conformer; modelled structurally so the Transport
15
15
  # layer needs no upward dependency on Catalog.
16
- interface _NamespaceRegistry
16
+ interface _ServiceRegistry
17
17
  def lookup: (String target) -> untyped
18
18
  end
19
19
  end
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.14.0
4
+ version: 0.16.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-08 00:00:00.000000000 Z
11
+ date: 2026-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -50,20 +50,22 @@ 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
- - lib/kobako/catalog/namespaces.rb
55
+ - lib/kobako/catalog/services.rb
55
56
  - lib/kobako/catalog/snippets.rb
56
57
  - lib/kobako/codec.rb
57
58
  - lib/kobako/codec/decoder.rb
58
59
  - lib/kobako/codec/encoder.rb
59
60
  - lib/kobako/codec/error.rb
60
- - lib/kobako/codec/factory.rb
61
+ - lib/kobako/codec/ext_types.rb
61
62
  - lib/kobako/codec/handle_walk.rb
63
+ - lib/kobako/codec/state.rb
62
64
  - lib/kobako/codec/utils.rb
63
65
  - lib/kobako/errors.rb
66
+ - lib/kobako/extension.rb
64
67
  - lib/kobako/fault.rb
65
68
  - lib/kobako/handle.rb
66
- - lib/kobako/namespace.rb
67
69
  - lib/kobako/outcome.rb
68
70
  - lib/kobako/outcome/panic.rb
69
71
  - lib/kobako/pool.rb
@@ -88,20 +90,22 @@ 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
- - sig/kobako/catalog/namespaces.rbs
95
+ - sig/kobako/catalog/services.rbs
93
96
  - sig/kobako/catalog/snippets.rbs
94
97
  - sig/kobako/codec.rbs
95
98
  - sig/kobako/codec/decoder.rbs
96
99
  - sig/kobako/codec/encoder.rbs
97
100
  - sig/kobako/codec/error.rbs
98
- - sig/kobako/codec/factory.rbs
101
+ - sig/kobako/codec/ext_types.rbs
99
102
  - sig/kobako/codec/handle_walk.rbs
103
+ - sig/kobako/codec/state.rbs
100
104
  - sig/kobako/codec/utils.rbs
101
105
  - sig/kobako/errors.rbs
106
+ - sig/kobako/extension.rbs
102
107
  - sig/kobako/fault.rbs
103
108
  - sig/kobako/handle.rbs
104
- - sig/kobako/namespace.rbs
105
109
  - sig/kobako/outcome.rbs
106
110
  - sig/kobako/outcome/panic.rbs
107
111
  - sig/kobako/pool.rbs