kobako 0.28.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +170 -0
  4. data/Cargo.lock +111 -82
  5. data/README.md +154 -36
  6. data/ROADMAP.md +5 -0
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +86 -0
  9. data/crates/kobako-runtime/Cargo.toml +27 -6
  10. data/crates/kobako-runtime/README.md +13 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +8 -0
  12. data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
  13. data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
  14. data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
  15. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
  16. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
  17. data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
  18. data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
  19. data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
  20. data/crates/kobako-runtime/src/filesystem.rs +392 -0
  21. data/crates/kobako-runtime/src/grant.rs +20 -0
  22. data/crates/kobako-runtime/src/lib.rs +11 -1
  23. data/crates/kobako-runtime/src/mount.rs +178 -0
  24. data/crates/kobako-runtime/src/profile.rs +4 -4
  25. data/crates/kobako-runtime/src/runtime.rs +12 -0
  26. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  27. data/crates/kobako-runtime/src/yielder.rs +11 -6
  28. data/crates/kobako-transport/CHANGELOG.md +17 -0
  29. data/crates/kobako-transport/Cargo.toml +1 -1
  30. data/crates/kobako-transport/README.md +1 -1
  31. data/crates/kobako-transport/src/abi.rs +1 -1
  32. data/crates/kobako-transport/src/envelope/fault.rs +2 -1
  33. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  34. data/crates/kobako-transport/src/envelope/reply.rs +32 -14
  35. data/crates/kobako-transport/src/envelope/request.rs +18 -8
  36. data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
  37. data/crates/kobako-wasmtime/Cargo.toml +21 -16
  38. data/crates/kobako-wasmtime/README.md +5 -3
  39. data/crates/kobako-wasmtime/src/abi.rs +66 -32
  40. data/crates/kobako-wasmtime/src/ambient.rs +63 -4
  41. data/crates/kobako-wasmtime/src/cache.rs +185 -74
  42. data/crates/kobako-wasmtime/src/config.rs +58 -1
  43. data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
  44. data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
  45. data/crates/kobako-wasmtime/src/driver.rs +46 -44
  46. data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
  47. data/crates/kobako-wasmtime/src/imports.rs +46 -0
  48. data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
  49. data/crates/kobako-wasmtime/src/invocation.rs +35 -46
  50. data/crates/kobako-wasmtime/src/io.rs +5 -202
  51. data/crates/kobako-wasmtime/src/lib.rs +4 -0
  52. data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
  53. data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
  54. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
  55. data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
  56. data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
  57. data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
  58. data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
  59. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
  60. data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
  61. data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
  62. data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
  63. data/crates/kobako-wasmtime/src/preview1.rs +461 -0
  64. data/crates/kobako-wasmtime/src/trap.rs +39 -19
  65. data/crates/kobako-wasmtime/src/wasi.rs +317 -0
  66. data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
  67. data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
  68. data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
  69. data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
  70. data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
  71. data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
  72. data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -0
  73. data/data/kobako.wasm +0 -0
  74. data/ext/kobako/Cargo.toml +4 -2
  75. data/ext/kobako/src/runtime/bridge.rs +154 -81
  76. data/ext/kobako/src/runtime/escape.rs +53 -0
  77. data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
  78. data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
  79. data/ext/kobako/src/runtime/filesystem.rs +236 -0
  80. data/ext/kobako/src/runtime/gvl.rs +7 -1
  81. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  82. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  83. data/ext/kobako/src/runtime.rs +120 -199
  84. data/lib/kobako/catalog/flags.rb +2 -14
  85. data/lib/kobako/catalog/grants.rb +30 -0
  86. data/lib/kobako/catalog/mount.rb +27 -0
  87. data/lib/kobako/catalog/services.rb +11 -0
  88. data/lib/kobako/catalog/snippets.rb +5 -9
  89. data/lib/kobako/catalog.rb +11 -0
  90. data/lib/kobako/codec/decoder.rb +1 -0
  91. data/lib/kobako/codec/ext_types.rb +1 -1
  92. data/lib/kobako/codec/state.rb +19 -3
  93. data/lib/kobako/codec/value_budget.rb +86 -0
  94. data/lib/kobako/codec.rb +7 -0
  95. data/lib/kobako/context.rb +38 -11
  96. data/lib/kobako/errors.rb +15 -14
  97. data/lib/kobako/filesystem.rb +38 -0
  98. data/lib/kobako/pool.rb +2 -2
  99. data/lib/kobako/sandbox.rb +40 -12
  100. data/lib/kobako/sandbox_options.rb +39 -12
  101. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +1 -1
  102. data/lib/kobako/snippet.rb +1 -1
  103. data/lib/kobako/transport/block_failure.rb +35 -0
  104. data/lib/kobako/transport/dispatcher.rb +30 -26
  105. data/lib/kobako/transport/exposure.rb +5 -7
  106. data/lib/kobako/transport/reflection.rb +79 -23
  107. data/lib/kobako/transport/yielder.rb +8 -30
  108. data/lib/kobako/transport.rb +1 -0
  109. data/lib/kobako/version.rb +1 -1
  110. data/lib/kobako.rb +1 -0
  111. data/sig/kobako/catalog/flags.rbs +0 -4
  112. data/sig/kobako/catalog/grants.rbs +13 -0
  113. data/sig/kobako/catalog/mount.rbs +28 -0
  114. data/sig/kobako/catalog/services.rbs +4 -0
  115. data/sig/kobako/catalog/snippets.rbs +1 -1
  116. data/sig/kobako/catalog.rbs +1 -0
  117. data/sig/kobako/codec/state.rbs +7 -1
  118. data/sig/kobako/codec/value_budget.rbs +23 -0
  119. data/sig/kobako/codec.rbs +2 -0
  120. data/sig/kobako/context.rbs +14 -6
  121. data/sig/kobako/errors.rbs +4 -4
  122. data/sig/kobako/filesystem.rbs +88 -0
  123. data/sig/kobako/guest_yielder.rbs +9 -0
  124. data/sig/kobako/pool.rbs +1 -1
  125. data/sig/kobako/runtime.rbs +21 -49
  126. data/sig/kobako/sandbox.rbs +7 -1
  127. data/sig/kobako/sandbox_options.rbs +24 -2
  128. data/sig/kobako/snapshot.rbs +20 -0
  129. data/sig/kobako/snippet/bytecode.rbs +12 -0
  130. data/sig/kobako/transport/block_failure.rbs +13 -0
  131. data/sig/kobako/transport/dispatcher.rbs +3 -3
  132. data/sig/kobako/transport/exposure.rbs +1 -1
  133. data/sig/kobako/transport/reflection.rbs +14 -1
  134. data/sig/kobako/transport/yielder.rbs +3 -7
  135. data/sig/kobako/transport.rbs +2 -2
  136. metadata +60 -7
  137. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -1,5 +1,124 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.19.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.18.0...kobako-wasmtime-v0.19.0) (2026-10-26)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * **io:** File.open raises ArgumentError for a mode such as bw, where b leads, or wbt, holding both b and t, as CRuby does, instead of opening the file.
9
+ * **wasmtime:** a call through a directory descriptor answers ENOENT when the directory at its path is not the one it opened, where it used to reach that directory.
10
+ * **wasmtime:** a struct literal no longer builds the wasmtime driver's Config outside its crate; start from `Config::default()` and assign each field.
11
+ * **runtime:** a struct literal no longer builds OpenOptions, Metadata, DirEntry, Snapshot, Capture, or Usage outside kobako-runtime; use their `new` or `Default` and assign each field.
12
+ * the wasmtime Config and the SDK Options gain a setup_timeout field, so a struct literal that names every field must add it; one built from Options::default() is unaffected.
13
+ * **wasmtime:** a Service answer past 16 MiB now raises a Service failure in the guest where it used to raise the transport error, and an answer the host cannot write into guest memory now traps the invocation.
14
+ * the Guest ABI is version 5, so a Guest Binary built against version 4 is refused at Sandbox construction until rebuilt. The Yielder trait of kobako-runtime gains frame and takes the frame in yield_to_block; the Guest trait of kobako-core and the MrbGuest trait of kobako-mruby take the frame in yield_to_block; Downcall::Yield carries frame and args.
15
+ * **io:** putc, IO#read and SystemCallError.new raise RangeError for those numbers instead of writing, refusing or ignoring them.
16
+ * **guest:** the default memory_limit is now 4 MiB. JSON.parse in the guest answers an Integer for every integer within 64 bits, where it used to answer a Float past 32 bits. An integer past 64 bits now raises
17
+ * **wasmtime:** a struct literal building the wasmtime driver's Config must set the new `open_file_limit` field.
18
+ * **io:** a script writing to a file through `IO#write` beneath a Mount now raises on a failed write where it used to go on.
19
+ * **io:** a script rescuing IOError for a failed read or write beneath a Mount must rescue the EIO error class, or SystemCallError, instead.
20
+ * **guest:** a Service or Extension backend bound at the path File fails its first invocation, naming the segment the Guest Binary now defines.
21
+ * **runtime:** the Runtime trait's invoke takes the invocation's Grants as a fourth argument, so every engine implementing it must accept them.
22
+
23
+ ### Features
24
+
25
+ * check what a rename moves as it moves, refusing a directory beneath a Policy ([23794bf](https://github.com/elct9620/kobako/commit/23794bfcb54e148126e29873fa9b52d9e3de8d8c))
26
+ * **guest:** carry mruby-bigint so an Integer has no width of its own ([a74266d](https://github.com/elct9620/kobako/commit/a74266d3e7b1295b75770b62894b232195f9b3bb))
27
+ * **guest:** ship File in the default Guest Binary over the filesystem Grants ([535527b](https://github.com/elct9620/kobako/commit/535527b2c6beb82fe37758df62c3804ce5c7191a)), closes [#78](https://github.com/elct9620/kobako/issues/78)
28
+ * **guest:** widen the guest Integer to 64 bits ([7236a26](https://github.com/elct9620/kobako/commit/7236a26285dfdef175048f659c1094dd3bbb5b4d))
29
+ * **io:** create a file only where nothing is through File.open's wx ([56c5665](https://github.com/elct9620/kobako/commit/56c5665e4c87ac30b8035aaed207ce840efad25c))
30
+ * **io:** open a file by an Integer mode of File::Constants flags ([e1f484c](https://github.com/elct9620/kobako/commit/e1f484c713df4d894744833836cf9a29ba9677ee))
31
+ * **io:** open a file through wasi-libc so a create needs no write ([6d50ca4](https://github.com/elct9620/kobako/commit/6d50ca4d79b70956d113f0798255300aa8a7fe92))
32
+ * let a Host App bound how long a Guest Binary takes to be set up ([2addab0](https://github.com/elct9620/kobako/commit/2addab002550539e3ebb24bdd599dd424c2dc3d9))
33
+ * **runtime:** hand each invocation its Grants and let an engine declare the kinds it supports ([cba90ee](https://github.com/elct9620/kobako/commit/cba90eed09c8afcb23080cef1c9cbb3650648472)), closes [#78](https://github.com/elct9620/kobako/issues/78)
34
+ * **runtime:** keep a directory descriptor's identity its own while open ([6267642](https://github.com/elct9620/kobako/commit/6267642ea831917be76a0101e8d206ccfa8834c1))
35
+ * **runtime:** let a dispatch handler abandon its invocation ([179da9e](https://github.com/elct9620/kobako/commit/179da9ee1f31fed9e6d0c149faee0ad55f8db487))
36
+ * **runtime:** let a Filesystem's file refuse its close ([af16007](https://github.com/elct9620/kobako/commit/af1600797b098796dd6f329672c7d04e2e1cd2fa)), closes [#78](https://github.com/elct9620/kobako/issues/78)
37
+ * **runtime:** let a Mount decide its own access as the one kind of Grant ([5c9189e](https://github.com/elct9620/kobako/commit/5c9189eea8ac61cf165dca1b38e459c4f14acdd0)), closes [#78](https://github.com/elct9620/kobako/issues/78)
38
+ * **runtime:** let a Mount's Policy judge the spelling the host stores ([5281057](https://github.com/elct9620/kobako/commit/52810573f22d2a3edfae9b34b7767a5f440fba15))
39
+ * **runtime:** let the records a Filesystem or an engine builds gain fields ([c0ef88d](https://github.com/elct9620/kobako/commit/c0ef88d0b6df16d651e4c49226e41667307d772f))
40
+ * **runtime:** name a file grown past what a Filesystem keeps ([7bac3e6](https://github.com/elct9620/kobako/commit/7bac3e65534097b85d93f5967b6c73a979869a78))
41
+ * **runtime:** name each code a Filesystem may fail with ([1d980fa](https://github.com/elct9620/kobako/commit/1d980fa4328bad42e8a8665adfec9fc844690e97))
42
+ * **runtime:** refuse a Mount at a path that is no absolute guest path ([a525758](https://github.com/elct9620/kobako/commit/a5257589f9d831b9a3b6d1952df6e5c8469630e1))
43
+ * **wasmtime:** answer a Filesystem out of space as ENOSPC ([c648ebf](https://github.com/elct9620/kobako/commit/c648ebf63a871b7d4417110de00decf1738a19d6))
44
+ * **wasmtime:** cap the descriptors one invocation holds open ([03409c1](https://github.com/elct9620/kobako/commit/03409c1437f71312c584a92e22054b90241535a3))
45
+ * **wasmtime:** let Config gain a cap without breaking a frontend ([55d8093](https://github.com/elct9620/kobako/commit/55d80933b958bab67f1b94deb7633272dc56f972))
46
+ * **wasmtime:** present each filesystem Grant to the guest as a preopen ([68149a7](https://github.com/elct9620/kobako/commit/68149a7adc912172b72e7a34bd39082a3b2a9e95)), closes [#78](https://github.com/elct9620/kobako/issues/78)
47
+
48
+
49
+ ### Bug Fixes
50
+
51
+ * append through a Filesystem's file only in one step ([0509ca7](https://github.com/elct9620/kobako/commit/0509ca70d2d5a832303973ba0f7280be0731fd3d))
52
+ * ask a mount's block once for a read ([f89bd16](https://github.com/elct9620/kobako/commit/f89bd16c1a71ed1d385ab9d3ac5974ac3f0a9103))
53
+ * follow no link swapped in after a Policy judges a path ([ed93213](https://github.com/elct9620/kobako/commit/ed9321303dd45e113fc413e99e1240118cc4bbe4))
54
+ * hold an Overlay's lock across each call, waiting without the GVL ([b8e7342](https://github.com/elct9620/kobako/commit/b8e734273fdc577a40be97c43c46d9548d66c625))
55
+ * **io:** build Errno and IOError as CRuby does ([da77c47](https://github.com/elct9620/kobako/commit/da77c4725d3701ba982f5425b1fcf2fb0038ac89))
56
+ * **io:** give File::Constants CRuby's Linux values ([14cadd8](https://github.com/elct9620/kobako/commit/14cadd88ac5a5dcb8f73b252b68c493488b3e78a))
57
+ * **io:** keep the number a SystemCallError is built with ([5fd327a](https://github.com/elct9620/kobako/commit/5fd327add59173cd5756fecb41d97aebcba7b881))
58
+ * **io:** raise each code the engine answers as its own Errno ([eb6382e](https://github.com/elct9620/kobako/commit/eb6382e506a30e82576fa12ab9859e7d35be2b17))
59
+ * **io:** raise Errno::EIO for a file failure no other code names ([8a0447d](https://github.com/elct9620/kobako/commit/8a0447d2d09ea83e6f7771cf0411bfd6deec3d1a)), closes [#78](https://github.com/elct9620/kobako/issues/78)
60
+ * **io:** raise RangeError for a number past what putc, read and errno take ([6f87cc0](https://github.com/elct9620/kobako/commit/6f87cc08dff7146415f80ca25cd75ddbec4391c0))
61
+ * **io:** raise the error a failed file write names ([bcc8e00](https://github.com/elct9620/kobako/commit/bcc8e00662741fcac3b91f58c0b60f4a79d2ab9f))
62
+ * **io:** write a file to the end when its Filesystem takes it in parts ([029b439](https://github.com/elct9620/kobako/commit/029b43939a3a48bc2925057df69bfcb85b1a0548))
63
+ * keep a link within the directory a descriptor opened ([952dd05](https://github.com/elct9620/kobako/commit/952dd0556969683f44798da94f9fc7a17c1f6d55))
64
+ * run a stored block as the block of the Call that carried it ([bf33dc1](https://github.com/elct9620/kobako/commit/bf33dc1fd40c83498c6c12b0be52b3a835a144cf))
65
+ * **runtime:** make every Filesystem answer the methods that keep a guarantee ([00f5af6](https://github.com/elct9620/kobako/commit/00f5af63f34ac7722249746d0b0eb566da618803))
66
+ * **runtime:** report a merged directory's identity alike through an Overlay's lstat ([ded3b4a](https://github.com/elct9620/kobako/commit/ded3b4aba78efa678318c38b139b5c9c76b5d3fd))
67
+ * **wasmtime:** answer a Call the host cannot serve with the reason ([cf60dcc](https://github.com/elct9620/kobako/commit/cf60dccc63a0d0cfc959f701b7212c78beafa528))
68
+ * **wasmtime:** answer a path past Linux's limits as NAMETOOLONG ([af43917](https://github.com/elct9620/kobako/commit/af43917e30a634d6c1bd70813044967bd1057ba3))
69
+ * **wasmtime:** answer a stat that follows no link with the link itself ([ad0072e](https://github.com/elct9620/kobako/commit/ad0072e03ecc363487598a507de20a8f7aec2927))
70
+ * **wasmtime:** answer a wait to write a file by what its Mount permits ([bf863db](https://github.com/elct9620/kobako/commit/bf863db998edeab60f9276fc53e8adcf8cf98364))
71
+ * **wasmtime:** answer an offset past the largest signed offset as EINVAL ([b94788e](https://github.com/elct9620/kobako/commit/b94788e1c1fc9d86f647da4396bcba3eaec16242))
72
+ * **wasmtime:** answer an unlink with a trailing slash by the name itself ([94e7250](https://github.com/elct9620/kobako/commit/94e72505cfc027319c2139e15488eb2682268d49))
73
+ * **wasmtime:** answer descriptor flags, advice, and clock waits as preview1 does ([c1aec63](https://github.com/elct9620/kobako/commit/c1aec632445c3bf216be6375552c799d7877670f))
74
+ * **wasmtime:** answer dot paths, creates, and far seeks as Linux does ([68c234e](https://github.com/elct9620/kobako/commit/68c234e06859b93fb986bdbab92cca944ce39700))
75
+ * **wasmtime:** answer EINVAL for a guest path holding a NUL ([1c681d6](https://github.com/elct9620/kobako/commit/1c681d6e723f259ab2b3acc5e3a33d85499a2ef5))
76
+ * **wasmtime:** answer ENOENT for an open of a missing path that makes nothing ([48e3944](https://github.com/elct9620/kobako/commit/48e3944c64d7ef410fcc4595d80ba0284f257a09))
77
+ * **wasmtime:** answer every preview1 call that reads a descriptor from kobako's own table ([d0e39ae](https://github.com/elct9620/kobako/commit/d0e39aebd6d729d49111340af7f2af4e37aa7909)), closes [#78](https://github.com/elct9620/kobako/issues/78)
78
+ * **wasmtime:** answer fd_allocate beneath a Mount as unsupported ([e3d2071](https://github.com/elct9620/kobako/commit/e3d20713e4474cd637dacfece99e52b80d7ad2f6))
79
+ * **wasmtime:** answer what stands at a path before a Mount refuses an open ([81e02dc](https://github.com/elct9620/kobako/commit/81e02dc5e7d500b2971ab5d4a670d38601f89389))
80
+ * **wasmtime:** ask a Policy once for an open that may create ([a4613cb](https://github.com/elct9620/kobako/commit/a4613cb8cff89af51f55ce090433c61268105789))
81
+ * **wasmtime:** bound a buffer outside memory before a file's access is checked ([8b76f76](https://github.com/elct9620/kobako/commit/8b76f764a6336d2e77baa2ca6fe63dfd7663ce19))
82
+ * **wasmtime:** bound reads, writable waits, and a FIFO's names as preview1 hosts do ([6d89e55](https://github.com/elct9620/kobako/commit/6d89e55541f3366815dd7ed271e813a6ba41bcb3))
83
+ * **wasmtime:** bound what one preview1 call copies to wasmtime's hostcall fuel ([097c554](https://github.com/elct9620/kobako/commit/097c55426c1e71467bb1e372abc190331dd3e601))
84
+ * **wasmtime:** check path_open's flags as wasmtime-wasi does ([22eb494](https://github.com/elct9620/kobako/commit/22eb4944117260831e92fe616b02888078c02b0c))
85
+ * **wasmtime:** close the file fd_renumber moves a descriptor onto ([0f6148d](https://github.com/elct9620/kobako/commit/0f6148d536ed3e9e96749c2e4a60bf6275332655))
86
+ * **wasmtime:** give each writer of a cached artifact its own temp file ([995a46e](https://github.com/elct9620/kobako/commit/995a46e99dc1c485a303bb7f023b4e668a2801a3))
87
+ * **wasmtime:** grant the compile instance no ambient authority ([c3edc71](https://github.com/elct9620/kobako/commit/c3edc71ab99dac57cbabfc685a7cc60a5a5c06ac))
88
+ * **wasmtime:** hold the whole invocation to its deadline ([231e325](https://github.com/elct9620/kobako/commit/231e32592699008adf3105050bee8b5528ea27a8))
89
+ * **wasmtime:** judge the cache directory itself, not where a link leads ([6baf6fd](https://github.com/elct9620/kobako/commit/6baf6fd2ad845cea83049c4b2bb51174aa205cd0))
90
+ * **wasmtime:** keep a write and its file position within the largest signed offset ([29d4c34](https://github.com/elct9620/kobako/commit/29d4c341fc5a47ee43fdd249918e1f3e68120f86))
91
+ * **wasmtime:** key the compiled-artifact cache by the engine that compiles it ([b43bad2](https://github.com/elct9620/kobako/commit/b43bad2fe176c153ed82a3a8825698414df78dfa))
92
+ * **wasmtime:** leave a link named with a trailing slash in place ([9b3cdea](https://github.com/elct9620/kobako/commit/9b3cdea6dc00353c5c4896ec29b88eebdaddc8ab))
93
+ * **wasmtime:** list a directory once for each listing a guest reads ([c333001](https://github.com/elct9620/kobako/commit/c3330011725c3dba01ea248ed7d1dfea31085af0))
94
+ * **wasmtime:** look up what stands before a dot path as Linux does ([0898eaf](https://github.com/elct9620/kobako/commit/0898eafb55397c84ca9007561ddb35dd007ed2b8))
95
+ * **wasmtime:** place an append at the end as the file stands when it lands ([d7291c4](https://github.com/elct9620/kobako/commit/d7291c4f32fb1f5f2494c69c76796476bbcf4d43))
96
+ * **wasmtime:** reach the artifact cache through the directory it checked ([462fe91](https://github.com/elct9620/kobako/commit/462fe918d516cd639e405b06a5e280a9e58624df))
97
+ * **wasmtime:** refuse a `..` that steps out of a host link ([4599386](https://github.com/elct9620/kobako/commit/45993868c7334376f5d1a455afbd15fe4a2146bc))
98
+ * **wasmtime:** refuse a directory made in an opened directory's place ([5e67d4c](https://github.com/elct9620/kobako/commit/5e67d4cd2a52e3aa37bdd615fe1ae101a618db88))
99
+ * **wasmtime:** refuse a link an open asks not to follow ([123a01b](https://github.com/elct9620/kobako/commit/123a01b01f59cc69c0e3a23f9aeb01aa89132c07))
100
+ * **wasmtime:** refuse a link beneath a Policy before the Policy judges the path ([0e08e7b](https://github.com/elct9620/kobako/commit/0e08e7b463344ae34b5feb1787f8af8a6c719063))
101
+ * **wasmtime:** refuse a link to a directory an open asks not to follow ([eec218f](https://github.com/elct9620/kobako/commit/eec218fdd5584f641070032cf1110ef1683eaae5))
102
+ * **wasmtime:** refuse a path climbing above the directory it is opened through ([ae52746](https://github.com/elct9620/kobako/commit/ae52746761624249b7ff1e1fa09ece4ea8130ced))
103
+ * **wasmtime:** refuse a path through a link beneath a Mount with a Policy ([3da1641](https://github.com/elct9620/kobako/commit/3da16416692ade01db2d9cf366d54d3ec5f50605))
104
+ * **wasmtime:** refuse a rename of a file onto a path ending in a slash ([486a0ab](https://github.com/elct9620/kobako/commit/486a0ab3584772b58e181d5de57ef505e610eb78))
105
+ * **wasmtime:** refuse an argument preview1 cannot hold in wasmtime-wasi's order ([9c76e21](https://github.com/elct9620/kobako/commit/9c76e2181a0dabd81f02723154cc4290215fcf70))
106
+ * **wasmtime:** refuse to remove or move a Mount's root ([9f2e000](https://github.com/elct9620/kobako/commit/9f2e000115a4dd4aecfcc4b1cd39b23f6f20fe05))
107
+ * **wasmtime:** report a host FIFO or device as of an unknown filetype ([5a212e7](https://github.com/elct9620/kobako/commit/5a212e7a7c0f069ffdcc56b850fab3778863d9ee))
108
+ * **wasmtime:** require wasmtime 49.0.4 for the aarch64 br_table fix ([390e30b](https://github.com/elct9620/kobako/commit/390e30ba51ed4175820f2bb230e3ad5b0ad1e9a5))
109
+ * **wasmtime:** resolve a `..` and a trailing slash as a host does ([d2d04c1](https://github.com/elct9620/kobako/commit/d2d04c1fff24d8829b2cf4fcbf628565f35eeadc))
110
+ * **wasmtime:** resolve a final dot through the name before it ([7a0d3d8](https://github.com/elct9620/kobako/commit/7a0d3d8b23d47d4ba5c942682841434e74d6bf6a))
111
+ * **wasmtime:** resolve a link a stat names with a trailing slash ([6da98fa](https://github.com/elct9620/kobako/commit/6da98facbccaa9bf6d8f8280a56841e19a7c879b))
112
+ * **wasmtime:** size a file opened only to read as wasmtime-wasi and Linux answer ([810f5c8](https://github.com/elct9620/kobako/commit/810f5c8e863dc5bdbe82f8a19c7559fd7eee03f9))
113
+ * **wasmtime:** sync a cached artifact before renaming it into place ([097efc7](https://github.com/elct9620/kobako/commit/097efc77599545bb2a5e729c75d23082470ed8f4))
114
+ * **wasmtime:** take wasmtime 49.0.3 for its sandbox advisories ([7672592](https://github.com/elct9620/kobako/commit/76725923cac50473c7e3a21cc5121e3cbba0cba5))
115
+ * **wasmtime:** trap a guest field address past the address space ([de5d47e](https://github.com/elct9620/kobako/commit/de5d47e10aa5d978573e46a7cb3453ff248d006e))
116
+
117
+
118
+ ### Performance Improvements
119
+
120
+ * **io:** answer IO#read with a length in one read the file holds ([e9161a2](https://github.com/elct9620/kobako/commit/e9161a2018c1617157025de275f77890e865b7d1)), closes [#78](https://github.com/elct9620/kobako/issues/78)
121
+
3
122
  ## [0.18.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.17.0...kobako-wasmtime-v0.18.0) (2026-10-13)
4
123
 
5
124
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  [package]
17
17
  name = "kobako-wasmtime"
18
- version = "0.18.0"
18
+ version = "0.19.0"
19
19
  edition = "2021"
20
20
  # wasmtime's own floor.
21
21
  rust-version = "1.96"
@@ -30,17 +30,19 @@ categories = ["wasm", "virtualization"]
30
30
  # The engine-neutral contract this crate implements. The version pin
31
31
  # rides the linked release group; the path keeps in-tree builds (and
32
32
  # the Ruby gem, which ships both crates) resolving locally.
33
- kobako-runtime = { version = "0.18.0", path = "../kobako-runtime" }
33
+ kobako-runtime = { version = "0.19.0", path = "../kobako-runtime" }
34
34
  # The fixed tier: the core envelope this driver shuttles and the ABI
35
35
  # values it drives the exports against.
36
- kobako-transport = { version = "0.18.0", path = "../kobako-transport" }
36
+ kobako-transport = { version = "0.19.0", path = "../kobako-transport" }
37
37
  # wasmtime — host-side embedder for kobako.wasm. We disable default-features
38
38
  # and opt back in only what kobako needs: a Cranelift-backed runtime that can
39
39
  # compile a pre-built wasm32-wasip1 module on the host triple, plus the `wat`
40
40
  # feature so test fixtures can be expressed as text.
41
- # `cache` / `parallel-compilation` / `pooling` / `component-model` / `async`
42
- # are intentionally off — kobako runs short-lived synchronous sandboxes.
43
- wasmtime = { version = "49.0.2", default-features = false, features = [
41
+ # `cache` / `parallel-compilation` / `pooling` are intentionally off —
42
+ # kobako runs short-lived synchronous sandboxes. `component-model` and
43
+ # `async` still arrive through wasmtime-wasi's `p1`, which builds on `p2`;
44
+ # kobako loads only core Modules, so none of that code is reached.
45
+ wasmtime = { version = "49.0.4", default-features = false, features = [
44
46
  "cranelift",
45
47
  "runtime",
46
48
  "gc",
@@ -49,19 +51,22 @@ wasmtime = { version = "49.0.2", default-features = false, features = [
49
51
  "demangle",
50
52
  "wat",
51
53
  ] }
52
- # wasmtime-wasi provides WASI preview1 support for routing guest stdout/stderr
53
- # into in-memory buffers. The `p1` feature enables the
54
- # WasiCtxBuilder + preview1 adapter which wires fd 1/2 to pipes. We omit
55
- # `p2` (component-model) and `p0`/`p3` (async) because kobako runs
56
- # synchronous sandboxes only.
57
- wasmtime-wasi = { version = "49.0.2", default-features = false, features = ["p1"] }
54
+ # wasmtime-wasi answers the preview1 calls that read no descriptor
55
+ # (args, environ, clocks, random) and supplies the memory pipes the
56
+ # capture channels write into; kobako answers every call that reads its
57
+ # own descriptor table. `p1` is the only feature kobako asks for; it
58
+ # enables `p2` itself, while `p0` and `p3` stay off.
59
+ wasmtime-wasi = { version = "49.0.4", default-features = false, features = ["p1"] }
60
+ # bytes is the buffer type wasmtime-wasi's stream traits take, for the
61
+ # capture pipes kobako's own preview1 imports write into.
62
+ bytes = "1.11"
58
63
  # sha2 keys the on-disk compiled-module cache by Guest Binary content
59
64
  # (see cache.rs); a collision would load the wrong artifact, so the
60
65
  # hash must be cryptographic.
61
66
  sha2 = "0.11"
62
67
 
63
- # libc supplies geteuid for the cache-directory ownership check gating
64
- # the unsafe artifact deserialize (see cache.rs); std exposes a file's
65
- # owner but not the process's effective uid.
68
+ # rustix reaches the cache directory through one descriptor, checked by
69
+ # owner, so the unsafe artifact deserialize reads only what that
70
+ # directory holds (see cache.rs); std has no `*at` calls.
66
71
  [target.'cfg(unix)'.dependencies]
67
- libc = "0.2"
72
+ rustix = { version = "1.1.4", features = ["fs", "process"] }
@@ -10,10 +10,12 @@ contract surface.
10
10
 
11
11
  | Mechanic | What the driver holds |
12
12
  |---|---|
13
- | Compilation | process-wide Engine and compiled-Module caches, with an on-disk AOT (`.cwasm`) artifact cache keyed by Guest Binary content |
13
+ | Compilation | process-wide Engine and compiled-Module caches |
14
+ | Disk cache | AOT `.cwasm` artifacts on Unix, keyed by Guest Binary content and engine |
14
15
  | Instantiation | a pre-linked `InstancePre` per guest path; every invocation runs on a fresh instance and discards its Store afterwards |
15
- | Bounds | the epoch-based wall-clock timeout and the per-invocation linear-memory cap |
16
+ | Bounds | epoch-based wall-clock deadlines on setup and on each invocation, and the per-invocation linear-memory cap |
16
17
  | Ambient denial | under the default hermetic profile, frozen clocks and a constant RNG |
18
+ | Filesystems | a preopen per Mount, and its own capped descriptor table |
17
19
 
18
20
  The kobako Ruby gem's native ext is the first frontend; a Rust host
19
21
  SDK consumes the same surface.
@@ -22,7 +24,7 @@ SDK consumes the same surface.
22
24
 
23
25
  ```toml
24
26
  [dependencies]
25
- kobako-wasmtime = "0.18.0" # x-release-please-version
27
+ kobako-wasmtime = "0.19.0" # x-release-please-version
26
28
  ```
27
29
 
28
30
  ## License
@@ -5,44 +5,41 @@
5
5
  //! alongside the `InstancePre` it guards (`crate::instance_pre`) and is
6
6
  //! amortised with it, rather than once per `Driver::new`.
7
7
 
8
+ use std::time::Duration;
9
+
8
10
  use wasmtime::{AsContextMut, InstancePre, Store as WtStore};
9
11
 
10
12
  use crate::cache::shared_engine;
11
- use crate::config::Config;
12
13
  use crate::invocation::Invocation;
13
- use crate::{io, trap};
14
- use kobako_runtime::error::SetupError;
15
- use kobako_runtime::profile::Profile;
14
+ use crate::trap;
15
+ use crate::wasi::Wasi;
16
+ use kobako_runtime::error::{SetupError, Trap};
16
17
  use kobako_transport::abi::ABI_VERSION;
17
18
 
18
- /// Caps for the throwaway probe instance. Instantiation runs the
19
- /// artifact's start section, so the capture pipes are sized to keep
20
- /// nothing — the probe reads neither — and the strongest isolation rung
21
- /// is taken, since reading an artifact's version grants no ambient
22
- /// authority. The timeout and memory caps stay off because both arm per
23
- /// invocation and there is none here.
24
- const PROBE_CONFIG: Config = Config {
25
- timeout: None,
26
- memory_limit: None,
27
- stdout_limit: Some(0),
28
- stderr_limit: Some(0),
29
- profile: Profile::Hermetic,
30
- };
31
-
32
19
  /// An absent or unequal version is a deterministic artifact fault, so
33
- /// every failure is a `SetupError`. The probe's own WASI context keeps a
34
- /// guest whose start section touches WASI on that path instead of
35
- /// panicking in `Invocation::wasi_mut`.
36
- pub(crate) fn verify(pre: &InstancePre<Invocation>) -> Result<(), SetupError> {
37
- let mut store = WtStore::new(shared_engine()?, Invocation::new(None));
38
- // Epoch interruption is on engine-wide and a fresh Store's deadline
39
- // has already elapsed, so the probe must name one it cannot reach.
40
- store.set_epoch_deadline(trap::NO_TIMEOUT_EPOCH_DELTA);
41
- io::install_wasi(&mut store, &PROBE_CONFIG, &[])
42
- .map_err(|t| SetupError::Dead(t.to_string()))?;
20
+ /// every failure is a `SetupError`. `setup_timeout` bounds instantiating
21
+ /// the artifact and reading its version, so a start function that never
22
+ /// returns fails construction instead of hanging it.
23
+ pub(crate) fn verify(
24
+ pre: &InstancePre<Invocation>,
25
+ setup_timeout: Option<Duration>,
26
+ ) -> Result<(), SetupError> {
27
+ let wasi = Wasi::for_probe().map_err(|t| SetupError::Dead(t.to_string()))?;
28
+ // The memory cap stays off: it arms per invocation, and the probe is
29
+ // none.
30
+ let mut store = WtStore::new(shared_engine()?, Invocation::new(None, wasi));
31
+ trap::arm_deadline(&mut store, setup_timeout);
32
+ let past_deadline = |err: &wasmtime::Error| {
33
+ matches!(trap::cap_trap(err), Some(Trap::Timeout(_))).then(|| {
34
+ SetupError::Dead(format!(
35
+ "the Guest Binary did not finish being set up within {:?}",
36
+ setup_timeout.unwrap_or_default()
37
+ ))
38
+ })
39
+ };
43
40
  let instance = pre
44
41
  .instantiate(store.as_context_mut())
45
- .map_err(trap::instantiate_err)?;
42
+ .map_err(|e| past_deadline(&e).unwrap_or_else(|| trap::instantiate_err(e)))?;
46
43
  let probe = instance
47
44
  .get_typed_func::<(), u32>(store.as_context_mut(), "__kobako_abi_version")
48
45
  .map_err(|_| {
@@ -52,9 +49,11 @@ pub(crate) fn verify(pre: &InstancePre<Invocation>) -> Result<(), SetupError> {
52
49
  ))
53
50
  })?;
54
51
  let reported = probe.call(store.as_context_mut(), ()).map_err(|e| {
55
- SetupError::Dead(format!(
56
- "failed to read the Guest Binary's ABI version: {e}"
57
- ))
52
+ past_deadline(&e).unwrap_or_else(|| {
53
+ SetupError::Dead(format!(
54
+ "failed to read the Guest Binary's ABI version: {e}"
55
+ ))
56
+ })
58
57
  })?;
59
58
  if reported != ABI_VERSION {
60
59
  return Err(SetupError::Dead(format!(
@@ -65,3 +64,38 @@ pub(crate) fn verify(pre: &InstancePre<Invocation>) -> Result<(), SetupError> {
65
64
  }
66
65
  Ok(())
67
66
  }
67
+
68
+ #[cfg(test)]
69
+ mod tests {
70
+ use std::path::Path;
71
+ use std::time::Duration;
72
+
73
+ use kobako_runtime::error::SetupError;
74
+ use kobako_runtime::profile::Profile;
75
+
76
+ use crate::{Config, Driver};
77
+
78
+ // @behavior RT-112
79
+ #[test]
80
+ fn a_guest_binary_that_never_finishes_instantiating_fails_construction() {
81
+ const FIXTURE: &str = concat!(
82
+ env!("CARGO_MANIFEST_DIR"),
83
+ "/../../test/fixtures/minimal_spinning_start.wat"
84
+ );
85
+ let config = Config {
86
+ timeout: None,
87
+ setup_timeout: Some(Duration::from_millis(300)),
88
+ memory_limit: None,
89
+ stdout_limit: None,
90
+ stderr_limit: None,
91
+ open_file_limit: None,
92
+ profile: Profile::Hermetic,
93
+ };
94
+ let built = Driver::new(Path::new(FIXTURE), config);
95
+ assert!(
96
+ matches!(&built, Err(SetupError::Dead(message)) if message.contains("within 300ms")),
97
+ "a Guest Binary whose start never returns through Driver::new must fail construction \
98
+ once the setup deadline passes, saying so"
99
+ );
100
+ }
101
+ }
@@ -1,6 +1,7 @@
1
- //! Denial of guest ambient authority at the WASI layer — the one grant
2
- //! that separates the hermetic rung from permissive;
3
- //! `io::install_wasi` wires these sources on `Hermetic` only.
1
+ //! The guest's time and entropy at the WASI layer — the one ambient
2
+ //! authority that separates the hermetic rung from permissive. `Wasi::new`
3
+ //! wires the frozen sources on `Hermetic` and the live clock on
4
+ //! `Permissive`; `Clocks` is what the guest's waits pass on under either.
4
5
  //!
5
6
  //! `wasmtime-wasi`'s `WasiCtxBuilder` defaults the guest's `wasi:clocks` to
6
7
  //! the host wall / monotonic clock and `wasi:random` to a fresh per-context
@@ -16,7 +17,7 @@
16
17
  //! wasmtime epoch interruption against a host `Instant`, never the guest's
17
18
  //! frozen `wasi:clocks/monotonic-clock`.
18
19
 
19
- use std::time::Duration;
20
+ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
20
21
 
21
22
  use wasmtime_wasi::random::Deterministic;
22
23
  use wasmtime_wasi::{HostMonotonicClock, HostWallClock};
@@ -53,6 +54,64 @@ pub(crate) fn deterministic_rng() -> Deterministic {
53
54
  Deterministic::new(vec![0])
54
55
  }
55
56
 
57
+ /// The host's monotonic clock read from a fixed origin. The guest's
58
+ /// `clock_time_get` and a wait it subscribes to share it, so both agree on
59
+ /// what an instant means.
60
+ #[derive(Clone, Copy)]
61
+ pub(crate) struct LiveMonotonicClock {
62
+ origin: Instant,
63
+ }
64
+
65
+ impl LiveMonotonicClock {
66
+ pub(crate) fn new() -> Self {
67
+ Self {
68
+ origin: Instant::now(),
69
+ }
70
+ }
71
+ }
72
+
73
+ impl HostMonotonicClock for LiveMonotonicClock {
74
+ fn resolution(&self) -> u64 {
75
+ 1
76
+ }
77
+
78
+ fn now(&self) -> u64 {
79
+ u64::try_from(self.origin.elapsed().as_nanos()).unwrap_or(u64::MAX)
80
+ }
81
+ }
82
+
83
+ /// The clocks the guest observes. A wait it subscribes to passes on the
84
+ /// same clocks: frozen ones never advance, so the wait ends at once.
85
+ #[derive(Clone, Copy)]
86
+ pub(crate) enum Clocks {
87
+ Frozen,
88
+ Live(LiveMonotonicClock),
89
+ }
90
+
91
+ impl Clocks {
92
+ pub(crate) fn monotonic_now(&self) -> u64 {
93
+ match self {
94
+ Self::Frozen => FrozenMonotonicClock.now(),
95
+ Self::Live(clock) => clock.now(),
96
+ }
97
+ }
98
+
99
+ pub(crate) fn wall_now(&self) -> u64 {
100
+ let since_epoch = match self {
101
+ Self::Frozen => FrozenWallClock.now(),
102
+ Self::Live(_) => SystemTime::now()
103
+ .duration_since(UNIX_EPOCH)
104
+ .unwrap_or(Duration::ZERO),
105
+ };
106
+ u64::try_from(since_epoch.as_nanos()).unwrap_or(u64::MAX)
107
+ }
108
+
109
+ /// Whether a wait on these clocks takes host time.
110
+ pub(crate) fn advances(&self) -> bool {
111
+ matches!(self, Self::Live(_))
112
+ }
113
+ }
114
+
56
115
  #[cfg(test)]
57
116
  mod tests {
58
117
  //! Pin the frozen-clock contract: the guest must observe no real wall