kobako 0.27.0 → 0.29.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +241 -0
  4. data/Cargo.lock +132 -126
  5. data/README.md +240 -147
  6. data/ROADMAP.md +28 -22
  7. data/SECURITY.md +2 -2
  8. data/crates/kobako-runtime/CHANGELOG.md +117 -0
  9. data/crates/kobako-runtime/Cargo.toml +27 -6
  10. data/crates/kobako-runtime/README.md +16 -10
  11. data/crates/kobako-runtime/src/dispatch.rs +8 -0
  12. data/crates/kobako-runtime/src/error.rs +8 -9
  13. data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
  14. data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
  15. data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
  16. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
  17. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
  18. data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
  19. data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
  20. data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
  21. data/crates/kobako-runtime/src/filesystem.rs +392 -0
  22. data/crates/kobako-runtime/src/grant.rs +20 -0
  23. data/crates/kobako-runtime/src/lib.rs +12 -2
  24. data/crates/kobako-runtime/src/mount.rs +178 -0
  25. data/crates/kobako-runtime/src/profile.rs +4 -4
  26. data/crates/kobako-runtime/src/runtime.rs +43 -17
  27. data/crates/kobako-runtime/src/snapshot.rs +33 -15
  28. data/crates/kobako-runtime/src/yielder.rs +14 -6
  29. data/crates/kobako-transport/CHANGELOG.md +45 -0
  30. data/crates/kobako-transport/Cargo.toml +1 -1
  31. data/crates/kobako-transport/README.md +21 -12
  32. data/crates/kobako-transport/src/abi.rs +11 -14
  33. data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
  34. data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
  35. data/crates/kobako-transport/src/envelope/fault.rs +2 -1
  36. data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
  37. data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
  38. data/crates/kobako-transport/src/envelope/reply.rs +103 -61
  39. data/crates/kobako-transport/src/envelope/request.rs +305 -0
  40. data/crates/kobako-transport/src/envelope/run.rs +1 -2
  41. data/crates/kobako-transport/src/envelope/tag.rs +49 -0
  42. data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
  43. data/crates/kobako-transport/src/envelope.rs +19 -12
  44. data/crates/kobako-transport/src/lib.rs +0 -2
  45. data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
  46. data/crates/kobako-wasmtime/Cargo.toml +23 -16
  47. data/crates/kobako-wasmtime/README.md +6 -4
  48. data/crates/kobako-wasmtime/src/abi.rs +67 -36
  49. data/crates/kobako-wasmtime/src/ambient.rs +64 -5
  50. data/crates/kobako-wasmtime/src/cache.rs +204 -127
  51. data/crates/kobako-wasmtime/src/capture.rs +4 -11
  52. data/crates/kobako-wasmtime/src/config.rs +69 -15
  53. data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
  54. data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
  55. data/crates/kobako-wasmtime/src/driver.rs +174 -125
  56. data/crates/kobako-wasmtime/src/exports.rs +88 -34
  57. data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
  58. data/crates/kobako-wasmtime/src/imports.rs +46 -0
  59. data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
  60. data/crates/kobako-wasmtime/src/invocation.rs +84 -412
  61. data/crates/kobako-wasmtime/src/io.rs +89 -0
  62. data/crates/kobako-wasmtime/src/lib.rs +7 -24
  63. data/crates/kobako-wasmtime/src/limiter.rs +253 -0
  64. data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
  65. data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
  66. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
  67. data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
  68. data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
  69. data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
  70. data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
  71. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
  72. data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
  73. data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
  74. data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
  75. data/crates/kobako-wasmtime/src/preview1.rs +461 -0
  76. data/crates/kobako-wasmtime/src/trap.rs +66 -59
  77. data/crates/kobako-wasmtime/src/wasi.rs +317 -0
  78. data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
  79. data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
  80. data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
  81. data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
  82. data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
  83. data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
  84. data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
  85. data/data/kobako.wasm +0 -0
  86. data/ext/kobako/Cargo.toml +4 -2
  87. data/ext/kobako/src/runtime/bridge.rs +177 -113
  88. data/ext/kobako/src/runtime/errors.rs +26 -32
  89. data/ext/kobako/src/runtime/escape.rs +53 -0
  90. data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
  91. data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
  92. data/ext/kobako/src/runtime/filesystem.rs +236 -0
  93. data/ext/kobako/src/runtime/gvl.rs +11 -13
  94. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  95. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  96. data/ext/kobako/src/runtime.rs +190 -312
  97. data/lib/kobako/capture.rb +1 -4
  98. data/lib/kobako/catalog/extensions.rb +21 -59
  99. data/lib/kobako/catalog/flags.rb +29 -0
  100. data/lib/kobako/catalog/grants.rb +30 -0
  101. data/lib/kobako/catalog/handles.rb +7 -54
  102. data/lib/kobako/catalog/mount.rb +27 -0
  103. data/lib/kobako/catalog/services.rb +17 -60
  104. data/lib/kobako/catalog/snippets.rb +41 -69
  105. data/lib/kobako/catalog.rb +13 -10
  106. data/lib/kobako/codec/decoder.rb +10 -28
  107. data/lib/kobako/codec/encoder.rb +3 -22
  108. data/lib/kobako/codec/error.rb +6 -27
  109. data/lib/kobako/codec/ext_types.rb +72 -94
  110. data/lib/kobako/codec/handle_walk.rb +65 -130
  111. data/lib/kobako/codec/nesting.rb +3 -8
  112. data/lib/kobako/codec/state.rb +21 -11
  113. data/lib/kobako/codec/utils.rb +6 -34
  114. data/lib/kobako/codec/value_budget.rb +86 -0
  115. data/lib/kobako/codec.rb +10 -22
  116. data/lib/kobako/constant_name.rb +8 -0
  117. data/lib/kobako/context.rb +83 -117
  118. data/lib/kobako/errors.rb +57 -132
  119. data/lib/kobako/execution.rb +16 -21
  120. data/lib/kobako/extension.rb +23 -40
  121. data/lib/kobako/filesystem.rb +38 -0
  122. data/lib/kobako/handle.rb +7 -12
  123. data/lib/kobako/outcome.rb +51 -80
  124. data/lib/kobako/payload/arguments.rb +1 -16
  125. data/lib/kobako/payload.rb +4 -12
  126. data/lib/kobako/pool.rb +25 -55
  127. data/lib/kobako/runtime.rb +7 -21
  128. data/lib/kobako/sandbox.rb +128 -137
  129. data/lib/kobako/sandbox_options.rb +71 -90
  130. data/lib/kobako/snippet/bytecode.rb +11 -0
  131. data/lib/kobako/snippet/source.rb +1 -16
  132. data/lib/kobako/snippet.rb +2 -10
  133. data/lib/kobako/transport/block_failure.rb +35 -0
  134. data/lib/kobako/transport/call.rb +1 -14
  135. data/lib/kobako/transport/dispatcher.rb +127 -206
  136. data/lib/kobako/transport/error.rb +6 -20
  137. data/lib/kobako/transport/exposure.rb +10 -17
  138. data/lib/kobako/transport/reflection.rb +108 -63
  139. data/lib/kobako/transport/run.rb +17 -71
  140. data/lib/kobako/transport/yielder.rb +20 -94
  141. data/lib/kobako/transport.rb +4 -15
  142. data/lib/kobako/unresolved.rb +4 -11
  143. data/lib/kobako/usage.rb +2 -6
  144. data/lib/kobako/version.rb +1 -1
  145. data/lib/kobako.rb +1 -0
  146. data/sig/kobako/catalog/extensions.rbs +2 -2
  147. data/sig/kobako/catalog/flags.rbs +13 -0
  148. data/sig/kobako/catalog/grants.rbs +13 -0
  149. data/sig/kobako/catalog/mount.rbs +28 -0
  150. data/sig/kobako/catalog/services.rbs +3 -5
  151. data/sig/kobako/catalog/snippets.rbs +5 -5
  152. data/sig/kobako/catalog.rbs +1 -0
  153. data/sig/kobako/codec/ext_types.rbs +10 -8
  154. data/sig/kobako/codec/handle_walk.rbs +9 -7
  155. data/sig/kobako/codec/state.rbs +7 -1
  156. data/sig/kobako/codec/utils.rbs +2 -2
  157. data/sig/kobako/codec/value_budget.rbs +23 -0
  158. data/sig/kobako/codec.rbs +4 -2
  159. data/sig/kobako/constant_name.rbs +3 -0
  160. data/sig/kobako/context.rbs +15 -8
  161. data/sig/kobako/errors.rbs +10 -4
  162. data/sig/kobako/filesystem.rbs +88 -0
  163. data/sig/kobako/guest_yielder.rbs +9 -0
  164. data/sig/kobako/outcome.rbs +6 -8
  165. data/sig/kobako/pool.rbs +1 -1
  166. data/sig/kobako/runtime.rbs +21 -45
  167. data/sig/kobako/sandbox.rbs +11 -1
  168. data/sig/kobako/sandbox_options.rbs +25 -7
  169. data/sig/kobako/snapshot.rbs +20 -0
  170. data/sig/kobako/snippet/bytecode.rbs +12 -0
  171. data/sig/kobako/transport/block_failure.rbs +13 -0
  172. data/sig/kobako/transport/dispatcher.rbs +15 -19
  173. data/sig/kobako/transport/exposure.rbs +1 -1
  174. data/sig/kobako/transport/reflection.rbs +19 -4
  175. data/sig/kobako/transport/run.rbs +1 -3
  176. data/sig/kobako/transport/yielder.rbs +5 -9
  177. data/sig/kobako/transport.rbs +9 -8
  178. metadata +75 -10
  179. data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
  180. data/crates/kobako-wasmtime/src/frames.rs +0 -273
  181. data/lib/kobako/snippet/binary.rb +0 -26
  182. data/rust-toolchain.toml +0 -15
  183. data/sig/kobako/snippet/binary.rbs +0 -12
data/lib/kobako/errors.rb CHANGED
@@ -1,103 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Top-level Kobako namespace.
4
3
  module Kobako
5
- # Error taxonomy.
6
- #
7
- # Every +Kobako::Sandbox+ invocation (+#eval+ or +#run+) either returns a
8
- # value or raises exactly one of TrapError, SandboxError, or ServiceError.
9
- # Attribution is decided after the guest binary returns control to the
10
- # host: first the Wasm-trap layer, then the outcome-envelope tag.
11
- # SetupError and PoolTimeoutError sit outside the invocation taxonomy and
12
- # never pass through that attribution decision. Each class below
13
- # documents its own layer.
14
-
15
- # Base for all kobako-raised errors so callers that want to ignore the
16
- # taxonomy can rescue a single class.
4
+ # Every Sandbox#eval or Sandbox#run returns, or raises exactly one of
5
+ # TrapError, SandboxError, or ServiceError. SetupError and PoolTimeoutError
6
+ # arise outside any invocation.
7
+
8
+ # The base of every error kobako raises.
17
9
  class Error < StandardError; end
18
10
 
19
- # Carries the frozen +Kobako::Execution+ of the run that failed, so a
20
- # rescue reads its captures and usage exactly as a successful caller reads
21
- # them off the return value. Mixed only into the three invocation-outcome
22
- # classes (+TrapError+ / +SandboxError+ / +ServiceError+ and their
23
- # subclasses); +SetupError+ and +PoolTimeoutError+ arise outside any
24
- # invocation and carry no Execution. Defaults to +nil+ — a pre-flight
25
- # failure that ran no invocation leaves it unset.
26
- module CarriesExecution
11
+ module Recorded # :nodoc:
27
12
  attr_reader :execution
28
13
 
29
- # Attach the failed run's +Kobako::Execution+ and return +self+ so the
30
- # raise site reads +raise error.with_execution(execution)+.
31
- def with_execution(execution)
14
+ def with_execution(execution) # :nodoc:
32
15
  @execution = execution
33
16
  self
34
17
  end
35
18
  end
36
19
 
37
- # Wasm engine layer. Raised when the Wasm execution engine crashed
38
- # (trap, OOM, unreachable) or when the wire layer detected a structural
39
- # violation that signals a corrupted guest execution environment (an
40
- # outcome the core envelope cannot frame, an absent one included).
41
- #
42
- # Two named subclasses cover the configured per-invocation caps:
43
- #
44
- # * TimeoutError — wall-clock +timeout+ exceeded.
45
- # * MemoryLimitError — guest +memory.grow+ would exceed
46
- # +memory_limit+.
47
- #
48
- # Host Apps that only care about "guest is unrecoverable, discard the
49
- # Sandbox" can rescue +TrapError+ and ignore the subclass; Host Apps that
50
- # want to surface a specific reason to operators can rescue the subclass
51
- # first.
20
+ # The engine stopped the guest: a trap, a cap reached, or an outcome that
21
+ # could not be read. The next invocation on the same Sandbox runs as usual.
22
+ # +execution+ is the failed run's Execution, so a rescue reads its captures
23
+ # and usage as a successful caller would; it is +nil+ when the failure came
24
+ # before the guest ran.
52
25
  class TrapError < Error
53
- include CarriesExecution
26
+ include Recorded
54
27
  end
55
28
 
56
- # Wall-clock timeout cap exhausted: the absolute deadline
57
- # +entry_time + timeout+ passed and the next guest wasm safepoint
58
- # trapped. The Sandbox is unrecoverable after this point; discard and
59
- # recreate before another execution.
29
+ # The invocation ran past its +timeout+.
60
30
  class TimeoutError < TrapError; end
61
31
 
62
- # Linear-memory cap exhausted: a guest +memory.grow+ would have pushed
63
- # linear memory past the configured +memory_limit+. The Sandbox is
64
- # unrecoverable after this point; discard and recreate before another
65
- # execution.
32
+ # The invocation grew guest memory past its +memory_limit+.
66
33
  class MemoryLimitError < TrapError; end
67
34
 
68
- # Construction-layer error raised by +Kobako::Sandbox.new+ /
69
- # +Kobako::Runtime.from_path+ when the wasm runtime cannot be built
70
- # from the configured +wasm_path+ before any invocation runs —
71
- # an unreadable artifact, bytes that are not a valid Wasm module, or
72
- # engine / linker / instantiation setup failure. Construction
73
- # is not an invocation, so +SetupError+ sits beside the invocation
74
- # taxonomy under +Kobako::Error+ rather than under +TrapError+: no
75
- # Sandbox is produced, so the +TrapError+ "discard and recreate"
76
- # recovery contract does not apply.
35
+ # A Sandbox could not be built: the Guest Binary is unreadable or not a
36
+ # valid module, the runtime failed to start, or it provides less isolation
37
+ # than +profile+ asks for.
77
38
  class SetupError < Error; end
78
39
 
79
- # The named +SetupError+ subclass for the common, actionable case:
80
- # the Guest Binary artifact is absent at +wasm_path+ — the pre-build
81
- # state on a fresh clone before +bundle exec rake compile+. Host Apps
82
- # that only need "the Sandbox could not be set up" rescue +SetupError+;
83
- # those wanting to special-case the unbuilt-artifact state rescue
84
- # +ModuleNotBuiltError+ first.
40
+ # The Guest Binary is missing at +wasm_path+.
85
41
  class ModuleNotBuiltError < SetupError; end
86
42
 
87
- # The structured attribution the two invocation-failure classes carry
88
- # from a decoded guest exception — its +origin+, original +klass+, and
89
- # +backtrace_lines+ — so a Host App can inspect a failure beyond its
90
- # message. Mixed into both rather than promoted to a shared superclass
91
- # because +SandboxError+ and +ServiceError+ sit in distinct branches of
92
- # the invocation-outcome taxonomy under +Kobako::Error+.
93
- #
94
- # Data specific to one kind of failure rides a named reader on the
95
- # subclass that failure raises, the way Ruby pairs NameError#name with
96
- # +NameError#local_variables+ — see UndefinedEntrypointError.
97
- module Diagnosable
43
+ module Diagnosable # :nodoc:
98
44
  attr_reader :origin, :klass, :backtrace_lines
99
45
 
100
- def initialize(message, origin: nil, klass: nil, backtrace_lines: nil)
46
+ def initialize(message, origin: nil, klass: nil, backtrace_lines: nil) # :nodoc:
101
47
  super(message)
102
48
  @origin = origin
103
49
  @klass = klass
@@ -105,93 +51,72 @@ module Kobako
105
51
  end
106
52
  end
107
53
 
108
- # Sandbox / wire layer. Raised when the guest ran to completion but
109
- # execution failed due to a mruby script error, a protocol fault, or a
110
- # payload the host's codec could not decode out of a well-framed
111
- # outcome.
54
+ # The guest failed: the script raised or did not compile, the Guest Binary
55
+ # could not start the run, or what it produced could not be read.
56
+ # +origin+ (<tt>"sandbox"</tt> or <tt>"service"</tt>), +klass+ and
57
+ # +backtrace_lines+ say where it came from, which class raised it, and its
58
+ # backtrace. +execution+ is the failed run's Execution, as on TrapError.
112
59
  class SandboxError < Error
113
60
  include Diagnosable
114
- include CarriesExecution
61
+ include Recorded
115
62
  end
116
63
 
117
- # Service layer. Raised when a Service capability call inside a mruby
118
- # script reported an application-level failure that the script did not
119
- # rescue. The base class covers a Service that ran and raised; the two
120
- # subclasses below cover the calls that never reached one, so a Host App
121
- # routes them apart with +rescue+ instead of by reading the message.
64
+ # A Service call failed and the guest left it unrescued. It carries
65
+ # +origin+, +klass+, +backtrace_lines+ and +execution+ as SandboxError does.
122
66
  class ServiceError < Error
123
67
  include Diagnosable
124
- include CarriesExecution
68
+ include Recorded
125
69
  end
126
70
 
127
- # The ServiceError subclass raised when the call reached no Service
128
- # method: the bound path holds nothing, the Capability Handle is not
129
- # live in this invocation, or the method is absent or outside the guest
130
- # surface. The causes stay indistinguishable — an opaque target must
131
- # disclose nothing about which methods it defines — so what a Host App
132
- # learns is that this call will not succeed by being retried.
71
+ # The call reached no Service method: nothing is bound there, the Handle
72
+ # is not live, or the guest may not call that method. The causes look the
73
+ # same on purpose, so a target discloses nothing about what it defines.
133
74
  class NoServiceError < ServiceError; end
134
75
 
135
- # The ServiceError subclass raised when the call reached the Service
136
- # method but its arguments did not fit — an unknown keyword, or an
137
- # arity mismatch.
76
+ # The Service method refused its arguments: the call would not bind, or
77
+ # the method raised +ArgumentError+ over what it was given.
138
78
  class ServiceArgumentError < ServiceError; end
139
79
 
140
- # Raised at a Service method's +yield+ site when the guest block it
141
- # yielded to raised. A Service rescues it the way it would rescue a
142
- # block's exception without a Sandbox between the two frames; leaving it
143
- # unrescued returns it to the guest, which re-raises the exception it
144
- # raised in the first place. It therefore never reaches the Host App as
145
- # an invocation outcome and carries no Execution. +klass+ names the
146
- # guest-side class, which has no host counterpart to rebuild.
80
+ # Raised at a Service's +yield+ when the guest block raised; rescue it as
81
+ # you would the block's own exception. Left unrescued, the guest sees its
82
+ # own exception again, so it never reaches the Host App.
147
83
  class BlockError < Error
148
84
  include Diagnosable
149
85
  end
150
86
 
151
- # Raised at a Service method's +yield+ site when a yield argument has no
152
- # wire representation, so the round-trip never left the host and the
153
- # block never ran. As BlockError, a Service rescues it at that site and
154
- # may go on, so it never reaches the Host App as an invocation outcome;
155
- # unrescued, the dispatch answers as that Service having failed.
87
+ # Raised at a Service's +yield+ when an argument cannot cross to the guest,
88
+ # so the block never ran. Left unrescued, the Service call fails.
156
89
  class YieldValueError < Error; end
157
90
 
158
- # HandleExhaustedError is the canonical SandboxError subclass for the
159
- # id-cap-hit path. Raised when the per-invocation Handle ID counter in
160
- # Catalog::Handles reaches +0x7fff_ffff+ (2³¹ − 1) and further
161
- # allocation would exceed the cap.
91
+ # Raised at a Service's +yield+ when the guest serves no block, so the
92
+ # block never ran. Left unrescued, the Service call fails.
93
+ class UnsupportedError < Error; end
94
+
95
+ # One invocation issued more Handles than an id can number (2**31 - 1).
162
96
  class HandleExhaustedError < SandboxError; end
163
97
 
164
- # BytecodeError is the SandboxError subclass raised when a
165
- # +#preload(binary:)+ snippet fails structural validation during the
166
- # first invocation's snippet replay against a fresh +mrb_state+ (RITE
167
- # version mismatch or corrupt body). Bytecode that loads cleanly and
168
- # then raises at top level surfaces as plain +SandboxError+ with the
169
- # natural mruby class preserved. Inherits from SandboxError so a single
170
- # +rescue Kobako::SandboxError+ covers both source and bytecode
171
- # snippet failures while callers wanting bytecode-specific handling
172
- # can +rescue Kobako::BytecodeError+ directly.
98
+ # A <tt>preload(binary:)</tt> snippet failed to load. Bytecode that loads
99
+ # and then raises is a plain SandboxError.
173
100
  class BytecodeError < SandboxError; end
174
101
 
175
- # UndefinedEntrypointError is the SandboxError subclass raised when a
176
- # +#run+ target names no top-level constant in the guest. It carries the
177
- # +#name+ that was asked for and the +#available+ names it could have
178
- # been — the top-level constants the preloaded snippets contributed — so
179
- # a caller corrects the name from the error rather than by reading the
180
- # guest source. Ruby pairs NameError#name with
181
- # +NameError#local_variables+ for the same reason.
102
+ # The Guest Binary does not support a flag the Sandbox set, so no script
103
+ # ran. Retrying without the flag, or on a Guest Binary that supports it,
104
+ # can succeed.
105
+ class UnsupportedFlagError < SandboxError; end
106
+
107
+ # Sandbox#run named no top-level constant.
182
108
  class UndefinedEntrypointError < SandboxError
109
+ # The constant asked for, and the ones the preloaded snippets define.
183
110
  attr_reader :name, :available
184
111
 
185
- def initialize(message, name: nil, available: [], **)
112
+ def initialize(message, name: nil, available: [], **) # :nodoc:
186
113
  super(message, **)
187
114
  @name = name
188
115
  @available = available
189
116
  end
190
117
  end
191
118
 
192
- # Pool checkout layer. Raised by +Kobako::Pool#with+ when the checkout
193
- # wait exceeded the configured +checkout_timeout+ while every slot was
194
- # held. No Sandbox state is touched — retrying succeeds as soon as a holder
195
- # returns its Sandbox.
119
+ # Pool#with waited past +checkout_timeout+ with every slot held. Retrying
120
+ # succeeds once a holder returns its Sandbox.
196
121
  class PoolTimeoutError < Error; end
197
122
  end
@@ -4,25 +4,20 @@ require_relative "capture"
4
4
  require_relative "usage"
5
5
 
6
6
  module Kobako
7
- # Kobako::Execution — the frozen result of one +Sandbox#eval+ / +#run+: the
8
- # decoded +#value+ plus the run's output captures and +#usage+. A successful
9
- # run returns it; a failed run raises an error carrying the same frozen
10
- # Execution on the error's +#execution+, so a rescue reads the captures and
11
- # usage exactly as a successful caller reads them off the return value. On a
12
- # failed run +#value+ is +nil+ — only the captures and usage are meaningful.
7
+ # The record of one Sandbox#eval or Sandbox#run: the guest's #value, its
8
+ # output, and its #usage. A failed run raises instead, and the error carries
9
+ # the same record on +execution+, where #value is +nil+.
13
10
  #
14
- # +#failed?+ tells the returned Execution from the carried one even when both
15
- # +#value+ are +nil+ (a run whose last expression was +nil+ versus a failed
16
- # one). It holds no reference to the raising error, so the error carries the
17
- # Execution but not the reverse.
11
+ # #failed? tells the two apart when both #value are +nil+: a script whose
12
+ # last expression was +nil+ versus one that failed.
18
13
  class Execution
19
- # The deserialized guest value the run produced; +nil+ on a failed run.
14
+ # The guest value the run produced; +nil+ on a failed run.
20
15
  attr_reader :value
21
16
 
22
- # The +Kobako::Usage+ resource accounting for this run.
17
+ # What the run spent against its caps, as a Usage.
23
18
  attr_reader :usage
24
19
 
25
- def initialize(value:, usage:, stdout:, stderr:, failed:)
20
+ def initialize(value:, usage:, stdout:, stderr:, failed:) # :nodoc:
26
21
  @value = value
27
22
  @usage = usage
28
23
  @stdout_capture = stdout
@@ -31,22 +26,22 @@ module Kobako
31
26
  freeze
32
27
  end
33
28
 
34
- # Returns +true+ iff the run failed — +false+ on the Execution +#eval+ /
35
- # +#run+ returned, +true+ on the one a raised error carries.
29
+ # Whether the run failed: +false+ on the Execution #eval or #run
30
+ # returned, +true+ on the one a raised error carries.
36
31
  def failed? = @failed
37
32
 
38
- # Bytes the guest wrote to stdout during this run as a UTF-8 String,
39
- # clipped at +stdout_limit+; the content carries no truncation sentinel,
40
- # so use +#stdout_truncated?+ to observe overflow.
33
+ # What the guest wrote to stdout, up to +stdout_limit+ bytes: a UTF-8
34
+ # String, or a binary one when the bytes are not valid UTF-8. The text
35
+ # carries no sign it was cut; #stdout_truncated? says so.
41
36
  def stdout = @stdout_capture.bytes
42
37
 
43
- # Bytes the guest wrote to stderr during this run. Mirror of #stdout.
38
+ # What the guest wrote to stderr; see #stdout.
44
39
  def stderr = @stderr_capture.bytes
45
40
 
46
- # Returns +true+ iff stdout capture reached +stdout_limit+ during this run.
41
+ # Whether stdout reached +stdout_limit+.
47
42
  def stdout_truncated? = @stdout_capture.truncated?
48
43
 
49
- # Returns +true+ iff stderr capture reached +stderr_limit+ during this run.
44
+ # Whether stderr reached +stderr_limit+.
50
45
  def stderr_truncated? = @stderr_capture.truncated?
51
46
  end
52
47
  end
@@ -1,48 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- # Kobako::Extension — a guest idiom paired with an optional host backend,
5
- # installed on a Sandbox via +Sandbox#install+. It composes the existing
6
- # +#preload+ (the guest +source+) and +#bind+ (the +backend+) verbs into
7
- # one setup unit, so guest code sees a native-style constant whose pure
8
- # methods run in-guest and whose privileged methods dispatch to the
9
- # backend.
4
+ # Guest source paired with an optional host backend, installed with
5
+ # Sandbox#install: the guest sees one constant whose pure methods run in the
6
+ # guest and whose privileged ones call the backend. Any object answering the
7
+ # same four readers installs the same way.
10
8
  #
11
- # The four readers form the contract +#install+ duck-types on:
12
- #
13
- # * +name+ — a Symbol matching +/\A[A-Z]\w*\z/+, the preloaded snippet's
14
- # canonical backtrace name and the +depends_on+ match key. Independent
15
- # of any bound path.
16
- # * +source+ — the mruby idiom as a String; always present, since an
17
- # Extension always carries a guest idiom. A host object with no idiom
18
- # is bound with +#bind+ directly.
19
- # * +backend+ — an +Extension::Backend+ or +nil+ for a pure-guest
20
- # Extension.
21
- # * +depends_on+ — Symbol names of Extensions that must also be
22
- # installed, checked for presence at the first invocation.
23
- #
24
- # +Kobako::Extension+ is the bundled value type; any object exposing the
25
- # four readers is equally valid, so a Host App or gem may supply its own.
9
+ # [+name+] A constant-form Symbol: the snippet's backtrace name, and
10
+ # what another Extension's +depends_on+ names.
11
+ # [+source+] The mruby source, a String. A host object with no source
12
+ # is bound with Sandbox#bind instead.
13
+ # [+backend+] A Backend, or +nil+ for an Extension that runs entirely in
14
+ # the guest.
15
+ # [+depends_on+] Names of Extensions that must also be installed, checked
16
+ # at the first invocation.
26
17
  class Extension < Data.define(:name, :source, :backend, :depends_on)
27
- # Kobako::Extension::Backend — the host attachment of an Extension,
28
- # pairing +path+ (the constant path the backend binds at, single-segment
29
- # +"File"+ or nested +"MyApp::Store"+, spelling the guest constant the
30
- # idiom routes to) with the source of the bound object, declared by
31
- # explicit keyword:
18
+ # The host object an Extension's constant at +path+ calls, such as
19
+ # <tt>"MyApp::Store"</tt>, given by keyword:
32
20
  #
33
- # * +object:+ — a static object, fixed for the Sandbox's life.
34
- # * +provider:+ — a no-argument callable invoked once per invocation to
35
- # yield that invocation's object, so a fresh object backs the path
36
- # every invocation. A provider that raises propagates its exception to
37
- # the invocation caller and leaves the guest unrun; the next
38
- # invocation resolves it afresh.
39
- # * neither — a fillable, defaulting to +Kobako::Unresolved+ until the
40
- # host supplies the invocation's object.
21
+ # [+object:+] One object for the Sandbox's life.
22
+ # [+provider:+] A callable taking no arguments, called once per invocation
23
+ # for that invocation's object. If it raises, the exception
24
+ # reaches the caller and the guest does not run.
25
+ # [neither] A fillable Service, supplied per invocation through
26
+ # Context#bind.
41
27
  #
42
- # The kind is chosen by keyword, never inferred from whether the value is
43
- # callable, so a static object that is itself callable is expressed
44
- # directly with +object:+. Giving both +object:+ and +provider:+ raises
45
- # +ArgumentError+.
28
+ # The keyword decides the kind, so a callable object meant as itself is
29
+ # given as +object:+. Giving both raises ArgumentError.
46
30
  class Backend < Data.define(:path, :object, :provider)
47
31
  def initialize(path:, object: nil, provider: nil)
48
32
  if !object.nil? && !provider.nil?
@@ -54,8 +38,7 @@ module Kobako
54
38
  end
55
39
  end
56
40
 
57
- # +backend+ and +depends_on+ default to absent so the common
58
- # pure-idiom and single-backend shapes stay terse.
41
+ # +backend+ defaults to none, and +depends_on+ to no other Extension.
59
42
  def initialize(name:, source:, backend: nil, depends_on: [])
60
43
  super
61
44
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ # The Filesystems a Sandbox can mount for the guest's +File+, each
5
+ # answered in Rust by the kind of the same name. Hand one to
6
+ # Sandbox#mount:
7
+ #
8
+ # sandbox.mount("/data", Kobako::Filesystem::Memory.new("config.yml" => yaml))
9
+ #
10
+ # [<tt>Memory.new(files = {}, limit: 64 MiB, inodes:)</tt>]
11
+ # Files held in memory for as long as the object lives, seeded from a
12
+ # Hash of path to contents. Every invocation shares them.
13
+ # [<tt>Temporary.new(limit: 64 MiB, inodes:)</tt>]
14
+ # Files held in memory that every invocation starts empty.
15
+ # [<tt>Directory.new(path)</tt>]
16
+ # A host directory and everything beneath it. Nothing outside it is
17
+ # reachable, a symbolic link pointing out included.
18
+ # [<tt>Overlay.new.layer(filesystem, access: :read)</tt>]
19
+ # Layers stacked into one view, the first added on top, each +:read+
20
+ # or +:read_write+ on its own. A change lands in a writable Layer, and
21
+ # a read-only one stays as it was. Each +layer+ answers a new Overlay.
22
+ #
23
+ # A Memory or Temporary is bounded as a tmpfs mount is: its files hold
24
+ # at most +limit+ bytes together, and it holds at most +inodes+ files and
25
+ # directories, its root among them; +nil+ lifts either bound. Without
26
+ # +inodes+ it holds one for each 4 KiB of +limit+, and the root. Past
27
+ # either, the guest raises Errno::ENOSPC.
28
+ #
29
+ # Any object answering +open+ and +stat+ mounts too, as a Filesystem
30
+ # written in Ruby; its methods take the names of +File+, +Dir+, and +IO+.
31
+ # Its +stat+ answers +directory?+, +size+, and +ino+, an +ino+ naming one
32
+ # directory for as long as the guest holds it open. Its names match
33
+ # exactly, as a mount's block judges them as given. A name may hold a
34
+ # <tt>\\</tt>, which the guest's paths never separate on, so one joined
35
+ # to a Windows host path is the Filesystem's to refuse.
36
+ module Filesystem
37
+ end
38
+ end
data/lib/kobako/handle.rb CHANGED
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
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.
4
+ # A guest-held reference to a host object, named by id. Host code cannot
5
+ # construct one, so no Handle is ever made from a caller-chosen id.
9
6
  class Handle < Data.define(:id)
10
- # ID 0 is the invalid sentinel and is never allocated.
7
+ # The smallest id; 0 never names a Handle.
11
8
  MIN_ID = 1
12
- # The positive half of a signed 32-bit integer, so an id fits either
13
- # side of the wire unchanged.
9
+ # The largest id, the positive half of a signed 32-bit integer; an
10
+ # invocation needing more raises HandleExhaustedError.
14
11
  MAX_ID = 0x7fff_ffff
15
12
 
16
- def initialize(id:)
13
+ def initialize(id:) # :nodoc:
17
14
  raise ArgumentError, "Handle id must be Integer" unless id.is_a?(Integer)
18
15
  raise ArgumentError, "Handle id #{id} out of range [#{MIN_ID}, #{MAX_ID}]" unless id.between?(MIN_ID, MAX_ID)
19
16
 
@@ -23,9 +20,7 @@ module Kobako
23
20
  private_class_method :new
24
21
  undef_method :with
25
22
 
26
- # The Host Gem's own constructor, for the wire decoder and the Handle
27
- # allocator; it runs the same id checks +.new+ would.
28
- def self.restore(id)
23
+ def self.restore(id) # :nodoc:
29
24
  allocate.tap { |handle| handle.send(:initialize, id: id) }
30
25
  end
31
26
  end