kobako 0.28.0 → 0.30.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +377 -0
  4. data/Cargo.lock +181 -154
  5. data/README.md +99 -459
  6. data/ROADMAP.md +7 -1
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +354 -0
  9. data/crates/kobako-runtime/Cargo.toml +38 -7
  10. data/crates/kobako-runtime/README.md +17 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +9 -1
  12. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  13. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  14. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  15. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  16. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  17. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  18. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  19. data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
  20. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  21. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  29. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
  30. data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
  31. data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
  32. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  33. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  37. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  38. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  39. data/crates/kobako-runtime/src/filesystem.rs +26 -0
  40. data/crates/kobako-runtime/src/grant.rs +20 -0
  41. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  42. data/crates/kobako-runtime/src/lib.rs +16 -1
  43. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  44. data/crates/kobako-runtime/src/locking.rs +218 -0
  45. data/crates/kobako-runtime/src/mount.rs +105 -0
  46. data/crates/kobako-runtime/src/profile.rs +140 -4
  47. data/crates/kobako-runtime/src/runtime.rs +12 -0
  48. data/crates/kobako-runtime/src/sched.rs +210 -0
  49. data/crates/kobako-runtime/src/security.rs +696 -0
  50. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  51. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  52. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  53. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  54. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  55. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  56. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  57. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  58. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  59. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  60. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  61. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  62. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  63. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  64. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  65. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  66. data/crates/kobako-runtime/src/vfs.rs +60 -0
  67. data/crates/kobako-runtime/src/yielder.rs +11 -6
  68. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  70. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  71. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  72. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  73. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  74. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  75. data/crates/kobako-transport/CHANGELOG.md +23 -0
  76. data/crates/kobako-transport/Cargo.toml +3 -3
  77. data/crates/kobako-transport/README.md +6 -5
  78. data/crates/kobako-transport/src/abi.rs +1 -1
  79. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  80. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  81. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  83. data/crates/kobako-transport/src/envelope/fault.rs +4 -3
  84. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  85. data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
  86. data/crates/kobako-transport/src/envelope/reply.rs +34 -16
  87. data/crates/kobako-transport/src/envelope/request.rs +19 -9
  88. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  89. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  90. data/crates/kobako-transport/src/envelope.rs +2 -2
  91. data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
  92. data/crates/kobako-wasmtime/Cargo.toml +25 -19
  93. data/crates/kobako-wasmtime/README.md +13 -7
  94. data/crates/kobako-wasmtime/src/abi.rs +75 -49
  95. data/crates/kobako-wasmtime/src/ambient.rs +64 -6
  96. data/crates/kobako-wasmtime/src/cache.rs +188 -77
  97. data/crates/kobako-wasmtime/src/config.rs +77 -5
  98. data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
  99. data/crates/kobako-wasmtime/src/driver.rs +70 -135
  100. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  101. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  102. data/crates/kobako-wasmtime/src/imports.rs +52 -0
  103. data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
  104. data/crates/kobako-wasmtime/src/invocation.rs +43 -74
  105. data/crates/kobako-wasmtime/src/lib.rs +5 -4
  106. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  107. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
  108. data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
  109. data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
  110. data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
  111. data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
  112. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  113. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
  114. data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
  115. data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
  116. data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
  117. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  118. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  119. data/crates/kobako-wasmtime/src/preview1.rs +512 -0
  120. data/crates/kobako-wasmtime/src/trap.rs +45 -25
  121. data/crates/kobako-wasmtime/src/wasi.rs +333 -0
  122. data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
  123. data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
  124. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  125. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
  126. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
  127. data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
  128. data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
  129. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
  130. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  131. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  132. data/data/kobako.wasm +0 -0
  133. data/ext/kobako/Cargo.toml +4 -2
  134. data/ext/kobako/src/runtime/bridge.rs +154 -81
  135. data/ext/kobako/src/runtime/errors.rs +1 -1
  136. data/ext/kobako/src/runtime/escape.rs +67 -0
  137. data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
  138. data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
  139. data/ext/kobako/src/runtime/filesystem.rs +208 -0
  140. data/ext/kobako/src/runtime/gvl.rs +49 -8
  141. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  142. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  143. data/ext/kobako/src/runtime.rs +125 -199
  144. data/lib/kobako/catalog/extensions.rb +5 -5
  145. data/lib/kobako/catalog/flags.rb +2 -14
  146. data/lib/kobako/catalog/grants.rb +30 -0
  147. data/lib/kobako/catalog/handles.rb +6 -6
  148. data/lib/kobako/catalog/mount.rb +19 -0
  149. data/lib/kobako/catalog/services.rb +13 -2
  150. data/lib/kobako/catalog/snippets.rb +11 -15
  151. data/lib/kobako/catalog.rb +11 -0
  152. data/lib/kobako/codec/decoder.rb +10 -9
  153. data/lib/kobako/codec/ext_types.rb +4 -6
  154. data/lib/kobako/codec/handle_walk.rb +4 -6
  155. data/lib/kobako/codec/nesting.rb +7 -7
  156. data/lib/kobako/codec/state.rb +19 -3
  157. data/lib/kobako/codec/utils.rb +1 -1
  158. data/lib/kobako/codec/value_budget.rb +86 -0
  159. data/lib/kobako/codec.rb +7 -0
  160. data/lib/kobako/context.rb +40 -13
  161. data/lib/kobako/errors.rb +15 -14
  162. data/lib/kobako/filesystem.rb +69 -0
  163. data/lib/kobako/payload/arguments.rb +2 -2
  164. data/lib/kobako/pool.rb +6 -6
  165. data/lib/kobako/sandbox.rb +55 -27
  166. data/lib/kobako/sandbox_options.rb +39 -13
  167. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
  168. data/lib/kobako/snippet/source.rb +1 -1
  169. data/lib/kobako/snippet.rb +1 -1
  170. data/lib/kobako/transport/block_failure.rb +35 -0
  171. data/lib/kobako/transport/dispatcher.rb +33 -29
  172. data/lib/kobako/transport/exposure.rb +7 -9
  173. data/lib/kobako/transport/reflection.rb +79 -23
  174. data/lib/kobako/transport/run.rb +5 -5
  175. data/lib/kobako/transport/yielder.rb +9 -31
  176. data/lib/kobako/transport.rb +1 -0
  177. data/lib/kobako/version.rb +1 -1
  178. data/lib/kobako.rb +35 -0
  179. data/release-please-config.json +1 -0
  180. data/sig/kobako/catalog/extensions.rbs +3 -3
  181. data/sig/kobako/catalog/flags.rbs +0 -4
  182. data/sig/kobako/catalog/grants.rbs +13 -0
  183. data/sig/kobako/catalog/handles.rbs +3 -3
  184. data/sig/kobako/catalog/mount.rbs +24 -0
  185. data/sig/kobako/catalog/services.rbs +5 -1
  186. data/sig/kobako/catalog/snippets.rbs +4 -4
  187. data/sig/kobako/catalog.rbs +1 -0
  188. data/sig/kobako/codec/decoder.rbs +1 -1
  189. data/sig/kobako/codec/handle_walk.rbs +1 -1
  190. data/sig/kobako/codec/nesting.rbs +2 -2
  191. data/sig/kobako/codec/state.rbs +7 -1
  192. data/sig/kobako/codec/utils.rbs +1 -1
  193. data/sig/kobako/codec/value_budget.rbs +23 -0
  194. data/sig/kobako/codec.rbs +3 -2
  195. data/sig/kobako/context.rbs +14 -6
  196. data/sig/kobako/errors.rbs +4 -4
  197. data/sig/kobako/filesystem.rbs +93 -0
  198. data/sig/kobako/guest_yielder.rbs +9 -0
  199. data/sig/kobako/payload/arguments.rbs +1 -1
  200. data/sig/kobako/pool.rbs +3 -3
  201. data/sig/kobako/runtime.rbs +21 -49
  202. data/sig/kobako/sandbox.rbs +9 -5
  203. data/sig/kobako/sandbox_options.rbs +25 -3
  204. data/sig/kobako/snapshot.rbs +20 -0
  205. data/sig/kobako/snippet/bytecode.rbs +12 -0
  206. data/sig/kobako/transport/block_failure.rbs +13 -0
  207. data/sig/kobako/transport/dispatcher.rbs +4 -4
  208. data/sig/kobako/transport/exposure.rbs +2 -2
  209. data/sig/kobako/transport/reflection.rbs +14 -1
  210. data/sig/kobako/transport/run.rbs +3 -3
  211. data/sig/kobako/transport/yielder.rbs +3 -7
  212. data/sig/kobako/transport.rbs +3 -3
  213. metadata +116 -15
  214. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  215. data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
  216. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
  217. data/crates/kobako-wasmtime/src/io.rs +0 -286
  218. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -3,17 +3,6 @@
3
3
  module Kobako
4
4
  module Transport
5
5
  module Reflection # :nodoc:
6
- # Modules whose instance methods are ambient Ruby reflection /
7
- # metaprogramming surface (+send+, +public_send+, +instance_eval+,
8
- # +method+, +tap+, +instance_variable_get+, ...) rather than Service
9
- # behaviour. A guest-supplied method name resolving to one of these is
10
- # rejected: only methods the bound object itself exposes as Service
11
- # behaviour are reachable, and +public_send(:send, ...)+ would
12
- # otherwise let a guest pivot through +send+ into the private
13
- # +Kernel#eval+ / +#system+ surface (host RCE).
14
- META_OWNERS = [BasicObject, Kernel, Object, Module, Class].freeze
15
- private_constant :META_OWNERS
16
-
17
6
  # Callable gadget types whose own public methods are reflection surface
18
7
  # (+Proc#binding+ reaches +Binding#eval+, +Method#receiver+ / +#unbind+
19
8
  # hand back the underlying object) rather than Service behaviour. Only
@@ -37,11 +26,33 @@ module Kobako
37
26
  (exposure.exposes?(name) ? nil : "method #{name.inspect} is not exposed to the guest")
38
27
  end
39
28
 
29
+ # Whether +method+ is written in Ruby rather than built into the
30
+ # platform or copied from the surface every object shares. A native
31
+ # method has no source, and a built-in one names an +<internal:>+ file,
32
+ # so a copy, alias or rebinding of either keeps that mark wherever it
33
+ # is reached from.
34
+ def self.authored?(method)
35
+ file, = method.source_location
36
+ !file.nil? && !file.start_with?("<internal:") && !ambient_copy?(method)
37
+ end
38
+
40
39
  class << self
41
40
  private
42
41
 
42
+ # Whether +method+ is a copy, alias or rebinding of a Ruby method
43
+ # +Class+ inherits, such as the +name!+ alias OpenStruct makes of a
44
+ # mixed-in +try+. The copy is owned by the class that made it, but
45
+ # keeps the original's name and source.
46
+ def ambient_copy?(method)
47
+ name = method.original_name
48
+ Class.ancestors.any? do |owner|
49
+ (owner.method_defined?(name, false) || owner.private_method_defined?(name, false)) &&
50
+ owner.instance_method(name).source_location == method.source_location
51
+ end
52
+ end
53
+
43
54
  # Guard against ambient reflection methods. A public method whose
44
- # owner is a META_OWNERS or GADGET_OWNERS module — or a singleton
55
+ # owner +Class+ inherits or a GADGET_OWNERS module — or a singleton
45
56
  # class, the owner of every class-level method (+File.popen+ /
46
57
  # +Kernel.system+, unreachable via any fixed core-module list) — is
47
58
  # rejected, except CALLABLE_ALLOW on a gadget target (a bound lambda
@@ -55,26 +66,71 @@ module Kobako
55
66
  # +Delegator+) binds and calls the private method the guest passes as
56
67
  # its first argument (+Kernel#system+). A dynamic +method_missing+
57
68
  # 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+.
69
+ # which resolves through the +respond_to?+ opt-in, never by naming
70
+ # +method_missing+.
71
+ #
72
+ # A public method must also be authored, so a Service narrowing itself
73
+ # never reaches a built-in or ambient method by a copy, alias or
74
+ # rebinding an owner check cannot see; a non-public one is never
75
+ # reached through a dynamic opt-in either.
76
+ #
77
+ # A name with no method never reaches +instance_method+, which would
78
+ # keep a guest-chosen name as a Symbol for the life of the process.
60
79
  def ambient_refusal(target, name)
61
- return "method #{name.inspect} is not a Service method" if name == :method_missing
80
+ return service_refusal(name) if name == :method_missing
81
+
82
+ klass = lookup_class(target)
83
+ return public_method_refusal(target, name, klass) if klass.public_method_defined?(name)
84
+ return "no public method #{name.inspect} on target" unless target.respond_to?(name)
85
+ return service_refusal(name) if klass.method_defined?(name) || klass.private_method_defined?(name)
62
86
 
63
- owner = target.public_method(name).owner
87
+ owner_refusal(target, name, klass)
88
+ end
89
+
90
+ def public_method_refusal(target, name, klass)
91
+ method = klass.instance_method(name)
92
+ owner_refusal(target, name, method.owner) ||
93
+ (authored_for?(target, name, method, klass) ? nil : service_refusal(name))
94
+ end
95
+
96
+ # A bound callable keeps its callable names and a record its member
97
+ # readers, though the platform defines both.
98
+ def authored_for?(target, name, method, klass)
99
+ return true if Reflection.authored?(method)
100
+ return true if GADGET_OWNERS.include?(method.owner) && CALLABLE_ALLOW.include?(name)
101
+
102
+ (klass <= Struct || klass <= Data) && target.members.include?(name)
103
+ end
104
+
105
+ def service_refusal(name)
106
+ "method #{name.inspect} is not a Service method"
107
+ end
108
+
109
+ def owner_refusal(target, name, owner)
64
110
  return nil unless ambient_owner?(owner, target)
65
111
  return nil if GADGET_OWNERS.include?(owner) && CALLABLE_ALLOW.include?(name)
66
112
 
67
- "method #{name.inspect} is not a Service method"
68
- rescue NameError
69
- return nil if target.respond_to?(name)
113
+ service_refusal(name)
114
+ end
115
+
116
+ # The class a method lookup on +target+ starts at. A plain object's
117
+ # singleton class is taken only when it already holds a public
118
+ # method, since asking for it otherwise would create one.
119
+ def lookup_class(target)
120
+ return target.singleton_class if target.is_a?(Module) || target.singleton_methods.any?
70
121
 
71
- "no public method #{name.inspect} on target"
122
+ target.class
72
123
  end
73
124
 
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.
125
+ # Whatever +Class+ inherits is surface every object shares rather
126
+ # than Service behaviour: the reflection +BasicObject+ through +Class+
127
+ # define (+send+ pivots into +Kernel#eval+, a host RCE), and every
128
+ # module a library mixes into them, such as a +try+ dispatching by
129
+ # name or a +to_json+ dumping instance variables. A plain object's
130
+ # own singleton method (+def obj.x+) stays reachable, since only a
131
+ # Module target counts its singleton class as ambient.
76
132
  def ambient_owner?(owner, target)
77
- META_OWNERS.include?(owner) ||
133
+ owner >= Class ||
78
134
  GADGET_OWNERS.include?(owner) ||
79
135
  (target.is_a?(Module) && owner.singleton_class?)
80
136
  end
@@ -12,8 +12,8 @@ module Kobako
12
12
  class Run < Data.define(:entrypoint, :args, :kwargs) # :nodoc:
13
13
  def initialize(entrypoint:, args: [], kwargs: {})
14
14
  entrypoint = normalize_entrypoint(entrypoint)
15
- validate_args!(args)
16
- validate_kwargs!(kwargs)
15
+ validate_args(args)
16
+ validate_kwargs(kwargs)
17
17
  super
18
18
  end
19
19
 
@@ -21,7 +21,7 @@ module Kobako
21
21
  # invocation's table. The walk starts one level down, since +args+ and
22
22
  # +kwargs+ ride inside the payload document the wire's nesting bound
23
23
  # is counted from.
24
- def payload(handles)
24
+ def encode_payload(handles)
25
25
  Payload::Arguments.new(
26
26
  args: Codec::HandleWalk.deep_wrap(args, handles, 1),
27
27
  kwargs: Codec::HandleWalk.deep_wrap(kwargs, handles, 1)
@@ -46,14 +46,14 @@ module Kobako
46
46
  target_str.to_sym
47
47
  end
48
48
 
49
- def validate_args!(args)
49
+ def validate_args(args)
50
50
  raise ArgumentError, "arguments must be an Array" unless args.is_a?(Array)
51
51
  raise ArgumentError, forged_handle_message("arguments") if args.any?(Kobako::Handle)
52
52
  end
53
53
 
54
54
  # Checked here so the Host App sees the host-side message before any
55
55
  # encode boundary.
56
- def validate_kwargs!(kwargs)
56
+ def validate_kwargs(kwargs)
57
57
  raise ArgumentError, "keyword arguments must be a Hash" unless kwargs.is_a?(Hash)
58
58
 
59
59
  bad_keys = kwargs.each_key.grep_v(Symbol)
@@ -6,50 +6,34 @@ require_relative "../errors"
6
6
  module Kobako
7
7
  module Transport
8
8
  class Yielder # :nodoc:
9
- def initialize(guest_yielder, break_tag, handles)
9
+ def initialize(guest_yielder, break_tag, handles, failure)
10
10
  @guest_yielder = guest_yielder
11
11
  @break_tag = break_tag
12
12
  @handles = handles
13
+ @failure = failure
13
14
  @active = true
14
- @raised = nil
15
- end
16
-
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.
20
- #
21
- # The class travels with the message because not every block failure
22
- # is an exception the guest holds — a block value the guest refused
23
- # has a class to raise under and no object to continue.
24
- def fault_text(error)
25
- raised = @raised
26
- return if raised.nil? || !error.equal?(raised)
27
-
28
- "#{raised.klass}: #{raised.message}"
29
15
  end
30
16
 
31
17
  # 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.
18
+ # in it is restored. A block exit stays in the guest, so the Service
19
+ # is only unwound.
35
20
  def yield(*args)
36
21
  raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active
37
22
 
38
23
  arm, body, klass = @guest_yielder.call(encode_args(args))
39
24
  raise UnsupportedError, "the Sandbox runtime does not support yielding to a block" if arm == :unsupported
40
- raise remember(BlockError.new(body, klass: klass)) if arm == :error
25
+ raise @failure.remember(BlockError.new(body, klass: klass)) if arm == :error
41
26
 
42
- value, carried_handle = decode_body(body)
43
- throw @break_tag, value if arm == :break
27
+ throw @break_tag if arm == :break
44
28
 
45
- restore(value, carried_handle)
29
+ restore(*decode_body(body))
46
30
  end
47
31
 
48
32
  def to_proc
49
33
  method(:yield).to_proc
50
34
  end
51
35
 
52
- def invalidate!
36
+ def invalidate
53
37
  @active = false
54
38
  end
55
39
 
@@ -58,18 +42,12 @@ module Kobako
58
42
  # Restated so the Service reads a refusal of its own argument rather
59
43
  # than a codec class it never named.
60
44
  def encode_args(args)
61
- Kobako::Codec::Nesting.assert_within_bound!(args)
45
+ Kobako::Codec::Nesting.assert_within_bound(args)
62
46
  Kobako::Codec::Encoder.encode(args)
63
47
  rescue Kobako::Codec::Error => e
64
48
  raise YieldValueError, "Service yielded a value the block cannot receive: #{e.message}"
65
49
  end
66
50
 
67
- # Only the newest is kept: a Service that rescued an earlier one and
68
- # yielded again has already handled it.
69
- def remember(error)
70
- @raised = error
71
- end
72
-
73
51
  # The tracking bracket opens only around this decode: the guest
74
52
  # re-entry may run nested dispatches whose own brackets would
75
53
  # otherwise pollute the signal.
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "transport/call"
4
4
  require_relative "transport/run"
5
+ require_relative "transport/block_failure"
5
6
  require_relative "transport/yielder"
6
7
  require_relative "transport/error"
7
8
  require_relative "transport/exposure"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.28.0"
4
+ VERSION = "0.30.0"
5
5
  end
data/lib/kobako.rb CHANGED
@@ -1,5 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Inside +lib/+, the code sits in tiers: a tier may use the tiers below it
4
+ # and never one above.
5
+ #
6
+ # Orchestration Sandbox, Pool, Runtime (+ ext), Context
7
+ # |
8
+ # Catalog setup-time registries + the per-invocation Handle table
9
+ # |
10
+ # Transport --+ call value objects + dispatch
11
+ # Outcome ----+ guest-result attribution
12
+ # | |
13
+ # Payload <---+ the [args, kwargs] shape a Call or Run carries
14
+ # | |
15
+ # Codec <-----+ byte-level payload wire
16
+ # |
17
+ # Root dependency-free value objects and error classes
18
+ #
19
+ # The Envelope has no tier here, because the native side frames and
20
+ # decodes it.
21
+ #
22
+ # These rules keep the tiers acyclic:
23
+ #
24
+ # [A type sits at the lowest tier that needs it]
25
+ # Kobako::Handle sits at the root, for Codec.
26
+ # [A native-defined handle that needs nothing in +lib/+ sits at the root]
27
+ # Kobako::Filesystem sits at the root, for the Catalog.
28
+ # [Outcome may require <tt>transport/error.rb</tt>]
29
+ # The gem contract fixes the class name.
30
+ # [<tt>Codec.track_handles</tt> wraps only the decode call]
31
+ # Wrapping more leaks its flag into re-entry.
32
+ #
33
+ # Namespace follows dependency direction, not which tier reads a type most.
34
+ # Kobako::Transport::Error stays in Transport despite the lateral edge from
35
+ # Outcome, because its file depends only on the root <tt>errors.rb</tt>.
36
+
3
37
  require_relative "kobako/version"
4
38
 
5
39
  begin
@@ -9,6 +43,7 @@ rescue LoadError
9
43
  require "kobako/kobako"
10
44
  end
11
45
 
46
+ require_relative "kobako/filesystem"
12
47
  require_relative "kobako/errors"
13
48
  require_relative "kobako/unresolved"
14
49
  require_relative "kobako/extension"
@@ -2,6 +2,7 @@
2
2
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
3
  "release-type": "ruby",
4
4
  "bump-minor-pre-major": true,
5
+ "commit-search-depth": 1000,
5
6
  "last-release-sha": "98509af508988f708bf0d7a76a718bb0428a177e",
6
7
  "packages": {
7
8
  ".": {
@@ -5,17 +5,17 @@ module Kobako
5
5
 
6
6
  def install: (untyped extension, snippets: Kobako::Catalog::Snippets, services: Kobako::Catalog::Services) -> self
7
7
 
8
- def assert_dependencies!: () -> self
8
+ def assert_dependencies: () -> self
9
9
 
10
10
  def resolve: () -> Hash[String, untyped]
11
11
 
12
12
  private
13
13
 
14
- def install_object: (untyped backend) -> untyped
14
+ def object_of: (untyped backend) -> untyped
15
15
 
16
16
  def resolve_backend: (untyped extension, Hash[String, untyped] resolved, Hash[untyped, untyped] by_provider) -> void
17
17
 
18
- def validate!: (untyped extension) -> void
18
+ def validate: (untyped extension) -> void
19
19
 
20
20
  def unmet_dependency: () -> [untyped, untyped]?
21
21
 
@@ -8,10 +8,6 @@ module Kobako
8
8
  def entries: () -> Array[tuple]
9
9
 
10
10
  def register: (Symbol | String name, String value, advisory: bool) -> nil
11
-
12
- private
13
-
14
- def utf8!: (String text, String what) -> String
15
11
  end
16
12
  end
17
13
  end
@@ -0,0 +1,13 @@
1
+ module Kobako
2
+ module Catalog
3
+ class Grants
4
+ def initialize: () -> void
5
+
6
+ def register: (Mount grant) -> nil
7
+
8
+ def include?: (Mount grant) -> bool
9
+
10
+ def entries: (?Hash[String, Mount] overrides) -> Array[Mount::tuple]
11
+ end
12
+ end
13
+ end
@@ -13,11 +13,11 @@ module Kobako
13
13
 
14
14
  private
15
15
 
16
- def reject_unwrappable!: (untyped object) -> void
16
+ def reject_unwrappable: (untyped object) -> void
17
17
 
18
- def ensure_capacity!: () -> void
18
+ def ensure_capacity: () -> void
19
19
 
20
- def require_bound!: (Integer id) -> void
20
+ def require_bound: (Integer id) -> void
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,24 @@
1
+ module Kobako
2
+ module Catalog
3
+ class Mount < Data
4
+ type access = Kobako::Filesystem::access
5
+
6
+ type policy = ^(String path, access access) -> boolish
7
+
8
+ type tuple = [String, access, Kobako::Filesystem::mountable, policy?]
9
+
10
+ attr_reader at: String
11
+ attr_reader access: access
12
+ attr_reader filesystem: Kobako::Filesystem::mountable
13
+ attr_reader policy: policy?
14
+
15
+ def self.new: (at: untyped, access: untyped, filesystem: untyped, policy: policy?) -> Mount
16
+
17
+ def key: () -> String
18
+
19
+ def conflict: (Mount other) -> String?
20
+
21
+ def to_native: () -> tuple
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,8 @@
1
1
  module Kobako
2
2
  module Catalog
3
3
  class Services
4
+ RESERVED_NAMESPACE: String
5
+
4
6
  def initialize: () -> void
5
7
 
6
8
  def bind: (Symbol | String path, untyped object) -> self
@@ -13,7 +15,9 @@ module Kobako
13
15
 
14
16
  private
15
17
 
16
- def validate_path!: (Symbol | String path) -> String
18
+ def validate_path: (Symbol | String path) -> String
19
+
20
+ def reserved?: (String path) -> bool
17
21
 
18
22
  def collision?: (String path) -> bool
19
23
  end
@@ -1,7 +1,7 @@
1
1
  module Kobako
2
2
  module Catalog
3
3
  class Snippets
4
- type entry = Kobako::Snippet::Source | Kobako::Snippet::Binary
4
+ type entry = Kobako::Snippet::Source | Kobako::Snippet::Bytecode
5
5
 
6
6
  type tuple = [Symbol, String?, String]
7
7
 
@@ -15,11 +15,11 @@ module Kobako
15
15
 
16
16
  private
17
17
 
18
- def register_source!: (String? code, (Symbol | String)? name) -> nil
18
+ def register_source: (String? code, (Symbol | String)? name) -> nil
19
19
 
20
- def ensure_source_args!: (String? code, (Symbol | String)? name) -> [String, Symbol | String]
20
+ def ensure_source_args: (String? code, (Symbol | String)? name) -> [String, Symbol | String]
21
21
 
22
- def register_binary!: (String bytes) -> nil
22
+ def register_binary: (String bytes) -> nil
23
23
 
24
24
  def entry_tuple: (entry) -> tuple
25
25
 
@@ -1,4 +1,5 @@
1
1
  module Kobako
2
2
  module Catalog
3
+ def self.to_utf8: (String text, String what) -> String
3
4
  end
4
5
  end
@@ -6,7 +6,7 @@ module Kobako
6
6
 
7
7
  private
8
8
 
9
- def self.validate_utf8!: (untyped value) -> void
9
+ def self.validate_utf8: (untyped value) -> void
10
10
  end
11
11
  end
12
12
  end
@@ -11,7 +11,7 @@ module Kobako
11
11
 
12
12
  private
13
13
 
14
- def self.guard_nesting!: (Integer depth) -> void
14
+ def self.guard_nesting: (Integer depth) -> void
15
15
 
16
16
  def self.deep_wrap_hash: (Hash[untyped, untyped] hash, Kobako::Codec::_HandleTable handles, Integer depth) -> Hash[untyped, untyped]
17
17
 
@@ -1,9 +1,9 @@
1
1
  module Kobako
2
2
  module Codec
3
3
  module Nesting
4
- def self.assert_within_bound!: (untyped value, ?Integer depth) -> void
4
+ def self.assert_within_bound: (untyped value, ?Integer depth) -> void
5
5
 
6
- def self.assert_members_within_bound!: (Array[untyped] members, Integer depth) -> void
6
+ def self.assert_members_within_bound: (Array[untyped] members, Integer depth) -> void
7
7
  end
8
8
  end
9
9
  end
@@ -5,8 +5,14 @@ module Kobako
5
5
 
6
6
  def self.current: () -> State
7
7
 
8
+ @carried_handle: bool
9
+ @value_budget: Integer?
10
+
11
+ attr_reader value_budget: Integer?
12
+
8
13
  def track_handles: [T] { () -> T } -> [T, bool]
9
- def record_handle!: () -> void
14
+ def record_handle: () -> void
15
+ def within_value_budget: [T] (Integer? budget) { () -> T } -> T
10
16
 
11
17
  private
12
18
 
@@ -1,7 +1,7 @@
1
1
  module Kobako
2
2
  module Codec
3
3
  module Utils
4
- def self.assert_utf8!: (String string, String label) -> void
4
+ def self.assert_utf8: (String string, String label) -> void
5
5
 
6
6
  def self.with_boundary: [T] () { () -> T } -> T
7
7
  end
@@ -0,0 +1,23 @@
1
+ module Kobako
2
+ module Codec
3
+ module ValueBudget
4
+ SCALARS: Hash[Integer, Integer]
5
+
6
+ COUNTS: Hash[Integer, [Integer, Integer]]
7
+
8
+ LENGTHS: Hash[Integer, [Integer, Integer]]
9
+
10
+ UINT: Hash[Integer, String]
11
+
12
+ def self.assert_within: (String bytes, Integer? budget) -> void
13
+
14
+ private
15
+
16
+ def self.step: (String bytes, Integer pos) -> [Integer?, Integer]
17
+
18
+ def self.step_framed:(String bytes, Integer pos, Integer marker) -> [Integer?, Integer]
19
+
20
+ def self.uint: (String bytes, Integer pos, Integer width) -> Integer
21
+ end
22
+ end
23
+ end
data/sig/kobako/codec.rbs CHANGED
@@ -2,8 +2,7 @@ module Kobako
2
2
  module Codec
3
3
  MAX_NESTING_DEPTH: Integer
4
4
 
5
- # A value in the closed wire type set (docs/wire/payload-msgpack.md
6
- # § Type Mapping): the scalar leaves, a Capability Handle, and Array / Hash
5
+ # A value in the closed wire type set: the scalar leaves, a Capability Handle, and Array / Hash
7
6
  # containers of the same. The by-value payloads that cross the wire —
8
7
  # dispatch arguments and success values — inhabit this type; a Fault is
9
8
  # an envelope shape, not a wire_value.
@@ -14,6 +13,8 @@ module Kobako
14
13
 
15
14
  def self.track_handles: [T] { () -> T } -> [T, bool]
16
15
 
16
+ def self.within_value_budget: [T] (Integer? budget) { () -> T } -> T
17
+
17
18
  # The Handle allocator/resolver a wire walk works against: alloc
18
19
  # registers a non-wire-representable object and returns its Handle,
19
20
  # fetch resolves a wire Handle id back to the live object. The
@@ -1,15 +1,21 @@
1
1
  module Kobako
2
2
  class Context
3
+ type preamble = [Array[String], Array[Kobako::Catalog::Snippets::tuple], Array[Kobako::Catalog::Flags::tuple]]
4
+
5
+ @mounts: Hash[String, Kobako::Catalog::Mount]?
6
+
3
7
  def initialize: (
4
8
  runtime: Kobako::Runtime,
5
9
  services: Kobako::Catalog::Services,
6
- snippets: Kobako::Catalog::Snippets,
7
- flags: Kobako::Catalog::Flags,
8
- extensions: Kobako::Catalog::Extensions
10
+ extensions: Kobako::Catalog::Extensions,
11
+ grants: Kobako::Catalog::Grants,
12
+ preamble: preamble
9
13
  ) -> void
10
14
 
11
15
  def bind: (Symbol | String path, untyped object) -> self
12
16
 
17
+ def mount: (String at, Kobako::Filesystem::mountable filesystem, ?access: Kobako::Filesystem::access) ?{ (String path, Kobako::Filesystem::access access) -> boolish } -> self
18
+
13
19
  def lookup: (Symbol | String path) -> Kobako::Transport::Exposure
14
20
 
15
21
  def eval: (String code) ?{ (Kobako::Context) -> void } -> Kobako::Execution
@@ -18,18 +24,20 @@ module Kobako
18
24
 
19
25
  private
20
26
 
27
+ def setup: () -> Kobako::Runtime::setup
28
+
21
29
  def collect_overrides: () { (Kobako::Context) -> void } -> void
22
30
 
23
31
  def dispatch_handler: () -> Kobako::Transport::dispatch_proc
24
32
 
25
- def populate_observability!: (Kobako::Runtime::Snapshot snapshot) -> void
33
+ def populate_observability:(Kobako::Snapshot snapshot) -> void
26
34
 
27
35
  def with_verb: (Kobako::TrapError err, Symbol verb) -> Kobako::TrapError
28
36
 
29
37
  def build_execution: (untyped value, failed: bool) -> Kobako::Execution
30
38
 
31
- def settle_outcome: (Kobako::Runtime::Snapshot snapshot, Symbol verb, Symbol? entrypoint) -> Kobako::Execution
39
+ def settle_outcome: (Kobako::Snapshot snapshot, Symbol verb, Symbol? entrypoint) -> Kobako::Execution
32
40
 
33
- def invoke!: (Symbol verb, ?entrypoint: Symbol?) { () -> Kobako::Runtime::Snapshot } -> Kobako::Execution
41
+ def invoke: (Symbol verb, ?entrypoint: Symbol?) { () -> Kobako::Snapshot } -> Kobako::Execution
34
42
  end
35
43
  end
@@ -2,14 +2,14 @@ module Kobako
2
2
  class Error < StandardError
3
3
  end
4
4
 
5
- module CarriesExecution
5
+ module Recorded
6
6
  attr_reader execution: Kobako::Execution?
7
7
 
8
8
  def with_execution: (Kobako::Execution execution) -> self
9
9
  end
10
10
 
11
11
  class TrapError < Error
12
- include CarriesExecution
12
+ include Recorded
13
13
  end
14
14
 
15
15
  class TimeoutError < TrapError
@@ -39,12 +39,12 @@ module Kobako
39
39
 
40
40
  class SandboxError < Error
41
41
  include Diagnosable
42
- include CarriesExecution
42
+ include Recorded
43
43
  end
44
44
 
45
45
  class ServiceError < Error
46
46
  include Diagnosable
47
- include CarriesExecution
47
+ include Recorded
48
48
  end
49
49
 
50
50
  class NoServiceError < ServiceError