kobako 0.10.0 → 0.11.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +7 -0
  4. data/Cargo.lock +1 -1
  5. data/README.md +14 -7
  6. data/data/kobako.wasm +0 -0
  7. data/ext/kobako/Cargo.toml +2 -2
  8. data/ext/kobako/src/runtime/ambient.rs +1 -1
  9. data/ext/kobako/src/runtime/cache.rs +3 -3
  10. data/ext/kobako/src/runtime/capture.rs +1 -1
  11. data/ext/kobako/src/runtime/config.rs +3 -4
  12. data/ext/kobako/src/runtime/dispatch.rs +6 -6
  13. data/ext/kobako/src/runtime/exports.rs +1 -1
  14. data/ext/kobako/src/runtime/instance_pre.rs +1 -1
  15. data/ext/kobako/src/runtime/invocation.rs +27 -27
  16. data/ext/kobako/src/runtime/trap.rs +5 -5
  17. data/ext/kobako/src/runtime.rs +44 -45
  18. data/ext/kobako/src/snapshot.rs +2 -2
  19. data/lib/kobako/capture.rb +5 -7
  20. data/lib/kobako/catalog/handles.rb +24 -31
  21. data/lib/kobako/catalog/namespaces.rb +19 -27
  22. data/lib/kobako/catalog/snippets.rb +10 -16
  23. data/lib/kobako/codec/utils.rb +6 -9
  24. data/lib/kobako/errors.rb +33 -39
  25. data/lib/kobako/handle.rb +2 -3
  26. data/lib/kobako/namespace.rb +8 -11
  27. data/lib/kobako/outcome.rb +12 -14
  28. data/lib/kobako/pool.rb +18 -24
  29. data/lib/kobako/sandbox.rb +61 -83
  30. data/lib/kobako/sandbox_options.rb +5 -9
  31. data/lib/kobako/snapshot.rb +2 -4
  32. data/lib/kobako/snippet/binary.rb +1 -3
  33. data/lib/kobako/snippet/source.rb +1 -2
  34. data/lib/kobako/snippet.rb +1 -2
  35. data/lib/kobako/transport/dispatcher.rb +39 -38
  36. data/lib/kobako/transport/request.rb +1 -1
  37. data/lib/kobako/transport/run.rb +23 -28
  38. data/lib/kobako/transport/yielder.rb +11 -17
  39. data/lib/kobako/transport.rb +2 -3
  40. data/lib/kobako/usage.rb +10 -13
  41. data/lib/kobako/version.rb +1 -1
  42. data/sig/kobako/transport/dispatcher.rbs +2 -0
  43. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f94d5012ea43dae1d5bd51d2b8f0f6c7357edc259c7771577d3454c7055a5f1d
4
- data.tar.gz: 494087c5769aa0ce019ac47d705a1426fb237840ab3c88700afdcc970816b43d
3
+ metadata.gz: cc8e33cc57bfd43cf4d4e06def6536c8c0d45fd4ccd5bbf04b13a4187db67f0a
4
+ data.tar.gz: 8656976c144bb23226686b2c8f60830d3fd635f637c52caaa824bd3e168e07f2
5
5
  SHA512:
6
- metadata.gz: 9cd941922d385068bcb6e78849f5d43dc68e8bc36e2a6a04b245e406c488a2313cffc697441f69c2f76f3ea154924c67e594271009308702aa4f72c514bc0983
7
- data.tar.gz: 5a3b07b25b6ea434f69731749c5756d802aa585d2f10cdb3097dafdaf442d1d1da61a1c338cbb0eb3506915d2ba968baf7906607ec97059f1da5ac1070cd6950
6
+ metadata.gz: 35258fb35a0accee9beea36f01c4fb6cb2c658a2aefcb3ae4ebb7c17decd4e97f1b35e155121ae77456eed6d529d7ec13195ba43a3bf8a3ad43724d11ae5512b
7
+ data.tar.gz: 06ea66fd5877fc58fc6e5a21805d434e53968004e8c00577264ca547b10ae7aa4f4196798a3fa18da332fd730cc8fe441ac8e37dbfe2527653fed0da598d8bbf
@@ -1 +1 @@
1
- {".":"0.10.0","wasm/kobako-core":"0.5.0","wasm/kobako":"0.5.0","wasm/kobako-io":"0.5.0","wasm/kobako-regexp":"0.5.0","wasm/kobako-baker":"0.5.0"}
1
+ {".":"0.11.0","wasm/kobako-core":"0.5.0","wasm/kobako":"0.5.0","wasm/kobako-io":"0.5.0","wasm/kobako-regexp":"0.5.0","wasm/kobako-baker":"0.5.0"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0](https://github.com/elct9620/kobako/compare/v0.10.0...v0.11.0) (2026-06-13)
4
+
5
+
6
+ ### Features
7
+
8
+ * **transport:** narrow guest-reachable methods via respond_to_guest? ([7a25fe3](https://github.com/elct9620/kobako/commit/7a25fe3b440b523b8a692b7601d08877b1305b0d))
9
+
3
10
  ## [0.10.0](https://github.com/elct9620/kobako/compare/v0.9.2...v0.10.0) (2026-06-12)
4
11
 
5
12
 
data/Cargo.lock CHANGED
@@ -878,7 +878,7 @@ dependencies = [
878
878
 
879
879
  [[package]]
880
880
  name = "kobako"
881
- version = "0.10.0"
881
+ version = "0.11.0"
882
882
  dependencies = [
883
883
  "libc",
884
884
  "magnus",
data/README.md CHANGED
@@ -69,7 +69,7 @@ The script executes inside the Wasm guest. It cannot read your filesystem, open
69
69
 
70
70
  ### Services
71
71
 
72
- Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior.md`](docs/behavior.md) B-07..B-12.
72
+ Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior/registration.md`](docs/behavior/registration.md) B-07..B-12.
73
73
 
74
74
  ```ruby
75
75
  class User
@@ -93,7 +93,7 @@ Names must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the hos
93
93
 
94
94
  ### Output Capture
95
95
 
96
- Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior.md`](docs/behavior.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
96
+ Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
97
97
 
98
98
  ```ruby
99
99
  result = sandbox.eval(<<~RUBY)
@@ -134,7 +134,7 @@ end
134
134
 
135
135
  ### Resource Limits
136
136
 
137
- Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior.md`](docs/behavior.md) B-35).
137
+ Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-35).
138
138
 
139
139
  ```ruby
140
140
  sandbox = Kobako::Sandbox.new(
@@ -202,7 +202,9 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
202
202
 
203
203
  ### Pooling
204
204
 
205
- For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior.md`](docs/behavior.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
205
+ For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior/runtime.md`](docs/behavior/runtime.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
206
+
207
+ `Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. B-49 bakes the shared boot state into the artifact and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the ~30 µs host-side `Sandbox.new`. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a significant gain (~4-5% in the [serverless example](examples/serverless/README.md), and proportionally less once the script itself does real work).
206
208
 
207
209
  ```ruby
208
210
  pool = Kobako::Pool.new(slots: 4) do |sandbox|
@@ -221,7 +223,7 @@ Sandboxes construct lazily on first demand. `#with` yields a Sandbox with empty
221
223
 
222
224
  ### Service Blocks
223
225
 
224
- A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`docs/behavior.md`](docs/behavior.md) B-23..B-30.
226
+ A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`docs/behavior/yield.md`](docs/behavior/yield.md) B-23..B-30.
225
227
 
226
228
  ```ruby
227
229
  sandbox.define(:Seq).bind(:Map, ->(items, &blk) { items.map(&blk) })
@@ -232,7 +234,7 @@ sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
232
234
 
233
235
  ### Handle Management
234
236
 
235
- A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior.md`](docs/behavior.md) B-13..B-21, B-34, B-37).
237
+ A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior/dispatch.md`](docs/behavior/dispatch.md) B-13..B-21, B-34, B-37).
236
238
 
237
239
  ```ruby
238
240
  class Greeter
@@ -268,7 +270,7 @@ This is deliberate, not a leak. Handle IDs run to 2³¹ − 1 per invocation and
268
270
 
269
271
  ### Snippets & Entrypoints
270
272
 
271
- `Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior.md`](docs/behavior.md) B-31..B-33).
273
+ `Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior/invocation.md`](docs/behavior/invocation.md) B-31..B-33).
272
274
 
273
275
  ```ruby
274
276
  sandbox = Kobako::Sandbox.new
@@ -320,6 +322,11 @@ sandbox.define(:Cfg).bind(:Settings, ThemeReader.new) # not: bind(:Settings, Ap
320
322
  sandbox.eval('Cfg::Settings.color') # => "#3366ff" — every other method raises NoMethodError
321
323
  ```
322
324
 
325
+ When a purpose-built wrapper is more than you need, an object can gate its own surface in
326
+ place: a private `respond_to_guest?(name)` answers, per method, whether the guest may call
327
+ it. Returning `false` for every name makes the object opaque — a credential the guest
328
+ forwards to another Service but never reads — while a named subset becomes an allow-list.
329
+
323
330
  Guest code can name any `<Namespace>::<Member>` path, but a forged name only resolves to
324
331
  something you bound — the real authorization gate is this host-side allowlist. Give each
325
332
  trust context its own Sandbox, and see [`docs/security.md`](docs/security.md) for the rest
data/data/kobako.wasm CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "kobako"
3
- version = "0.10.0"
3
+ version = "0.11.0"
4
4
  edition = "2021"
5
5
  authors = ["Aotokitsuruya <contact@aotoki.me>"]
6
6
  license = "Apache-2.0"
@@ -27,7 +27,7 @@ wasmtime = { version = "45.0.0", default-features = false, features = [
27
27
  "wat",
28
28
  ] }
29
29
  # wasmtime-wasi provides WASI preview1 support for routing guest stdout/stderr
30
- # into in-memory buffers (docs/behavior.md §B-04). The `p1` feature enables the
30
+ # into in-memory buffers. The `p1` feature enables the
31
31
  # WasiCtxBuilder + preview1 adapter which wires fd 1/2 to pipes. We omit
32
32
  # `p2` (component-model) and `p0`/`p3` (async) because kobako runs
33
33
  # synchronous sandboxes only.
@@ -11,7 +11,7 @@
11
11
  //!
12
12
  //! The host wall-clock cap is unaffected: the per-invocation timeout runs on
13
13
  //! wasmtime epoch interruption against a host `Instant`, never the guest's
14
- //! frozen `wasi:clocks/monotonic-clock` (docs/behavior.md B-01).
14
+ //! frozen `wasi:clocks/monotonic-clock`.
15
15
 
16
16
  use std::time::Duration;
17
17
 
@@ -11,7 +11,7 @@
11
11
  //!
12
12
  //! Across processes, the Cranelift compile cost is amortised by a
13
13
  //! best-effort `.cwasm` disk cache keyed by the SHA-256 of the Guest
14
- //! Binary bytes (docs/behavior.md B-01); every cache failure falls
14
+ //! Binary bytes; every cache failure falls
15
15
  //! back to in-process compilation.
16
16
  //!
17
17
  //! Concurrency: under Ruby's GVL only one thread can execute Rust code
@@ -37,7 +37,7 @@ static SHARED_ENGINE: OnceLock<WtEngine> = OnceLock::new();
37
37
  static MODULE_CACHE: OnceLock<Mutex<HashMap<PathBuf, WtModule>>> = OnceLock::new();
38
38
 
39
39
  /// Ticker cadence for the process-singleton epoch ticker. Bounds the
40
- /// granularity of the docs/behavior.md B-01 wall-clock timeout: the
40
+ /// granularity of the wall-clock timeout: the
41
41
  /// `epoch_deadline_callback` fires once per tick (`Continue(1)`), so the
42
42
  /// trap can lag the deadline by at most one tick under nominal
43
43
  /// scheduling. 10 ms keeps the lag small enough that it does not skew
@@ -57,7 +57,7 @@ const EPOCH_TICK: Duration = Duration::from_millis(10);
57
57
  ///
58
58
  /// Also enables `epoch_interruption(true)` so every Store can install an
59
59
  /// `epoch_deadline_callback` for the per-run wall-clock cap
60
- /// (docs/behavior.md B-01, E-19). The first call spawns the process-singleton ticker
60
+ /// cap. The first call spawns the process-singleton ticker
61
61
  /// thread that drives `engine.increment_epoch()` at `EPOCH_TICK`
62
62
  /// cadence; subsequent calls reuse the same engine and ticker.
63
63
  pub(crate) fn shared_engine() -> Result<&'static WtEngine, MagnusError> {
@@ -1,6 +1,6 @@
1
1
  //! Per-channel stdout / stderr capture sizing and clipping.
2
2
  //!
3
- //! Two pure helpers shared by the run path (docs/behavior.md B-04): one
3
+ //! Two pure helpers shared by the run path: one
4
4
  //! sizes the per-run `MemoryOutputPipe`, the other clips a captured
5
5
  //! snapshot back to the configured cap and reports whether the cap was
6
6
  //! exceeded. Kept channel-agnostic (a function of `cap`, not of which
@@ -13,11 +13,10 @@ use std::time::Duration;
13
13
  /// Wall-clock and output caps for one `Runtime`. `None` on any field
14
14
  /// disables that cap.
15
15
  pub(crate) struct Config {
16
- /// Wall-clock cap for one guest `#eval` / `#run` (docs/behavior.md
17
- /// B-01, E-19). Stamped into a per-run `Instant` deadline by
18
- /// `Runtime::prime_caps`.
16
+ /// Wall-clock cap for one guest `#eval` / `#run`. Stamped into a
17
+ /// per-run `Instant` deadline by `Runtime::prime_caps`.
19
18
  pub(crate) timeout: Option<Duration>,
20
- /// Byte cap for guest stdout capture (docs/behavior.md B-01 / B-04).
19
+ /// Byte cap for guest stdout capture.
21
20
  /// Sizes the per-run `MemoryOutputPipe` and computes the truncation
22
21
  /// flag in `Runtime::build_snapshot`.
23
22
  pub(crate) stdout_limit_bytes: Option<usize>,
@@ -3,7 +3,7 @@
3
3
  //! When the guest invokes the wasm import declared in
4
4
  //! `wasm/kobako-core/src/abi.rs`, wasmtime calls back into the host
5
5
  //! through the closure registered by `instance_pre::build_linker`.
6
- //! That closure delegates here. The dispatcher (docs/behavior.md B-12 / B-13):
6
+ //! That closure delegates here. The dispatcher:
7
7
  //!
8
8
  //! 1. Reads the Request bytes from guest linear memory.
9
9
  //! 2. Invokes the Ruby-side dispatch Proc bound via
@@ -55,8 +55,8 @@ use wasmtime::Caller;
55
55
  use super::invocation::Invocation;
56
56
 
57
57
  // ============================================================
58
- // Active-caller pointer for the per-thread Invocation slot (B-24, B-28,
59
- // SPEC.md Single-Invocation Slot).
58
+ // Active-caller pointer for the per-thread Invocation slot
59
+ // (SPEC.md Single-Invocation Slot).
60
60
  // ============================================================
61
61
  //
62
62
  // `Runtime#yield_to_active_invocation` (whose body is the
@@ -73,8 +73,8 @@ use super::invocation::Invocation;
73
73
  // The pointer is therefore erased to `NonNull<()>` and parked in a
74
74
  // per-thread slot — the materialised form of the SPEC.md
75
75
  // "Single-Invocation Slot" invariant. The single-threaded wasm
76
- // execution per Sandbox (B-22) plus the LIFO re-entry shape of nested
77
- // dispatch frames (B-28) ensures no aliasing across threads or across
76
+ // execution per Sandbox plus the LIFO re-entry shape of nested
77
+ // dispatch frames ensures no aliasing across threads or across
78
78
  // frames; the recovery invariant lives at `current_caller`. The
79
79
  // pointer is set on entry to `handle` and restored to the outer
80
80
  // frame's value on every exit through a drop guard.
@@ -85,7 +85,7 @@ thread_local! {
85
85
 
86
86
  /// RAII guard that saves the previous `ACTIVE_CALLER` value on
87
87
  /// installation and restores it on drop. Nested `__kobako_dispatch`
88
- /// frames stack within one Invocation (B-28) — the inner frame's `set`
88
+ /// frames stack within one Invocation — the inner frame's `set`
89
89
  /// swaps in its own pointer while remembering the outer's; drop
90
90
  /// restores the outer so its continuation (e.g. iterating over another
91
91
  /// guest block) still finds a live caller.
@@ -4,7 +4,7 @@
4
4
  //! `Runtime::instantiate` resolves the ABI exports the run path drives
5
5
  //! (`__kobako_eval` / `__kobako_run` / `__kobako_take_outcome` /
6
6
  //! `__kobako_alloc`) plus the `memory` export against each fresh
7
- //! per-invocation instance (docs/behavior.md B-49) and bundles their
7
+ //! per-invocation instance and bundles their
8
8
  //! typed handles here, so the invocation body passes one struct around
9
9
  //! rather than re-resolving exports by name at every step. Distinct
10
10
  //! from `super::cache` (the process-wide Engine / Module cache): this
@@ -29,7 +29,7 @@ static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocatio
29
29
  /// Look up `path` in the per-path `InstancePre` cache, wiring the
30
30
  /// Linker and resolving the Module's imports on a miss. Compilation
31
31
  /// faults surface through `cached_module`; import-resolution faults
32
- /// raise `Kobako::SetupError` (docs/behavior.md E-41).
32
+ /// raise `Kobako::SetupError`.
33
33
  pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>, MagnusError> {
34
34
  let cache = INSTANCE_PRE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
35
35
 
@@ -3,15 +3,14 @@
3
3
  //! for the lifetime of one `Runtime::eval` / `Runtime::run` call).
4
4
  //!
5
5
  //! Owned as the data of each per-invocation `wasmtime::Store`
6
- //! (docs/behavior.md B-49) and threaded through every host import —
6
+ //! and threaded through every host import —
7
7
  //! the `__kobako_dispatch` dispatcher reads the bound dispatch Proc,
8
8
  //! while the run-path methods on `crate::runtime::Runtime` install the
9
- //! invocation's WASI context + pipes at Store creation
10
- //! (docs/behavior.md B-03 / B-04).
9
+ //! invocation's WASI context + pipes at Store creation.
11
10
  //!
12
11
  //! The slot also carries the per-invocation wall-clock deadline
13
- //! (docs/behavior.md B-01, E-19) and the per-invocation linear-memory
14
- //! delta cap `MemoryLimiter` (docs/behavior.md B-01, E-20). Both are
12
+ //! and the per-invocation linear-memory
13
+ //! delta cap `MemoryLimiter`. Both are
15
14
  //! read from the wasmtime `epoch_deadline_callback` / `ResourceLimiter`
16
15
  //! callbacks installed in `crate::runtime::Runtime::new_store`. The
17
16
  //! memory cap measures only the `memory.grow` delta past the linear-
@@ -52,7 +51,7 @@ impl Invocation {
52
51
  /// Build a fresh per-Store host state. `memory_limit` carries the
53
52
  /// `Sandbox#memory_limit` cap in bytes (or `None` to disable the cap);
54
53
  /// it is read from the wasmtime `ResourceLimiter` callback every
55
- /// time the guest grows linear memory (docs/behavior.md B-01, E-20).
54
+ /// time the guest grows linear memory.
56
55
  pub(super) fn new(memory_limit: Option<usize>) -> Self {
57
56
  Self {
58
57
  wasi: None,
@@ -69,7 +68,7 @@ impl Invocation {
69
68
  /// Install a freshly-built WASI context plus the matching stdout/stderr
70
69
  /// pipe clones. Called from `crate::runtime::Runtime::eval` /
71
70
  /// `crate::runtime::Runtime::run` at the top of every guest
72
- /// invocation (docs/behavior.md B-03 / B-04).
71
+ /// invocation.
73
72
  pub(super) fn install_wasi(
74
73
  &mut self,
75
74
  wasi: WasiP1Ctx,
@@ -127,7 +126,7 @@ impl Invocation {
127
126
 
128
127
  /// Replace the per-run wall-clock deadline. `Some(at)` makes the
129
128
  /// epoch-deadline callback trap once `Instant::now() >= at`; `None`
130
- /// disables the cap. Called at the top of every `#run` (docs/behavior.md B-01).
129
+ /// disables the cap. Called at the top of every `#run`.
131
130
  pub(super) fn set_deadline(&mut self, deadline: Option<Instant>) {
132
131
  self.deadline = deadline;
133
132
  }
@@ -149,7 +148,7 @@ impl Invocation {
149
148
  &mut self.limiter
150
149
  }
151
150
 
152
- /// Arm the docs/behavior.md E-20 memory cap for one guest run with
151
+ /// Arm the memory cap for one guest run with
153
152
  /// the current linear-memory size as the baseline. The limiter
154
153
  /// charges only the `memory.grow` delta past `baseline` against
155
154
  /// the cap, so the mruby image's initial allocation and the
@@ -162,23 +161,23 @@ impl Invocation {
162
161
  self.limiter.activate(baseline);
163
162
  }
164
163
 
165
- /// Disarm the docs/behavior.md E-20 memory cap. See
164
+ /// Disarm the memory cap. See
166
165
  /// `Invocation::arm_memory_cap`.
167
166
  pub(super) fn disarm_memory_cap(&mut self) {
168
167
  self.limiter.deactivate();
169
168
  }
170
169
 
171
- /// Stamp the wall-clock entry instant for the docs/behavior.md
172
- /// B-35 `wall_time` measurement. Called at the top of every
170
+ /// Stamp the wall-clock entry instant for the `wall_time`
171
+ /// measurement. Called at the top of every
173
172
  /// invocation immediately before the guest export call so the
174
- /// bracket matches the `timeout` deadline accounting (B-01) and
173
+ /// bracket matches the `timeout` deadline accounting and
175
174
  /// excludes post-run host bookkeeping such as `OUTCOME_BUFFER`
176
175
  /// decoding.
177
176
  pub(super) fn start_wall_clock(&mut self) {
178
177
  self.wall_entry = Some(Instant::now());
179
178
  }
180
179
 
181
- /// Close the docs/behavior.md B-35 `wall_time` measurement
180
+ /// Close the `wall_time` measurement
182
181
  /// started by `Invocation::start_wall_clock`. Idempotent — a
183
182
  /// stop with no matching start (e.g. if the guest export call
184
183
  /// never executed because of a host-side allocation failure)
@@ -190,13 +189,13 @@ impl Invocation {
190
189
  }
191
190
 
192
191
  /// Return the wall-clock duration the most recent invocation
193
- /// spent inside the guest export call (docs/behavior.md B-35).
192
+ /// spent inside the guest export call.
194
193
  /// Zero before the first invocation.
195
194
  pub(super) fn wall_time(&self) -> Duration {
196
195
  self.wall_time
197
196
  }
198
197
 
199
- /// Return the docs/behavior.md B-35 `memory_peak` — the high-
198
+ /// Return the `memory_peak` — the high-
200
199
  /// water mark of the per-invocation `memory.grow` delta past the
201
200
  /// linear-memory size captured at invocation entry. Zero before
202
201
  /// the first invocation.
@@ -206,7 +205,7 @@ impl Invocation {
206
205
  }
207
206
 
208
207
  /// Resource limiter that enforces the per-invocation `memory_limit`
209
- /// cap from docs/behavior.md B-01 / E-20.
208
+ /// cap.
210
209
  ///
211
210
  /// `max_memory` is the byte cap on per-invocation growth (`None` disables
212
211
  /// the cap). `baseline` is the linear-memory size captured at invocation
@@ -249,9 +248,9 @@ impl MemoryLimiter {
249
248
  /// the cap is dormant by default — the module's declared initial
250
249
  /// memory is allocated during `Linker::instantiate` and the
251
250
  /// per-invocation budget excludes anything that existed before
252
- /// arming (docs/behavior.md B-01 Notes, E-20). Also clears the
251
+ /// arming. Also clears the
253
252
  /// per-invocation `MemoryLimiter::peak` high-water so the
254
- /// docs/behavior.md B-35 `memory_peak` accounting restarts from
253
+ /// `memory_peak` accounting restarts from
255
254
  /// zero for the new invocation.
256
255
  fn activate(&mut self, baseline: usize) {
257
256
  self.baseline = baseline;
@@ -270,9 +269,9 @@ impl MemoryLimiter {
270
269
  /// Return the high-water mark of the per-invocation
271
270
  /// `memory.grow` delta past `baseline` observed since the last
272
271
  /// `MemoryLimiter::activate`. Read after the guest export
273
- /// returns to populate `Kobako::Usage#memory_peak`
274
- /// (docs/behavior.md B-35). Pinned to the last accepted grow —
275
- /// rejected `desired` values that trip the docs/behavior.md E-20
272
+ /// returns to populate `Kobako::Usage#memory_peak`.
273
+ /// Pinned to the last accepted grow —
274
+ /// rejected `desired` values that trip the memory
276
275
  /// cap never update the peak, so the reported value never exceeds
277
276
  /// `memory_limit`.
278
277
  pub(super) fn peak(&self) -> usize {
@@ -312,8 +311,9 @@ impl ResourceLimiter for MemoryLimiter {
312
311
  }
313
312
  }
314
313
 
315
- /// Marker error returned from `MemoryLimiter::memory_growing` on
316
- /// docs/behavior.md E-20. Downcast from the wasmtime trap error to surface as
314
+ /// Marker error returned from `MemoryLimiter::memory_growing` when the
315
+ /// per-invocation memory cap is exceeded. Downcast from the wasmtime
316
+ /// trap error to surface as
317
317
  /// `Kobako::MemoryLimitError` on the Ruby side. Callers use the
318
318
  /// `Display` impl below — no field is read directly — so the inner
319
319
  /// state stays private.
@@ -347,9 +347,9 @@ impl std::fmt::Display for MemoryLimitTrap {
347
347
 
348
348
  impl std::error::Error for MemoryLimitTrap {}
349
349
 
350
- /// Marker error returned from the epoch-deadline callback on
351
- /// docs/behavior.md E-19. Downcast from the wasmtime trap error to
352
- /// surface as `Kobako::TimeoutError` on the Ruby side.
350
+ /// Marker error returned from the epoch-deadline callback when the
351
+ /// wall-clock deadline is exceeded. Downcast from the wasmtime trap
352
+ /// error to surface as `Kobako::TimeoutError` on the Ruby side.
353
353
  #[derive(Debug)]
354
354
  pub(crate) struct TimeoutTrap;
355
355
 
@@ -17,7 +17,7 @@ use super::invocation::{Invocation, MemoryLimitTrap, TimeoutTrap};
17
17
  use super::{memory_limit_err, setup_err, timeout_err, trap_err};
18
18
 
19
19
  /// Epoch-deadline callback installed on every Store. Read the per-run
20
- /// wall-clock deadline from `Invocation` (docs/behavior.md B-01) and trap with
20
+ /// wall-clock deadline from `Invocation` and trap with
21
21
  /// `TimeoutTrap` once the deadline has passed; otherwise extend the
22
22
  /// next check by one tick of the process-wide epoch ticker. When the
23
23
  /// deadline is `None` the callback should not fire under normal
@@ -41,9 +41,9 @@ pub(super) fn epoch_deadline_callback(
41
41
  /// without the magnus surface.
42
42
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
43
43
  enum TrapClass {
44
- /// docs/behavior.md E-19 wall-clock cap path.
44
+ /// Wall-clock cap path.
45
45
  Timeout,
46
- /// docs/behavior.md E-20 linear-memory cap path.
46
+ /// Linear-memory cap path.
47
47
  MemoryLimit,
48
48
  /// Any other wasmtime error — surfaces as the base
49
49
  /// `Kobako::TrapError`.
@@ -117,8 +117,8 @@ fn other_trap_message(err: &wasmtime::Error) -> String {
117
117
  }
118
118
 
119
119
  /// Map an instantiation error to `Kobako::SetupError`. Instantiation runs
120
- /// during `from_path` construction, before any invocation — docs/behavior.md
121
- /// E-41 classifies every such failure as a construction setup fault, not a
120
+ /// during `from_path` construction, before any invocation — every such
121
+ /// failure is a construction setup fault, not a
122
122
  /// per-invocation cap outcome. The memory cap is dormant during
123
123
  /// instantiation (see `Invocation::arm_memory_cap` /
124
124
  /// `Invocation::disarm_memory_cap`) and the epoch deadline is not yet