kobako 0.26.0-aarch64-linux → 0.28.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +103 -0
  4. data/README.md +156 -118
  5. data/ROADMAP.md +23 -22
  6. data/SECURITY.md +1 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +38 -0
  8. data/crates/kobako-runtime/README.md +10 -12
  9. data/crates/kobako-transport/CHANGELOG.md +35 -0
  10. data/crates/kobako-transport/README.md +43 -27
  11. data/crates/kobako-wasmtime/CHANGELOG.md +51 -0
  12. data/crates/kobako-wasmtime/README.md +8 -10
  13. data/data/kobako.wasm +0 -0
  14. data/lib/kobako/3.3/kobako.so +0 -0
  15. data/lib/kobako/3.4/kobako.so +0 -0
  16. data/lib/kobako/4.0/kobako.so +0 -0
  17. data/lib/kobako/capture.rb +1 -4
  18. data/lib/kobako/catalog/extensions.rb +21 -59
  19. data/lib/kobako/catalog/flags.rb +41 -0
  20. data/lib/kobako/catalog/handles.rb +7 -54
  21. data/lib/kobako/catalog/services.rb +7 -61
  22. data/lib/kobako/catalog/snippets.rb +42 -66
  23. data/lib/kobako/catalog.rb +2 -10
  24. data/lib/kobako/codec/decoder.rb +9 -28
  25. data/lib/kobako/codec/encoder.rb +3 -22
  26. data/lib/kobako/codec/error.rb +6 -27
  27. data/lib/kobako/codec/ext_types.rb +72 -94
  28. data/lib/kobako/codec/handle_walk.rb +65 -130
  29. data/lib/kobako/codec/nesting.rb +3 -8
  30. data/lib/kobako/codec/state.rb +4 -10
  31. data/lib/kobako/codec/utils.rb +6 -34
  32. data/lib/kobako/codec.rb +3 -22
  33. data/lib/kobako/constant_name.rb +8 -0
  34. data/lib/kobako/context.rb +51 -112
  35. data/lib/kobako/errors.rb +51 -127
  36. data/lib/kobako/execution.rb +16 -21
  37. data/lib/kobako/extension.rb +23 -40
  38. data/lib/kobako/handle.rb +7 -12
  39. data/lib/kobako/outcome.rb +51 -80
  40. data/lib/kobako/payload/arguments.rb +1 -16
  41. data/lib/kobako/payload.rb +4 -12
  42. data/lib/kobako/pool.rb +23 -53
  43. data/lib/kobako/runtime.rb +7 -21
  44. data/lib/kobako/sandbox.rb +96 -133
  45. data/lib/kobako/sandbox_options.rb +38 -84
  46. data/lib/kobako/snippet/binary.rb +1 -16
  47. data/lib/kobako/snippet/source.rb +1 -16
  48. data/lib/kobako/snippet.rb +1 -9
  49. data/lib/kobako/transport/call.rb +1 -14
  50. data/lib/kobako/transport/dispatcher.rb +112 -195
  51. data/lib/kobako/transport/error.rb +6 -20
  52. data/lib/kobako/transport/exposure.rb +5 -10
  53. data/lib/kobako/transport/reflection.rb +39 -50
  54. data/lib/kobako/transport/run.rb +17 -71
  55. data/lib/kobako/transport/yielder.rb +21 -73
  56. data/lib/kobako/transport.rb +3 -15
  57. data/lib/kobako/unresolved.rb +4 -11
  58. data/lib/kobako/usage.rb +2 -6
  59. data/lib/kobako/version.rb +1 -1
  60. data/sig/kobako/catalog/extensions.rbs +2 -2
  61. data/sig/kobako/catalog/flags.rbs +17 -0
  62. data/sig/kobako/catalog/services.rbs +0 -6
  63. data/sig/kobako/catalog/snippets.rbs +4 -4
  64. data/sig/kobako/codec/ext_types.rbs +10 -8
  65. data/sig/kobako/codec/handle_walk.rbs +9 -7
  66. data/sig/kobako/codec/utils.rbs +2 -2
  67. data/sig/kobako/codec.rbs +2 -2
  68. data/sig/kobako/constant_name.rbs +3 -0
  69. data/sig/kobako/context.rbs +2 -3
  70. data/sig/kobako/errors.rbs +6 -0
  71. data/sig/kobako/outcome.rbs +6 -8
  72. data/sig/kobako/runtime.rbs +12 -8
  73. data/sig/kobako/sandbox.rbs +4 -0
  74. data/sig/kobako/sandbox_options.rbs +2 -6
  75. data/sig/kobako/transport/dispatcher.rbs +14 -18
  76. data/sig/kobako/transport/reflection.rbs +5 -3
  77. data/sig/kobako/transport/run.rbs +1 -3
  78. data/sig/kobako/transport/yielder.rbs +3 -3
  79. data/sig/kobako/transport.rbs +7 -6
  80. metadata +6 -3
  81. data/rust-toolchain.toml +0 -11
@@ -4,31 +4,17 @@ require_relative "../errors"
4
4
 
5
5
  module Kobako
6
6
  module Transport
7
- # +Kobako::SandboxError+ subclass raised when the host detects a
8
- # structural violation of the wire contract while reading what the
9
- # guest produced — an invocation value the payload codec cannot
10
- # decode. Distinct from a Wasm trap (engine signalled the guest
11
- # runtime is unrecoverable) and from a normal sandbox-layer failure
12
- # (the script raised but the protocol was respected): a
13
- # +Transport::Error+ always indicates the guest runtime is corrupted —
14
- # the only safe recovery is to discard the Sandbox and start a new
15
- # invocation.
16
- #
17
- # Inherits from +Kobako::SandboxError+ so a single
18
- # +rescue Kobako::SandboxError+ still catches it; callers that want
19
- # to distinguish wire-violation paths from script failures can
20
- # +rescue Kobako::Transport::Error+ directly.
7
+ # The guest broke the exchange with the host: it produced a value that
8
+ # could not be decoded, or a dispatch it made could not complete. Being a
9
+ # SandboxError, it is caught by rescuing that too.
21
10
  class Error < Kobako::SandboxError
22
- def initialize(message, diagnostic: nil, **)
11
+ def initialize(message, diagnostic: nil, **) # :nodoc:
23
12
  super(message, **)
24
13
  @diagnostic = diagnostic
25
14
  end
26
15
 
27
- # The codec fault behind this violation, appended to Ruby's own
28
- # rendering. A caller cannot act on the inner "Symbol payload must
29
- # be …" wording, so it stays out of #message; an operator
30
- # triaging a corrupted runtime still needs it, and
31
- # +detailed_message+ is where Ruby puts text of exactly that kind.
16
+ # Appends the codec's own reason, which an operator needs and a caller
17
+ # cannot act on, so #message leaves it out.
32
18
  def detailed_message(...)
33
19
  rendered = super
34
20
  return rendered if @diagnostic.nil?
@@ -6,16 +6,11 @@ require_relative "reflection"
6
6
 
7
7
  module Kobako
8
8
  module Transport
9
- # The methods a host object lets the guest call through one reference
10
- # to it — a bound path or a Capability Handle — fixed when the reference
11
- # is made. An object defining its own private +respond_to_guest?(name)+
12
- # is asked on every dispatch; any other exposes only what its own class
13
- # and the object itself define in source, since the methods a Host App
14
- # cannot foresee handing over are the ones it never wrote.
15
- #
16
- # Built on the +class X < Data.define(...)+ subclass form so the class
17
- # body is fully Steep-visible; see +.rubocop.yml+ for the rationale.
18
- class Exposure < Data.define(:object, :names)
9
+ # The methods one reference lets the guest call, fixed when the reference
10
+ # is made. Only what the object's own class and the object define in
11
+ # source is exposed, since a Host App cannot foresee handing over what it
12
+ # never wrote.
13
+ class Exposure < Data.define(:object, :names) # :nodoc:
19
14
  PREDICATE = :respond_to_guest?
20
15
  private_constant :PREDICATE
21
16
 
@@ -2,15 +2,7 @@
2
2
 
3
3
  module Kobako
4
4
  module Transport
5
- # The reflection floor a guest→host dispatch must clear before the
6
- # Dispatcher reaches +public_send+: which method names on a resolved
7
- # target count as Service behaviour, and which are Ruby's ambient
8
- # metaprogramming surface.
9
- #
10
- # Answers with a refusal reason rather than raising, so the error
11
- # taxonomy stays with the Dispatcher and this module holds only the
12
- # policy.
13
- module Reflection
5
+ module Reflection # :nodoc:
14
6
  # Modules whose instance methods are ambient Ruby reflection /
15
7
  # metaprogramming surface (+send+, +public_send+, +instance_eval+,
16
8
  # +method+, +tap+, +instance_variable_get+, ...) rather than Service
@@ -36,59 +28,56 @@ module Kobako
36
28
  # callable exposes by default, so the floor and the Exposure agree.
37
29
  CALLABLE_ALLOW = %i[call [] yield arity lambda?].freeze
38
30
 
39
- module_function
40
-
41
31
  # The reason +name+ is unreachable through +exposure+, or +nil+ when
42
32
  # the dispatch may proceed. Composes the ambient-surface floor with the
43
33
  # reference's Exposure, in that order: the Exposure only narrows and
44
34
  # can never re-open what the floor rejects.
45
- def refusal(exposure, name)
35
+ def self.refusal(exposure, name)
46
36
  ambient_refusal(exposure.object, name) ||
47
37
  (exposure.exposes?(name) ? nil : "method #{name.inspect} is not exposed to the guest")
48
38
  end
49
39
 
50
- # Guard against ambient reflection methods. A public method whose
51
- # owner is a META_OWNERS or GADGET_OWNERS module — or a singleton
52
- # class, the owner of every class-level method (+File.popen+ /
53
- # +Kernel.system+, unreachable via any fixed core-module list) — is
54
- # rejected, except CALLABLE_ALLOW on a gadget target (a bound lambda
55
- # stays invocable). A name with no concrete public method is allowed
56
- # only when the target opts into it via +respond_to?+ (dynamic
57
- # +method_missing+ Services), since the dangerous methods are all
58
- # concretely defined and therefore never reach that branch.
59
- #
60
- # +method_missing+ named explicitly is refused whatever its owner:
61
- # it is Ruby's dynamic-dispatch hook, and a public override (a
62
- # +Delegator+) binds and calls the private method the guest passes as
63
- # its first argument (+Kernel#system+). A dynamic +method_missing+
64
- # Service is untouched — the guest reaches it by the virtual name,
65
- # which resolves through the +NameError+ branch below, never by
66
- # naming +method_missing+.
67
- def ambient_refusal(target, name)
68
- return "method #{name.inspect} is not a Service method" if name == :method_missing
40
+ class << self
41
+ private
69
42
 
70
- owner = target.public_method(name).owner
71
- return nil unless ambient_owner?(owner, target)
72
- return nil if GADGET_OWNERS.include?(owner) && CALLABLE_ALLOW.include?(name)
43
+ # Guard against ambient reflection methods. A public method whose
44
+ # owner is a META_OWNERS or GADGET_OWNERS module — or a singleton
45
+ # class, the owner of every class-level method (+File.popen+ /
46
+ # +Kernel.system+, unreachable via any fixed core-module list) — is
47
+ # rejected, except CALLABLE_ALLOW on a gadget target (a bound lambda
48
+ # stays invocable). A name with no concrete public method is allowed
49
+ # only when the target opts into it via +respond_to?+ (dynamic
50
+ # +method_missing+ Services), since the dangerous methods are all
51
+ # concretely defined and therefore never reach that branch.
52
+ #
53
+ # +method_missing+ named explicitly is refused whatever its owner:
54
+ # it is Ruby's dynamic-dispatch hook, and a public override (a
55
+ # +Delegator+) binds and calls the private method the guest passes as
56
+ # its first argument (+Kernel#system+). A dynamic +method_missing+
57
+ # Service is untouched — the guest reaches it by the virtual name,
58
+ # which resolves through the +NameError+ branch below, never by
59
+ # naming +method_missing+.
60
+ def ambient_refusal(target, name)
61
+ return "method #{name.inspect} is not a Service method" if name == :method_missing
73
62
 
74
- "method #{name.inspect} is not a Service method"
75
- rescue NameError
76
- return nil if target.respond_to?(name)
63
+ owner = target.public_method(name).owner
64
+ return nil unless ambient_owner?(owner, target)
65
+ return nil if GADGET_OWNERS.include?(owner) && CALLABLE_ALLOW.include?(name)
77
66
 
78
- "no public method #{name.inspect} on target"
79
- end
67
+ "method #{name.inspect} is not a Service method"
68
+ rescue NameError
69
+ return nil if target.respond_to?(name)
70
+
71
+ "no public method #{name.inspect} on target"
72
+ end
80
73
 
81
- # Whether +name+'s +owner+ is ambient surface rather than Service
82
- # behaviour: a core meta module, a callable gadget type, or — when
83
- # +target+ is itself a Class or Module bound as a Service — the
84
- # singleton class that owns its class-level API (+File.popen+ /
85
- # +Kernel.system+, unreachable via any fixed core-module list). A plain
86
- # object's own singleton method (+def obj.x+) stays reachable, since
87
- # +target+ is not a Module there.
88
- def ambient_owner?(owner, target)
89
- META_OWNERS.include?(owner) ||
90
- GADGET_OWNERS.include?(owner) ||
91
- (target.is_a?(Module) && owner.singleton_class?)
74
+ # A plain object's own singleton method (+def obj.x+) stays reachable,
75
+ # since only a Module target counts its singleton class as ambient.
76
+ def ambient_owner?(owner, target)
77
+ META_OWNERS.include?(owner) ||
78
+ GADGET_OWNERS.include?(owner) ||
79
+ (target.is_a?(Module) && owner.singleton_class?)
80
+ end
92
81
  end
93
82
  end
94
83
  end
@@ -1,44 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../constant_name"
3
4
  require_relative "../handle"
4
5
  require_relative "../codec"
5
6
  require_relative "../payload"
6
7
 
7
8
  module Kobako
8
- # See lib/kobako/transport.rb for the umbrella module doc; this file
9
- # owns +Run+, the host-side value object for one +#run+ request — the
10
- # native side frames it into the envelope +__kobako_run+ consumes.
11
9
  module Transport
12
- # Host-side value object for a single +Sandbox#run+ invocation
13
- # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
14
- #
15
- # A Run captures the host-layer concept of "a single +#run+
16
- # call": the entrypoint constant name plus its positional and keyword
17
- # arguments. Host pre-flight (entrypoint type / name pattern, forged
18
- # Handle, kwargs-key type) is enforced at construction so the Value
19
- # Object is the single source of truth — anything that passes
20
- # +Run.new+ is safe to ship to the guest.
21
- #
22
- # +#payload+ takes the invocation's +Catalog::Handles+ and routes any
23
- # non-wire-representable +args+ / +kwargs+ leaf through it as a
24
- # +Kobako::Handle+ — the symmetric counterpart of the guest→host wrap
25
- # path in the dispatcher. A +Kobako::Handle+ that arrives **already
26
- # constructed** in the caller's +args+ / +kwargs+ is rejected at
27
- # construction: legitimate Handles only enter Host App code through
28
- # error fields, so a Handle reaching the call site is by definition
29
- # smuggled in. +Runtime#run+ frames that payload with the entrypoint
30
- # into the Run envelope the +__kobako_run+ command buffer carries.
31
- #
32
- # Built on the +class X < Data.define(...)+ subclass form (the
33
- # Steep-friendly shape — see +.rubocop.yml+ for the rationale).
34
- class Run < Data.define(:entrypoint, :args, :kwargs)
35
- # Ruby constant-name pattern enforced on the +entrypoint+ Symbol.
36
- # Parallel to
37
- # +Kobako::Catalog::Snippets::NAME_PATTERN+; the two constants name the
38
- # same regex but cover distinct surfaces (snippet identity vs.
39
- # entrypoint resolution) so a future divergence stays local.
40
- NAME_PATTERN = /\A[A-Z]\w*\z/
41
-
10
+ # A Handle already in the arguments is refused, since a caller never
11
+ # legitimately holds one.
12
+ class Run < Data.define(:entrypoint, :args, :kwargs) # :nodoc:
42
13
  def initialize(entrypoint:, args: [], kwargs: {})
43
14
  entrypoint = normalize_entrypoint(entrypoint)
44
15
  validate_args!(args)
@@ -46,61 +17,42 @@ module Kobako
46
17
  super
47
18
  end
48
19
 
49
- # Encode this Run's arguments as the codec payload the Run
50
- # envelope carries — +Runtime#run+ frames it with the entrypoint.
51
- # Walks +args+ / +kwargs+ through Codec::HandleWalk.deep_wrap so
52
- # any non-wire-representable leaf is allocated into +handler+ and
53
- # replaced with a +Kobako::Handle+; the +handler+ argument is the
54
- # invocation's table, sharing the same allocator the guest→host
55
- # return path uses. A wrapped leaf rides as ext 0x01 in its
56
- # original position (docs/wire/payload-msgpack.md § ext 0x01).
57
- # The walk starts one level down, since +args+ and +kwargs+ ride
58
- # inside the payload document the wire's nesting bound is counted
59
- # from.
60
- def payload(handler)
20
+ # A leaf with no wire form becomes a Capability Handle in the
21
+ # invocation's table. The walk starts one level down, since +args+ and
22
+ # +kwargs+ ride inside the payload document the wire's nesting bound
23
+ # is counted from.
24
+ def payload(handles)
61
25
  Payload::Arguments.new(
62
- args: Codec::HandleWalk.deep_wrap(args, handler, 1),
63
- kwargs: Codec::HandleWalk.deep_wrap(kwargs, handler, 1)
26
+ args: Codec::HandleWalk.deep_wrap(args, handles, 1),
27
+ kwargs: Codec::HandleWalk.deep_wrap(kwargs, handles, 1)
64
28
  ).encode
65
29
  end
66
30
 
67
31
  private
68
32
 
69
- # The target must be a Symbol or String (TypeError, not
70
- # ArgumentError — the wrong-type case is a Host App programming
71
- # error before the run reaches the guest). After +.to_s+
72
- # the value must match NAME_PATTERN (ArgumentError), rejecting
73
- # +::+-segmented names and any non-constant form.
33
+ # A target of the wrong type raises TypeError, a Host App programming
34
+ # error caught before the run reaches the guest.
74
35
  def normalize_entrypoint(target)
75
36
  unless target.is_a?(Symbol) || target.is_a?(String)
76
37
  raise TypeError, "entrypoint must be a Symbol or String, got #{target.class}"
77
38
  end
78
39
 
79
40
  target_str = target.to_s
80
- unless NAME_PATTERN.match?(target_str)
41
+ unless CONSTANT_NAME.match?(target_str)
81
42
  raise ArgumentError,
82
- "entrypoint must match #{NAME_PATTERN.inspect} (got #{target.inspect})"
43
+ "entrypoint must match #{CONSTANT_NAME.inspect} (got #{target.inspect})"
83
44
  end
84
45
 
85
46
  target_str.to_sym
86
47
  end
87
48
 
88
- # +args+ must not contain a +Kobako::Handle+. The Handle
89
- # allocator lives inside the Host Gem; legitimate paths surface
90
- # Handle objects only through raised error fields, so a Handle
91
- # reaching +args+ is a forged or smuggled token. Non-wire-
92
- # representable arguments that are not Handles are handled by
93
- # auto-wrap inside +#payload+ — the reject path is reserved
94
- # for Handle objects specifically.
95
49
  def validate_args!(args)
96
50
  raise ArgumentError, "arguments must be an Array" unless args.is_a?(Array)
97
51
  raise ArgumentError, forged_handle_message("arguments") if args.any?(Kobako::Handle)
98
52
  end
99
53
 
100
- # Reject a non-Symbol kwargs key, and a +Kobako::Handle+ arriving
101
- # as a kwargs value (same forged-token principle as the +args+
102
- # branch). Both checks live here so the Host App sees the
103
- # host-side error message before any encode / decode boundary.
54
+ # Checked here so the Host App sees the host-side message before any
55
+ # encode boundary.
104
56
  def validate_kwargs!(kwargs)
105
57
  raise ArgumentError, "keyword arguments must be a Hash" unless kwargs.is_a?(Hash)
106
58
 
@@ -112,12 +64,6 @@ module Kobako
112
64
  raise ArgumentError, forged_handle_message("keyword argument values") if kwargs.each_value.any?(Kobako::Handle)
113
65
  end
114
66
 
115
- # Single source of truth for the forged-Handle reject message so the
116
- # args and kwargs branches stay phrased identically. Message stays in
117
- # caller vocabulary: it names the affected slot and the reason
118
- # without leaking internal SPEC identifiers or self-referential
119
- # architecture terms — the error is raised BY kobako, so saying
120
- # "allocated by the Host Gem" reads as third-person about self.
121
67
  def forged_handle_message(slot)
122
68
  "#{slot} must not contain a Kobako::Handle — " \
123
69
  "Handles are created internally by the Sandbox and cannot be passed in"
@@ -4,51 +4,19 @@ require_relative "../codec"
4
4
  require_relative "../errors"
5
5
 
6
6
  module Kobako
7
- # See lib/kobako/transport.rb for the umbrella module doc; this file
8
- # owns the host-side object that materialises a guest-supplied block as
9
- # a Ruby callable the Service method can yield into.
10
7
  module Transport
11
- # Host-side stand-in for a guest-supplied block.
12
- #
13
- # Each guest call that carries +block_given: true+ gets a Yielder
14
- # that the Dispatcher hands to the Service method as +&block+. The
15
- # Service method observes it as an ordinary Ruby Proc through
16
- # #to_proc; +yield val+ / +block.call(val)+ invokes #yield, which
17
- # serialises the positional args, re-enters the guest via the injected
18
- # +yield_to_guest+ lambda, and reifies the Yield Reply into Ruby
19
- # control flow:
20
- #
21
- # * ok — return the decoded value to +yield+'s caller
22
- # * break — +throw break_tag, value+ so the Dispatcher's +catch+
23
- # frame unwinds the Service method
24
- # * error — raise a BlockError at the Service's yield site,
25
- # carrying the guest's class on +#klass+
26
- #
27
- # The Dispatcher calls #invalidate! from its +ensure+ block once
28
- # dispatch completes; any later call to a stashed Yielder then raises
29
- # +LocalJumpError+ — the observable shape of an escaped Yielder.
30
- class Yielder
31
- # +yield_to_guest+ is the ext's per-dispatch
32
- # +Kobako::Runtime::GuestYielder+, which #yield invokes to re-enter
33
- # the guest: it takes the argument payload and answers the reply
34
- # already split into +[arm, body, class]+. +break_tag+ is the
35
- # +catch+ throw tag the Dispatcher matches against to unwind the
36
- # Service on a break. +handler+ is the invocation's +Kobako::Catalog::Handles+,
37
- # used to restore a Capability Handle in the block's ok value back to
38
- # its host object before it reaches the Service +yield+ site.
39
- def initialize(yield_to_guest, break_tag, handler)
40
- @yield_to_guest = yield_to_guest
8
+ class Yielder # :nodoc:
9
+ def initialize(guest_yielder, break_tag, handles)
10
+ @guest_yielder = guest_yielder
41
11
  @break_tag = break_tag
42
- @handler = handler
12
+ @handles = handles
43
13
  @active = true
44
14
  @raised = nil
45
15
  end
46
16
 
47
- # The text the guest's own block failure crosses back as, or +nil+
48
- # when +error+ is not the BlockError this Yielder raised. Identity
49
- # rather than class: a Service that rescued the block's failure and
50
- # raised its own has reported something else, and the guest must
51
- # hear about that instead.
17
+ # Identity rather than class: a Service that rescued the block's
18
+ # failure and raised its own has reported something else, and the
19
+ # guest must hear about that instead.
52
20
  #
53
21
  # The class travels with the message because not every block failure
54
22
  # is an exception the guest holds — a block value the guest refused
@@ -60,17 +28,15 @@ module Kobako
60
28
  "#{raised.klass}: #{raised.message}"
61
29
  end
62
30
 
63
- # Re-enter the guest with +args+ and reify the Yield Reply into
64
- # Ruby control flow. Raises +LocalJumpError+ if called after
65
- # #invalidate!. The ok value is consumed by the host Service
66
- # method, so a Capability Handle in it is restored to its host object.
67
- # The break value unwinds past the Service back to the guest
68
- # bound-constant call, so it passes through verbatim — a Handle stays a
69
- # Handle and rides back on the same id rather than churning a new one.
31
+ # The ok value is consumed by the host Service, so a Capability Handle
32
+ # in it is restored. The break value unwinds back to the guest, so it
33
+ # passes through verbatim and rides back on the same id rather than
34
+ # churning a new one.
70
35
  def yield(*args)
71
36
  raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active
72
37
 
73
- arm, body, klass = @yield_to_guest.call(encode_args(args))
38
+ arm, body, klass = @guest_yielder.call(encode_args(args))
39
+ raise UnsupportedError, "the Sandbox runtime does not support yielding to a block" if arm == :unsupported
74
40
  raise remember(BlockError.new(body, klass: klass)) if arm == :error
75
41
 
76
42
  value, carried_handle = decode_body(body)
@@ -79,26 +45,18 @@ module Kobako
79
45
  restore(value, carried_handle)
80
46
  end
81
47
 
82
- # The Proc the Dispatcher passes as +&block+, binding #yield so a
83
- # Service method's +yield+ / +block.call+ drives the round-trip.
84
48
  def to_proc
85
49
  method(:yield).to_proc
86
50
  end
87
51
 
88
- # Mark this Yielder dead. Called by the Dispatcher's +ensure+ block
89
- # when the originating dispatch frame returns; any later #yield
90
- # call then raises +LocalJumpError+.
91
52
  def invalidate!
92
53
  @active = false
93
54
  end
94
55
 
95
56
  private
96
57
 
97
- # Encode what the Service is yielding. Arguments outside the wire
98
- # type set, or nesting past the wire's bound as one list, fail here,
99
- # before the guest is re-entered, and are restated so the Service
100
- # reads a refusal of its own argument rather than a codec class it
101
- # never named.
58
+ # Restated so the Service reads a refusal of its own argument rather
59
+ # than a codec class it never named.
102
60
  def encode_args(args)
103
61
  Kobako::Codec::Nesting.assert_within_bound!(args)
104
62
  Kobako::Codec::Encoder.encode(args)
@@ -106,33 +64,23 @@ module Kobako
106
64
  raise YieldValueError, "Service yielded a value the block cannot receive: #{e.message}"
107
65
  end
108
66
 
109
- # Hold onto the BlockError being raised so #fault_text can recognise
110
- # it if it comes back unrescued, and return it so the raise site reads
111
- # as one statement. Only the newest is kept: a Service that rescued
112
- # an earlier one and yielded again has already handled it.
67
+ # Only the newest is kept: a Service that rescued an earlier one and
68
+ # yielded again has already handled it.
113
69
  def remember(error)
114
70
  @raised = error
115
71
  end
116
72
 
117
- # Decode a value-carrying arm's payload, answering the value and
118
- # whether the decode carried a Capability Handle. The tracking
119
- # bracket opens only around this decode: the guest re-entry may run
120
- # nested dispatches whose own brackets would otherwise pollute the
121
- # signal.
73
+ # The tracking bracket opens only around this decode: the guest
74
+ # re-entry may run nested dispatches whose own brackets would
75
+ # otherwise pollute the signal.
122
76
  def decode_body(body)
123
77
  Kobako::Codec.track_handles { Kobako::Codec::Decoder.decode(body) }
124
78
  end
125
79
 
126
- # Restore any Capability Handle in a block's ok value to its host
127
- # object via the injected +Catalog::Handles+. Only the
128
- # ok path calls this — host code consumes the ok value, whereas a
129
- # break value returns to the guest and stays a Handle. A response
130
- # whose decode carried no Handle resolves to itself, so the walk is
131
- # skipped entirely.
132
80
  def restore(value, carried_handle)
133
81
  return value unless carried_handle
134
82
 
135
- Kobako::Codec::HandleWalk.deep_restore(value, @handler)
83
+ Kobako::Codec::HandleWalk.deep_restore(value, @handles)
136
84
  end
137
85
  end
138
86
  end
@@ -9,20 +9,8 @@ require_relative "transport/reflection"
9
9
  require_relative "transport/dispatcher"
10
10
 
11
11
  module Kobako
12
- # Kobako::Transport — host↔guest message transport namespace. Houses the
13
- # host side of one Call/Reply exchange: the call value objects +Call+
14
- # (guest→host, as the native side decoded it) and +Run+ (host→guest), the
15
- # +Dispatcher+ that answers a routed Call, the +Yielder+ that re-enters
16
- # the guest for a block, the +Reflection+ floor a dispatch must clear, the
17
- # +Exposure+ each reference authorizes a call against, and +Error+ for a
18
- # wire violation the host detects. Each invocation's
19
- # +Context+ composes them into the dispatch +Proc+ it passes +Runtime+ for
20
- # that run.
21
- #
22
- # "RPC" was deliberately not chosen — it implies a cross-process boundary that
23
- # kobako does not have, since host and guest share one OS thread and
24
- # one wasm linear memory. See
25
- # {SPEC.md Refinement → Internal Concepts}[link:../../SPEC.md].
26
- module Transport
12
+ # The host side of the guest↔host exchange. Only Transport::Error is
13
+ # public; the rest serves one invocation's dispatch.
14
+ module Transport # :nodoc:
27
15
  end
28
16
  end
@@ -1,17 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- # Kobako::Unresolved — the sentinel backing a fillable Service path: one
5
- # declared with +Sandbox#bind(path)+ carrying no object. It reserves the
6
- # path's Frame 1 slot so the guest sees the bound constant while the host
7
- # defers the object it stands for. A guest dispatch to an unfilled fillable
8
- # is refused as an unresolved target and surfaces as +Kobako::ServiceError+
9
- # when the guest leaves it unrescued — the same capability-failure channel
10
- # as an idiom with no backend bound.
11
- #
12
- # A single shared value; only its identity distinguishes it, so the dispatch
13
- # layer recognises it with +equal?+. A Host App may name it at a +bind+ site
14
- # to declare a fillable explicitly.
4
+ # Stands for the object of a fillable Service, one declared with
5
+ # Sandbox#bind and no object. Context#bind supplies the object for an
6
+ # invocation; a call to one left unfilled fails as a ServiceError. Passing
7
+ # it to Sandbox#bind declares a fillable explicitly.
15
8
  module Unresolved
16
9
  end
17
10
  end
data/lib/kobako/usage.rb CHANGED
@@ -12,14 +12,10 @@ module Kobako
12
12
  # cap, even when the cap was hit.
13
13
  #
14
14
  # Filled on every outcome, traps included, so a Host App that rescues a
15
- # trap can read from the error's +#execution+ how much of the budget the
15
+ # trap reads from the error's +execution+ how much of the budget the
16
16
  # invocation used.
17
- #
18
- # Built on the +class X < Data.define(...)+ subclass form (the
19
- # Steep-friendly shape — see +.rubocop.yml+ for the rationale).
20
17
  class Usage < Data.define(:wall_time, :memory_peak)
21
- # Pre-run sentinel. A fresh +Kobako::Context+ holds it until its guest
22
- # runs, so an Execution's +#usage+ is never +nil+.
18
+ # The usage of a run that has not started: no time, no memory.
23
19
  EMPTY = new(wall_time: 0.0, memory_peak: 0)
24
20
  end
25
21
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.26.0"
4
+ VERSION = "0.28.0"
5
5
  end
@@ -5,7 +5,7 @@ module Kobako
5
5
 
6
6
  def install: (untyped extension, snippets: Kobako::Catalog::Snippets, services: Kobako::Catalog::Services) -> self
7
7
 
8
- def seal!: () -> self
8
+ def assert_dependencies!: () -> self
9
9
 
10
10
  def resolve: () -> Hash[String, untyped]
11
11
 
@@ -17,7 +17,7 @@ module Kobako
17
17
 
18
18
  def validate!: (untyped extension) -> void
19
19
 
20
- def assert_dependencies!: () -> void
20
+ def unmet_dependency: () -> [untyped, untyped]?
21
21
 
22
22
  def symbolize: (untyped name) -> untyped
23
23
  end
@@ -0,0 +1,17 @@
1
+ module Kobako
2
+ module Catalog
3
+ class Flags
4
+ type tuple = [String, String, bool]
5
+
6
+ def initialize: () -> void
7
+
8
+ def entries: () -> Array[tuple]
9
+
10
+ def register: (Symbol | String name, String value, advisory: bool) -> nil
11
+
12
+ private
13
+
14
+ def utf8!: (String text, String what) -> String
15
+ end
16
+ end
17
+ end
@@ -1,8 +1,6 @@
1
1
  module Kobako
2
2
  module Catalog
3
3
  class Services
4
- NAME_PATTERN: Regexp
5
-
6
4
  def initialize: () -> void
7
5
 
8
6
  def bind: (Symbol | String path, untyped object) -> self
@@ -13,10 +11,6 @@ module Kobako
13
11
 
14
12
  def paths: () -> Array[String]
15
13
 
16
- def seal!: () -> self
17
-
18
- def sealed?: () -> bool
19
-
20
14
  private
21
15
 
22
16
  def validate_path!: (Symbol | String path) -> String
@@ -1,21 +1,21 @@
1
1
  module Kobako
2
2
  module Catalog
3
3
  class Snippets
4
- NAME_PATTERN: Regexp
5
-
6
4
  type entry = Kobako::Snippet::Source | Kobako::Snippet::Binary
7
5
 
8
6
  type tuple = [Symbol, String?, String]
9
7
 
10
8
  def initialize: () -> void
11
9
 
12
- def register: (?code: String?, ?name: (Symbol | String)?, ?binary: String?) -> (Symbol | nil)
10
+ def register: (?code: String?, ?name: (Symbol | String)?, ?binary: String?) -> nil
13
11
 
14
12
  def entries: () -> Array[tuple]
15
13
 
14
+ def compile: () { (Array[tuple] entries) -> Array[String]? } -> nil
15
+
16
16
  private
17
17
 
18
- def register_source!: (String? code, (Symbol | String)? name) -> Symbol
18
+ def register_source!: (String? code, (Symbol | String)? name) -> nil
19
19
 
20
20
  def ensure_source_args!: (String? code, (Symbol | String)? name) -> [String, Symbol | String]
21
21