Class: Kobako::Sandbox
- Inherits:
-
Object
- Object
- Kobako::Sandbox
- Extended by:
- Forwardable
- Defined in:
- lib/kobako/sandbox.rb,
sig/kobako/sandbox.rbs
Overview
Kobako::Sandbox — the user-facing entry point for executing guest mruby scripts inside a wasmtime-hosted Wasm module.
The Sandbox owns the reusable configuration — the Kobako::Runtime and
the Kobako::Catalog::Services / Catalog::Snippets / Catalog::Extensions
registries. Each #eval / #run seals that config on the first call and
drives one guest invocation through a fresh Kobako::Context — its own
Handle table, dispatch Proc, captures, and usage — so the reusable
Sandbox holds no per-invocation state. The underlying wasmtime Engine and
compiled Module are cached at process scope by the native ext and never
surface to Ruby — constructing many Sandboxes amortises both costs
automatically.
A run's observables — the captured #stdout / #stderr (bounded by
stdout_limit / stderr_limit, enforced inside the WASI pipe) with their
#stdout_truncated? / #stderr_truncated? predicates, and #usage — live
on the Kobako::Execution each #eval / #run returns, or on the one its
raised error carries. The Sandbox itself keeps none of them, so nothing a
run observes carries into the next one.
Instance Attribute Summary collapse
-
#options ⇒ Kobako::SandboxOptions
readonly
Returns the value of attribute options.
-
#wasm_path ⇒ String
readonly
Returns the value of attribute wasm_path.
Instance Method Summary collapse
-
#begin_invocation! ⇒ void
Per-invocation prologue on the config tier: seals the Service / snippet / Extension registries on the first call (idempotent — asserting Extension dependencies then).
-
#bind(path, object = Unresolved) ⇒ Kobako::Sandbox
Bind
objectas the Service reachable atpath— a Symbol or String of one or more +::+-separated constant-form segments (+"MyService::KV"+ or a top-level"File"). -
#build_runtime! ⇒ Kobako::Runtime
Construct the
Runtimewith the requested isolation profile and refuse one whose declared posture falls below the request —SandboxOptions#enforce_floor!owns the ladder comparison, so a runtime that cannot honor the request never runs guest code. -
#eval(code, &block) ⇒ void
Execute a guest mruby source string in a fresh
mrb_state. - #gvl ⇒ Symbol
-
#initialize(wasm_path: nil) ⇒ Object
constructor
Every keyword beyond wasm_path forwards verbatim to SandboxOptions.new (the four caps + the profile floor).
-
#install(*extensions) ⇒ Kobako::Sandbox
Install one or more Extensions — each a guest idiom (+source+) paired with an optional host
backend, composed onto the Sandbox through#preloadand#bind. - #memory_limit ⇒ Integer?
-
#new_invocation ⇒ Kobako::Context
Seal the config on the first invocation and return a fresh per-invocation
Contextfor the verb to drive. -
#preload(code: nil, name: nil, binary: nil) ⇒ Object
Register a snippet on this Sandbox in one of two forms:.
- #profile ⇒ Symbol
-
#run(target, *args, **kwargs, &block) ⇒ void
Dispatch into a preloaded entrypoint constant.
- #stderr_limit ⇒ Integer?
- #stdout_limit ⇒ Integer?
-
#timeout ⇒ Float?
Forwarded to @options via Forwardable#def_delegators.
Constructor Details
#initialize(wasm_path: nil) ⇒ Object
Every keyword beyond wasm_path forwards verbatim to SandboxOptions.new (the four caps + the profile floor).
55 56 57 58 59 60 61 62 |
# File 'lib/kobako/sandbox.rb', line 55 def initialize(wasm_path: nil, **) @wasm_path = wasm_path || Kobako::Runtime.default_path @options = SandboxOptions.new(**) @services = Kobako::Catalog::Services.new @snippets = Catalog::Snippets.new @extensions = Catalog::Extensions.new @runtime = build_runtime! end |
Instance Attribute Details
#options ⇒ Kobako::SandboxOptions (readonly)
Returns the value of attribute options.
35 36 37 |
# File 'lib/kobako/sandbox.rb', line 35 def @options end |
#wasm_path ⇒ String (readonly)
Returns the value of attribute wasm_path.
35 36 37 |
# File 'lib/kobako/sandbox.rb', line 35 def wasm_path @wasm_path end |
Instance Method Details
#begin_invocation! ⇒ void
This method returns an undefined value.
Per-invocation prologue on the config tier: seals the Service / snippet /
Extension registries on the first call (idempotent — asserting Extension
dependencies then). Per-invocation provider resolution and observable
state live on the Context, not here.
204 205 206 207 208 |
# File 'lib/kobako/sandbox.rb', line 204 def begin_invocation! @services.seal! @snippets.seal! @extensions.seal! end |
#bind(path, object = Unresolved) ⇒ Kobako::Sandbox
Bind object as the Service reachable at path — a Symbol or
String of one or more +::+-separated constant-form segments
(+"MyService::KV"+ or a top-level "File"). Returns self for
chaining.
Called with only a path, it declares a fillable Service:
bind(path) reserves the path for Kobako::Unresolved, so the guest
sees the constant while the host defers the object it stands for. A
guest dispatch to an unfilled fillable surfaces as
Kobako::ServiceError when left unrescued.
Raises ArgumentError when a segment is malformed, when path
collides with an existing binding (a name is a bound Service or a
grouping prefix, never both), or when called after the first
invocation has sealed Service registration.
79 80 81 82 |
# File 'lib/kobako/sandbox.rb', line 79 def bind(path, object = Unresolved) @services.bind(path, object) self end |
#build_runtime! ⇒ Kobako::Runtime
Construct the Runtime with the requested isolation profile and
refuse one whose declared posture falls below the request —
SandboxOptions#enforce_floor! owns the ladder comparison, so a
runtime that cannot honor the request never runs guest code.
183 184 185 186 187 188 189 |
# File 'lib/kobako/sandbox.rb', line 183 def build_runtime! runtime = Kobako::Runtime.from_path(@wasm_path, @options.timeout, @options.memory_limit, @options.stdout_limit, @options.stderr_limit, @options.profile, @options.gvl) @options.enforce_floor!(runtime.profile) runtime end |
#eval(code, &block) ⇒ void
This method returns an undefined value.
Execute a guest mruby source string in a fresh mrb_state. code is
the mruby source as a UTF-8 String. Returns the deserialized last
expression of the source.
Source delivery uses the WASI stdin three-frame protocol
(docs/wire-codec.md Invocation channels):
Frame 1 carries the msgpack-encoded preamble (Service registry
snapshot), Frame 2 carries the user source UTF-8 bytes, and
Frame 3 carries the snippet table registered via #preload.
Each frame is prefixed by a 4-byte big-endian u32 length; Frame 3 is
mandatory-presence — an empty snippet table sends an empty msgpack
array, never an absent frame.
The first invocation seals the Service registry and snippet table;
subsequent #bind / #preload calls raise ArgumentError.
Raises Kobako::TrapError on a Wasm trap or wire-violation fallback;
Kobako::SandboxError when the guest ran to completion but failed
(including when code is nil or not a String, or when a preloaded
snippet's replay raises); Kobako::ServiceError on an unrescued
Service capability failure.
171 172 173 174 175 |
# File 'lib/kobako/sandbox.rb', line 171 def eval(code, &block) raise SandboxError, "code must be a String, got #{code.class}" unless code.is_a?(String) new_invocation.eval(code, &block) end |
#gvl ⇒ Symbol
14 |
# File 'sig/kobako/sandbox.rbs', line 14
def gvl: () -> Symbol
|
#install(*extensions) ⇒ Kobako::Sandbox
Install one or more Extensions — each a guest idiom (+source+) paired
with an optional host backend, composed onto the Sandbox through
#preload and #bind. An Extension is any object exposing
name / source / backend / depends_on; Kobako::Extension is
the bundled value type. Returns self.
Raises ArgumentError for a malformed Extension, a call after the
first invocation seals registration, or — at that first invocation —
an unmet depends_on.
93 94 95 96 97 98 |
# File 'lib/kobako/sandbox.rb', line 93 def install(*extensions) raise ArgumentError, "cannot install after first Sandbox invocation" if @services.sealed? extensions.each { |extension| @extensions.install(extension, snippets: @snippets, services: @services) } self end |
#memory_limit ⇒ Integer?
10 |
# File 'sig/kobako/sandbox.rbs', line 10
def memory_limit: () -> Integer?
|
#new_invocation ⇒ Kobako::Context
Seal the config on the first invocation and return a fresh
per-invocation Context for the verb to drive. The Context owns this
run's Handle table, resolved Extension backends, captures, and usage, so
no per-invocation state is written back onto the shared config.
195 196 197 198 |
# File 'lib/kobako/sandbox.rb', line 195 def new_invocation begin_invocation! Context.new(runtime: @runtime, services: @services, snippets: @snippets, extensions: @extensions) end |
#preload(code:, name:) ⇒ Kobako::Sandbox #preload(binary:) ⇒ Kobako::Sandbox
Register a snippet on this Sandbox in one of two forms:
* +preload(code: source, name: Name)+ — +source+ is mruby source
as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. Compile
failures surface as +Kobako::SandboxError+ on the first
invocation's replay. The +name+
becomes the snippet's +(snippet:Name)+ backtrace filename and
is the dedupe key that rejects a duplicate +code:+ snippet.
* +preload(binary: bytes)+ — +bytes+ is precompiled RITE
bytecode as a +String+. The canonical name, when present,
lives in the bytecode's embedded +debug_info+ and is resolved
by the guest at load time; the host treats the bytes as
opaque. Structural failures surface as +Kobako::BytecodeError+
on the first invocation.
Subsequent invocations (+#eval+ or #run) replay every registered
snippet — in insertion order — against the fresh mrb_state
before per-invocation source or entrypoint resolution.
Returns self to allow chaining.
Raises ArgumentError when neither form's keyword set is
supplied, when both forms are mixed (e.g., code: and binary:
together, or binary: paired with name:), when code / bytes
is not a String, when name does not match the constant
pattern, when name duplicates an already-registered code: form
snippet, or when called after the first invocation has sealed the
snippet table.
128 129 130 131 132 133 |
# File 'lib/kobako/sandbox.rb', line 128 def preload(code: nil, name: nil, binary: nil) raise ArgumentError, "cannot preload after first Sandbox invocation" if @services.sealed? @snippets.register(code: code, name: name, binary: binary) self end |
#profile ⇒ Symbol
13 |
# File 'sig/kobako/sandbox.rbs', line 13
def profile: () -> Symbol
|
#run(target, *args, **kwargs, &block) ⇒ void
This method returns an undefined value.
Dispatch into a preloaded entrypoint constant. Delegates host
pre-flight and wire encoding to Kobako::Transport::Run /
Kobako::Transport::Run#encode: a non-Symbol/String target raises
TypeError, while a target failing the constant pattern, a forged
Kobako::Handle in args / kwargs, or a non-Symbol kwargs key
raise ArgumentError. The guest resolves target as a top-level
constant, calls #call on it with args / kwargs, and returns the
deserialized result. The first invocation seals the Service registry
and snippet table. Runtime errors follow the same three-class
taxonomy as #eval.
145 146 147 148 |
# File 'lib/kobako/sandbox.rb', line 145 def run(target, *args, **kwargs, &block) run_envelope = Transport::Run.new(entrypoint: target, args: args, kwargs: kwargs) new_invocation.run(run_envelope, &block) end |
#stderr_limit ⇒ Integer?
12 |
# File 'sig/kobako/sandbox.rbs', line 12
def stderr_limit: () -> Integer?
|
#stdout_limit ⇒ Integer?
11 |
# File 'sig/kobako/sandbox.rbs', line 11
def stdout_limit: () -> Integer?
|
#timeout ⇒ Float?
Forwarded to @options via Forwardable#def_delegators.
9 |
# File 'sig/kobako/sandbox.rbs', line 9
def timeout: () -> Float?
|