kobako 0.23.0 → 0.25.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +71 -0
  4. data/Cargo.lock +156 -110
  5. data/README.md +19 -18
  6. data/ROADMAP.md +12 -12
  7. data/crates/kobako-runtime/CHANGELOG.md +7 -0
  8. data/crates/kobako-runtime/Cargo.toml +2 -2
  9. data/crates/kobako-runtime/README.md +1 -1
  10. data/crates/kobako-runtime/src/profile.rs +2 -2
  11. data/crates/kobako-transport/CHANGELOG.md +7 -0
  12. data/crates/kobako-transport/Cargo.toml +1 -1
  13. data/crates/kobako-transport/README.md +1 -1
  14. data/crates/kobako-transport/src/abi.rs +2 -0
  15. data/crates/kobako-transport/src/envelope/bytes.rs +6 -0
  16. data/crates/kobako-transport/src/envelope/call.rs +9 -0
  17. data/crates/kobako-transport/src/envelope/error_record.rs +3 -0
  18. data/crates/kobako-transport/src/envelope/fault.rs +5 -0
  19. data/crates/kobako-transport/src/envelope/invocation_frames.rs +9 -0
  20. data/crates/kobako-transport/src/envelope/outcome.rs +11 -0
  21. data/crates/kobako-transport/src/envelope/reply.rs +9 -0
  22. data/crates/kobako-transport/src/envelope/run.rs +3 -0
  23. data/crates/kobako-wasmtime/CHANGELOG.md +9 -0
  24. data/crates/kobako-wasmtime/Cargo.toml +5 -5
  25. data/crates/kobako-wasmtime/README.md +1 -1
  26. data/crates/kobako-wasmtime/src/ambient.rs +2 -0
  27. data/crates/kobako-wasmtime/src/capture.rs +8 -0
  28. data/crates/kobako-wasmtime/src/frames.rs +3 -1
  29. data/crates/kobako-wasmtime/src/guest_mem.rs +8 -0
  30. data/crates/kobako-wasmtime/src/invocation.rs +10 -0
  31. data/crates/kobako-wasmtime/src/trap.rs +6 -0
  32. data/data/kobako.wasm +0 -0
  33. data/ext/kobako/Cargo.toml +1 -1
  34. data/lib/kobako/capture.rb +8 -22
  35. data/lib/kobako/catalog/handles.rb +33 -9
  36. data/lib/kobako/catalog/services.rb +9 -7
  37. data/lib/kobako/context.rb +8 -8
  38. data/lib/kobako/handle.rb +10 -40
  39. data/lib/kobako/sandbox.rb +5 -0
  40. data/lib/kobako/transport/dispatcher.rb +28 -26
  41. data/lib/kobako/transport/exposure.rb +100 -0
  42. data/lib/kobako/transport/reflection.rb +19 -20
  43. data/lib/kobako/transport.rb +4 -2
  44. data/lib/kobako/usage.rb +11 -22
  45. data/lib/kobako/version.rb +1 -1
  46. data/release-please-config.json +1 -1
  47. data/sig/kobako/catalog/handles.rbs +2 -0
  48. data/sig/kobako/catalog/services.rbs +1 -1
  49. data/sig/kobako/context.rbs +1 -1
  50. data/sig/kobako/transport/dispatcher.rbs +7 -7
  51. data/sig/kobako/transport/exposure.rbs +43 -0
  52. data/sig/kobako/transport/reflection.rbs +1 -3
  53. data/sig/kobako/transport.rbs +11 -1
  54. metadata +3 -1
@@ -30,8 +30,8 @@ module Kobako
30
30
  @services = services
31
31
  @snippets = snippets
32
32
  @extensions = extensions
33
- @resolved = {} # : Hash[String, untyped]
34
- @overrides = {} # : Hash[String, untyped]
33
+ @resolved = {} # : Hash[String, Transport::Exposure]
34
+ @overrides = {} # : Hash[String, Transport::Exposure]
35
35
  @spent = false
36
36
  @handler = Catalog::Handles.new
37
37
  @stdout_capture = @stderr_capture = Capture::EMPTY
@@ -52,11 +52,11 @@ module Kobako
52
52
  key = path.to_s
53
53
  raise ArgumentError, "cannot override undeclared path #{key.inspect}" unless @services.bound?(key)
54
54
 
55
- @overrides[key] = object
55
+ @overrides[key] = Transport::Exposure.of(object)
56
56
  self
57
57
  end
58
58
 
59
- # Resolve a Service +path+ to the object backing it this invocation,
59
+ # Resolve a Service +path+ to the Exposure backing it this invocation,
60
60
  # layering the per-eval +ctx.bind+ overrides over this Context's
61
61
  # per-invocation provider results over the Sandbox's static base bindings.
62
62
  # An unbound path raises +KeyError+; a fillable left unfilled resolves to
@@ -67,10 +67,10 @@ module Kobako
67
67
  # handler is the sole caller.
68
68
  def lookup(path)
69
69
  key = path.to_s
70
- object = @overrides.fetch(key) { @resolved.fetch(key) { @services.lookup(path) } }
71
- raise KeyError, "service #{path} is declared but unresolved this invocation" if Unresolved.equal?(object)
70
+ exposure = @overrides.fetch(key) { @resolved.fetch(key) { @services.lookup(path) } }
71
+ raise KeyError, "service #{path} is declared but unresolved this invocation" if Unresolved.equal?(exposure.object)
72
72
 
73
- object
73
+ exposure
74
74
  end
75
75
 
76
76
  # Execute a guest mruby source string in a fresh +mrb_state+ and return the
@@ -193,7 +193,7 @@ module Kobako
193
193
  # return value. A could-not-start fault ran no invocation at all, so it
194
194
  # carries no Execution and gains only the verb prefix.
195
195
  def invoke!(verb, entrypoint: nil)
196
- @resolved = @extensions.resolve
196
+ @resolved = @extensions.resolve.transform_values { |object| Transport::Exposure.of(object) }
197
197
  begin
198
198
  snapshot = yield
199
199
  rescue Kobako::TrapError => e
data/lib/kobako/handle.rb CHANGED
@@ -1,38 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- # Wire-level value object for an ext-0x01 Capability Handle, used in both
5
- # directions across the Sandbox boundary: as a Service method's return
6
- # value (guest→host return path) and as a +#run+ argument auto-wrapped
7
- # by the host.
8
- #
9
- # SPEC pins the binary layout to fixext 4 with a 4-byte big-endian u32
10
- # payload ({docs/wire-codec.md}[link:../../docs/wire-codec.md]
11
- # § Ext Types → ext 0x01). ID 0 is reserved as the invalid sentinel;
12
- # the maximum valid ID is 0x7fff_ffff (2^31 - 1).
13
- #
14
- # The constructor is internal to the Host Gem. +Kobako::Handle.new+ is
15
- # privatised so Host App code cannot fabricate a Handle from a bare
16
- # integer; legitimate Handle instances enter Host App code only as
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
21
- # sites: +Kobako::Codec::ExtTypes#unpack_handle+ (wire decode) and
22
- # +Kobako::Codec::HandleWalk.deep_wrap+ / +Kobako::Transport::Dispatcher#wrap_as_handle+
23
- # (allocator paths). Both live inside +lib/kobako/+ and are not part
24
- # of any public surface.
25
- #
26
- # The mruby counterpart +Kobako::Handle+ lives inside the Wasm guest
27
- # under the same canonical name and shares neither code nor instances
28
- # with this host-side class.
4
+ # A Capability Handle as it crosses the wire (ext 0x01, laid out in
5
+ # {docs/wire-codec.md}[link:../../docs/wire-codec.md]), in either
6
+ # direction. Host App code cannot construct one: +.new+ is private and
7
+ # +#with+ removed, so no Handle is ever made from a caller-chosen id.
8
+ # The guest's +Kobako::Handle+ shares only the name.
29
9
  class Handle < Data.define(:id)
30
- # Inclusive lower bound on the wire Handle ID. ID 0 is reserved as
31
- # the invalid sentinel and is never allocated.
10
+ # ID 0 is the invalid sentinel and is never allocated.
32
11
  MIN_ID = 1
33
- # Inclusive upper bound on the wire Handle ID. The cap matches the
34
- # u32 signed-positive range so Handle IDs fit in a signed integer
35
- # on either side of the wire without re-encoding.
12
+ # The positive half of a signed 32-bit integer, so an id fits either
13
+ # side of the wire unchanged.
36
14
  MAX_ID = 0x7fff_ffff
37
15
 
38
16
  def initialize(id:)
@@ -45,16 +23,8 @@ module Kobako
45
23
  private_class_method :new
46
24
  undef_method :with
47
25
 
48
- # Host Gem–internal factory. Allocates the Data instance through
49
- # +Class#allocate+ and dispatches +#initialize+ explicitly so the
50
- # invariant checks still run, while keeping the public +.new+
51
- # privatised against Host App callers.
52
- #
53
- # Two collaborators call this: the codec when an ext 0x01 frame is
54
- # decoded off the wire, and the allocator paths when a host-side
55
- # Ruby object is registered into the Sandbox's +Catalog::Handles+. Both
56
- # paths live inside +lib/kobako/+ and treat this method as a
57
- # package-private constructor.
26
+ # The Host Gem's own constructor, for the wire decoder and the Handle
27
+ # allocator; it runs the same id checks +.new+ would.
58
28
  def self.restore(id)
59
29
  allocate.tap { |handle| handle.send(:initialize, id: id) }
60
30
  end
@@ -66,6 +66,11 @@ module Kobako
66
66
  # (+"MyService::KV"+ or a top-level +"File"+). Returns +self+ for
67
67
  # chaining.
68
68
  #
69
+ # The guest reaches the public methods +object+'s own class and +object+
70
+ # itself define in source, fixed at this call — nothing inherited, mixed
71
+ # in, or built into the platform. An +object+ defining a private
72
+ # +respond_to_guest?(name)+ decides instead, asked on every call.
73
+ #
69
74
  # Called with only a +path+, it declares a fillable Service:
70
75
  # +bind(path)+ reserves the path for +Kobako::Unresolved+, so the guest
71
76
  # sees the constant while the host defers the object it stands for. A
@@ -95,9 +95,9 @@ module Kobako
95
95
  # so the reply-shaping and the failure boundary stay one glance wide.
96
96
  def run(call, resolver, handler, yielder)
97
97
  arguments, carried_handle = decode_arguments(call.payload)
98
- receiver = resolve_target(call.target, resolver, handler)
98
+ exposure = resolve_target(call.target, resolver, handler)
99
99
  args, kwargs = resolve_call_args(arguments, handler, carried_handle)
100
- catch(BREAK_THROW) { invoke(receiver, call.method_name, args, kwargs, yielder) }
100
+ catch(BREAK_THROW) { invoke(exposure, call.method_name, args, kwargs, yielder) }
101
101
  end
102
102
 
103
103
  # Decode the Call's payload into its arguments, reporting whether any
@@ -150,20 +150,22 @@ module Kobako
150
150
  fault("runtime", "#{error.class}: #{error.message}")
151
151
  end
152
152
 
153
- # Dispatch +method+ on +target+. +kwargs+ is already Symbol-keyed
154
- # (the +Payload::Arguments+ invariant pins it). The empty-kwargs branch omits
155
- # the +**+ splat so Ruby 3.x's strict kwargs separation does not
156
- # reject calls to no-kwarg methods when the wire carries the
157
- # uniform empty-map shape.
153
+ # Dispatch +method+ on the object behind +exposure+, once the Exposure
154
+ # the guest's reference carries permits it. +kwargs+ is already
155
+ # Symbol-keyed (the +Payload::Arguments+ invariant pins it). The
156
+ # empty-kwargs branch omits the +**+ splat so Ruby 3.x's strict kwargs
157
+ # separation does not reject calls to no-kwarg methods when the wire
158
+ # carries the uniform empty-map shape.
158
159
  #
159
160
  # +yielder+ is the host-side Yielder materialised when the guest
160
161
  # call site supplied a block; its Yielder#to_proc
161
162
  # rides the +&block+ slot. +&nil+ is a no-op block argument in Ruby,
162
163
  # so the same call site handles both cases without an explicit
163
164
  # conditional.
164
- def invoke(target, method, args, kwargs, yielder = nil)
165
+ def invoke(exposure, method, args, kwargs, yielder = nil)
165
166
  name = method.to_sym
166
- reject_unreachable!(target, name)
167
+ reject_unreachable!(exposure, name)
168
+ target = exposure.object
167
169
  block = yielder&.to_proc
168
170
  if kwargs.empty?
169
171
  target.public_send(name, *args, &block)
@@ -173,12 +175,12 @@ module Kobako
173
175
  end
174
176
 
175
177
  # Guard the +public_send+ below: Reflection decides what counts as
176
- # Service behaviour on this target, and its refusal reason becomes
177
- # the guest's +undefined+ fault. Both the ambient-surface floor and
178
- # the target's own narrowing predicate answer through it, so a
179
- # rejected name discloses nothing about which of the two refused.
180
- def reject_unreachable!(target, name)
181
- reason = Reflection.refusal(target, name)
178
+ # Service behaviour through this reference, and its refusal reason
179
+ # becomes the guest's +undefined+ fault. Both the ambient-surface
180
+ # floor and the reference's Exposure answer through it, so a rejected
181
+ # name discloses nothing about which of the two refused.
182
+ def reject_unreachable!(exposure, name)
183
+ reason = Reflection.refusal(exposure, name)
182
184
  raise UndefinedTargetError, reason if reason
183
185
  end
184
186
 
@@ -192,18 +194,18 @@ module Kobako
192
194
  raise UndefinedTargetError, e.message
193
195
  end
194
196
 
195
- # Resolve a Call target to the Ruby object the path +resolver+ (or
196
- # Catalog::Handles) holds. The native side already discriminated the
197
- # two forms off the core envelope's +kind+ tag: a String is a bound
198
- # constant's path, an Integer is a Capability Handle id. No
199
- # else-branch is needed — the envelope layer is the system boundary
200
- # that enforces the invariant.
197
+ # Resolve a Call target to the Exposure the path +resolver+ (or
198
+ # Catalog::Handles) holds for it. The native side already
199
+ # discriminated the two forms off the core envelope's +kind+ tag: a
200
+ # String is a bound constant's path, an Integer is a Capability Handle
201
+ # id. No else-branch is needed — the envelope layer is the system
202
+ # boundary that enforces the invariant.
201
203
  def resolve_target(target, resolver, handler)
202
204
  case target
203
205
  when String
204
206
  resolve_path(target, resolver)
205
207
  when Integer
206
- require_live_object!(target, handler)
208
+ resolve_handle(target, handler)
207
209
  end
208
210
  end
209
211
 
@@ -213,10 +215,10 @@ module Kobako
213
215
  raise UndefinedTargetError, e.message
214
216
  end
215
217
 
216
- # Resolve +id+ through the Catalog::Handles. An unknown id
217
- # surfaces as UndefinedTargetError.
218
- def require_live_object!(id, handler)
219
- handler.fetch(id)
218
+ # Resolve +id+ to the Exposure its Handle was minted with. An unknown
219
+ # id surfaces as UndefinedTargetError.
220
+ def resolve_handle(id, handler)
221
+ handler.exposure(id)
220
222
  rescue Kobako::SandboxError => e
221
223
  raise UndefinedTargetError, e.message
222
224
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ require_relative "reflection"
6
+
7
+ module Kobako
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)
19
+ PREDICATE = :respond_to_guest?
20
+ private_constant :PREDICATE
21
+
22
+ # Kernel's own reflection, bound onto each object so a forwarder or a
23
+ # BasicObject answers from its own method table rather than through
24
+ # +method_missing+ / +respond_to_missing?+.
25
+ CLASS_OF = Kernel.instance_method(:class)
26
+ SINGLETON_CLASS_OF = Kernel.instance_method(:singleton_class)
27
+ SINGLETON_METHODS = Kernel.instance_method(:singleton_methods)
28
+ PRIVATE_METHODS = Kernel.instance_method(:private_methods)
29
+ private_constant :CLASS_OF, :SINGLETON_CLASS_OF, :SINGLETON_METHODS, :PRIVATE_METHODS
30
+
31
+ CALLABLE = Set.new(Reflection::CALLABLE_ALLOW).freeze
32
+ NOTHING = Set.new.freeze
33
+ private_constant :CALLABLE, :NOTHING
34
+
35
+ # The Exposure +object+ carries from this moment on. +surfaces+ caches
36
+ # each class's own surface for a caller that makes many references at
37
+ # once (a Handle table), so two objects of one class enumerate it once.
38
+ def self.of(object, surfaces = {})
39
+ klass = CLASS_OF.bind_call(object)
40
+ names = narrows_itself?(object, klass) ? nil : default_names(object, klass, surfaces)
41
+ new(object: object, names: names)
42
+ end
43
+
44
+ # Whether the guest may call +name+ through this reference. The
45
+ # object's own predicate is consulted with the private surface
46
+ # included, so the guest's +public_send+ can never reach it.
47
+ def exposes?(name)
48
+ return names.include?(name) if names
49
+
50
+ object.__send__(PREDICATE, name) ? true : false
51
+ end
52
+
53
+ class << self
54
+ private
55
+
56
+ # Whether +object+ defines the narrowing predicate itself — read off
57
+ # the method tables, so a forwarder's target or a catch-all
58
+ # +respond_to_missing?+ cannot answer for it.
59
+ def narrows_itself?(object, klass)
60
+ klass.method_defined?(PREDICATE) || klass.private_method_defined?(PREDICATE) ||
61
+ PRIVATE_METHODS.bind_call(object, false).include?(PREDICATE) ||
62
+ SINGLETON_METHODS.bind_call(object, false).include?(PREDICATE)
63
+ end
64
+
65
+ # A callable keeps its callable names; a class-level surface or a
66
+ # forwarder has no surface of its own to expose.
67
+ def default_names(object, klass, surfaces)
68
+ case object
69
+ when Proc, Method then CALLABLE
70
+ when Module, Delegator then NOTHING
71
+ else with_singleton(object, surfaces[klass] ||= class_names(object, klass))
72
+ end
73
+ end
74
+
75
+ def class_names(object, klass)
76
+ names = authored(klass)
77
+ case object
78
+ when Struct, Data then names.concat(object.members)
79
+ end
80
+ Set.new(names).freeze
81
+ end
82
+
83
+ def with_singleton(object, names)
84
+ return names if SINGLETON_METHODS.bind_call(object, false).empty?
85
+
86
+ names.union(authored(SINGLETON_CLASS_OF.bind_call(object))).freeze
87
+ end
88
+
89
+ # The public methods +owner+ itself defines in source. A method with
90
+ # no source, or whose source is the platform's own, is built in.
91
+ def authored(owner)
92
+ owner.public_instance_methods(false).select do |name|
93
+ file, = owner.instance_method(name).source_location
94
+ file && !file.start_with?("<internal:")
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -32,18 +32,19 @@ module Kobako
32
32
 
33
33
  # The sole methods reachable on a GADGET_OWNERS target: invoking it
34
34
  # (+call+ / +[]+ / +yield+) and the harmless +arity+ / +lambda?+
35
- # describers that aid guest-side debugging.
35
+ # describers that aid guest-side debugging. Also the whole of what a
36
+ # callable exposes by default, so the floor and the Exposure agree.
36
37
  CALLABLE_ALLOW = %i[call [] yield arity lambda?].freeze
37
- private_constant :CALLABLE_ALLOW
38
38
 
39
39
  module_function
40
40
 
41
- # The reason +name+ is unreachable on +target+, or +nil+ when the
42
- # dispatch may proceed. Composes the ambient-surface floor with the
43
- # target's own opt-in narrowing, in that order: the predicate only
44
- # narrows and can never re-open what the floor rejects.
45
- def refusal(target, name)
46
- ambient_refusal(target, name) || narrowing_refusal(target, name)
41
+ # The reason +name+ is unreachable through +exposure+, or +nil+ when
42
+ # the dispatch may proceed. Composes the ambient-surface floor with the
43
+ # reference's Exposure, in that order: the Exposure only narrows and
44
+ # can never re-open what the floor rejects.
45
+ def refusal(exposure, name)
46
+ ambient_refusal(exposure.object, name) ||
47
+ (exposure.exposes?(name) ? nil : "method #{name.inspect} is not exposed to the guest")
47
48
  end
48
49
 
49
50
  # Guard against ambient reflection methods. A public method whose
@@ -55,7 +56,17 @@ module Kobako
55
56
  # only when the target opts into it via +respond_to?+ (dynamic
56
57
  # +method_missing+ Services), since the dangerous methods are all
57
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+.
58
67
  def ambient_refusal(target, name)
68
+ return "method #{name.inspect} is not a Service method" if name == :method_missing
69
+
59
70
  owner = target.public_method(name).owner
60
71
  return nil unless ambient_owner?(owner, target)
61
72
  return nil if GADGET_OWNERS.include?(owner) && CALLABLE_ALLOW.include?(name)
@@ -79,18 +90,6 @@ module Kobako
79
90
  GADGET_OWNERS.include?(owner) ||
80
91
  (target.is_a?(Module) && owner.singleton_class?)
81
92
  end
82
-
83
- # Consult the target's opt-in narrowing predicate. A bound object may
84
- # define a private +respond_to_guest?(name)+ to restrict which of its
85
- # methods the guest reaches; a falsy answer refuses the dispatch. It
86
- # is consulted with the private surface included so the guest's
87
- # +public_send+ dispatch can never reach +respond_to_guest?+ itself.
88
- def narrowing_refusal(target, name)
89
- return nil unless target.respond_to?(:respond_to_guest?, true)
90
- return nil if target.__send__(:respond_to_guest?, name)
91
-
92
- "method #{name.inspect} is not exposed to the guest"
93
- end
94
93
  end
95
94
  end
96
95
  end
@@ -4,6 +4,7 @@ require_relative "transport/call"
4
4
  require_relative "transport/run"
5
5
  require_relative "transport/yielder"
6
6
  require_relative "transport/error"
7
+ require_relative "transport/exposure"
7
8
  require_relative "transport/reflection"
8
9
  require_relative "transport/dispatcher"
9
10
 
@@ -12,8 +13,9 @@ module Kobako
12
13
  # host side of one Call/Reply exchange: the call value objects +Call+
13
14
  # (guest→host, as the native side decoded it) and +Run+ (host→guest), the
14
15
  # +Dispatcher+ that answers a routed Call, the +Yielder+ that re-enters
15
- # the guest for a block, the +Reflection+ floor a dispatch must clear, and
16
- # +Error+ for a wire violation the host detects. Each invocation's
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
17
19
  # +Context+ composes them into the dispatch +Proc+ it passes +Runtime+ for
18
20
  # that run.
19
21
  #
data/lib/kobako/usage.rb CHANGED
@@ -1,30 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- # Per-last-invocation resource accounting for a +Kobako::Sandbox+.
5
- # Carries two readers populated by every +#eval+ / +#run+ invocation:
4
+ # What one invocation spent against its caps, measured the way the caps
5
+ # measure it:
6
6
  #
7
- # * +wall_time+ — the Float number of seconds the guest export call
8
- # spent inside wasmtime during the most recent invocation. The
9
- # measurement bracket aligns with the +timeout+ deadline; time spent
10
- # in host Service callbacks is included, but everything that runs
11
- # after the guest export returns — the post-export
12
- # +OUTCOME_BUFFER+ fetch and decode, plus stdout / stderr capture
13
- # readout — is excluded.
14
- # * +memory_peak+ — the Integer high-water mark, in bytes, of the
15
- # per-invocation +memory.grow+ delta past the linear-memory size
16
- # captured at invocation entry. Same baseline accounting as
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.
7
+ # * +wall_time+ — Float seconds inside the guest, the span the
8
+ # +timeout+ deadline governs; Service callbacks count, reading the
9
+ # result and the captures afterwards does not.
10
+ # * +memory_peak+ — Integer bytes of memory the invocation grew,
11
+ # against the same baseline as +memory_limit+; it never exceeds the
12
+ # cap, even when the cap was hit.
22
13
  #
23
- # Both readers are populated on every outcome, including +TrapError+
24
- # branches, so the Host App can read +#usage+ off the run's
25
- # +Kobako::Execution+ — the one a raised error carries on +#execution+ —
26
- # after rescuing a trap to diagnose how much of the budget the failing
27
- # invocation consumed.
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
16
+ # invocation used.
28
17
  #
29
18
  # Built on the +class X < Data.define(...)+ subclass form (the
30
19
  # Steep-friendly shape — see +.rubocop.yml+ for the rationale).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.23.0"
4
+ VERSION = "0.25.0"
5
5
  end
@@ -5,9 +5,9 @@
5
5
  "last-release-sha": "98509af508988f708bf0d7a76a718bb0428a177e",
6
6
  "packages": {
7
7
  ".": {
8
- "component": "kobako",
9
8
  "include-component-in-tag": false,
10
9
  "release-type": "ruby",
10
+ "version-file": "lib/kobako/version.rb",
11
11
  "exclude-paths": ["wasm", "crates"]
12
12
  },
13
13
  "wasm/kobako-core": {
@@ -7,6 +7,8 @@ module Kobako
7
7
 
8
8
  def fetch: (Integer id) -> untyped
9
9
 
10
+ def exposure: (Integer id) -> Kobako::Transport::Exposure
11
+
10
12
  def size: () -> Integer
11
13
 
12
14
  private
@@ -9,7 +9,7 @@ module Kobako
9
9
 
10
10
  def bound?: (Symbol | String path) -> bool
11
11
 
12
- def lookup: (Symbol | String target) -> untyped
12
+ def lookup: (Symbol | String target) -> Kobako::Transport::Exposure
13
13
 
14
14
  def paths: () -> Array[String]
15
15
 
@@ -9,7 +9,7 @@ module Kobako
9
9
 
10
10
  def bind: (Symbol | String path, untyped object) -> self
11
11
 
12
- def lookup: (Symbol | String path) -> untyped
12
+ def lookup: (Symbol | String path) -> Kobako::Transport::Exposure
13
13
 
14
14
  def eval: (String code) ?{ (Kobako::Context) -> void } -> Kobako::Execution
15
15
 
@@ -14,9 +14,9 @@ module Kobako
14
14
 
15
15
 
16
16
 
17
- def self?.dispatch: (Kobako::Transport::Call call, Kobako::Transport::_ServiceRegistry resolver, Kobako::Codec::_HandleTable handler, Kobako::Transport::_GuestYielder yield_to_guest) -> [bool, String, String?]
17
+ def self?.dispatch: (Kobako::Transport::Call call, Kobako::Transport::_ServiceRegistry resolver, Kobako::Transport::_HandleTable handler, Kobako::Transport::_GuestYielder yield_to_guest) -> [bool, String, String?]
18
18
 
19
- def self?.run: (Kobako::Transport::Call call, Kobako::Transport::_ServiceRegistry resolver, Kobako::Codec::_HandleTable handler, Kobako::Transport::Yielder? yielder) -> untyped
19
+ def self?.run: (Kobako::Transport::Call call, Kobako::Transport::_ServiceRegistry resolver, Kobako::Transport::_HandleTable handler, Kobako::Transport::Yielder? yielder) -> untyped
20
20
 
21
21
  def self?.resolve_call_args: (Kobako::Payload::Arguments arguments, Kobako::Codec::_HandleTable handler, bool carried_handle) -> [Array[untyped], Hash[Symbol, untyped]]
22
22
 
@@ -24,18 +24,18 @@ module Kobako
24
24
 
25
25
  def self?.caught_fault: (StandardError error, Kobako::Transport::Yielder? yielder) -> [String, String]
26
26
 
27
- def self?.invoke: (untyped target, String method, Array[untyped] args, Hash[Symbol, untyped] kwargs, ?Kobako::Transport::Yielder? yielder) -> untyped
27
+ def self?.invoke: (Kobako::Transport::Exposure exposure, String method, Array[untyped] args, Hash[Symbol, untyped] kwargs, ?Kobako::Transport::Yielder? yielder) -> untyped
28
28
 
29
- def self?.reject_unreachable!: (untyped target, Symbol name) -> void
29
+ def self?.reject_unreachable!: (Kobako::Transport::Exposure exposure, Symbol name) -> void
30
30
 
31
31
 
32
32
  def self?.resolve_arg: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
33
33
 
34
- def self?.resolve_target: (String | Integer target, Kobako::Transport::_ServiceRegistry resolver, Kobako::Codec::_HandleTable handler) -> untyped
34
+ def self?.resolve_target: (String | Integer target, Kobako::Transport::_ServiceRegistry resolver, Kobako::Transport::_HandleTable handler) -> Kobako::Transport::Exposure
35
35
 
36
- def self?.resolve_path: (String path, Kobako::Transport::_ServiceRegistry resolver) -> untyped
36
+ def self?.resolve_path: (String path, Kobako::Transport::_ServiceRegistry resolver) -> Kobako::Transport::Exposure
37
37
 
38
- def self?.require_live_object!: (Integer id, Kobako::Codec::_HandleTable handler) -> untyped
38
+ def self?.resolve_handle: (Integer id, Kobako::Transport::_HandleTable handler) -> Kobako::Transport::Exposure
39
39
 
40
40
  def self?.encode_ok: (untyped value, Kobako::Codec::_HandleTable handler) -> String
41
41
 
@@ -0,0 +1,43 @@
1
+ module Kobako
2
+ module Transport
3
+ class Exposure < Data
4
+ PREDICATE: Symbol
5
+ CLASS_OF: UnboundMethod
6
+ SINGLETON_CLASS_OF: UnboundMethod
7
+ SINGLETON_METHODS: UnboundMethod
8
+ PRIVATE_METHODS: UnboundMethod
9
+ CALLABLE: Set[Symbol]
10
+ NOTHING: Set[Symbol]
11
+
12
+ attr_reader object: untyped
13
+ attr_reader names: Set[Symbol]?
14
+
15
+ def self.new: (object: untyped, names: Set[Symbol]?) -> Exposure
16
+ | (untyped, Set[Symbol]?) -> Exposure
17
+
18
+ def self.of: (untyped object, ?Hash[Module, Set[Symbol]] surfaces) -> Exposure
19
+
20
+ def initialize: (object: untyped, names: Set[Symbol]?) -> void
21
+
22
+ def with: (?object: untyped, ?names: Set[Symbol]?) -> Exposure
23
+
24
+ def ==: (untyped other) -> bool
25
+
26
+ def hash: () -> Integer
27
+
28
+ def exposes?: (Symbol name) -> bool
29
+
30
+ private
31
+
32
+ def self.narrows_itself?: (untyped object, Module klass) -> bool
33
+
34
+ def self.default_names: (untyped object, Module klass, Hash[Module, Set[Symbol]] surfaces) -> Set[Symbol]
35
+
36
+ def self.class_names: (untyped object, Module klass) -> Set[Symbol]
37
+
38
+ def self.with_singleton: (untyped object, Set[Symbol] names) -> Set[Symbol]
39
+
40
+ def self.authored: (Module owner) -> Array[Symbol]
41
+ end
42
+ end
43
+ end
@@ -5,13 +5,11 @@ module Kobako
5
5
  GADGET_OWNERS: Array[Module]
6
6
  CALLABLE_ALLOW: Array[Symbol]
7
7
 
8
- def self?.refusal: (untyped target, Symbol name) -> String?
8
+ def self?.refusal: (Kobako::Transport::Exposure exposure, Symbol name) -> String?
9
9
 
10
10
  def self?.ambient_refusal: (untyped target, Symbol name) -> String?
11
11
 
12
12
  def self?.ambient_owner?: (Module owner, untyped target) -> bool
13
-
14
- def self?.narrowing_refusal: (untyped target, Symbol name) -> String?
15
13
  end
16
14
  end
17
15
  end
@@ -20,7 +20,17 @@ module Kobako
20
20
  # production conformer; modelled structurally so the Transport
21
21
  # layer needs no upward dependency on Catalog.
22
22
  interface _ServiceRegistry
23
- def lookup: (String target) -> untyped
23
+ def lookup: (String target) -> Exposure
24
+ end
25
+
26
+ # The per-invocation Handle table as a dispatch reads it: the codec's
27
+ # allocate / restore pair, plus the Exposure a Handle was minted with,
28
+ # which authorizes a call made through it. Kobako::Catalog::Handles is
29
+ # the production conformer.
30
+ interface _HandleTable
31
+ include Kobako::Codec::_HandleTable
32
+
33
+ def exposure: (Integer id) -> Exposure
24
34
  end
25
35
  end
26
36
  end
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.23.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
@@ -138,6 +138,7 @@ files:
138
138
  - lib/kobako/transport/call.rb
139
139
  - lib/kobako/transport/dispatcher.rb
140
140
  - lib/kobako/transport/error.rb
141
+ - lib/kobako/transport/exposure.rb
141
142
  - lib/kobako/transport/reflection.rb
142
143
  - lib/kobako/transport/run.rb
143
144
  - lib/kobako/transport/yielder.rb
@@ -180,6 +181,7 @@ files:
180
181
  - sig/kobako/transport/call.rbs
181
182
  - sig/kobako/transport/dispatcher.rbs
182
183
  - sig/kobako/transport/error.rbs
184
+ - sig/kobako/transport/exposure.rbs
183
185
  - sig/kobako/transport/reflection.rbs
184
186
  - sig/kobako/transport/run.rbs
185
187
  - sig/kobako/transport/yielder.rbs