kobako 0.9.2 → 0.11.0

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Cargo.lock +3 -1
  5. data/README.md +47 -19
  6. data/data/kobako.wasm +0 -0
  7. data/ext/kobako/Cargo.toml +12 -2
  8. data/ext/kobako/src/runtime/ambient.rs +1 -1
  9. data/ext/kobako/src/runtime/cache.rs +170 -6
  10. data/ext/kobako/src/runtime/capture.rs +1 -1
  11. data/ext/kobako/src/runtime/config.rs +3 -4
  12. data/ext/kobako/src/runtime/dispatch.rs +8 -8
  13. data/ext/kobako/src/runtime/exports.rs +32 -21
  14. data/ext/kobako/src/runtime/instance_pre.rs +97 -0
  15. data/ext/kobako/src/runtime/invocation.rs +36 -93
  16. data/ext/kobako/src/runtime/trap.rs +5 -5
  17. data/ext/kobako/src/runtime.rs +389 -403
  18. data/ext/kobako/src/snapshot.rs +2 -2
  19. data/lib/kobako/capture.rb +5 -7
  20. data/lib/kobako/catalog/handles.rb +28 -39
  21. data/lib/kobako/catalog/namespaces.rb +31 -20
  22. data/lib/kobako/catalog/snippets.rb +18 -16
  23. data/lib/kobako/codec/decoder.rb +5 -1
  24. data/lib/kobako/codec/utils.rb +6 -9
  25. data/lib/kobako/errors.rb +40 -36
  26. data/lib/kobako/handle.rb +2 -3
  27. data/lib/kobako/namespace.rb +17 -6
  28. data/lib/kobako/outcome.rb +12 -14
  29. data/lib/kobako/pool.rb +176 -0
  30. data/lib/kobako/sandbox.rb +68 -88
  31. data/lib/kobako/sandbox_options.rb +5 -9
  32. data/lib/kobako/snapshot.rb +2 -4
  33. data/lib/kobako/snippet/binary.rb +1 -3
  34. data/lib/kobako/snippet/source.rb +1 -2
  35. data/lib/kobako/snippet.rb +1 -2
  36. data/lib/kobako/transport/dispatcher.rb +39 -38
  37. data/lib/kobako/transport/request.rb +1 -1
  38. data/lib/kobako/transport/run.rb +23 -28
  39. data/lib/kobako/transport/yielder.rb +11 -17
  40. data/lib/kobako/transport.rb +2 -3
  41. data/lib/kobako/usage.rb +10 -13
  42. data/lib/kobako/version.rb +1 -1
  43. data/lib/kobako.rb +1 -0
  44. data/release-please-config.json +16 -1
  45. data/sig/kobako/catalog/handles.rbs +0 -2
  46. data/sig/kobako/errors.rbs +3 -0
  47. data/sig/kobako/namespace.rbs +2 -0
  48. data/sig/kobako/pool.rbs +44 -0
  49. data/sig/kobako/sandbox.rbs +2 -2
  50. data/sig/kobako/transport/dispatcher.rbs +2 -0
  51. metadata +4 -1
@@ -20,8 +20,7 @@ module Kobako
20
20
  # The module is stateless — all mutable state is threaded through
21
21
  # arguments so Dispatcher has no instance variables and no side
22
22
  # effects beyond mutating the Catalog::Handles via +alloc+ when a
23
- # non-wire-representable return value must be wrapped
24
- # ({docs/behavior.md B-14}[link:../../../docs/behavior.md]).
23
+ # non-wire-representable return value must be wrapped.
25
24
  #
26
25
  # Entry point:
27
26
  #
@@ -29,7 +28,7 @@ module Kobako
29
28
  # # => msgpack-encoded Response bytes (never raises)
30
29
  module Dispatcher
31
30
  # Throw tag for the {Yielder}'s break unwind back to the
32
- # dispatcher's +catch+ frame (B-25). +private_constant+ is a
31
+ # dispatcher's +catch+ frame. +private_constant+ is a
33
32
  # convention boundary — not a defence.
34
33
  BREAK_THROW = :__kobako_break__
35
34
  private_constant :BREAK_THROW
@@ -38,16 +37,14 @@ module Kobako
38
37
 
39
38
  # Internal sentinel raised when target resolution fails. Mapped to
40
39
  # Response.error with type="undefined". Contained at the wire boundary —
41
- # not part of the public Kobako error taxonomy
42
- # ({docs/behavior.md E-12}[link:../../../docs/behavior.md]).
40
+ # not part of the public Kobako error taxonomy.
43
41
  class UndefinedTargetError < StandardError; end
44
42
 
45
43
  # Modules whose instance methods are ambient Ruby reflection /
46
44
  # metaprogramming surface (+send+, +public_send+, +instance_eval+,
47
45
  # +method+, +tap+, +instance_variable_get+, ...) rather than Service
48
46
  # behaviour. A guest-supplied method name resolving to one of these is
49
- # rejected ({docs/behavior.md B-42}[link:../../../docs/behavior.md]):
50
- # only methods the bound object itself exposes as Service behaviour are
47
+ # rejected: only methods the bound object itself exposes as Service behaviour are
51
48
  # reachable, and +public_send(:send, ...)+ would otherwise let a guest
52
49
  # pivot through +send+ into the private +Kernel#eval+ / +#system+
53
50
  # surface (host RCE).
@@ -58,7 +55,7 @@ module Kobako
58
55
  # (+Proc#binding+ reaches +Binding#eval+, +Method#receiver+ / +#unbind+
59
56
  # hand back the underlying object) rather than Service behaviour. Only
60
57
  # {CALLABLE_ALLOW} is reachable on a target of these types; a bound
61
- # lambda stays invocable, its reflective surface does not (B-42).
58
+ # lambda stays invocable, its reflective surface does not.
62
59
  GADGET_OWNERS = [Proc, Method, UnboundMethod, Binding].freeze
63
60
  private_constant :GADGET_OWNERS
64
61
 
@@ -69,8 +66,7 @@ module Kobako
69
66
  private_constant :CALLABLE_ALLOW
70
67
 
71
68
  # Dispatch a single transport request and return the encoded
72
- # Response bytes ({docs/behavior.md B-12}[link:../../../docs/behavior.md]).
73
- # Invoked from the +Runtime#on_dispatch+ Proc that
69
+ # Response bytes. Invoked from the +Runtime#on_dispatch+ Proc that
74
70
  # +Kobako::Sandbox#initialize+ installs on the ext side; +namespaces+,
75
71
  # +handler+, and +yield_to_guest+ are captured in that Proc's
76
72
  # closure so the Dispatcher stays stateless and the registry doesn't
@@ -99,18 +95,17 @@ module Kobako
99
95
  # round-trip back to the host-side Ruby object before the call
100
96
  # reaches +public_send+.
101
97
  def resolve_call_args(request, handler)
102
- args = request.args.map { |v| resolve_arg(v, handler) }
103
- kwargs = request.kwargs.transform_values { |v| resolve_arg(v, handler) }
104
- [args, kwargs]
98
+ [request.args.map { |v| resolve_arg(v, handler) },
99
+ request.kwargs.transform_values { |v| resolve_arg(v, handler) }]
105
100
  end
106
101
 
107
102
  # Map an error caught at the dispatch boundary to a +Response.error+
108
103
  # envelope. +error+ is the +StandardError+ caught by {#dispatch}'s
109
- # rescue. Returns a msgpack-encoded Response envelope (binary). Three
110
- # error buckets ({docs/behavior.md B-12}[link:../../../docs/behavior.md]):
104
+ # rescue. Returns a msgpack-encoded Response envelope (binary). Four
105
+ # error buckets:
111
106
  # +Kobako::Codec::Error+ → type="runtime" (malformed request);
112
- # +UndefinedTargetError+ → type="undefined" (E-13); +ArgumentError+ →
113
- # type="argument" (B-12 arity mismatch); everything else →
107
+ # +UndefinedTargetError+ → type="undefined"; +ArgumentError+ →
108
+ # type="argument" (arity mismatch); everything else →
114
109
  # type="runtime".
115
110
  def encode_caught_error(error)
116
111
  case error
@@ -129,14 +124,14 @@ module Kobako
129
124
  # uniform empty-map shape.
130
125
  #
131
126
  # +yielder+ is the host-side {Yielder} materialised when the guest
132
- # call site supplied a block ({docs/behavior.md
133
- # B-23}[link:../../../docs/behavior.md]); its {Yielder#to_proc}
127
+ # call site supplied a block; its {Yielder#to_proc}
134
128
  # rides the +&block+ slot. +&nil+ is a no-op block argument in Ruby,
135
129
  # so the same call site handles both cases without an explicit
136
130
  # conditional.
137
131
  def invoke(target, method, args, kwargs, yielder = nil)
138
132
  name = method.to_sym
139
133
  reject_meta_method!(target, name)
134
+ reject_unexposed!(target, name)
140
135
  block = yielder&.to_proc
141
136
  if kwargs.empty?
142
137
  target.public_send(name, *args, &block)
@@ -145,9 +140,8 @@ module Kobako
145
140
  end
146
141
  end
147
142
 
148
- # Guard the +public_send+ below against ambient reflection methods
149
- # ({docs/behavior.md B-42}[link:../../../docs/behavior.md]). A public
150
- # method whose owner is a {META_OWNERS} or {GADGET_OWNERS} module is
143
+ # Guard the +public_send+ below against ambient reflection methods.
144
+ # A public method whose owner is a {META_OWNERS} or {GADGET_OWNERS} module is
151
145
  # rejected, except {CALLABLE_ALLOW} on a gadget target (a bound lambda
152
146
  # stays invocable). A name with no concrete public method is allowed
153
147
  # only when the target opts into it via +respond_to?+ (dynamic
@@ -166,17 +160,26 @@ module Kobako
166
160
  raise UndefinedTargetError, "no public method #{name.inspect} on target"
167
161
  end
168
162
 
169
- # {docs/behavior.md B-16}[link:../../../docs/behavior.md] A Kobako::Handle arriving as a positional or keyword
163
+ # Consult the target's opt-in narrowing predicate. A bound object
164
+ # may define a private +respond_to_guest?(name)+ to restrict which of its
165
+ # methods the guest reaches; a falsy answer rejects the dispatch.
166
+ # The predicate composes beneath {#reject_meta_method!} — it only narrows,
167
+ # never re-opening the reflection surface the floor rejects — and is
168
+ # consulted with the private surface included so the guest's +public_send+
169
+ # dispatch can never reach +respond_to_guest?+ itself.
170
+ def reject_unexposed!(target, name)
171
+ return unless target.respond_to?(:respond_to_guest?, true)
172
+ return if target.__send__(:respond_to_guest?, name)
173
+
174
+ raise UndefinedTargetError, "method #{name.inspect} is not exposed to the guest"
175
+ end
176
+
177
+ # A Kobako::Handle arriving as a positional or keyword
170
178
  # argument identifies a host-side object previously allocated by a prior
171
- # transport call's Handle wrap (B-14). Resolve it back to the Ruby object before
179
+ # transport call's Handle wrap. Resolve it back to the Ruby object before
172
180
  # the dispatch reaches +public_send+.
173
181
  def resolve_arg(value, handler)
174
- case value
175
- when Kobako::Handle
176
- require_live_object!(value.id, handler)
177
- else
178
- value
179
- end
182
+ value.is_a?(Kobako::Handle) ? require_live_object!(value.id, handler) : value
180
183
  end
181
184
 
182
185
  # Resolve a Request target to the Ruby object the registry (or
@@ -205,7 +208,7 @@ module Kobako
205
208
  require_live_object!(handle.id, handler)
206
209
  end
207
210
 
208
- # Resolve +id+ through the Catalog::Handles. An unknown id (E-13)
211
+ # Resolve +id+ through the Catalog::Handles. An unknown id
209
212
  # surfaces as UndefinedTargetError.
210
213
  def require_live_object!(id, handler)
211
214
  handler.fetch(id)
@@ -214,11 +217,10 @@ module Kobako
214
217
  end
215
218
 
216
219
  # Encode +value+ as a +Response.ok+ envelope. When the value is not
217
- # wire-representable per {docs/behavior.md B-13}[link:../../../docs/behavior.md]'s type
218
- # mapping, the +UnsupportedType+ rescue routes it through the
220
+ # wire-representable per the codec's type mapping, the
221
+ # +UnsupportedType+ rescue routes it through the
219
222
  # Catalog::Handles via {#wrap_as_handle} and re-encodes with the Capability
220
- # Handle in place ({docs/behavior.md B-14}[link:../../../docs/behavior.md]). The happy
221
- # path encodes exactly once.
223
+ # Handle in place. The happy path encodes exactly once.
222
224
  def encode_ok(value, handler)
223
225
  response = Kobako::Transport::Response.ok(value)
224
226
  response.encode
@@ -227,9 +229,8 @@ module Kobako
227
229
  end
228
230
 
229
231
  # Allocate +value+ in the Sandbox's Catalog::Handles and return a +Handle+
230
- # that the wire codec can carry ({docs/behavior.md B-14}[link:../../../docs/behavior.md]).
231
- # Used as the fallback path of {#encode_ok} when +value+ has no wire
232
- # representation.
232
+ # that the wire codec can carry. Used as the fallback path of
233
+ # {#encode_ok} when +value+ has no wire representation.
233
234
  def wrap_as_handle(value, handler)
234
235
  handler.alloc(value)
235
236
  end
@@ -16,7 +16,7 @@ module Kobako
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
19
- # site supplied a block (B-23); the block body itself never crosses the
19
+ # site supplied a block; the block body itself never crosses the
20
20
  # wire.
21
21
  #
22
22
  # Built on the +class X < Data.define(...)+ subclass form so the
@@ -9,26 +9,24 @@ module Kobako
9
9
  # consumed by +__kobako_run+.
10
10
  module Transport
11
11
  # Host-side value object for a single +Sandbox#run+ invocation
12
- # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md];
13
- # {docs/behavior.md B-31}[link:../../../docs/behavior.md]).
12
+ # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
14
13
  #
15
14
  # A Run captures the host-layer concept of "a single +#run+
16
15
  # call": the entrypoint constant name plus its positional and keyword
17
- # arguments. Host pre-flight (E-24 / E-25 / E-29 / E-30) is enforced at
18
- # construction so the Value Object is the single source of truth
19
- # anything that passes +Run.new+ is safe to encode and ship to
20
- # the guest.
16
+ # arguments. Host pre-flight (entrypoint type / name pattern, forged
17
+ # Handle, kwargs-key type) is enforced at construction so the Value
18
+ # Object is the single source of truth anything that passes
19
+ # +Run.new+ is safe to encode and ship to the guest.
21
20
  #
22
21
  # Run is the host→guest entrypoint dispatch envelope (the +#run+
23
22
  # request shape), the symmetric counterpart to the guest→host
24
23
  # +Request+ envelope. +#encode+ takes the Sandbox's
25
24
  # +Catalog::Handles+ and routes any non-wire-representable +args+ /
26
- # +kwargs+ leaf through it as a +Kobako::Handle+
27
- # ({docs/behavior.md B-34}[link:../../../docs/behavior.md]) — the
25
+ # +kwargs+ leaf through it as a +Kobako::Handle+ — the
28
26
  # symmetric counterpart of the guest→host wrap path in the
29
- # dispatcher (B-14). A +Kobako::Handle+ that arrives **already
27
+ # dispatcher. A +Kobako::Handle+ that arrives **already
30
28
  # constructed** in the caller's +args+ / +kwargs+ is rejected at
31
- # construction (E-29): legitimate Handles only enter Host App code
29
+ # construction: legitimate Handles only enter Host App code
32
30
  # through error fields, so a Handle reaching the call site is by
33
31
  # definition smuggled in. The +#encode+ output is the "Run envelope"
34
32
  # that ships through the +__kobako_run+ command buffer.
@@ -36,8 +34,8 @@ module Kobako
36
34
  # Built on the +class X < Data.define(...)+ subclass form (the
37
35
  # Steep-friendly shape — see +lib/kobako/outcome/panic.rb+).
38
36
  class Run < Data.define(:entrypoint, :args, :kwargs)
39
- # Ruby constant-name pattern enforced on the +entrypoint+ Symbol
40
- # ({docs/behavior.md E-25}[link:../../../docs/behavior.md]). Parallel to
37
+ # Ruby constant-name pattern enforced on the +entrypoint+ Symbol.
38
+ # Parallel to
41
39
  # +Kobako::Catalog::Snippets::NAME_PATTERN+; the two constants name the
42
40
  # same regex but cover distinct surfaces (snippet identity vs.
43
41
  # entrypoint resolution) so a future divergence stays local.
@@ -55,10 +53,9 @@ module Kobako
55
53
  # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
56
54
  # Walks +args+ / +kwargs+ through {Codec::Utils.deep_wrap} so any
57
55
  # non-wire-representable leaf is allocated into +handler+ and
58
- # replaced with a +Kobako::Handle+
59
- # ({docs/behavior.md B-34}[link:../../../docs/behavior.md]); the
56
+ # replaced with a +Kobako::Handle+; the
60
57
  # +handler+ argument is the Sandbox's table, sharing the same
61
- # allocator the guest→host return path (B-14) uses.
58
+ # allocator the guest→host return path uses.
62
59
  #
63
60
  # Layout: msgpack map with string keys +"entrypoint"+ (Symbol via
64
61
  # ext 0x00), +"args"+ (Array), +"kwargs"+ (Map with Symbol keys);
@@ -74,9 +71,9 @@ module Kobako
74
71
 
75
72
  private
76
73
 
77
- # E-24: target must be a Symbol or String (TypeError, not
74
+ # The target must be a Symbol or String (TypeError, not
78
75
  # ArgumentError — the wrong-type case is a Host App programming
79
- # error before the run reaches the guest). E-25: after +.to_s+
76
+ # error before the run reaches the guest). After +.to_s+
80
77
  # the value must match NAME_PATTERN (ArgumentError), rejecting
81
78
  # +::+-segmented names and any non-constant form.
82
79
  def normalize_entrypoint(target)
@@ -93,12 +90,12 @@ module Kobako
93
90
  target_str.to_sym
94
91
  end
95
92
 
96
- # E-29: +args+ must not contain a +Kobako::Handle+. The Handle
93
+ # +args+ must not contain a +Kobako::Handle+. The Handle
97
94
  # allocator lives inside the Host Gem; legitimate paths surface
98
95
  # Handle objects only through raised error fields, so a Handle
99
96
  # reaching +args+ is a forged or smuggled token. Non-wire-
100
97
  # representable arguments that are not Handles are handled by
101
- # auto-wrap inside +#encode+ (B-34) — the reject path is reserved
98
+ # auto-wrap inside +#encode+ — the reject path is reserved
102
99
  # for Handle objects specifically.
103
100
  def validate_args!(args)
104
101
  raise ArgumentError, "arguments must be an Array" unless args.is_a?(Array)
@@ -107,11 +104,10 @@ module Kobako
107
104
  args
108
105
  end
109
106
 
110
- # E-30 covers the non-Symbol kwargs-key case; E-29 also rejects a
111
- # +Kobako::Handle+ arriving as a kwargs value (same forged-token
112
- # principle as the +args+ branch). Both checks live here so the
113
- # Host App sees the host-side error message before any encode /
114
- # decode boundary.
107
+ # Reject a non-Symbol kwargs key, and a +Kobako::Handle+ arriving
108
+ # as a kwargs value (same forged-token principle as the +args+
109
+ # branch). Both checks live here so the Host App sees the
110
+ # host-side error message before any encode / decode boundary.
115
111
  def validate_kwargs!(kwargs)
116
112
  raise ArgumentError, "keyword arguments must be a Hash" unless kwargs.is_a?(Hash)
117
113
 
@@ -125,11 +121,10 @@ module Kobako
125
121
  kwargs
126
122
  end
127
123
 
128
- # Single source of truth for the E-29 reject message so the args
129
- # and kwargs branches stay phrased identically. Message stays in
124
+ # Single source of truth for the forged-Handle reject message so the
125
+ # args and kwargs branches stay phrased identically. Message stays in
130
126
  # caller vocabulary: it names the affected slot and the reason
131
- # without leaking SPEC anchor identifiers (B-xx / E-xx live in
132
- # source comments, not user-visible errors) or self-referential
127
+ # without leaking internal SPEC identifiers or self-referential
133
128
  # architecture terms — the error is raised BY kobako, so saying
134
129
  # "allocated by the Host Gem" reads as third-person about self.
135
130
  def forged_handle_message(slot)
@@ -8,29 +8,25 @@ module Kobako
8
8
  # owns the host-side object that materialises a guest-supplied block as
9
9
  # a Ruby callable the Service method can yield into.
10
10
  module Transport
11
- # Host-side stand-in for a guest-supplied block (B-23).
11
+ # Host-side stand-in for a guest-supplied block.
12
12
  #
13
13
  # Each guest call that carries +block_given: true+ gets a Yielder
14
14
  # that the Dispatcher hands to the Service method as +&block+. The
15
15
  # Service method observes it as an ordinary Ruby Proc through
16
16
  # {#to_proc}; +yield val+ / +block.call(val)+ invokes {#yield}, which
17
17
  # serialises the positional args, re-enters the guest via the injected
18
- # +yield_to_guest+ lambda
19
- # ({docs/behavior.md B-24}[link:../../../docs/behavior.md]), and
20
- # reifies the +YieldResponse+ into Ruby control flow:
18
+ # +yield_to_guest+ lambda, and reifies the +YieldResponse+ into Ruby
19
+ # control flow:
21
20
  #
22
21
  # * +tag 0x01+ ok — return the decoded value to +yield+'s caller
23
22
  # * +tag 0x02+ break — +throw break_tag, value+ so the Dispatcher's
24
23
  # +catch+ frame unwinds the Service method
25
- # ({docs/behavior.md B-25}[link:../../../docs/behavior.md])
26
24
  # * +tag 0x04+ error — raise the +{class, message}+ payload at the
27
25
  # Service's yield site
28
26
  #
29
27
  # The Dispatcher calls {#invalidate!} from its +ensure+ block once
30
28
  # dispatch completes; any later call to a stashed Yielder then raises
31
- # +LocalJumpError+ — the observable shape of
32
- # {docs/behavior.md E-23}[link:../../../docs/behavior.md] (escaped
33
- # Yielder).
29
+ # +LocalJumpError+ — the observable shape of an escaped Yielder.
34
30
  class Yielder
35
31
  # +yield_to_guest+ is a +String → String+ callable (typically
36
32
  # +Runtime#yield_to_active_invocation+ bound through a lambda) that
@@ -38,8 +34,7 @@ module Kobako
38
34
  # throw tag the Dispatcher matches against to unwind the Service on
39
35
  # +tag 0x02+. +handler+ is the Sandbox's +Kobako::Catalog::Handles+,
40
36
  # used to restore a Capability Handle in the block's ok value back to
41
- # its host object before it reaches the Service +yield+ site
42
- # ({docs/behavior.md B-37}[link:../../../docs/behavior.md]).
37
+ # its host object before it reaches the Service +yield+ site.
43
38
  def initialize(yield_to_guest, break_tag, handler)
44
39
  @yield_to_guest = yield_to_guest
45
40
  @break_tag = break_tag
@@ -49,10 +44,10 @@ module Kobako
49
44
 
50
45
  # Re-enter the guest with +args+ and reify the YieldResponse into
51
46
  # Ruby control flow. Raises +LocalJumpError+ if called after
52
- # {#invalidate!} (E-23). The ok value is consumed by the host Service
53
- # method, so a Capability Handle in it is restored to its host object
54
- # (B-37). The break value unwinds past the Service back to the guest
55
- # Member call (B-25), so it passes through verbatim — a Handle stays a
47
+ # {#invalidate!}. The ok value is consumed by the host Service
48
+ # method, so a Capability Handle in it is restored to its host object.
49
+ # The break value unwinds past the Service back to the guest
50
+ # Member call, so it passes through verbatim — a Handle stays a
56
51
  # Handle and rides back on the same id rather than churning a new one.
57
52
  def yield(*args)
58
53
  raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active
@@ -73,7 +68,7 @@ module Kobako
73
68
 
74
69
  # Mark this Yielder dead. Called by the Dispatcher's +ensure+ block
75
70
  # when the originating dispatch frame returns; any later {#yield}
76
- # call then raises +LocalJumpError+ (E-23).
71
+ # call then raises +LocalJumpError+.
77
72
  def invalidate!
78
73
  @active = false
79
74
  end
@@ -81,8 +76,7 @@ module Kobako
81
76
  private
82
77
 
83
78
  # Restore any Capability Handle in a block's ok value to its host
84
- # object via the injected +Catalog::Handles+
85
- # ({docs/behavior.md B-37}[link:../../../docs/behavior.md]). Only the
79
+ # object via the injected +Catalog::Handles+. Only the
86
80
  # ok path calls this — host code consumes the ok value, whereas a
87
81
  # break value returns to the guest and stays a Handle. Walks nested
88
82
  # Array / Hash one level at a time; a plain value passes through
@@ -13,9 +13,8 @@ module Kobako
13
13
  # Houses the envelope value objects (Request / Response / Run / Yield),
14
14
  # the guest→host +Dispatcher+, and the host→guest +Yielder+.
15
15
  # +Sandbox#initialize+ composes them onto the
16
- # +Runtime+ as a dispatch +Proc+ + +yield_to_guest+ lambda pair
17
- # ({docs/behavior.md B-12}[link:../../docs/behavior.md]). "RPC" was
18
- # deliberately not chosen — it implies a cross-process boundary that
16
+ # +Runtime+ as a dispatch +Proc+ + +yield_to_guest+ lambda pair.
17
+ # "RPC" was deliberately not chosen — it implies a cross-process boundary that
19
18
  # kobako does not have, since host and guest share one OS thread and
20
19
  # one wasm linear memory. See
21
20
  # {SPEC.md Refinement → Internal Concepts}[link:../../SPEC.md].
data/lib/kobako/usage.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- # Per-last-invocation resource accounting for a +Kobako::Sandbox+
5
- # ({docs/behavior.md B-35}[link:../../docs/behavior.md]). Carries two
6
- # readers populated by every +#eval+ / +#run+ invocation:
4
+ # Per-last-invocation resource accounting for a +Kobako::Sandbox+.
5
+ # Carries two readers populated by every +#eval+ / +#run+ invocation:
7
6
  #
8
7
  # * +wall_time+ — the Float number of seconds the guest export call
9
8
  # spent inside wasmtime during the most recent invocation. The
10
- # measurement bracket aligns with the +timeout+ deadline
11
- # ({docs/behavior.md B-01}[link:../../docs/behavior.md]); time spent
9
+ # measurement bracket aligns with the +timeout+ deadline; time spent
12
10
  # in host Service callbacks is included, but everything that runs
13
11
  # after the guest export returns — the post-export
14
12
  # +OUTCOME_BUFFER+ fetch and decode, plus stdout / stderr capture
@@ -16,11 +14,11 @@ module Kobako
16
14
  # * +memory_peak+ — the Integer high-water mark, in bytes, of the
17
15
  # per-invocation +memory.grow+ delta past the linear-memory size
18
16
  # captured at invocation entry. Same baseline accounting as
19
- # +memory_limit+ ({docs/behavior.md E-20}[link:../../docs/behavior.md]):
20
- # the mruby image's initial allocation and any prior-invocation
21
- # watermark sit outside the measurement. On +MemoryLimitError+
22
- # +memory_peak+ never exceeds the configured cap because the
23
- # rejected +desired+ value is not promoted into the high-water.
17
+ # +memory_limit+: the mruby image's initial allocation and any
18
+ # prior-invocation watermark sit outside the measurement. On
19
+ # +MemoryLimitError+ +memory_peak+ never exceeds the configured
20
+ # cap because the rejected +desired+ value is not promoted into
21
+ # the high-water.
24
22
  #
25
23
  # Both readers are populated on every outcome, including +TrapError+
26
24
  # branches, so the Host App can read +Sandbox#usage+ after rescuing a
@@ -31,9 +29,8 @@ module Kobako
31
29
  # Built on the +class X < Data.define(...)+ subclass form (the
32
30
  # Steep-friendly shape — see +lib/kobako/outcome/panic.rb+).
33
31
  class Usage < Data.define(:wall_time, :memory_peak)
34
- # Pre-invocation sentinel ({docs/behavior.md B-35}[link:../../docs/behavior.md]).
35
- # Reused by +Sandbox+ before any invocation has run so callers do not
36
- # need to handle a +nil+ +#usage+.
32
+ # Pre-invocation sentinel. Reused by +Sandbox+ before any invocation
33
+ # has run so callers do not need to handle a +nil+ +#usage+.
37
34
  EMPTY = new(wall_time: 0.0, memory_peak: 0)
38
35
  end
39
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.9.2"
4
+ VERSION = "0.11.0"
5
5
  end
data/lib/kobako.rb CHANGED
@@ -15,3 +15,4 @@ require_relative "kobako/catalog"
15
15
  require_relative "kobako/runtime"
16
16
  require_relative "kobako/snapshot"
17
17
  require_relative "kobako/sandbox"
18
+ require_relative "kobako/pool"
@@ -73,13 +73,28 @@
73
73
  "path": "/wasm/kobako-regexp/README.md"
74
74
  }
75
75
  ]
76
+ },
77
+ "wasm/kobako-baker": {
78
+ "component": "kobako-baker",
79
+ "release-type": "rust",
80
+ "extra-files": [
81
+ {
82
+ "type": "toml",
83
+ "path": "/wasm/kobako-baker/Cargo.lock",
84
+ "jsonpath": "$.package[?(@.name=='kobako-baker')].version"
85
+ },
86
+ {
87
+ "type": "generic",
88
+ "path": "/wasm/kobako-baker/README.md"
89
+ }
90
+ ]
76
91
  }
77
92
  },
78
93
  "plugins": [
79
94
  {
80
95
  "type": "linked-versions",
81
96
  "groupName": "kobako guest crates",
82
- "components": ["kobako-core", "kobako-rs", "kobako-io", "kobako-regexp"]
97
+ "components": ["kobako-core", "kobako-rs", "kobako-io", "kobako-regexp", "kobako-baker"]
83
98
  }
84
99
  ],
85
100
  "extra-files": [
@@ -1,8 +1,6 @@
1
1
  module Kobako
2
2
  module Catalog
3
3
  class Handles
4
- UNWRAPPABLE_TYPES: Array[Module]
5
-
6
4
  def initialize: (?next_id: Integer) -> void
7
5
 
8
6
  def alloc: (untyped object) -> Kobako::Handle
@@ -52,4 +52,7 @@ module Kobako
52
52
 
53
53
  class BytecodeError < SandboxError
54
54
  end
55
+
56
+ class PoolTimeoutError < Error
57
+ end
55
58
  end
@@ -8,6 +8,8 @@ module Kobako
8
8
 
9
9
  def bind: (Symbol | String member, untyped object) -> self
10
10
 
11
+ def seal!: () -> self
12
+
11
13
  def fetch: (Symbol | String member) -> untyped
12
14
 
13
15
  def to_preamble: () -> [String, Array[String]]
@@ -0,0 +1,44 @@
1
+ module Kobako
2
+ class Pool
3
+ DEFAULT_CHECKOUT_TIMEOUT_SECONDS: Float
4
+
5
+ @slots: Integer
6
+ @checkout_timeout: Float?
7
+ @sandbox_options: Hash[Symbol, untyped]
8
+ @setup: ^(Kobako::Sandbox) -> void | nil
9
+ @idle: Array[Kobako::Sandbox]
10
+ @constructed: Integer
11
+ @mutex: Thread::Mutex
12
+ @slot_freed: Thread::ConditionVariable
13
+
14
+ def initialize: (
15
+ slots: Integer,
16
+ ?checkout_timeout: (Float | Integer)?,
17
+ **untyped sandbox_options
18
+ ) ?{ (Kobako::Sandbox) -> void } -> void
19
+
20
+ def with: [T] () { (Kobako::Sandbox) -> T } -> T
21
+
22
+ private
23
+
24
+ def checkout: () -> Kobako::Sandbox
25
+
26
+ def acquire: () -> Kobako::Sandbox
27
+
28
+ def claim_or_wait: (Float? deadline) -> [Symbol, Kobako::Sandbox?]
29
+
30
+ def await_slot!: (Float? deadline) -> void
31
+
32
+ def construct_slot: () -> Kobako::Sandbox
33
+
34
+ def checkin: (Kobako::Sandbox sandbox) -> void
35
+
36
+ def release_capacity!: () -> void
37
+
38
+ def monotonic_now: () -> Float
39
+
40
+ def validate_slots!: (untyped slots) -> void
41
+
42
+ def normalize_checkout_timeout: ((Float | Integer)? checkout_timeout) -> Float?
43
+ end
44
+ end
@@ -38,12 +38,12 @@ module Kobako
38
38
 
39
39
  def eval: (String code) -> untyped
40
40
 
41
+ def reset_invocation_state!: () -> void
42
+
41
43
  private
42
44
 
43
45
  def install_dispatch_proc!: () -> void
44
46
 
45
- def reset_invocation_state!: () -> void
46
-
47
47
  def begin_invocation!: () -> void
48
48
 
49
49
  def read_usage!: () -> void
@@ -22,6 +22,8 @@ module Kobako
22
22
 
23
23
  def self?.reject_meta_method!: (untyped target, Symbol name) -> void
24
24
 
25
+ def self?.reject_unexposed!: (untyped target, Symbol name) -> void
26
+
25
27
  def self?.resolve_arg: (untyped value, Kobako::Catalog::Handles handler) -> untyped
26
28
 
27
29
  def self?.resolve_target: (String | Kobako::Handle target, Kobako::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler) -> untyped
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kobako
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
@@ -65,6 +65,7 @@ files:
65
65
  - ext/kobako/src/runtime/dispatch.rs
66
66
  - ext/kobako/src/runtime/exports.rs
67
67
  - ext/kobako/src/runtime/guest_mem.rs
68
+ - ext/kobako/src/runtime/instance_pre.rs
68
69
  - ext/kobako/src/runtime/invocation.rs
69
70
  - ext/kobako/src/runtime/trap.rs
70
71
  - ext/kobako/src/snapshot.rs
@@ -86,6 +87,7 @@ files:
86
87
  - lib/kobako/namespace.rb
87
88
  - lib/kobako/outcome.rb
88
89
  - lib/kobako/outcome/panic.rb
90
+ - lib/kobako/pool.rb
89
91
  - lib/kobako/runtime.rb
90
92
  - lib/kobako/sandbox.rb
91
93
  - lib/kobako/sandbox_options.rb
@@ -123,6 +125,7 @@ files:
123
125
  - sig/kobako/namespace.rbs
124
126
  - sig/kobako/outcome.rbs
125
127
  - sig/kobako/outcome/panic.rbs
128
+ - sig/kobako/pool.rbs
126
129
  - sig/kobako/runtime.rbs
127
130
  - sig/kobako/sandbox.rbs
128
131
  - sig/kobako/sandbox_options.rbs