@openclaw/openshell-sandbox 2026.10.5-beta.4 → 2026.10.10-beta.2

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 (240) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
  3. package/node_modules/@openclaw/fs-safe/README.md +20 -4
  4. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
  5. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  6. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
  7. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  8. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
  9. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
  10. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
  11. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
  12. package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
  13. package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
  14. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
  15. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
  16. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
  17. package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
  18. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
  19. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
  20. package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
  21. package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
  22. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
  23. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
  24. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
  25. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
  26. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
  27. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
  28. package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
  29. package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
  30. package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
  31. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
  32. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
  33. package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
  34. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
  35. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
  36. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
  37. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
  38. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
  39. package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
  41. package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
  42. package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
  43. package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
  44. package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
  45. package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
  46. package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
  47. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
  48. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
  52. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
  53. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
  54. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
  55. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
  56. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  57. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
  58. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
  59. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
  60. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
  61. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
  62. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
  63. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
  64. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
  65. package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
  66. package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
  67. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
  68. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
  69. package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
  70. package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
  71. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
  72. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
  73. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
  77. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
  78. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
  79. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
  81. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
  82. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
  84. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
  85. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
  86. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
  87. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
  88. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
  89. package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
  90. package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
  91. package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
  92. package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
  93. package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
  94. package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
  95. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
  96. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
  97. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
  98. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
  99. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  100. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
  101. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
  102. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
  103. package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
  104. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
  105. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
  106. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
  107. package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
  108. package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
  109. package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
  110. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
  111. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
  112. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
  113. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
  114. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
  115. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
  116. package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
  117. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
  118. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
  119. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
  120. package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
  121. package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
  122. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
  123. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
  124. package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
  125. package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
  126. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
  127. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
  128. package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
  129. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
  130. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
  131. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
  132. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
  134. package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
  135. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
  137. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
  139. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
  140. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
  141. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
  142. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
  143. package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
  144. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
  145. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
  146. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
  147. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
  148. package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
  149. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
  150. package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
  151. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
  152. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
  153. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
  154. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
  155. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
  156. package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
  157. package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
  158. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
  159. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
  160. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
  161. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
  162. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
  163. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
  164. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
  165. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
  166. package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
  167. package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
  168. package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
  170. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
  172. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
  174. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
  175. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
  176. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
  177. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
  178. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
  179. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
  180. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
  181. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
  182. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
  183. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
  184. package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
  185. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  186. package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
  187. package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
  188. package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
  189. package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
  190. package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
  191. package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
  192. package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
  193. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
  194. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
  196. package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
  197. package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
  198. package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
  199. package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
  200. package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
  201. package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
  202. package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
  203. package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
  204. package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
  205. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
  206. package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
  207. package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
  208. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
  209. package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
  210. package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
  211. package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
  212. package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
  213. package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
  214. package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
  215. package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
  216. package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
  217. package/node_modules/@openclaw/fs-safe/package.json +21 -12
  218. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  219. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  220. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  221. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  222. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  223. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
  224. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  225. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
  226. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  227. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  228. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  229. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  230. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  231. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  232. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  233. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  234. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  235. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
  236. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  237. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  238. package/package.json +5 -5
  239. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
  240. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
@@ -100,6 +100,29 @@ On Linux x64 or arm64 with Docker, run `pnpm build`, copy the matching GNU artif
100
100
  artifact and run native security and no-replace move tests in Rocky Linux 8
101
101
  (glibc 2.28). Both GNU architectures execute this load test on matching runners.
102
102
 
103
+ ### Windows ARM64 builds
104
+
105
+ `windows-11-arm` builds and runs the ARM64 MSVC addon using native ARM64 Node
106
+ and Rust, and separately tests x64 Node and Rust under Windows emulation. Both
107
+ lanes run the native suite, main-thread and Worker private-creation/descriptor
108
+ proof, and root-only npm/pnpm install smoke. PE inspection matches the loaded
109
+ consumer binary's hash, architecture and required NT imports. Omitted-optionals
110
+ creation probes in the x64-emulated cell have a 600-second deadline: three
111
+ pre-helper measurements had a 183.18-second median, with 140 PowerShell launches
112
+ accounting for 98–99% of wall time. This is over three times that median, rounded
113
+ up to a whole minute. Native ARM64 and x64 cells, and every other consumer probe,
114
+ retain their 120-second deadlines. Release builds
115
+ also run the ARM64 native security and Worker tests before uploading the binding.
116
+
117
+ The only linked C codec is `zstd-sys`; `cargo test --workspace --locked` and the
118
+ release addon build compile it for `aarch64-pc-windows-msvc`. Bzip2 uses
119
+ `libbz2-rs-sys`, and deflate uses `miniz_oxide`, both Rust implementations.
120
+ The portable WASM build uses the checksum-pinned x64 WASI SDK under Windows
121
+ emulation because that SDK has no Windows ARM64 host archive. Its compiler and
122
+ archiver produce wasm32 only; the addon still uses MSVC for the selected target.
123
+ Bun 1.4.2 has no Windows ARM64 artifact; the existing Windows x64 Bun lanes remain
124
+ the Bun runtime proof. Native Windows ARM64 support is qualified with Node.
125
+
103
126
  ## Test
104
127
 
105
128
  ```bash
@@ -213,6 +236,16 @@ checkout synchronization; record the tested source revision or diff, Testbox ID,
213
236
  and Actions run. This backend is Linux-only and does not accept Crabbox's direct
214
237
  SSH `--script` or `--download` flags. The workflow provides no application secrets.
215
238
 
239
+ ### Node baseline benchmark
240
+
241
+ After `pnpm build` and `pnpm native:build`, run `pnpm benchmark:node --json results.json --markdown results.md` on an idle machine. This runs native `off` and `require` in separate processes and writes combined JSON, one JSON file per mode, and a side-by-side Markdown table. `require` fails if its binding cannot load. `--mode off` runs without a native build.
242
+
243
+ Use `--temp-root /path/on/the/target/filesystem` to select the measured storage (otherwise the OS temporary directory is used). In particular, avoid tmpfs for durable-write comparisons. Record the filesystem, OS, Node version, CPU and load with the results; the JSON includes filesystem magic, artifact hashes, runtime details and sample observations. The [benchmark guide](https://github.com/openclaw/fs-safe/tree/main/benchmarks#node-baseline-comparison) describes the workloads and optional Linux syscall tracing.
244
+
245
+ Defaults are seven alternating raw/safe/control blocks, three warmup invocations per arm, and 30 calls per sample (fewer for traversal and extraction). Each sample is a mean of timed calls; reported latency is the median sample mean, not a tail percentile. Setup, verification and cleanup are outside timing. Adjust `--iterations`, `--samples`, `--warmup`, or use `--filter read` for a focused run.
246
+
247
+ Ratios divide fs-safe time by raw Node time; smaller is faster. The A/A row and per-operation A/A column compare identical raw calls and show measurement noise. Raw Node is **not security-equivalent**: raw writes truncate in place, while fs-safe stages and atomically publishes; durable raw writes sync the file, while fs-safe also synchronizes publication where supported. Each row documents the actual comparison. Treat ratios near the A/A spread cautiously, and retain all samples when comparing changes. No benchmark changes a production safety or durability default.
248
+
216
249
  ### Method benchmarks
217
250
 
218
251
  `pnpm benchmark:methods` measures the callable library surface against synthetic
@@ -262,14 +295,14 @@ OS/CPU/libc selection, a native-required SHA-256 operation, and fresh-process
262
295
  optionals. Omitted-optionals installs also verify that all public subpaths can
263
296
  be imported, without implying every operation remains available.
264
297
 
265
- Host-only smoke supplies the six foreign packages using their unchanged real
298
+ Host-only smoke supplies the seven foreign packages using their unchanged real
266
299
  manifests and clearly marked synthetic, non-executable payloads. Every foreign
267
300
  metadata/tarball endpoint is checked before installation, so a missing fixture
268
301
  cannot masquerade as successful platform filtering. These temporary fixtures
269
302
  never enter `packages/`, release artifacts, or the publish manifest. They prove
270
303
  installer filtering, not foreign native compilation or execution. Full release
271
- collection uses the actual seven collected native tarballs instead. Run it with
272
- `pnpm package:collect` after assembling all seven real bindings; missing targets
304
+ collection uses the actual eight collected native tarballs instead. Run it with
305
+ `pnpm package:collect` after assembling all eight real bindings; missing targets
273
306
  fail collection. `pnpm package:collect --allow-host-only` exercises the same
274
307
  lifecycle boundary locally but proves only the host. Both collection commands
275
308
  require the pnpm lifecycle CLI path; JavaScript CLIs run through Node and standalone
@@ -277,7 +310,7 @@ require the pnpm lifecycle CLI path; JavaScript CLIs run through Node and standa
277
310
  direct `node` invocation without lifecycle metadata is unsupported. Archive
278
311
  codecs and their dependencies are packed from the installed dependency graph.
279
312
 
280
- PR CI builds and executes all seven shipped bindings. The existing check names
313
+ PR CI builds and executes all eight shipped bindings. The existing check names
281
314
  stay stable; extra runner/runtime combinations add checks without changing the
282
315
  repository ruleset. Native lanes run Node 24; the GNU host lanes also exercise
283
316
  Bun 1.4.2 (as do macOS and Windows).
@@ -294,6 +327,7 @@ Bun 1.4.2 (as do macOS and Windows).
294
327
  | Native check | `macos-15-intel` | x64 | Node 24, Bun 1.4.2 |
295
328
  | Native check | `fs-safe-windows-16core` (`windows-latest`) | x64 | Node 24, Bun 1.4.2 |
296
329
  | Native check | `windows-2022` (standard hosted) | x64 | Node 24, Bun 1.4.2 |
330
+ | Windows ARM64 host | `windows-11-arm` | arm64 native and x64 emulated | Node 24 |
297
331
  | Native check (Alpine 3.24) | `ubuntu-latest` | x64 / musl | Node 24 |
298
332
  | Native check (Alpine 3.24) | `ubuntu-24.04-arm` | arm64 / musl | Node 24 |
299
333
  | GNU glibc 2.28 build + Rocky Linux 8 load | `ubuntu-latest` | x64 / glibc | Node 24 |
@@ -302,11 +336,17 @@ Bun 1.4.2 (as do macOS and Windows).
302
336
  | Coverage | `ubuntu-latest`, `macos-15`, `fs-safe-windows-16core` | host | Node 22 |
303
337
 
304
338
  Both musl lanes also run root-only package smoke with the real host binding.
305
- The seven-target source build matrix still runs on release tags. The smoke writes manager versions,
339
+ The eight-target source build matrix still runs on release tags. The smoke writes manager versions,
306
340
  cases, and synthetic-fixture scope to `release-artifacts/consumer-proof.json`.
307
341
 
308
342
  ## Docs
309
343
 
344
+ `pnpm docs:check` runs examples against the built package and compiles every
345
+ TypeScript block in `README.md`, the lock API signatures, and the custom stale-lock
346
+ policy example against its declarations. Run `pnpm build` first.
347
+ The compiler supplies typed application context for excerpted variables such as
348
+ `download`; package API calls and callback values remain fully type-checked.
349
+
310
350
  The docs site is rendered recursively from Markdown files under `docs/` by `scripts/build-docs-site.mjs`. Build locally to preview:
311
351
 
312
352
  ```bash
@@ -68,12 +68,24 @@ On Linux, automatic byte copying also uses the native binding when available. It
68
68
 
69
69
  Tree-copy cleanup attempts every acquired close once, even when another close fails. Portable file handles close output before input; completed directories close their pinned source before target; the public wrapper then closes its source before the destination parent. A copy, identity, metadata, native-clone, or cancellation failure already observed at one of those scopes remains the reported value instead of being replaced by cleanup. If that scope otherwise succeeded, its first close failure is reported unchanged. Concurrent siblings have no global structural close order: the first observed sibling failure stops new work while admitted work settles. Portable copying records caller cancellation when it occurs, so a later abort during cleanup cannot replace an earlier copy failure.
70
70
 
71
+ For guarded per-file copying, [`Root.copyIn`](writing.md#copy-metadata-and-source-links) can preserve timestamps and Windows non-permission attributes with `preserveMetadata`, and copy literal links with `sourceSymlinks: "copy-link"`. Permission selection remains separate. Windows reparse payload handling is shared with this tree copier.
72
+
71
73
  Clones preserve file contents, empty directories, timestamps, executable modes where supported, and literal symbolic links. Editing a clone does not modify its source. Unsupported filesystem operations fail; callers may choose their own copy or checkout fallback after the failed operation has settled.
72
74
 
73
75
  Native Windows byte copies can store large zero-filled chunks as sparse ranges when the destination is initially empty and its filesystem supports sparse files. This still reads every source byte and creates an independent copy.
74
76
 
75
77
  The ReFS backend rejects files with alternate data streams and unsupported reparse-point types instead of silently losing their contents. Symbolic links and junctions are preserved.
76
78
 
79
+ For successful native ReFS clones through `copyTree` or `Root.copyIn`, all full
80
+ clusters are block-cloned; a final partial cluster may be copied. Both preserve exact bytes and logical file size.
81
+ The shared implementation keeps EOF at the source size and rounds only the final
82
+ clone request to the allocation-cluster boundary. Microsoft's
83
+ [block-cloning rules](https://learn.microsoft.com/en-us/windows/win32/fileio/block-cloning#restrictions-and-remarks)
84
+ require cluster-aligned ranges and setting destination EOF before cloning;
85
+ rounding a request does not promise physical sharing of a partial final cluster.
86
+ Windows x64 and ARM64 tests compare every full-cluster LCN for both APIs,
87
+ including the 4 MiB shared prefix of a 4 MiB + 1 byte file.
88
+
77
89
  Failed ReFS clones attempt to remove their partial output through the retained directory handles. On Windows versions that reject the ignore-readonly deletion flag, clone rollback retries without that flag so ordinary output can be removed. It never clears readonly attributes: readonly output can remain, and the original clone error includes the cleanup failure. Other processes retaining output handles can delay deletion beyond settlement; a failed call does not guarantee an absent destination.
78
90
 
79
91
  XFS and ZFS preserve regular-file and directory modes, timestamps, extended attributes, and ACLs. They reject special files, symlink extended attributes, non-UTF-8 names, and directory nesting deeper than 128 levels. Hardlinked source files become independent reflinked files. Portable byte copying preserves file contents, empty directories, modes where supported, file and directory timestamps, and literal symbolic links; it does not promise ownership, ACL, extended-attribute, alternate-stream, or sparse-layout preservation. On Windows, byte copying rejects unresolved symbolic links because Node does not expose their file/directory link type; resolved links keep their literal target and source type. POSIX dangling links are preserved. Choose a copying policy that meets the caller's metadata requirements; automatic copying can select either path.
@@ -192,6 +204,16 @@ Byte copying retains fractional file and directory access/modification timestamp
192
204
 
193
205
  After building the host native binding, run `pnpm test test/clone.test.ts test/copy-tree.test.ts`. APFS tests can use the normal macOS temporary directory. For Btrfs, ReFS, XFS, or ZFS, set `FS_SAFE_CLONE_TEST_ROOT` to an existing writable directory on that filesystem. The test creates and cleans only its own temporary children. An explicitly configured unsupported directory fails the test rather than silently skipping platform proof. XFS and ZFS metadata tests require the `attr` and `acl` utilities.
194
206
 
207
+ The `ReFS copyIn` CI workflow creates a disposable 2 GiB expanding VHD on
208
+ Windows Server 2025 x64 and Windows 11 ARM64. It formats the latter as a Dev
209
+ Drive, verifies that the mounted filesystem is ReFS, and sets
210
+ `FS_SAFE_CLONE_TEST_ROOT`. `test/root-copy-refs.test.ts` runs the built package on
211
+ the main thread and in a real Node Worker, checks shared physical extents with
212
+ `readWindowsFileExtents`, independent writes, exclusive publication, byte limits,
213
+ cancellation cleanup, and revocation between file copies. Both jobs also run the
214
+ tree clone suite, then detach and delete the VHD even after failure. A failed
215
+ volume setup fails the job; it never substitutes NTFS or skips the ReFS proof.
216
+
195
217
  Run `node scripts/clone-xfs-proof.mjs MOUNT` on a real XFS volume to verify the public API, hashes, independent writes, and shared physical extents. It requires `filefrag` from `e2fsprogs`. Add `no-reflink` for an XFS fixture formatted with reflinks disabled; strict copying must fail and automatic copying must succeed through byte copying.
196
218
 
197
219
  Run `node scripts/clone-zfs-proof.mjs MOUNT POOL` on a dedicated, otherwise idle Linux ZFS pool with compression and deduplication disabled. It verifies both `copyTree` and `Root.copyIn` through hashes and changes in the documented `bclonesaved` pool counter. It requires `zfs`, `zpool`, and `findmnt`, including permission to run `zpool sync`. Add `no-reflink` for a pool without block cloning to verify strict refusal and automatic byte fallback. The script creates and removes only its temporary directory; it does not create pools or change their properties.
@@ -226,6 +248,10 @@ readable/writable `fd`, with idempotent `close()` and `Symbol.dispose`, and retu
226
248
  bigint destination `identity` and `sourceIdentity` (`dev`, `ino`). Both identities
227
249
  are readonly and frozen. Closing releases the descriptor, not the file.
228
250
 
251
+ Use `fs.stat(path, { bigint: true })` for exact Windows file IDs.
252
+ `expectedSourceIdentity` requires bigint components and rejects Number inputs
253
+ with `FsSafeError("path-mismatch")`, including unsafe rounded Numbers.
254
+
229
255
  Callers that already pinned a source can pass `expectedSourceIdentity: { dev, ino }`
230
256
  from `fs.fstatSync(pinnedSourceFd, { bigint: true })`. fs-safe still admits and
231
257
  opens its own source descriptor, and compares its exact bigint identity before
@@ -280,15 +306,15 @@ for (const name of ["plugin.js", "package.json"]) {
280
306
  }
281
307
  ```
282
308
 
283
- On canonical POSIX paths, the batch retains root and ancestor directory
309
+ On canonical POSIX and ordinary Windows drive paths, the batch retains root and ancestor directory
284
310
  identities across files. Every use still checks those identities without
285
311
  following directory symlinks; root and parent canonical paths are rechecked.
286
312
  Source opens, source and destination file identities, hardlink policy, exclusive
287
313
  creation, bounds, permissions, and cleanup are checked separately for every file.
288
314
  No source descriptor, file contents, or successful file-admission result is cached.
289
315
  An ancestor replacement rejects even when the leaf parent has not changed.
290
- Windows and noncanonical path spellings keep the full per-file resolver and its
291
- alias handling. There is no option to bypass validation.
316
+ Windows namespaces, short-name spellings, trailing-dot/space components, and
317
+ noncanonical path spellings keep the full per-file resolver and its alias handling. There is no option to bypass validation.
292
318
 
293
319
  Keep the batch scoped to one capture or materialization, with unchanged source
294
320
  and destination directory topology. `close()` and `Symbol.dispose` idempotently
@@ -299,9 +325,23 @@ identity-checked cleanup contract; the batch is not an atomic tree transaction
299
325
  or a coherent snapshot of concurrent edits. The single-file helper also reuses
300
326
  directory admissions for the duration of its one copy.
301
327
 
302
- Both APIs reuse the freshly checked canonical root during initial POSIX path
328
+ Both APIs reuse the freshly checked canonical root during initial canonical path
303
329
  admission. This avoids repeatedly resolving every absolute root component while
304
330
  retaining lexical traversal and the before/after root and parent identity checks.
305
- Windows and root aliases retain the ordinary resolver, including its handling
331
+ Windows namespace spellings and root aliases retain the ordinary resolver, including its handling
306
332
  of dot segments in symlink targets. No caller changes are needed for the
307
333
  single-file improvement.
334
+
335
+ ## Exact identity receipts
336
+
337
+ `copyRootFileSync()` returns exact bigint `identity` (destination) and
338
+ `sourceIdentity` values. Pass `sourceIdentity` to `expectedSourceIdentity` to
339
+ bind another copy to that source, or the destination `identity` when copying the
340
+ new file. `Root.copyIn()` reports exact bigint `dev`/`ino` through
341
+ `onDestinationPublished`; retain that callback receipt when reopening the output.
342
+ `PublishedWriteIdentity` callbacks already use the same exact shape.
343
+
344
+ `readCloneFileMetadata()` now also supplies `exactIdentity`, derived from the
345
+ same native metadata record as its legacy `dev` and `ino` fields. These remain
346
+ point-in-time observations. Exact identity does not grant ownership or protect
347
+ against later replacement; use the operation's identity-checking API.
@@ -77,15 +77,16 @@ otherwise usable directory. A failed preflight identity check never syncs the
77
77
  descriptor; a replacement discovered after sync still rejects the operation.
78
78
 
79
79
  `DirectoryReceipt.identity` remains a numeric Node `Stats` object for metadata
80
- compatibility, projected from the same exact observation as the private
81
- identity. Library-created receipts and their identity objects retain a
80
+ compatibility. Its `exactIdentity` companion contains immutable bigint `dev`
81
+ and `ino`, projected from the same observation. Library-created receipts and their identity objects retain a
82
82
  private exact snapshot; mutating their public fields cannot change the
83
83
  directory authorized by that snapshot. Each returned receipt owns a mutable
84
84
  numeric metadata copy. Later admissions retain the original metadata snapshot
85
85
  even if advisory fields such as mode or timestamps were edited; changed paths
86
86
  or identity components reject. Pass the receipt or its original
87
- identity object through to later operations to retain this evidence. A copied
88
- or reconstructed numeric identity is accepted only when both components are
87
+ identity object through to later operations to retain this evidence. When copying
88
+ a receipt, preserve `exactIdentity`; pass it to `expectedExistingIdentity`. A copied
89
+ numeric identity without its exact companion is accepted only when both components are
89
90
  safe integers and, on Windows, nonzero. Rounded or unknown caller identities
90
91
  fail with `path-mismatch` rather than authorizing a different directory.
91
92
 
@@ -149,6 +150,27 @@ target. It pins the source with nonblocking `O_NOFOLLOW`, optionally verifies
149
150
  `expectedSourceIdentity`, tries a hardlink first, then synchronizes the target
150
151
  parent directory.
151
152
 
153
+ Pass `expectedSourceIdentity` from `fs.stat(path, { bigint: true })` (or
154
+ `fs.lstat`) for exact Windows file IDs. Unsafe Number components cannot prove
155
+ identity and reject with `FsSafeError("path-mismatch")` before publication;
156
+ safe integer Numbers remain supported. Internal source fences use bigint stats.
157
+
158
+ Every successful publication returns `exactIdentity: Readonly<{ dev: bigint;
159
+ ino: bigint }>` alongside the unchanged `identity: Stats` metadata view. Both
160
+ come from one final bigint `fstat` of the retained descriptor. Pass
161
+ `result.exactIdentity` back as `expectedSourceIdentity` when publishing that file
162
+ again. Passing the whole publication result is also supported: its explicit
163
+ `exactIdentity` takes precedence. `sameFileIdentity()` and
164
+ `copyRootFileSync().expectedSourceIdentity` accept that same receipt shape.
165
+
166
+ On Windows, the Number view may round an NTFS file ID; it must not be used for
167
+ identity checks. Do not re-stat a pathname to reconstruct an earlier publication
168
+ receipt: that can observe a replacement. After a failed publication,
169
+ `PublishFileExclusiveFailureDetails.exactTargetIdentity` identifies the created
170
+ target when known; `targetIdentity` remains the legacy numeric view. Retain the
171
+ exact failure field when inspecting a preserved target. Unknown or mismatching
172
+ identities still fail closed.
173
+
152
174
  Trusted relative source and target paths are resolved to absolute paths before
153
175
  authority checks. On Windows, ordinary drive-relative operands are anchored at
154
176
  entry before namespace-alias admission. A caller-supplied `parentReceipt` must
@@ -171,7 +193,7 @@ try {
171
193
  onSyncFailure: "preserve",
172
194
  parentReceipt: backupDirectory,
173
195
  });
174
- recordDurableBackup(result.identity, result.directorySync);
196
+ recordDurableBackup(result.exactIdentity, result.directorySync);
175
197
  } catch (error) {
176
198
  if (
177
199
  error instanceof FsSafeError &&
@@ -224,9 +246,11 @@ count limits need not mean the entire filesystem lacks hard-link support.
224
246
  `link-required` and `link-or-copy` do not use `renameat2` and are unchanged.
225
247
 
226
248
  `"link-required"` propagates an unsupported hardlink failure.
227
- `"link-or-copy"` falls back only for `EPERM`, `EXDEV`, `ENOTSUP`,
249
+ `"link-or-copy"` falls back only for `EACCES`, `EPERM`, `EXDEV`, `ENOTSUP`,
228
250
  `EOPNOTSUPP`, or `ENOSYS`; `isHardlinkFallbackError()` exposes that exact
229
- classifier. The fallback copies from the pinned source into a `wx` target,
251
+ classifier, including Android/Termux hardlink permission refusals. A genuine
252
+ permission denial still fails the fallback's exclusive target creation; `EEXIST`
253
+ never permits fallback. The fallback copies from the pinned source into a `wx` target,
230
254
  fsyncs it, and fences source and target identity and content before reporting
231
255
  success. `parentReceipt`, when supplied, must name the target's direct parent.
232
256
 
@@ -319,6 +343,9 @@ thread. Both paths stream bounded buffers rather than loading the file into memo
319
343
  The fallback sizes its scratch buffer to small files and grows it if a stale
320
344
  size hint is exceeded, while still probing for actual EOF and byte-limit overflow.
321
345
  Native mode `require` keeps its usual fail-closed loader semantics.
346
+ On Windows, pathname hashing can read through its privately owned handle
347
+ without reopening it. Hashing a caller-owned handle still uses an independent
348
+ reader to preserve the caller's position.
322
349
 
323
350
  ### Synchronous hashing
324
351
 
@@ -38,6 +38,14 @@ class FsSafeError extends Error {
38
38
 
39
39
  `cause` is available through the standard `Error` `cause` property when the failure was triggered by a `NodeJS.ErrnoException` (e.g. a wrapped `EACCES`). Inspect it for the original `code` / `errno` / `syscall` if you need finer-grained reporting.
40
40
 
41
+ Native Windows failures, including private directory and file creation failures,
42
+ retain the original numeric Win32 error in `errno` (for example, 183 for
43
+ `ERROR_ALREADY_EXISTS` or 5 for `ERROR_ACCESS_DENIED`). This also applies to the
44
+ Windows security command fallback. A wrapped failure retains that number on its
45
+ original `cause`; the semantic `FsSafeError.code` remains the contract. Policy
46
+ checks and preflight rejections that do not execute a failing Windows operation
47
+ have no Win32 error number. Node-managed filesystem errors retain Node's own errno.
48
+
41
49
  Guarded write preparation describes permission, read-only filesystem, and disk-space
42
50
  failures with messages such as `permission denied (EACCES)` or
43
51
  `no space left on device (ENOSPC)`. Other errno failures include their code in
@@ -56,6 +56,8 @@ can replace only an **empty directory created concurrently**; non-empty
56
56
  directories and non-directory targets are refused as `EEXIST`. An existing
57
57
  empty directory observed by the absence check is also refused. No empty claim
58
58
  directory is exposed before publishing the completed source.
59
+ An `ENOTDIR` rename failure rechecks the source before reporting a destination
60
+ collision; a detected source substitution reports `ESTALE` instead of `EEXIST`.
59
61
 
60
62
  Identity checks and mutation remain separate, not source-name compare-and-swap.
61
63
  The fallback verifies the published identity afterward. A file fallback failure
@@ -106,6 +106,7 @@ Use the main entry for the common surface, or the focused subpaths when you want
106
106
  | `@openclaw/fs-safe/json` | `tryReadJson`, `readJson`, `readJsonIfExists`, `writeJson`, sync variants. |
107
107
  | `@openclaw/fs-safe/store` | `fileStore()`, `fileStoreSync()`, and `jsonStore<T>()`. |
108
108
  | `@openclaw/fs-safe/secret` | Secret file read/write helpers. |
109
+ | `@openclaw/fs-safe/pipe` | Native-owned, reopenable anonymous pipes for one-shot child input. |
109
110
  | `@openclaw/fs-safe/atomic` | `replaceFileAtomic`, `writeTextAtomic`, `replaceDirectoryAtomic`, `movePathWithCopyFallback`. |
110
111
  | `@openclaw/fs-safe/durability` | Pinned directories, strict sync, durable directory creation, exclusive publication, and streaming SHA-256. |
111
112
  | `@openclaw/fs-safe/temp` | `tempWorkspace`, `withTempWorkspace`, sync variants, `resolveSecureTempRoot`. |
@@ -125,9 +126,11 @@ Use the main entry for the common surface, or the focused subpaths when you want
125
126
 
126
127
  `@openclaw/fs-safe` bundles an import-free WASM build of its Rust TAR parser and zstd/bzip2 codecs for guarded [archive extraction and bounded entry reads](archive.md). Plain TAR, gzip, zstd, and bzip2 work in `off` and missing-native `auto`, including installs with all optional dependencies omitted; gzip uses Node's built-in decoder. These archive fallbacks need no runtime interpreter, download, or consumer compiler. ZIP fallback uses lazily loaded optional `jszip` and reports a missing-dependency error without it. Public subpaths remain safe to import with all optional dependencies omitted, but imports do not prove native availability. Native `require` remains strict, and an available native operation's failure never triggers a WASM retry.
127
128
 
128
- There are no peer dependencies. Exact-version optional packages carry the seven
129
+ There are no peer dependencies. Exact-version optional packages carry the
129
130
  native targets and npm-compatible OS, CPU, and Linux libc filters install only
130
- the matching binary. Consumers do not run a Rust build, download code at
131
+ the matching binary. Windows ARM64 Node selects
132
+ `@openclaw/fs-safe-win32-arm64-msvc`; x64 Node on the same host selects the x64
133
+ package. Consumers do not run a Rust build, download code at
131
134
  runtime, or execute a postinstall step. Omitting optional dependencies keeps
132
135
  fallback-capable operations working in `auto` or `off`. Native-only
133
136
  features, including strict owned-tree temp cleanup, retained-directory staging,
@@ -145,42 +148,59 @@ before deploying with native mode `require` or native-only features.
145
148
  The native addon supports kernels without `openat2` (before Linux 5.6) and
146
149
  containers returning `ENOSYS` or probe-time `EPERM` for that syscall. Beneath
147
150
  opens use a guarded no-follow component walk and report `best-effort`.
148
- Nested no-clobber moves retain atomic `renameat2(RENAME_NOREPLACE)` and exact
149
- identity checks; keep native mode enabled. Strict bounded cleanup still
151
+ Nested no-clobber moves retain native parent admission and exact identity checks;
152
+ keep native mode enabled. If `renameat2(RENAME_NOREPLACE)` is also unsupported,
153
+ Linux `auto` uses [move fallbacks](writing.md#fs-move-from-to-options), including
154
+ a directory rename that can replace a concurrently created empty directory.
155
+ Native `require` refuses these fallbacks. Strict bounded cleanup still
150
156
  requires `openat2` with `RESOLVE_NO_XDEV` and reports `helper-unavailable`
151
157
  without it. See [Linux capability behavior and limits](native.md#linux-without-openat2).
152
158
 
153
159
  ### Windows security fallback
154
160
 
155
- Windows raw owner/DACL inspection, private-directory creation, and secure-file
156
- reads work without the addon in `auto` or `off` mode when system Windows
157
- PowerShell and its .NET `Add-Type` compilation support are available. The package
158
- ships a readable, fixed `.ps1` driver and adjacent `.cs` source and invokes the
159
- driver with Windows PowerShell `-File`. Paths are passed as data. The fallback
160
- does not generate helper scripts at runtime or use an encoded launcher.
161
- The driver addresses built-in commands by module name and limits module
162
- discovery to PowerShell's bundled system modules, avoiding broad command
163
- discovery scans during each helper startup.
164
-
165
- Normal PowerShell execution policy and Microsoft Defender policy must permit
166
- the packaged scripts, including their use of `Add-Type`. The package does not
167
- bypass restrictions, change policies, or add exclusions. Unsupported or
168
- disallowed command execution fails closed.
169
-
170
- The fallback preserves private DACLs at creation and inspects the same open
171
- handle that supplies secure-file bytes. Each capability emits a path-free
172
- `FS_SAFE_NATIVE_FALLBACK` warning once per process; each call adds PowerShell
173
- startup and compilation overhead. Execution or compilation failure also fails
174
- closed. Native `require` still rejects a missing binding or capability, and an
175
- available native operation's error never triggers a command retry. See
176
- [Permissions](permissions.md) and [Secure file reads](secure-file.md).
161
+ Windows pathname permission inspection, raw owner/DACL inspection, private-directory creation, and secure-file
162
+ reads work without the addon in `auto` or `off` mode using the packaged
163
+ `windows-security-helper.exe`. This prebuilt AnyCPU console application targets
164
+ .NET Framework 4.6.2 or newer (CLR v4.0.30319), with the modern path handling
165
+ used by Windows PowerShell 5.1. It runs as native x64 on x64 Windows and native ARM64 on Windows
166
+ 11 ARM with .NET Framework 4.8.1, including when the calling Node is x64.
167
+ No compiler, .NET SDK, or PowerShell is needed by this fallback at runtime.
168
+
169
+ Each check starts a separate helper process. The compatibility owner/ACL query
170
+ passes `["owner", targetPath]` as arguments and preserves .NET's named security
171
+ lookup and ACL normalization. Other paths and expected identities stay
172
+ in environment variables, and descriptor checks inherit the actual borrowed
173
+ handle through stdin. No security facts are cached or checks combined. The
174
+ fallback preserves private DACLs at creation and inspects the same open handle
175
+ that supplies secure-file bytes. Each capability emits a path-free
176
+ `FS_SAFE_NATIVE_FALLBACK` warning once per feature in each JavaScript context.
177
+ It names the native availability reason and expected package version; see
178
+ [troubleshooting fallback warnings](native-helper.md#troubleshooting-fallback-warnings).
179
+
180
+ Release Windows native addons and `windows-security-helper.exe` are
181
+ Authenticode-signed by **OpenClaw Foundation** with a timestamp. The executable
182
+ is included in the main npm package and covered by its npm provenance, just
183
+ like its JavaScript. Source lives in the repository; consumers receive only the
184
+ executable. Missing or disallowed execution fails closed with a rebuild/reinstall
185
+ hint. Signing does not guarantee acceptance by every Smart App Control,
186
+ AppLocker, or WDAC policy; local policy can still block execution in user
187
+ directories. There is no PowerShell retry. Such hosts can also restrict
188
+ PowerShell to Constrained Language Mode, where the former Add-Type fallback
189
+ was unavailable. The package does not bypass policies or add exclusions.
190
+
191
+ Native `require` still rejects a missing binding or capability, and an available
192
+ native operation's error never triggers a command retry. See
193
+ [Permissions](permissions.md), [Secure file reads](secure-file.md), and
194
+ [reproducible helper builds](native.md#windows-helper-build).
177
195
 
178
196
  ## Native helper policy
179
197
 
180
198
  ### Supported native platforms
181
199
 
182
200
  Prebuilt bindings cover Linux x64/arm64 (GNU glibc **2.28 or newer**, or musl),
183
- macOS x64/arm64, and Windows x64. The GNU baseline includes RHEL 8, Rocky Linux 8,
201
+ macOS x64/arm64, Windows x64/arm64, and FreeBSD 14.4+ x64/arm64. FreeBSD provides the
202
+ [documented subset](native.md#freebsd-14-4-and-newer); its other operations retain
203
+ guarded fallbacks in `auto`. The GNU baseline includes RHEL 8, Rocky Linux 8,
184
204
  and AlmaLinux 8. A compatible Node 22+ runtime and the kernel/filesystem features
185
205
  required by each operation are still necessary; a loadable addon alone does not
186
206
  guarantee every native capability. Systems older than glibc 2.28 are outside the
@@ -16,11 +16,11 @@ Current releases install the matching exact-version optional platform package.
16
16
  - Run on Node.js 22 or newer.
17
17
  - Update `@openclaw/fs-safe` and regenerate every lock or shrinkwrap file your
18
18
  deployment consumes.
19
- - Keep optional dependencies enabled if you want JavaScript ZIP/TAR support.
20
- Native loading no longer depends on Python. Keep optional dependencies enabled
21
- for the matching native package and JavaScript ZIP/TAR decoders. An install
22
- that omits them can still import fs-safe, but native-only features and missing
23
- JS archive decoders fail with actionable errors.
19
+ - Keep optional dependencies enabled for the matching native package and the
20
+ JSZip fallback. Current TAR/gzip/zstd/bzip2 fallbacks are bundled and work
21
+ with optional dependencies omitted. Native loading no longer depends on
22
+ Python. See [runtime dependencies](install.md#runtime-dependencies) for the
23
+ current installation contract.
24
24
 
25
25
  If you call `resolveRootPath()` or `resolveRootPathSync()` directly, upgrade to
26
26
  0.5: versions through 0.4.7 could approve an in-root symlink traversal that
@@ -170,7 +170,7 @@ See [File locks](sidecar-lock.md), [Secret files](secret-file.md), and
170
170
  ## 7. Gate native-only features
171
171
 
172
172
  Version 0.5 required native support for `createPrivateDirectory()`. Current
173
- releases also support a packaged PowerShell script in native `auto` and `off`
173
+ releases also support a packaged .NET Framework helper in native `auto` and `off`
174
174
  modes, retaining its creation-time protected DACL. Check the
175
175
  [Windows security fallback prerequisites](install.md#windows-security-fallback)
176
176
  before relying on this route. The API remains Windows-only, explicit native
@@ -31,7 +31,7 @@ Zstd/bzip2 TAR extraction and bounded reads required the binding in version 0.6;
31
31
  current versions also support bundled WASM codecs in `auto` and `off` through the
32
32
  same guarded TAR pipeline. ZIP fallback still requires optional JSZip.
33
33
  Windows private-directory creation required the binding in version 0.6; current
34
- releases also support [packaged Windows security scripts](install.md#windows-security-fallback)
34
+ releases also support [packaged Windows security helper](install.md#windows-security-fallback)
35
35
  in `auto` and `off` mode while preserving the creation-time protected DACL.
36
36
 
37
37
  ## Deployment checklist
@@ -5,8 +5,10 @@ description: "How fs-safe loads its platform-specific native filesystem primitiv
5
5
 
6
6
  # Native helper policy
7
7
 
8
- `@openclaw/fs-safe` declares seven exact-version optional packages for Linux
9
- x64/arm64 (glibc or musl), macOS x64/arm64, and Windows x64. Package-manager
8
+ `@openclaw/fs-safe` declares exact-version optional packages for Linux
9
+ x64/arm64 (glibc or musl), macOS x64/arm64, Windows x64/arm64, and FreeBSD x64/arm64.
10
+ The FreeBSD 14.4+ bindings expose the [qualified subset](native.md#freebsd-14-4-and-newer).
11
+ Package-manager
10
12
  OS, CPU, and libc filters install only the matching package. The loader requires
11
13
  that package lazily, without runtime downloads, postinstall scripts, or a
12
14
  consumer Rust build.
@@ -29,18 +31,87 @@ The equivalent environment variables are `FS_SAFE_NATIVE_MODE` and `OPENCLAW_FS_
29
31
  | `off` | Do not load a native package. Use supported fallbacks and reject native-only operations deterministically. |
30
32
  | `require` | Throw `FsSafeError("helper-unavailable")` instead of falling back when an operation needs the native binding and it cannot load. |
31
33
 
34
+ See [What needs the native binding](native.md#what-needs-the-native-binding)
35
+ for the native-only APIs and options.
36
+
32
37
  See [Archive extraction](archive.md) for native, bundled WASM, and ZIP backends.
33
38
  Native archive-operation failures are terminal; `auto` does not retry them through a
34
39
  fallback. `require` rejects missing bindings or required capabilities.
35
40
 
41
+ A loaded binding may provide only a subset of native operations. Each operation
42
+ checks its required capabilities before native dispatch: missing capabilities
43
+ select the existing guarded fallback in `auto`, or report `helper-unavailable`
44
+ in `require`. A failure from an available native method is still an operation
45
+ failure; it is not treated as a missing capability.
46
+
36
47
  Windows owner/DACL inspection, private-directory creation, and secure reads can
37
- use [PowerShell fallbacks](install.md#windows-security-fallback) in `auto` and
48
+ use [Windows helper fallbacks](install.md#windows-security-fallback) in `auto` and
38
49
  `off`; `require` stays strict, and native operation failures remain terminal.
39
50
  Bun macOS/Linux canonicalization uses the addon under its
40
51
  [runtime requirements](install.md#bun-runtime).
41
52
 
42
53
  Configure the mode once during startup. Loading is lazy and cached; changing from `auto` to `require` after a failed load changes failure policy but does not repeatedly probe the binary.
43
54
 
55
+ ## Runtime health checks
56
+
57
+ `getFsSafeNativeStatus(options?: { probe?: boolean }): Promise<FsSafeNativeStatus>` is
58
+ exported from `@openclaw/fs-safe/config` and the main entry point. It reports the
59
+ loader state without parsing warnings, on every supported runtime. A doctor can
60
+ explicitly probe without changing the configured mode or mutating the filesystem:
61
+
62
+ ```ts
63
+ import { getFsSafeNativeStatus } from "@openclaw/fs-safe/config";
64
+
65
+ const status = await getFsSafeNativeStatus({ probe: true });
66
+ console.log(status.loaded ? "Native helper active" : `Native helper: ${status.reason}`);
67
+ if (status.guidance) console.log(status.guidance);
68
+ ```
69
+
70
+ | Fields | Meaning |
71
+ |---|---|
72
+ | `mode`, `modeSource` | Effective policy and its source; only environment variable names are exposed. |
73
+ | `reason`, `loaded` | `not-probed`, `loaded`, `disabled`, `unsupported-target`, `package-missing`, `load-failed`, or `capability-missing`; `loaded` is true only for `loaded`. |
74
+ | `target` | Runtime platform/architecture/libc, including unsupported targets. |
75
+ | `packageName`, `packageVersion` | Expected platform package and exact pinned manifest version; absent for unsupported targets. |
76
+ | `loadErrorCode` | Allowlisted code or `UNKNOWN`, only for `load-failed`. |
77
+ | `guidance` | The same next-step text as fallback warnings; absent for `not-probed` and `loaded`. |
78
+
79
+ Await the returned promise. With no options, this API only reads the cache and
80
+ never loads the addon;
81
+ `not-probed` means no load has been attempted. With `{ probe: true }`, it uses
82
+ the existing lazy loader and cache. Mode `off` always reports `disabled` without
83
+ attempting a load. Failures are reported without throwing, including in `require`
84
+ mode. Core `capability-missing` does not expose internal function names, and a
85
+ `loaded` result does not promise that every operation is available or kernel-atomic.
86
+ Fields omit paths and raw loader errors.
87
+
88
+ Each module instance has its own status, so probe real Workers separately when
89
+ their health matters. After repairing an installation, start a fresh process or
90
+ Worker: the cached failure is not retried by another probe or mode change.
91
+ See the [full API reference](config.md#getfssafenativestatus-options) for exported
92
+ types and field details.
93
+
94
+ ## Troubleshooting fallback warnings
95
+
96
+ Windows security and private-creation fallbacks emit `FS_SAFE_NATIVE_FALLBACK`
97
+ once per feature in each JavaScript context (the main thread or a Worker).
98
+ Warnings identify the reason and, on supported targets, the expected platform
99
+ package and its exact version from fs-safe's manifest. They omit caller paths
100
+ and raw loader error messages. Other guarded fallbacks, including Linux/macOS
101
+ operations, do not currently emit this warning.
102
+
103
+ | Reason | Next step |
104
+ |---|---|
105
+ | `package-missing` | Reinstall with optional dependencies included: npm `--include=optional`, or pnpm without `--no-optional` / `optional=false`. Check that the lockfile and registry mirror include the named package at the exact version. Install for the deployed Node architecture and Linux libc. |
106
+ | `disabled` | The warning names the effective source: programmatic configuration, `FS_SAFE_NATIVE_MODE`, or `OPENCLAW_FS_SAFE_NATIVE_MODE`. If native loading is intended, set that source to `auto` or `require`. Programmatic configuration takes precedence, then `FS_SAFE_NATIVE_MODE`, then `OPENCLAW_FS_SAFE_NATIVE_MODE`. Keep intentional `off` settings for isolated runtimes. |
107
+ | `unsupported-target` | Portable mode is expected on this platform/architecture/libc target; no matching native package is published. Native-only operations remain unavailable. |
108
+ | `load-failed` | Check Node architecture, Linux libc compatibility, and OS code-integrity policy. A missing dependency inside an installed platform package is also a load failure. Reinstalling identical bytes may not help; resolve compatibility or approved signing/policy requirements without disabling security controls. |
109
+ | `capability-missing` | Use matching fs-safe and platform-package versions. A loaded binding may intentionally provide a platform-specific subset; see the [FreeBSD capabilities](native.md#freebsd-14-4-and-newer). |
110
+
111
+ Restart the process or Worker after repairing an installation: failed loads are
112
+ cached. These diagnostics do not change `auto`, `off`, or `require` behavior,
113
+ and a loaded addon does not imply that every operation is kernel-atomic.
114
+
44
115
  Native-created descriptors retain their originating native close operation through
45
116
  normal and error cleanup, including later mode changes. Node-created roots and
46
117
  directory handles keep Node's close operation, and borrowed handles keep their
@@ -109,10 +180,12 @@ is authoritative for operation and platform guarantees.
109
180
 
110
181
  ## Migration from the Python helper
111
182
 
112
- The deprecated configuration bridge maps Python settings to native modes and
113
- warns once; it never executes Python. Follow the
183
+ The Python configuration bridge has been removed. Old Python environment
184
+ settings are ignored without a warning, so an old `require` or `off` setting
185
+ does not select the corresponding native policy. Configure
186
+ `FS_SAFE_NATIVE_MODE` or `configureFsSafeNative()` explicitly. Follow the
114
187
  [0.5 migration checklist](migrating-to-0.5.md#2-replace-python-helper-configuration)
115
- for aliases, warning behavior, and precedence.
188
+ for the removed settings and current precedence.
116
189
 
117
190
  ## Related pages
118
191