kobako 0.13.0 → 0.14.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 +30 -0
  4. data/Cargo.lock +3 -3
  5. data/README.md +3 -3
  6. data/ROADMAP.md +26 -0
  7. data/crates/kobako-runtime/CHANGELOG.md +7 -0
  8. data/crates/kobako-runtime/Cargo.toml +1 -1
  9. data/crates/kobako-runtime/README.md +1 -1
  10. data/crates/kobako-runtime/src/error.rs +8 -0
  11. data/crates/kobako-wasmtime/CHANGELOG.md +7 -0
  12. data/crates/kobako-wasmtime/Cargo.toml +2 -2
  13. data/crates/kobako-wasmtime/README.md +1 -1
  14. data/crates/kobako-wasmtime/src/driver.rs +1 -1
  15. data/crates/kobako-wasmtime/src/frames.rs +1 -1
  16. data/crates/kobako-wasmtime/src/instance_pre.rs +7 -6
  17. data/crates/kobako-wasmtime/src/invocation.rs +4 -5
  18. data/crates/kobako-wasmtime/src/trap.rs +42 -21
  19. data/data/kobako.wasm +0 -0
  20. data/ext/kobako/Cargo.toml +1 -1
  21. data/lib/kobako/catalog/handles.rb +2 -2
  22. data/lib/kobako/catalog/namespaces.rb +2 -2
  23. data/lib/kobako/catalog/snippets.rb +1 -1
  24. data/lib/kobako/codec/decoder.rb +7 -7
  25. data/lib/kobako/codec/encoder.rb +2 -2
  26. data/lib/kobako/codec/error.rb +3 -3
  27. data/lib/kobako/codec/factory.rb +10 -10
  28. data/lib/kobako/codec/handle_walk.rb +17 -21
  29. data/lib/kobako/codec/utils.rb +9 -9
  30. data/lib/kobako/codec.rb +3 -3
  31. data/lib/kobako/errors.rb +14 -39
  32. data/lib/kobako/handle.rb +5 -2
  33. data/lib/kobako/namespace.rb +3 -3
  34. data/lib/kobako/outcome.rb +1 -0
  35. data/lib/kobako/sandbox.rb +10 -12
  36. data/lib/kobako/transport/dispatcher.rb +17 -14
  37. data/lib/kobako/transport/request.rb +2 -2
  38. data/lib/kobako/transport/response.rb +2 -2
  39. data/lib/kobako/transport/run.rb +1 -1
  40. data/lib/kobako/transport/yield.rb +3 -3
  41. data/lib/kobako/transport/yielder.rb +6 -6
  42. data/lib/kobako/version.rb +1 -1
  43. data/release-please-config.json +37 -7
  44. data/sig/kobako/codec/handle_walk.rbs +2 -2
  45. data/sig/kobako/codec.rbs +11 -0
  46. data/sig/kobako/handle.rbs +0 -2
  47. data/sig/kobako/transport/dispatcher.rbs +8 -8
  48. data/sig/kobako/transport/run.rbs +1 -1
  49. data/sig/kobako/transport/yielder.rbs +2 -2
  50. data/sig/kobako/transport.rbs +8 -0
  51. metadata +2 -1
@@ -5,16 +5,16 @@ require_relative "../handle"
5
5
  module Kobako
6
6
  module Codec
7
7
  # Substitutes Capability Handles into and out of a Ruby value tree at
8
- # the host↔guest boundary. {deep_wrap} allocates a +Kobako::Handle+ for
8
+ # the host↔guest boundary. #deep_wrap allocates a +Kobako::Handle+ for
9
9
  # each non-wire-representable leaf on the host→guest +#run+ argument
10
- # path; {deep_restore} resolves each wire-decoded Handle back to its
11
- # host object on every guest→host value path. {representable?} is the
12
- # by-value codec-type predicate that decides which leaves {deep_wrap}
10
+ # path; #deep_restore resolves each wire-decoded Handle back to its
11
+ # host object on every guest→host value path. #representable? is the
12
+ # by-value codec-type predicate that decides which leaves #deep_wrap
13
13
  # must wrap: the closed 12-entry wire type set
14
14
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Type
15
15
  # Mapping).
16
16
  #
17
- # All helpers are pure except {deep_wrap}, whose only side effect is
17
+ # All helpers are pure except #deep_wrap, whose only side effect is
18
18
  # allocating new Handle ids into the supplied table.
19
19
  module HandleWalk
20
20
  module_function
@@ -24,7 +24,7 @@ module Kobako
24
24
  # unsigned +uint 64+ maximum
25
25
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Type
26
26
  # Mapping #3, the +fixint+ / +int 8..64+ / +uint 8..64+ union).
27
- # Anchored as a +Range+ so {primitive_type?} stays a single
27
+ # Anchored as a +Range+ so #primitive_type? stays a single
28
28
  # dispatch line. This is the codec's encode domain — not to
29
29
  # be confused with the Handle id range, which lives on
30
30
  # +Kobako::Handle+ as +MIN_ID+ / +MAX_ID+ (1..2^31 − 1) and
@@ -41,13 +41,13 @@ module Kobako
41
41
  # representable. Integers outside the codec's signed-64 /
42
42
  # unsigned-64 union are rejected so the predicate agrees with the
43
43
  # msgpack gem's encode-time +RangeError+ behaviour the codec
44
- # already surfaces as {UnsupportedType}.
44
+ # already surfaces as UnsupportedType.
45
45
  def representable?(value)
46
46
  primitive_type?(value) || container_representable?(value)
47
47
  end
48
48
 
49
49
  # Deep-walk Array / Hash containers in +value+ and replace every
50
- # leaf that fails {representable?} with a +Kobako::Handle+
50
+ # leaf that fails #representable? with a +Kobako::Handle+
51
51
  # allocated from +handler+. The
52
52
  # walk only descends through representable container shapes
53
53
  # (Array, Hash) one structural level at a time; a non-representable
@@ -61,13 +61,9 @@ module Kobako
61
61
  # whose leaves are either representable or +Kobako::Handle+
62
62
  # tokens.
63
63
  #
64
- # The block bodies spell +HandleWalk.deep_wrap+ explicitly rather
65
- # than the unqualified +deep_wrap+ because +module_function+ makes
66
- # the instance copy of these helpers private; an implicit receiver
67
- # inside a block would resolve against the enclosing +self+
68
- # (still +HandleWalk+ at definition time, but the qualified form
69
- # keeps the dispatch readable when the recursive call sits inside a
70
- # Proc captured from elsewhere).
64
+ # Recursive calls spell the +HandleWalk.+ receiver so the dispatch
65
+ # stays valid even when a block is captured and run under a
66
+ # different +self+ (+module_function+ privatizes the instance copies).
71
67
  def deep_wrap(value, handler)
72
68
  case value
73
69
  when ::Array then value.map { |element| HandleWalk.deep_wrap(element, handler) }
@@ -79,7 +75,7 @@ module Kobako
79
75
 
80
76
  # Deep-walk Array / Hash containers in +value+ and replace every
81
77
  # +Kobako::Handle+ leaf with the host-side object +handler+ resolves
82
- # it to. The symmetric inverse of {deep_wrap}: that walk allocates objects
78
+ # it to. The symmetric inverse of #deep_wrap: that walk allocates objects
83
79
  # into Handles on the host→guest argument path; this walk resolves
84
80
  # Handles back to their objects wherever a guest→host payload carries
85
81
  # one — an invocation result, a yield-block result, or a dispatch
@@ -107,9 +103,9 @@ module Kobako
107
103
  end
108
104
  end
109
105
 
110
- # The non-container branch of {representable?}: returns +true+ for
106
+ # The non-container branch of #representable?: returns +true+ for
111
107
  # the scalar leaves and an existing Handle. Not part of the
112
- # public surface; reach for {representable?} instead.
108
+ # public surface; reach for #representable? instead.
113
109
  def primitive_type?(value)
114
110
  case value
115
111
  when ::NilClass, ::TrueClass, ::FalseClass, ::Float, ::String, ::Symbol, Kobako::Handle then true
@@ -118,10 +114,10 @@ module Kobako
118
114
  end
119
115
  end
120
116
 
121
- # The container branch of {representable?}: recurses into Array
117
+ # The container branch of #representable?: recurses into Array
122
118
  # elements and Hash key+value pairs through the public
123
- # {representable?}. Not part of the public surface; reach for
124
- # {representable?} instead.
119
+ # #representable?. Not part of the public surface; reach for
120
+ # #representable? instead.
125
121
  def container_representable?(value)
126
122
  case value
127
123
  when ::Array then value.all? { |element| HandleWalk.representable?(element) }
@@ -10,18 +10,18 @@ module Kobako
10
10
  # - UTF-8 assertion at the codec boundary
11
11
  # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md]
12
12
  # § str/bin Encoding Rules and § Ext Types → ext 0x00). Used by
13
- # {Decoder} when walking +str+ family payloads and by {Factory}
13
+ # Decoder when walking +str+ family payloads and by Factory
14
14
  # when validating the +ext 0x00+ Symbol payload.
15
15
  # - +ArgumentError+ translation at the codec boundary
16
- # ({with_boundary}) so the public taxonomy stays
17
- # {Kobako::Codec::Error}.
16
+ # (#with_boundary) so the public taxonomy stays
17
+ # Kobako::Codec::Error.
18
18
  #
19
19
  # Both helpers are pure — they only inspect inputs, never mutate them.
20
- # The host↔guest Handle substitution walk lives in {HandleWalk}.
20
+ # The host↔guest Handle substitution walk lives in HandleWalk.
21
21
  module Utils
22
22
  module_function
23
23
 
24
- # Raise {InvalidEncoding} unless +string+'s bytes are valid under
24
+ # Raise InvalidEncoding unless +string+'s bytes are valid under
25
25
  # its current encoding tag. +label+ is the caller-supplied prefix
26
26
  # for the error message (e.g. +"str payload"+, +"Symbol payload"+).
27
27
  def assert_utf8!(string, label)
@@ -32,13 +32,13 @@ module Kobako
32
32
 
33
33
  # Run +block+ at the codec boundary: a value object raises
34
34
  # +ArgumentError+ when an invariant is violated at construction, and
35
- # this helper surfaces that as {InvalidType} so the public taxonomy
36
- # stays {Kobako::Codec::Error} and never leaks +ArgumentError+ from
35
+ # this helper surfaces that as InvalidType so the public taxonomy
36
+ # stays Kobako::Codec::Error and never leaks +ArgumentError+ from
37
37
  # the Ruby standard library.
38
38
  #
39
39
  # Reach for this only where a value object is constructed outside a
40
- # {Decoder.decode} block, whose rescue already performs the same
41
- # mapping (worked example: {Factory#unpack_handle} building
40
+ # Decoder.decode block, whose rescue already performs the same
41
+ # mapping (worked example: Factory#unpack_handle building
42
42
  # +Handle.restore+ from a raw fixext payload). Do not use it for
43
43
  # general-purpose validation outside the codec boundary —
44
44
  # host-layer +ArgumentError+ values should propagate unchanged.
data/lib/kobako/codec.rb CHANGED
@@ -18,12 +18,12 @@ module Kobako
18
18
  # the kobako root so the codec can register them without depending
19
19
  # upward on Transport.
20
20
  #
21
- # Backed by the official +msgpack+ gem via {Factory}; {Encoder} and
22
- # {Decoder} are thin wrappers that register the three kobako-specific
21
+ # Backed by the official +msgpack+ gem via Factory; Encoder and
22
+ # Decoder are thin wrappers that register the three kobako-specific
23
23
  # ext types (0x00 Symbol, 0x01 Capability Handle, 0x02 Exception
24
24
  # envelope) on a single +MessagePack::Factory+ instance. The Rust side
25
25
  # mirrors this layer as the +codec+ module in the +kobako-codec+ crate;
26
- # the ext-code constants live as module-private values on {Factory}
26
+ # the ext-code constants live as module-private values on Factory
27
27
  # alongside +codec::EXT_SYMBOL+ / +codec::EXT_HANDLE+ /
28
28
  # +codec::EXT_ERRENV+ on that side.
29
29
  module Codec
data/lib/kobako/errors.rb CHANGED
@@ -4,38 +4,13 @@
4
4
  module Kobako
5
5
  # Error taxonomy.
6
6
  #
7
- # Every `Kobako::Sandbox` invocation (`#eval` or `#run`) either returns a value or raises
8
- # exactly one of three invocation-outcome classes. Attribution is decided after the
9
- # guest binary returns control to the host: first the Wasm-trap layer, then
10
- # the outcome-envelope tag.
11
- #
12
- # Three invocation-outcome branches:
13
- #
14
- # * {TrapError} — Wasm engine layer (trap, OOM, unreachable, or a
15
- # wire-violation fallback signalling a corrupted
16
- # guest runtime).
17
- # * {SandboxError} — sandbox / wire layer (mruby script error,
18
- # wire-decode failure on an otherwise valid tag,
19
- # Catalog::Handles exhaustion, output buffer overrun).
20
- # * {ServiceError} — service / capability layer (a Service capability
21
- # call that failed and was not rescued inside the
22
- # script).
23
- #
24
- # Two further branches sit outside the invocation taxonomy:
25
- #
26
- # * {SetupError} — construction layer. Raised by `Kobako::Sandbox.new`
27
- # when the wasm runtime cannot be built from the
28
- # configured +wasm_path+ before any invocation runs.
29
- # Not an invocation outcome, so it never passes
30
- # through the two-step attribution decision.
31
- # * {PoolTimeoutError} — pool checkout layer. Raised by `Kobako::Pool#with`
32
- # when the checkout wait exceeds +checkout_timeout+.
33
- #
34
- # Named subclasses:
35
- #
36
- # * {ModuleNotBuiltError} < {SetupError} — Guest Binary artifact absent
37
- # at +wasm_path+.
38
- # * {HandleExhaustedError} < {SandboxError} — Handle id cap hit.
7
+ # Every +Kobako::Sandbox+ invocation (+#eval+ or +#run+) either returns a
8
+ # value or raises exactly one of TrapError, SandboxError, or ServiceError.
9
+ # Attribution is decided after the guest binary returns control to the
10
+ # host: first the Wasm-trap layer, then the outcome-envelope tag.
11
+ # SetupError and PoolTimeoutError sit outside the invocation taxonomy and
12
+ # never pass through that attribution decision. Each class below
13
+ # documents its own layer.
39
14
 
40
15
  # Base for all kobako-raised errors so callers that want to ignore the
41
16
  # taxonomy can rescue a single class.
@@ -48,8 +23,8 @@ module Kobako
48
23
  #
49
24
  # Two named subclasses cover the configured per-invocation caps:
50
25
  #
51
- # * {TimeoutError} — wall-clock +timeout+ exceeded.
52
- # * {MemoryLimitError} — guest +memory.grow+ would exceed
26
+ # * TimeoutError — wall-clock +timeout+ exceeded.
27
+ # * MemoryLimitError — guest +memory.grow+ would exceed
53
28
  # +memory_limit+.
54
29
  #
55
30
  # Host Apps that only care about "guest is unrecoverable, discard the
@@ -128,14 +103,14 @@ module Kobako
128
103
  class HandleExhaustedError < SandboxError; end
129
104
 
130
105
  # BytecodeError is the SandboxError subclass raised when a
131
- # `#preload(binary:)` snippet fails structural validation during the
132
- # first invocation's snippet replay against a fresh `mrb_state` (RITE
106
+ # +#preload(binary:)+ snippet fails structural validation during the
107
+ # first invocation's snippet replay against a fresh +mrb_state+ (RITE
133
108
  # version mismatch or corrupt body). Bytecode that loads cleanly and
134
- # then raises at top level surfaces as plain `SandboxError` with the
109
+ # then raises at top level surfaces as plain +SandboxError+ with the
135
110
  # natural mruby class preserved. Inherits from SandboxError so a single
136
- # `rescue Kobako::SandboxError` covers both source and bytecode
111
+ # +rescue Kobako::SandboxError+ covers both source and bytecode
137
112
  # snippet failures while callers wanting bytecode-specific handling
138
- # can `rescue Kobako::BytecodeError` directly.
113
+ # can +rescue Kobako::BytecodeError+ directly.
139
114
  class BytecodeError < SandboxError; end
140
115
 
141
116
  # Pool checkout layer. Raised by +Kobako::Pool#with+ when the checkout
data/lib/kobako/handle.rb CHANGED
@@ -14,8 +14,10 @@ module Kobako
14
14
  # The constructor is internal to the Host Gem. +Kobako::Handle.new+ is
15
15
  # privatised so Host App code cannot fabricate a Handle from a bare
16
16
  # integer; legitimate Handle instances enter Host App code only as
17
- # fields on raised error objects. The Host Gem itself constructs
18
- # Handles through {.restore}, which exists at exactly two call
17
+ # fields on raised error objects. +#with+ Data's copy-with-changes
18
+ # constructor is removed for the same reason: a legitimate Handle
19
+ # must not derive a sibling with a caller-chosen id. The Host Gem itself constructs
20
+ # Handles through +.restore+, which exists at exactly two call
19
21
  # sites: +Kobako::Codec::Factory#unpack_handle+ (wire decode) and
20
22
  # +Kobako::Codec::HandleWalk.deep_wrap+ / +Kobako::Transport::Dispatcher#wrap_as_handle+
21
23
  # (allocator paths). Both live inside +lib/kobako/+ and are not part
@@ -41,6 +43,7 @@ module Kobako
41
43
  end
42
44
 
43
45
  private_class_method :new
46
+ undef_method :with
44
47
 
45
48
  # Host Gem–internal factory. Allocates the Data instance through
46
49
  # +Class#allocate+ and dispatches +#initialize+ explicitly so the
@@ -3,7 +3,7 @@
3
3
  module Kobako
4
4
  # A named grouping of Members for one Sandbox.
5
5
  # Returned by +Sandbox#define+. Each instance owns a flat name→object
6
- # table of Members; member binding is validated against {NAME_PATTERN}.
6
+ # table of Members; member binding is validated against NAME_PATTERN.
7
7
  class Namespace
8
8
  # Ruby constant-name pattern shared by Namespace and Member names.
9
9
  NAME_PATTERN = /\A[A-Z]\w*\z/
@@ -11,7 +11,7 @@ module Kobako
11
11
  attr_reader :name
12
12
 
13
13
  # Build a new Namespace. +name+ is an already-validated Namespace
14
- # name (must satisfy {NAME_PATTERN}; validation is the caller's
14
+ # name (must satisfy NAME_PATTERN; validation is the caller's
15
15
  # responsibility).
16
16
  def initialize(name)
17
17
  @name = name
@@ -38,7 +38,7 @@ module Kobako
38
38
 
39
39
  # Mark this Namespace as sealed. Called by
40
40
  # +Kobako::Catalog::Namespaces#seal!+ on the owning Sandbox's first
41
- # invocation; afterwards {#bind} raises +ArgumentError+. Idempotent;
41
+ # invocation; afterwards #bind raises +ArgumentError+. Idempotent;
42
42
  # returns +self+.
43
43
  def seal!
44
44
  @sealed = true
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "codec"
3
4
  require_relative "outcome/panic"
4
5
  require_relative "transport/error"
5
6
 
@@ -120,7 +120,9 @@ module Kobako
120
120
  # Register a snippet on this Sandbox in one of two forms:
121
121
  #
122
122
  # * +preload(code: source, name: Name)+ — +source+ is mruby source
123
- # as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. The +name+
123
+ # as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. Compile
124
+ # failures surface as +Kobako::SandboxError+ on the first
125
+ # invocation's replay. The +name+
124
126
  # becomes the snippet's +(snippet:Name)+ backtrace filename and
125
127
  # is the dedupe key that rejects a duplicate +code:+ snippet.
126
128
  # * +preload(binary: bytes)+ — +bytes+ is precompiled RITE
@@ -216,7 +218,7 @@ module Kobako
216
218
  # runtime that cannot honor the request never runs guest code.
217
219
  def build_runtime!
218
220
  runtime = Kobako::Runtime.from_path(@wasm_path, @options.timeout, @options.memory_limit,
219
- @options.stdout_limit, @options.stderr_limit, profile)
221
+ @options.stdout_limit, @options.stderr_limit, @options.profile)
220
222
  @options.enforce_floor!(runtime.profile)
221
223
  runtime
222
224
  end
@@ -257,10 +259,8 @@ module Kobako
257
259
  # the readout here also covers the trap path, where +Runtime#eval+ /
258
260
  # +#run+ raise instead of returning outcome bytes.
259
261
  #
260
- # The ext returns a positional 2-tuple +[wall_time, memory_peak]+
261
- # whose order matches the +Kobako::Usage+ field order; the
262
- # destructure-then-kwargs handoff below is the explicit
263
- # positional→keyword conversion point.
262
+ # The ext-side contract is positional: +Runtime#usage+ yields
263
+ # +[wall_time, memory_peak]+ in +Kobako::Usage+ field order.
264
264
  def read_usage!
265
265
  wall_time, memory_peak = @runtime.usage
266
266
  @usage = Usage.new(wall_time: wall_time, memory_peak: memory_peak)
@@ -283,16 +283,14 @@ module Kobako
283
283
 
284
284
  # Read the per-last-invocation output captures from the ext and wrap
285
285
  # them as +Kobako::Capture+ value objects. Runs in the +invoke!+
286
- # +ensure+ block next to {#read_usage!} for the same reason: the ext
286
+ # +ensure+ block next to #read_usage! for the same reason: the ext
287
287
  # stashes the captures on every outcome, so the readout also covers
288
288
  # the trap path, where +Runtime#eval+ / +#run+ raise instead of
289
289
  # returning outcome bytes — +#stdout+ / +#stderr+ keep the guest's
290
290
  # partial output readable after a rescue.
291
291
  #
292
- # The ext returns a positional 4-tuple
293
- # +[stdout_bytes, stdout_truncated, stderr_bytes, stderr_truncated]+;
294
- # the destructure-then-kwargs handoff below is the explicit
295
- # positional→keyword conversion point.
292
+ # The ext-side contract is positional: +Runtime#captures+ yields
293
+ # +[stdout_bytes, stdout_truncated, stderr_bytes, stderr_truncated]+.
296
294
  def read_captures!
297
295
  stdout_bytes, stdout_truncated, stderr_bytes, stderr_truncated = @runtime.captures
298
296
  @stdout_capture = Capture.new(bytes: stdout_bytes, truncated: stdout_truncated)
@@ -306,7 +304,7 @@ module Kobako
306
304
  # The yielded block must return the invocation's raw outcome bytes —
307
305
  # i.e. the value of +Runtime#eval+ / +#run+ — which the success path
308
306
  # feeds to +Outcome.decode+. Captures and usage are populated by the
309
- # +ensure+ readouts ({#read_usage!} / {#read_captures!}) on every
307
+ # +ensure+ readouts (#read_usage! / #read_captures!) on every
310
308
  # outcome, so +#stdout+ / +#stderr+ / +#usage+ stay readable after a
311
309
  # rescued trap.
312
310
  # The rescue chain is the single trap-translation boundary —
@@ -27,7 +27,7 @@ module Kobako
27
27
  # Kobako::Transport::Dispatcher.dispatch(request_bytes, namespaces, handler, yield_to_guest)
28
28
  # # => msgpack-encoded Response bytes (never raises)
29
29
  module Dispatcher
30
- # Throw tag for the {Yielder}'s break unwind back to the
30
+ # Throw tag for the Yielder's break unwind back to the
31
31
  # dispatcher's +catch+ frame. +private_constant+ is a
32
32
  # convention boundary — not a defence.
33
33
  BREAK_THROW = :__kobako_break__
@@ -54,12 +54,12 @@ module Kobako
54
54
  # Callable gadget types whose own public methods are reflection surface
55
55
  # (+Proc#binding+ reaches +Binding#eval+, +Method#receiver+ / +#unbind+
56
56
  # hand back the underlying object) rather than Service behaviour. Only
57
- # {CALLABLE_ALLOW} is reachable on a target of these types; a bound
57
+ # CALLABLE_ALLOW is reachable on a target of these types; a bound
58
58
  # lambda stays invocable, its reflective surface does not.
59
59
  GADGET_OWNERS = [Proc, Method, UnboundMethod, Binding].freeze
60
60
  private_constant :GADGET_OWNERS
61
61
 
62
- # The sole methods reachable on a {GADGET_OWNERS} target: invoking it
62
+ # The sole methods reachable on a GADGET_OWNERS target: invoking it
63
63
  # (+call+ / +[]+ / +yield+) and the harmless +arity+ / +lambda?+
64
64
  # describers that aid guest-side debugging.
65
65
  CALLABLE_ALLOW = %i[call [] yield arity lambda?].freeze
@@ -91,7 +91,7 @@ module Kobako
91
91
  end
92
92
 
93
93
  # Resolve positional and keyword arguments off +request+ in one
94
- # step. Both pass through {#resolve_arg} so Capability Handles
94
+ # step. Both pass through #resolve_arg so Capability Handles
95
95
  # round-trip back to the host-side Ruby object before the call
96
96
  # reaches +public_send+.
97
97
  def resolve_call_args(request, handler)
@@ -101,7 +101,7 @@ module Kobako
101
101
 
102
102
  # Map an error caught at the dispatch boundary to a +Response.error+
103
103
  # envelope (binary msgpack). +error+ is the +StandardError+ caught by
104
- # {#dispatch}'s rescue; the +type+ field tells the guest which kind
104
+ # #dispatch's rescue; the +type+ field tells the guest which kind
105
105
  # of failure it was so it can raise the matching proxy-side error.
106
106
  def encode_caught_error(error)
107
107
  case error
@@ -119,8 +119,8 @@ module Kobako
119
119
  # reject calls to no-kwarg methods when the wire carries the
120
120
  # uniform empty-map shape.
121
121
  #
122
- # +yielder+ is the host-side {Yielder} materialised when the guest
123
- # call site supplied a block; its {Yielder#to_proc}
122
+ # +yielder+ is the host-side Yielder materialised when the guest
123
+ # call site supplied a block; its Yielder#to_proc
124
124
  # rides the +&block+ slot. +&nil+ is a no-op block argument in Ruby,
125
125
  # so the same call site handles both cases without an explicit
126
126
  # conditional.
@@ -137,8 +137,8 @@ module Kobako
137
137
  end
138
138
 
139
139
  # Guard the +public_send+ below against ambient reflection methods.
140
- # A public method whose owner is a {META_OWNERS} or {GADGET_OWNERS} module is
141
- # rejected, except {CALLABLE_ALLOW} on a gadget target (a bound lambda
140
+ # A public method whose owner is a META_OWNERS or GADGET_OWNERS module is
141
+ # rejected, except CALLABLE_ALLOW on a gadget target (a bound lambda
142
142
  # stays invocable). A name with no concrete public method is allowed
143
143
  # only when the target opts into it via +respond_to?+ (dynamic
144
144
  # +method_missing+ Services), since the dangerous methods are all
@@ -159,7 +159,7 @@ module Kobako
159
159
  # Consult the target's opt-in narrowing predicate. A bound object
160
160
  # may define a private +respond_to_guest?(name)+ to restrict which of its
161
161
  # methods the guest reaches; a falsy answer rejects the dispatch.
162
- # The predicate composes beneath {#reject_meta_method!} — it only narrows,
162
+ # The predicate composes beneath #reject_meta_method! — it only narrows,
163
163
  # never re-opening the reflection surface the floor rejects — and is
164
164
  # consulted with the private surface included so the guest's +public_send+
165
165
  # dispatch can never reach +respond_to_guest?+ itself.
@@ -213,7 +213,7 @@ module Kobako
213
213
  # Encode +value+ as a +Response.ok+ envelope. When the value is not
214
214
  # wire-representable per the codec's type mapping, the
215
215
  # +UnsupportedType+ rescue routes it through the
216
- # Catalog::Handles via {#wrap_as_handle} and re-encodes with the Capability
216
+ # Catalog::Handles via #wrap_as_handle and re-encodes with the Capability
217
217
  # Handle in place. The happy path encodes exactly once.
218
218
  def encode_ok(value, handler)
219
219
  response = Kobako::Transport::Response.ok(value)
@@ -224,15 +224,18 @@ module Kobako
224
224
 
225
225
  # Allocate +value+ in the Sandbox's Catalog::Handles and return a +Handle+
226
226
  # that the wire codec can carry. Used as the fallback path of
227
- # {#encode_ok} when +value+ has no wire representation.
227
+ # #encode_ok when +value+ has no wire representation.
228
228
  def wrap_as_handle(value, handler)
229
229
  handler.alloc(value)
230
230
  end
231
231
 
232
+ # +message+ folds to UTF-8 first: Ruby core builds some exception
233
+ # messages as ASCII-8BIT (the arity ArgumentError, for one), and
234
+ # the codec would ride those as bin — a shape the guest refuses.
232
235
  def encode_error(type, message)
236
+ message = message.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
233
237
  fault = Kobako::Fault.new(type: type, message: message)
234
- response = Kobako::Transport::Response.error(fault)
235
- response.encode
238
+ Kobako::Transport::Response.error(fault).encode
236
239
  end
237
240
  end
238
241
  end
@@ -13,7 +13,7 @@ module Kobako
13
13
  # 5-element msgpack array:
14
14
  # +[target, method_name, args, kwargs, block_given]+. +target+ is
15
15
  # either a +String+ (+"<Namespace>::<Member>"+, e.g. +"MyService::KV"+)
16
- # or a {Handle}. SPEC pins +kwargs+ map keys to ext 0x00 Symbol;
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
19
  # site supplied a block; the block body itself never crosses the
@@ -43,7 +43,7 @@ module Kobako
43
43
  Codec::Encoder.encode([target, method_name, args, kwargs, block_given])
44
44
  end
45
45
 
46
- # Decode +bytes+ into a {Request}. Raises +Codec::InvalidType+ when the
46
+ # Decode +bytes+ into a Request. Raises +Codec::InvalidType+ when the
47
47
  # envelope is not the expected 5-element msgpack array, or when the
48
48
  # Value Object's construction invariants reject the decoded fields.
49
49
  def self.decode(bytes)
@@ -20,7 +20,7 @@ module Kobako
20
20
  #
21
21
  # 2-element msgpack array: +[status, value-or-fault]+. +status+ is 0
22
22
  # (success) or 1 (fault). For success the second element is the return
23
- # value; for fault it is a {Fault} (ext 0x02 envelope).
23
+ # value; for fault it is a Fault (ext 0x02 envelope).
24
24
  #
25
25
  # Built on the +class X < Data.define(...)+ subclass form so the
26
26
  # class body is fully Steep-visible; see +lib/kobako/outcome/panic.rb+
@@ -58,7 +58,7 @@ module Kobako
58
58
  Codec::Encoder.encode([status, payload])
59
59
  end
60
60
 
61
- # Decode +bytes+ into a {Response}. Raises +Codec::InvalidType+ when the
61
+ # Decode +bytes+ into a Response. Raises +Codec::InvalidType+ when the
62
62
  # envelope is not the expected 2-element msgpack array, or when the
63
63
  # Value Object's construction invariants reject the decoded fields.
64
64
  def self.decode(bytes)
@@ -51,7 +51,7 @@ module Kobako
51
51
  # Encode this Run to the msgpack bytes the guest's +__kobako_run+
52
52
  # entry point consumes as its command-buffer payload
53
53
  # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
54
- # Walks +args+ / +kwargs+ through {Codec::HandleWalk.deep_wrap} so
54
+ # Walks +args+ / +kwargs+ through Codec::HandleWalk.deep_wrap so
55
55
  # any non-wire-representable leaf is allocated into +handler+ and
56
56
  # replaced with a +Kobako::Handle+; the
57
57
  # +handler+ argument is the Sandbox's table, sharing the same
@@ -10,9 +10,9 @@ module Kobako
10
10
  # First byte of the YieldResponse for the success branch — body is
11
11
  # the block's return value encoded as a single msgpack value.
12
12
  TAG_OK = 0x01
13
- # First byte for `break val` — body is the break value.
13
+ # First byte for +break val+ — body is the break value.
14
14
  TAG_BREAK = 0x02
15
- # Reserved for future `return val` support; both sides reject this
15
+ # Reserved for future +return val+ support; both sides reject this
16
16
  # tag as a wire violation (YieldResponse envelope contract).
17
17
  TAG_RESERVED = 0x03
18
18
  # First byte for an error / fault outcome — body is a
@@ -58,7 +58,7 @@ module Kobako
58
58
  [tag].pack("C") + Codec::Encoder.encode(value)
59
59
  end
60
60
 
61
- # Decode +bytes+ into a {Yield}. Rejects empty input, the reserved
61
+ # Decode +bytes+ into a Yield. Rejects empty input, the reserved
62
62
  # tag 0x03, and any tag outside +LIVE_TAGS+ by raising
63
63
  # +Kobako::Codec::InvalidType+ — these are wire violations per the
64
64
  # SPEC's YieldResponse envelope contract.
@@ -13,7 +13,7 @@ module Kobako
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
- # {#to_proc}; +yield val+ / +block.call(val)+ invokes {#yield}, which
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
18
  # +yield_to_guest+ lambda, and reifies the +YieldResponse+ into Ruby
19
19
  # control flow:
@@ -24,13 +24,13 @@ module Kobako
24
24
  # * +tag 0x04+ error — raise the +{class, message}+ payload at the
25
25
  # Service's yield site
26
26
  #
27
- # The Dispatcher calls {#invalidate!} from its +ensure+ block once
27
+ # The Dispatcher calls #invalidate! from its +ensure+ block once
28
28
  # dispatch completes; any later call to a stashed Yielder then raises
29
29
  # +LocalJumpError+ — the observable shape of an escaped Yielder.
30
30
  class Yielder
31
31
  # +yield_to_guest+ is a +String → String+ callable (the ext's
32
32
  # per-dispatch +Kobako::Runtime::GuestYielder+) that
33
- # {#yield} invokes to re-enter the guest; +break_tag+ is the +catch+
33
+ # #yield invokes to re-enter the guest; +break_tag+ is the +catch+
34
34
  # throw tag the Dispatcher matches against to unwind the Service on
35
35
  # +tag 0x02+. +handler+ is the Sandbox's +Kobako::Catalog::Handles+,
36
36
  # used to restore a Capability Handle in the block's ok value back to
@@ -44,7 +44,7 @@ module Kobako
44
44
 
45
45
  # Re-enter the guest with +args+ and reify the YieldResponse into
46
46
  # Ruby control flow. Raises +LocalJumpError+ if called after
47
- # {#invalidate!}. The ok value is consumed by the host Service
47
+ # #invalidate!. The ok value is consumed by the host Service
48
48
  # method, so a Capability Handle in it is restored to its host object.
49
49
  # The break value unwinds past the Service back to the guest
50
50
  # Member call, so it passes through verbatim — a Handle stays a
@@ -60,14 +60,14 @@ module Kobako
60
60
  raise yield_failure(response.value, default: "yield error")
61
61
  end
62
62
 
63
- # The Proc the Dispatcher passes as +&block+, binding {#yield} so a
63
+ # The Proc the Dispatcher passes as +&block+, binding #yield so a
64
64
  # Service method's +yield+ / +block.call+ drives the round-trip.
65
65
  def to_proc
66
66
  method(:yield).to_proc
67
67
  end
68
68
 
69
69
  # Mark this Yielder dead. Called by the Dispatcher's +ensure+ block
70
- # when the originating dispatch frame returns; any later {#yield}
70
+ # when the originating dispatch frame returns; any later #yield
71
71
  # call then raises +LocalJumpError+.
72
72
  def invalidate!
73
73
  @active = false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.0"
5
5
  end