microsandbox-rb 0.15.0 → 0.16.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +99 -0
- data/Cargo.lock +54 -42
- data/DESIGN.md +1 -1
- data/README.md +39 -3
- data/ext/microsandbox/Cargo.toml +5 -5
- data/ext/microsandbox/src/error.rs +5 -0
- data/ext/microsandbox/src/sandbox.rs +182 -16
- data/lib/microsandbox/errors.rb +7 -0
- data/lib/microsandbox/sandbox.rb +296 -2
- data/lib/microsandbox/version.rb +2 -2
- data/sig/microsandbox.rbs +16 -0
- metadata +1 -1
|
@@ -18,11 +18,11 @@ use microsandbox::logs::{
|
|
|
18
18
|
LogCursor, LogEntry, LogOptions, LogSource, LogStreamOptions, LogStreamStart,
|
|
19
19
|
};
|
|
20
20
|
use microsandbox::sandbox::{
|
|
21
|
-
AttachOptionsBuilder, DiskImageFormat, EnvVar, FlatClone, FsEntry, FsEntryKind,
|
|
22
|
-
HostPermissions, Patch, PullPolicy, PullProgress, PullProgressHandle,
|
|
23
|
-
RootDiskBuilder, SandboxBuilder, SandboxHandle, SandboxMetrics,
|
|
24
|
-
SandboxModificationPatch, SandboxStatus, SandboxStopResult,
|
|
25
|
-
SecretModificationPatch, SecretSource, SecurityProfile, StatVirtualization,
|
|
21
|
+
AttachOptionsBuilder, DestroyOptions, DiskImageFormat, EnvVar, FlatClone, FsEntry, FsEntryKind,
|
|
22
|
+
FsMetadata, HostPermissions, Patch, PullPolicy, PullProgress, PullProgressHandle,
|
|
23
|
+
RestartOptions, RlimitResource, RootDiskBuilder, SandboxBuilder, SandboxHandle, SandboxMetrics,
|
|
24
|
+
SandboxModificationBuilder, SandboxModificationPatch, SandboxStatus, SandboxStopResult,
|
|
25
|
+
SecretBuilder, SecretModificationPatch, SecretSource, SecurityProfile, StatVirtualization,
|
|
26
26
|
};
|
|
27
27
|
use microsandbox::LogLevel;
|
|
28
28
|
use microsandbox::MicrosandboxResult;
|
|
@@ -167,7 +167,8 @@ impl Sandbox {
|
|
|
167
167
|
// Hash — guest (req), kind ("bind"/"named"/"tmpfs"/"disk"), source
|
|
168
168
|
// (bind/named/disk), size_mib (tmpfs/disk), format + fstype (disk),
|
|
169
169
|
// readonly/noexec/nosuid/nodev (bool), stat_virtualization,
|
|
170
|
-
// host_permissions
|
|
170
|
+
// host_permissions, override_uid/override_gid (u32 pair).
|
|
171
|
+
// Enum-valued options are validated up front (the
|
|
171
172
|
// volume closure can't return an error); the core validates the rest
|
|
172
173
|
// (e.g. rejecting stat_virtualization on tmpfs/disk) at create().
|
|
173
174
|
for m in conv::opt_hash_vec(opts, "volumes")? {
|
|
@@ -195,6 +196,14 @@ impl Sandbox {
|
|
|
195
196
|
let host_perms = conv::opt_string(m, "host_permissions")?
|
|
196
197
|
.map(|s| host_permissions_from_str(&s))
|
|
197
198
|
.transpose()?;
|
|
199
|
+
// v0.6.15: the fallback guest owner presented for host files that
|
|
200
|
+
// carry no per-file stat override. The Ruby layer validates the pair
|
|
201
|
+
// (both-or-neither, u32 range, bind/named only, not with
|
|
202
|
+
// stat_virtualization=off); whether a *named* volume is directory-
|
|
203
|
+
// or disk-backed is known only to the core, which rejects the
|
|
204
|
+
// disk-backed case at build().
|
|
205
|
+
let override_uid = conv::opt_u32(m, "override_uid")?;
|
|
206
|
+
let override_gid = conv::opt_u32(m, "override_gid")?;
|
|
198
207
|
// v0.6.7: mount-root symlink protection is on by default; this is
|
|
199
208
|
// the per-mount opt-out. Valid for bind/named-directory mounts —
|
|
200
209
|
// the core rejects it elsewhere at build().
|
|
@@ -251,6 +260,9 @@ impl Sandbox {
|
|
|
251
260
|
if let Some(hp) = host_perms {
|
|
252
261
|
mb = mb.host_permissions(hp);
|
|
253
262
|
}
|
|
263
|
+
if let (Some(uid), Some(gid)) = (override_uid, override_gid) {
|
|
264
|
+
mb = mb.owner(uid, gid);
|
|
265
|
+
}
|
|
254
266
|
if let Some(follow) = follow_root_symlinks {
|
|
255
267
|
mb = mb.follow_root_symlinks(follow);
|
|
256
268
|
}
|
|
@@ -509,6 +521,16 @@ impl Sandbox {
|
|
|
509
521
|
Ok(PullSession::new(handle, join))
|
|
510
522
|
}
|
|
511
523
|
|
|
524
|
+
/// Converge on a live sandbox with this name: connect to (or start) the
|
|
525
|
+
/// persisted one when it already exists, otherwise create it from `opts`.
|
|
526
|
+
/// Concurrent callers converge on the winning identity. The core rejects
|
|
527
|
+
/// the combination with `replace`.
|
|
528
|
+
fn connect_or_create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
529
|
+
let b = Self::build_builder(name, opts)?;
|
|
530
|
+
let inner = block_on(b.connect_or_create()).map_err(error::to_ruby)?;
|
|
531
|
+
Ok(Sandbox::from_inner(inner))
|
|
532
|
+
}
|
|
533
|
+
|
|
512
534
|
/// Restart a previously-defined sandbox by name.
|
|
513
535
|
fn start(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
514
536
|
let detached = conv::opt_bool(opts, "detached")?;
|
|
@@ -580,6 +602,13 @@ impl Sandbox {
|
|
|
580
602
|
self.inner.name().to_string()
|
|
581
603
|
}
|
|
582
604
|
|
|
605
|
+
/// The opaque, backend-assigned identity of this persisted sandbox
|
|
606
|
+
/// (v0.6.16). Stable for the sandbox's lifetime, and different once the
|
|
607
|
+
/// same name is removed and recreated.
|
|
608
|
+
fn id(&self) -> String {
|
|
609
|
+
self.inner.id().to_string()
|
|
610
|
+
}
|
|
611
|
+
|
|
583
612
|
/// Run a command (no shell). `args` is an Array of strings; `opts` is a
|
|
584
613
|
/// string-keyed Hash (cwd, user, env, timeout, tty, stdin).
|
|
585
614
|
fn exec(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<RHash, Error> {
|
|
@@ -644,17 +673,23 @@ impl Sandbox {
|
|
|
644
673
|
Ok(ExecHandle::from_core(handle))
|
|
645
674
|
}
|
|
646
675
|
|
|
647
|
-
/// Graceful stop
|
|
648
|
-
///
|
|
649
|
-
///
|
|
650
|
-
///
|
|
676
|
+
/// Graceful stop (SIGTERM→SIGKILL escalation with a 10s default), routed
|
|
677
|
+
/// straight through the live sandbox like every other lifecycle method here
|
|
678
|
+
/// and like both official bindings (`sdk/python/src/sandbox.rs`,
|
|
679
|
+
/// `sdk/ruby/ext/.../lib.rs`).
|
|
680
|
+
///
|
|
681
|
+
/// This deliberately does NOT re-fetch a `SandboxHandle` by name first. It
|
|
682
|
+
/// used to, back when the core's live `stop` was not identity-scoped; as of
|
|
683
|
+
/// v0.6.16 the core routes `stop` → `request_stop` → `stop_identified(name,
|
|
684
|
+
/// self.identity())`, so going through `self.inner` is what makes the stop
|
|
685
|
+
/// refuse a same-name replacement (`SandboxReplacedError`) instead of
|
|
686
|
+
/// terminating whatever sandbox happens to own the name right now. The old
|
|
687
|
+
/// by-name refetch threw that identity away.
|
|
688
|
+
///
|
|
689
|
+
/// Fine-grained control — a custom timeout or fire-and-return `request_*` —
|
|
690
|
+
/// lives on `SandboxHandle`, obtained via `Sandbox.get`.
|
|
651
691
|
fn stop(&self) -> Result<(), Error> {
|
|
652
|
-
|
|
653
|
-
block_on(async move {
|
|
654
|
-
let handle = microsandbox::sandbox::Sandbox::get(&name).await?;
|
|
655
|
-
handle.stop().await
|
|
656
|
-
})
|
|
657
|
-
.map_err(error::to_ruby)
|
|
692
|
+
block_on(self.inner.stop()).map_err(error::to_ruby)
|
|
658
693
|
}
|
|
659
694
|
|
|
660
695
|
/// Graceful stop, then wait for the process to exit. Returns an exit-status
|
|
@@ -680,6 +715,29 @@ impl Sandbox {
|
|
|
680
715
|
Ok(exit_status_to_hash(status))
|
|
681
716
|
}
|
|
682
717
|
|
|
718
|
+
/// Block until this exact sandbox reaches `status` (no built-in timeout),
|
|
719
|
+
/// returning a fresh handle. A same-name replacement raises
|
|
720
|
+
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
721
|
+
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
722
|
+
let status = sandbox_status_from_str(&status)?;
|
|
723
|
+
let handle = block_on(self.inner.wait_for_status(status)).map_err(error::to_ruby)?;
|
|
724
|
+
Ok(SbHandle::from_inner(handle))
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/// Stop and start this exact sandbox, returning the new live sandbox.
|
|
728
|
+
/// `opts` carries `force`/`timeout`/`detached`.
|
|
729
|
+
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
730
|
+
let options = restart_options(opts)?;
|
|
731
|
+
let inner = block_on(self.inner.restart_with(options)).map_err(error::to_ruby)?;
|
|
732
|
+
Ok(Sandbox::from_inner(inner))
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
736
|
+
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
737
|
+
let options = destroy_options(opts)?;
|
|
738
|
+
block_on(self.inner.destroy_with(options)).map_err(error::to_ruby)
|
|
739
|
+
}
|
|
740
|
+
|
|
683
741
|
/// Live status fetched from the backend (a round-trip per call).
|
|
684
742
|
fn status(&self) -> Result<String, Error> {
|
|
685
743
|
let status = block_on(self.inner.status()).map_err(error::to_ruby)?;
|
|
@@ -2096,6 +2154,55 @@ fn sandbox_status_str(status: SandboxStatus) -> &'static str {
|
|
|
2096
2154
|
}
|
|
2097
2155
|
}
|
|
2098
2156
|
|
|
2157
|
+
/// Parse a Ruby-facing status name back into a core `SandboxStatus`. The Ruby
|
|
2158
|
+
/// layer already validates the seven names (raising `ArgumentError`, the repo's
|
|
2159
|
+
/// idiom for a bad argument value), so reaching the fallback here means the
|
|
2160
|
+
/// native layer was called directly; keep it exhaustive rather than silent.
|
|
2161
|
+
fn sandbox_status_from_str(status: &str) -> Result<SandboxStatus, Error> {
|
|
2162
|
+
match status {
|
|
2163
|
+
"created" => Ok(SandboxStatus::Created),
|
|
2164
|
+
"starting" => Ok(SandboxStatus::Starting),
|
|
2165
|
+
"running" => Ok(SandboxStatus::Running),
|
|
2166
|
+
"draining" => Ok(SandboxStatus::Draining),
|
|
2167
|
+
"paused" => Ok(SandboxStatus::Paused),
|
|
2168
|
+
"stopped" => Ok(SandboxStatus::Stopped),
|
|
2169
|
+
"crashed" => Ok(SandboxStatus::Crashed),
|
|
2170
|
+
other => Err(error::base_error(format!(
|
|
2171
|
+
"unknown sandbox status {other:?} (expected created/starting/running/draining/\
|
|
2172
|
+
paused/stopped/crashed)"
|
|
2173
|
+
))),
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/// Build the core's `RestartOptions` from a string-keyed Hash
|
|
2178
|
+
/// (`force`/`timeout`/`detached`). An absent `timeout` keeps the core's
|
|
2179
|
+
/// ten-second graceful-shutdown default.
|
|
2180
|
+
fn restart_options(opts: RHash) -> Result<RestartOptions, Error> {
|
|
2181
|
+
let mut options = RestartOptions {
|
|
2182
|
+
force: conv::opt_bool(opts, "force")?,
|
|
2183
|
+
detached: conv::opt_bool(opts, "detached")?,
|
|
2184
|
+
..Default::default()
|
|
2185
|
+
};
|
|
2186
|
+
if let Some(secs) = conv::opt_f64(opts, "timeout")? {
|
|
2187
|
+
options.timeout = secs_to_duration(secs)?;
|
|
2188
|
+
}
|
|
2189
|
+
Ok(options)
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/// Build the core's `DestroyOptions` from a string-keyed Hash
|
|
2193
|
+
/// (`force`/`timeout`). An absent `timeout` keeps the core's ten-second
|
|
2194
|
+
/// graceful-shutdown default.
|
|
2195
|
+
fn destroy_options(opts: RHash) -> Result<DestroyOptions, Error> {
|
|
2196
|
+
let mut options = DestroyOptions {
|
|
2197
|
+
force: conv::opt_bool(opts, "force")?,
|
|
2198
|
+
..Default::default()
|
|
2199
|
+
};
|
|
2200
|
+
if let Some(secs) = conv::opt_f64(opts, "timeout")? {
|
|
2201
|
+
options.timeout = secs_to_duration(secs)?;
|
|
2202
|
+
}
|
|
2203
|
+
Ok(options)
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2099
2206
|
/// A `std::process::ExitStatus` as a Ruby Hash: `exit_code` (Integer or nil) and
|
|
2100
2207
|
/// `success` (Boolean). Returned by the live `Sandbox#wait` / `#stop_and_wait`.
|
|
2101
2208
|
fn exit_status_to_hash(status: std::process::ExitStatus) -> RHash {
|
|
@@ -2422,6 +2529,13 @@ impl SbHandle {
|
|
|
2422
2529
|
self.inner.name().to_string()
|
|
2423
2530
|
}
|
|
2424
2531
|
|
|
2532
|
+
/// The opaque, backend-assigned identity of the persisted sandbox this
|
|
2533
|
+
/// handle is bound to (v0.6.16). The lifecycle operations below refuse to
|
|
2534
|
+
/// act on a same-name replacement by comparing against it.
|
|
2535
|
+
fn id(&self) -> String {
|
|
2536
|
+
self.inner.id().to_string()
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2425
2539
|
/// Status snapshot captured when the handle was fetched (synchronous).
|
|
2426
2540
|
fn status(&self) -> String {
|
|
2427
2541
|
sandbox_status_str(self.inner.status_snapshot()).to_string()
|
|
@@ -2470,6 +2584,45 @@ impl SbHandle {
|
|
|
2470
2584
|
block_on(self.inner.request_drain()).map_err(error::to_ruby)
|
|
2471
2585
|
}
|
|
2472
2586
|
|
|
2587
|
+
/// Connect when this exact sandbox is running, wait while it is starting,
|
|
2588
|
+
/// or start it when it is created/stopped/crashed. `opts` carries
|
|
2589
|
+
/// `detached`, which applies only when a start is actually required.
|
|
2590
|
+
fn connect_or_start(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
2591
|
+
let detached = conv::opt_bool(opts, "detached")?;
|
|
2592
|
+
let inner = block_on(async {
|
|
2593
|
+
if detached {
|
|
2594
|
+
self.inner.connect_or_start_detached().await
|
|
2595
|
+
} else {
|
|
2596
|
+
self.inner.connect_or_start().await
|
|
2597
|
+
}
|
|
2598
|
+
})
|
|
2599
|
+
.map_err(error::to_ruby)?;
|
|
2600
|
+
Ok(Sandbox::from_inner(inner))
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
/// Block until this exact sandbox reaches `status` (no built-in timeout),
|
|
2604
|
+
/// returning a fresh handle. A same-name replacement raises
|
|
2605
|
+
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
2606
|
+
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
2607
|
+
let status = sandbox_status_from_str(&status)?;
|
|
2608
|
+
let handle = block_on(self.inner.wait_for_status(status)).map_err(error::to_ruby)?;
|
|
2609
|
+
Ok(SbHandle::from_inner(handle))
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
/// Stop and start this exact sandbox, returning the new live sandbox.
|
|
2613
|
+
/// `opts` carries `force`/`timeout`/`detached`.
|
|
2614
|
+
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
2615
|
+
let options = restart_options(opts)?;
|
|
2616
|
+
let inner = block_on(self.inner.restart_with(options)).map_err(error::to_ruby)?;
|
|
2617
|
+
Ok(Sandbox::from_inner(inner))
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
2621
|
+
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
2622
|
+
let options = destroy_options(opts)?;
|
|
2623
|
+
block_on(self.inner.destroy_with(options)).map_err(error::to_ruby)
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2473
2626
|
/// Block until the sandbox reaches a terminal state; returns a stop-result
|
|
2474
2627
|
/// Hash (name, status, exit_code, signal, observed_at_ms, source).
|
|
2475
2628
|
fn wait_until_stopped(&self) -> Result<RHash, Error> {
|
|
@@ -2607,6 +2760,10 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2607
2760
|
"create_with_progress",
|
|
2608
2761
|
function!(Sandbox::create_with_progress, 2),
|
|
2609
2762
|
)?;
|
|
2763
|
+
class.define_singleton_method(
|
|
2764
|
+
"connect_or_create",
|
|
2765
|
+
function!(Sandbox::connect_or_create, 2),
|
|
2766
|
+
)?;
|
|
2610
2767
|
class.define_singleton_method("start", function!(Sandbox::start, 2))?;
|
|
2611
2768
|
class.define_singleton_method("get", function!(Sandbox::get, 1))?;
|
|
2612
2769
|
class.define_singleton_method("list", function!(Sandbox::list, 0))?;
|
|
@@ -2614,6 +2771,7 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2614
2771
|
class.define_singleton_method("remove", function!(Sandbox::remove, 1))?;
|
|
2615
2772
|
|
|
2616
2773
|
class.define_method("name", method!(Sandbox::name, 0))?;
|
|
2774
|
+
class.define_method("id", method!(Sandbox::id, 0))?;
|
|
2617
2775
|
class.define_method("exec", method!(Sandbox::exec, 3))?;
|
|
2618
2776
|
class.define_method("shell", method!(Sandbox::shell, 2))?;
|
|
2619
2777
|
class.define_method("exec_stream", method!(Sandbox::exec_stream, 3))?;
|
|
@@ -2628,6 +2786,9 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2628
2786
|
class.define_method("kill", method!(Sandbox::kill, 0))?;
|
|
2629
2787
|
class.define_method("drain", method!(Sandbox::drain, 0))?;
|
|
2630
2788
|
class.define_method("wait", method!(Sandbox::wait, 0))?;
|
|
2789
|
+
class.define_method("wait_for_status", method!(Sandbox::wait_for_status, 1))?;
|
|
2790
|
+
class.define_method("restart", method!(Sandbox::restart, 1))?;
|
|
2791
|
+
class.define_method("destroy", method!(Sandbox::destroy, 1))?;
|
|
2631
2792
|
class.define_method("status", method!(Sandbox::status, 0))?;
|
|
2632
2793
|
class.define_method("owns_lifecycle", method!(Sandbox::owns_lifecycle, 0))?;
|
|
2633
2794
|
class.define_method("detach", method!(Sandbox::detach, 0))?;
|
|
@@ -2667,6 +2828,7 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2667
2828
|
|
|
2668
2829
|
let handle = native.define_class("SandboxHandle", ruby.class_object())?;
|
|
2669
2830
|
handle.define_method("name", method!(SbHandle::name, 0))?;
|
|
2831
|
+
handle.define_method("id", method!(SbHandle::id, 0))?;
|
|
2670
2832
|
handle.define_method("status", method!(SbHandle::status, 0))?;
|
|
2671
2833
|
handle.define_method("created_at_ms", method!(SbHandle::created_at_ms, 0))?;
|
|
2672
2834
|
handle.define_method("updated_at_ms", method!(SbHandle::updated_at_ms, 0))?;
|
|
@@ -2674,6 +2836,10 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2674
2836
|
handle.define_method("stop_with_timeout", method!(SbHandle::stop_with_timeout, 1))?;
|
|
2675
2837
|
handle.define_method("kill", method!(SbHandle::kill, 0))?;
|
|
2676
2838
|
handle.define_method("kill_with_timeout", method!(SbHandle::kill_with_timeout, 1))?;
|
|
2839
|
+
handle.define_method("connect_or_start", method!(SbHandle::connect_or_start, 1))?;
|
|
2840
|
+
handle.define_method("wait_for_status", method!(SbHandle::wait_for_status, 1))?;
|
|
2841
|
+
handle.define_method("restart", method!(SbHandle::restart, 1))?;
|
|
2842
|
+
handle.define_method("destroy", method!(SbHandle::destroy, 1))?;
|
|
2677
2843
|
handle.define_method("request_stop", method!(SbHandle::request_stop, 0))?;
|
|
2678
2844
|
handle.define_method("request_kill", method!(SbHandle::request_kill, 0))?;
|
|
2679
2845
|
handle.define_method("request_drain", method!(SbHandle::request_drain, 0))?;
|
data/lib/microsandbox/errors.rb
CHANGED
|
@@ -35,6 +35,13 @@ module Microsandbox
|
|
|
35
35
|
define_error(:SandboxNotRunningError, "sandbox-not-running")
|
|
36
36
|
define_error(:SandboxAlreadyExistsError, "sandbox-already-exists")
|
|
37
37
|
define_error(:SandboxStillRunningError, "sandbox-still-running")
|
|
38
|
+
# v0.6.16: a lifecycle operation was aimed at a sandbox identity that no
|
|
39
|
+
# longer owns the name — the name was removed and recreated behind the
|
|
40
|
+
# caller's back. Raised by the identity-checked convergent APIs
|
|
41
|
+
# (`wait_for_status`, `restart`, `destroy`, `connect_or_start`) rather than
|
|
42
|
+
# letting them act on the replacement. Mirrors the Python SDK's
|
|
43
|
+
# SandboxReplacedError.
|
|
44
|
+
define_error(:SandboxReplacedError, "sandbox-replaced")
|
|
38
45
|
|
|
39
46
|
# Execution errors --------------------------------------------------------
|
|
40
47
|
define_error(:ExecTimeoutError, "exec-timeout")
|