@openclaw/feishu 2026.9.8 → 2026.10.1-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 (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
@@ -29,25 +29,15 @@ The equivalent environment variables are `FS_SAFE_NATIVE_MODE` and `OPENCLAW_FS_
29
29
  | `off` | Do not load a native package. Use supported fallbacks and reject native-only operations deterministically. |
30
30
  | `require` | Throw `FsSafeError("helper-unavailable")` instead of falling back when an operation needs the native binding and it cannot load. |
31
31
 
32
- Plain TAR, gzip, zstd, and bzip2 extraction and bounded entry reads use a bundled,
33
- import-free WASM build of the same Rust TAR parser when native support is absent
34
- or disabled. Zstd/bzip2 codecs are bundled alongside the parser; gzip uses Node's
35
- built-in decoder. These archive fallbacks require no runtime interpreter or
36
- download. `off` disables the optional native filesystem helper, not the bundled
37
- WASM. `auto` prefers native and does not retry a native operation failure through
38
- WASM; `require` still rejects an unavailable native binding. ZIP fallback still
39
- requires optional `jszip`. `inspectTarArchive()` remains limited to plain TAR
40
- and gzip.
41
-
42
- Windows security operations can use the package's readable PowerShell/C# scripts
43
- in `auto` and `off`, subject to the [Windows security fallback prerequisites](install.md#windows-security-fallback).
44
-
45
- On Bun macOS/Linux, the [runtime path adapter](install.md#bun-runtime) uses the
46
- same Rust addon for system canonicalization in `auto` and `require`. No JIT is
47
- needed. With `off` or a missing addon in `auto`, Bun's own resolver retains its
48
- path and permission limitations. Canonicalization in `require` fails with
49
- `helper-unavailable` if the addon or its canonicalizer is missing, including
50
- when admitting a temp workspace. Containment and identity checks stay intact.
32
+ See [Archive extraction](archive.md) for native, bundled WASM, and ZIP backends.
33
+ Native archive-operation failures are terminal; `auto` does not retry them through a
34
+ fallback. `require` rejects missing bindings or required capabilities.
35
+
36
+ Windows owner/DACL inspection, private-directory creation, and secure reads can
37
+ use [PowerShell fallbacks](install.md#windows-security-fallback) in `auto` and
38
+ `off`; `require` stays strict, and native operation failures remain terminal.
39
+ Bun macOS/Linux canonicalization uses the addon under its
40
+ [runtime requirements](install.md#bun-runtime).
51
41
 
52
42
  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.
53
43
 
@@ -64,22 +54,12 @@ with Node's automatic worker-exit cleanup; `Worker.terminate()` can leave them
64
54
  open until process exit. The native close operation handles explicit cleanup,
65
55
  not forced worker termination.
66
56
 
67
- [`tempWorkspace()` and its scoped/sync variants](temp.md#private-temp-workspaces)
68
- remain available in every mode. Their default compatible cleanup uses guarded
69
- JavaScript quarantine when owned native tree removal is unavailable.
70
- `cleanupSafety: "require-bounded"` instead rejects before child creation unless
71
- no-replace quarantine plus descriptor-relative owned-tree removal are available.
72
- On Linux, admission probes the exact `openat2` child-directory flags, including
73
- `RESOLVE_NO_XDEV`, at runtime. An unavailable or denied probe selects compatible
74
- JavaScript cleanup even in global `require` mode; `require-bounded` rejects before
75
- child creation.
76
- Already-created strict workspaces retain their binding
77
- and descriptors across later mode changes.
78
-
79
- [`stageFileInDirectory()`](staged-file.md) always requires native support on
80
- Linux/macOS and rejects before creation when off, unavailable, or missing the
81
- required capability. Windows is unsupported for this lifecycle. This does not
82
- change the mode policy of existing fallback-capable APIs.
57
+ Temp workspaces have an independent `cleanupSafety` policy: `"compatible"`
58
+ can use guarded JavaScript cleanup even in native `require` mode;
59
+ `"require-bounded"` rejects before child creation without the required cleanup
60
+ capabilities. See the [temp workspace contract](temp.md#private-temp-workspaces).
61
+ [Retained-directory staging](staged-file.md) requires native support on Linux/macOS
62
+ and is unsupported on Windows.
83
63
 
84
64
  ## Native boundary
85
65
 
@@ -87,15 +67,9 @@ The internal Darwin descriptor ACL inspector requires its matching native
87
67
  capability in both `auto` and `require`; `off`, a missing package, or an older
88
68
  binding without `inspectDarwinAcl` rejects with `helper-unavailable`. Inspection
89
69
  failure or malformed facts reject with `permission-unverified`; there is no
90
- mode-bit or pathname fallback for this capability. Clone admission uses a fused
91
- descriptor-bound metadata and ACL observation, then compares immutable receipts
92
- with fresh no-follow pathname identity fences; pathnames never authorize ACL
93
- state. The payload ACL-clear readback is part of that fused observation. Once
94
- a clone payload exists, normalization and verification failures become terminal
95
- `EIO` errors (with the underlying status and detail retained), not capability
96
- signals that permit an ordinary-copy retry. Checked cleanup cannot undo that
97
- terminal classification.
98
- This addition does not change other APIs' native-mode or permission contracts.
70
+ mode-bit or pathname fallback for this capability. See
71
+ [Darwin clone normalization](native.md#the-beneath-model) for descriptor-bound
72
+ admission and terminal errors after a clone payload exists.
99
73
 
100
74
  The native layer exposes policy-free filesystem mechanisms: beneath-root
101
75
  open/mkdir/link, replace and no-replace rename, identity reads, archive decode/execution,
@@ -103,9 +77,9 @@ clone/copy/hash workers, POSIX canonicalization, and Windows security descriptor
103
77
  layer owns policy, retries, filters, budgets, modes, cleanup, error
104
78
  normalization, and the decision to fall back.
105
79
 
106
- - Linux uses `openat2` with `RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS`, `renameat`, and `renameat2(RENAME_NOREPLACE)`. Without `openat2`, beneath opens use the [no-follow component walk](native.md#linux-without-openat2) with exact identity checks and report `best-effort`. Direct-child no-replace renames borrow already-retained parent descriptors; deeper relative paths retain the guarded reopen. Owned-tree cleanup still requires `openat2` with `RESOLVE_NO_XDEV` and fails closed when unavailable.
107
- - macOS 15.4 and newer prefer `O_RESOLVE_BENEATH`; older kernels resolve components with `O_NOFOLLOW` and restart in-root symlinks from the pinned root descriptor. Both routes use an `F_GETPATH` post-open escape detector and report `best-effort` because directory rename races are not atomic with that check. Direct-child no-replace renames borrow already-retained parents without another `F_GETPATH`; deeper paths retain the guarded reopen. Publication uses `renameat` for replacement and `renameatx_np(RENAME_EXCL)` for no-replace; owned-tree cleanup uses descriptor-relative `openat`/`unlinkat`.
108
- - Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, and uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer. The dedicated retained-directory `renameNoReplaceWithIdentity` primitive compares its required exact source receipt with the handle opened for rename before mutation. Its internal mismatch status is normalized to public `path-mismatch`; the legacy four-argument `renameNoReplace` export and its other callers are unchanged. Owned trees are deleted through exact opened handles with `FileDispositionInfoEx`; symlink/reparse entries in owned trees are removed as leaves and never traversed. Descriptors crossing N-API are converted only by the host executable's paired `uv_get_osfhandle` and `uv_open_osfhandle` exports. A runtime without both exports is unsupported for these native operations; the binding never guesses a raw HANDLE or uses a foreign CRT descriptor table. Descriptor-producing operations also require that same host's synchronous libuv close and request-management APIs before exporting an owned descriptor.
80
+ Platform mechanisms and containment limits are documented in the
81
+ [security model](security-model.md#containment-guarantees-by-platform) and
82
+ [native architecture](native.md#the-beneath-model).
109
83
 
110
84
  Linux root lookups reject negative descriptor sentinels before borrowing a handle or resolving a relative path; they never substitute the process working directory for an admitted root. Public Root operations already supply retained, admitted handles.
111
85
 
@@ -120,58 +94,23 @@ through the retained parents without another receipt, duplicate, reopen, or
120
94
  macOS `F_GETPATH`; the documented final source-name substitution window remains
121
95
  there. Deeper names retain guarded parent traversal.
122
96
 
123
- Native primitives back create-only and replacing pinned writes, no-clobber
124
- `Root.move()`, async sidecar creation, guarded publication, archive acceleration,
125
- and direct Windows ACL operations. Windows secure-file reads require
126
- descriptor-bound owner/DACL facts. In native `auto` or `off` mode, a missing
127
- binding or capability can use a packaged PowerShell script that inspects the
128
- borrowed handle. Raw owner/DACL inspection and private-directory creation also support
129
- this fallback, subject to the [Windows security fallback prerequisites](install.md#windows-security-fallback).
130
- Each capability emits a path-free warning once per process and adds PowerShell
131
- startup and compilation overhead per call. Native `require` rejects missing
132
- capabilities, and native operation failures remain terminal. No-clobber moves fail with
133
- `helper-unavailable` when descriptor-relative parent admission or the atomic
134
- no-replace rename is unavailable; they never use a check followed by a replacing
135
- rename. Equivalent JavaScript paths remain available for documented
136
- fallback-capable features. See [Native architecture](native.md#javascript-fallback-guarantees-and-delta)
137
- for the exact difference.
138
-
139
- The guarded JavaScript mutation path is detection-based, not containment-atomic.
140
- If a same-privilege peer can replace a writable parent after its identity guard
141
- but before Node resolves a pathname mutation, the mutation can land outside the
142
- intended root before the post-operation guard throws. Native `require` ensures the addon is present, but does not require a
143
- `kernel-atomic` resolver; inspect containment and use OS isolation when that
144
- concurrent attacker is part of the threat model.
145
-
146
- `openBeneath()` returns `{ fd, containment }`. `containment` is
147
- `"kernel-atomic"` for Linux `openat2` and `"best-effort"` for the Linux fallback, macOS and
148
- Windows. Public JavaScript root open/read/writable results also expose the
149
- field and report `"best-effort"`; the label reports mechanism, not policy.
97
+ No-clobber `Root.move()` fails with `helper-unavailable` if descriptor-relative
98
+ parent admission or atomic no-replace rename is unavailable; it never substitutes
99
+ a check followed by a replacing rename. See the
100
+ [fallback contract](native.md#javascript-fallback-guarantees-and-delta).
150
101
 
151
- ## Migration from the Python helper
102
+ Guarded JavaScript mutations can have out-of-root effects before a post-check
103
+ detects a hostile parent swap. Native `require` does not require a kernel-atomic
104
+ resolver: inspect the operation's `containment` and use OS isolation for hostile
105
+ concurrent actors. The [security model](security-model.md#native-root-mutation-capabilities)
106
+ is authoritative for operation and platform guarantees.
152
107
 
153
- Version 0.5 removes the Python worker and interpreter-path selection. The mode
154
- contract is unchanged, so migrate startup configuration directly:
108
+ ## Migration from the Python helper
155
109
 
156
- | Python helper configuration | Native replacement |
157
- |---|---|
158
- | `configureFsSafePython({ mode: "auto" })` | `configureFsSafeNative({ mode: "auto" })` |
159
- | `configureFsSafePython({ mode: "off" })` | `configureFsSafeNative({ mode: "off" })` |
160
- | `configureFsSafePython({ mode: "require" })` | `configureFsSafeNative({ mode: "require" })` |
161
- | `FS_SAFE_PYTHON_MODE` | `FS_SAFE_NATIVE_MODE` |
162
- | `OPENCLAW_FS_SAFE_PYTHON_MODE` | `OPENCLAW_FS_SAFE_NATIVE_MODE` |
163
- | `pythonPath`, `FS_SAFE_PYTHON`, and the OpenClaw interpreter-path aliases | Remove; prebuilt bindings do not use an interpreter path |
164
-
165
- In 0.5, `configureFsSafePython` and the legacy Python environment names
166
- remain only as an upgrade bridge. On the first config read they emit one
167
- `DeprecationWarning` with code `FS_SAFE_PYTHON_DEPRECATED`, state the mapped
168
- native mode, and then apply that mode. A legacy interpreter path without an
169
- explicit mode maps to `auto` and the path itself is ignored. Native config has
170
- the normal precedence over legacy environment config.
171
-
172
- There is no silent alias and no Python execution fallback. The bridge exists
173
- only to make shipped 0.4 configuration visible and predictable while the
174
- consumer performs its 0.5 upgrade.
110
+ The deprecated configuration bridge maps Python settings to native modes and
111
+ warns once; it never executes Python. Follow the
112
+ [0.5 migration checklist](migrating-to-0.5.md#2-replace-python-helper-configuration)
113
+ for aliases, warning behavior, and precedence.
175
114
 
176
115
  ## Related pages
177
116
 
@@ -17,7 +17,7 @@ guarded JavaScript path. Native loading is lazy; installs do not compile Rust,
17
17
  run postinstall code, or fetch binaries at runtime. Seven exact-version optional
18
18
  packages are filtered by OS, CPU, and Linux libc, so an installation receives
19
19
  only its matching prebuilt binding.
20
- Native-only formats fail explicitly
20
+ Native-only operations fail explicitly
21
21
  instead of substituting a weaker implementation.
22
22
 
23
23
  ## The beneath model
@@ -235,11 +235,8 @@ not bypass the byte limit.
235
235
 
236
236
  ## Mode semantics
237
237
 
238
- | Mode | Native loading | Fallback |
239
- |---|---|---|
240
- | `auto` | Try once, cache the result | Use guarded JavaScript when safe; reject native-only operations |
241
- | `require` | Try once, cache the result | Throw `FsSafeError("helper-unavailable")` |
242
- | `off` | Never attempt a binding load | Use guarded JavaScript when safe; reject native-only operations |
238
+ See [Native helper policy](native-helper.md#modes) for the `auto`, `require`, and
239
+ `off` contracts and cached loader behavior.
243
240
 
244
241
  `sha256FileSync()` is a synchronous Node implementation in all three modes and
245
242
  does not load the binding. Use asynchronous `sha256File()` for native hashing
@@ -251,19 +248,10 @@ Features without a safe fallback, including no-clobber
251
248
  when native support is absent or off. Staging is currently Linux/macOS only and
252
249
  rejects Windows with `unsupported-platform`.
253
250
 
254
- Windows raw owner/DACL inspection, private-directory creation, and secure-file
255
- descriptor inspection can use a package-shipped, readable `.ps1` driver and
256
- adjacent `.cs` source in `auto` or `off` mode when their binding or capability is
257
- unavailable. System Windows PowerShell runs the fixed driver with `-File`;
258
- paths remain data, with no runtime-generated helper script or encoded launcher.
259
- The [Windows security fallback prerequisites](install.md#windows-security-fallback)
260
- apply, and unsupported or disallowed command execution fails closed. This route
261
- preserves raw ACL facts, private DACLs at creation, and descriptor-bound secure
262
- reads, and emits a path-free `FS_SAFE_NATIVE_FALLBACK` warning once per capability
263
- per process. Each call adds PowerShell startup and compilation overhead.
264
- `require` rejects missing capabilities without a command, and an available native operation's
265
- failure never triggers this fallback. See [Permissions](permissions.md) and
266
- [Secure file reads](secure-file.md) for error and platform contracts.
251
+ Windows owner/DACL inspection, private-directory creation, and secure-file
252
+ inspection support [PowerShell fallbacks](install.md#windows-security-fallback)
253
+ in `auto` and `off`. `require` rejects missing capabilities, and native operation
254
+ failures are terminal.
267
255
 
268
256
  The staged-file owner also serves POSIX native pinned writes, including streaming.
269
257
  Unpublished files remain at `0600`; requested modes are applied through the
@@ -304,7 +292,12 @@ denied-path decisions remain in TypeScript.
304
292
  Public policy does not change with the selected mechanism: traversal and link
305
293
  rejection, archive filters/limits/modes, exclusive target creation, source and
306
294
  target identity fencing, publication cleanup receipts, and secret/lock policy
307
- remain TypeScript-owned. What changes is the syscall strength or availability:
295
+ remain TypeScript-owned. Windows buffered replacement writes with an omitted
296
+ `mutationSymlinks` policy retain the existing
297
+ [final-link and parent-junction differences](writing.md#windows-link-modes)
298
+ between native and legacy JavaScript paths; use explicit `"reject"` for uniform
299
+ link rejection. Apart from that legacy compatibility exception, what changes
300
+ is the syscall strength or availability:
308
301
 
309
302
  The table compares underlying mechanisms. On Node, public `Root.open()`,
310
303
  `Root.read()`, and `Root.openWritable()` use guarded Node file opens and report
@@ -328,6 +321,15 @@ infer native loading from timing.
328
321
 
329
322
  ## Loader security
330
323
 
324
+ Native initialization runs in the async context captured when fs-safe's loader
325
+ module is evaluated. Import fs-safe during application startup, outside request
326
+ or lease scopes, so the addon's process-lifetime housekeeping cannot retain the
327
+ first operation's `AsyncLocalStorage` stores. Importing still does not load the
328
+ addon: only the first native operation pays the registration cost. Subsequent
329
+ operations and their callbacks retain their own caller context. Dynamically
330
+ importing fs-safe for the first time inside a request scope captures that import
331
+ scope instead; this boundary does not clear an already active import context.
332
+
331
333
  Importing fs-safe never executes a child process. Linux libc selection uses
332
334
  the Node process report, then the ELF `PT_INTERP` field of `process.execPath`,
333
335
  then conventional musl library filenames. An installed compatibility loader
@@ -36,7 +36,7 @@ isPathInside("/srv/uploads", "/srv/uploads-other/x"); // false
36
36
  isPathInside("/srv/uploads", "/srv/uploads"); // true (root itself counts)
37
37
  ```
38
38
 
39
- The check is platform-aware: on Windows, paths are normalized for case and separator before comparison. This is deliberately a string-only, lexical answer; case folding does not prove that two differently cased prefixes name the same directory on a case-sensitive Windows directory. Root operations perform their own filesystem-identity admission when containment depends on case folding.
39
+ The check is platform-aware: on Windows, paths are normalized for case and separator before comparison. This is deliberately a string-only, lexical answer; case folding does not prove that two differently cased prefixes name the same directory on a case-sensitive Windows directory. Root operations perform their own filesystem-identity admission when containment depends on case folding. A target on a different UNC share or device namespace than `rootDir` is never inside it: hosts and shares compare with ASCII-only case folding, and namespace spellings whose share or device cannot be identified count as outside unless spelled exactly under `rootDir` (see the [security model](security-model.md)).
40
40
  It is a lexical comparison, not a filesystem-admission boundary: it does not
41
41
  reject Windows alternate data streams or index-allocation aliases. Use a
42
42
  filesystem operation such as `root()` when a caller-controlled path will be
@@ -85,8 +85,7 @@ Structured ACLs containing only canonical SIDs are classified directly from
85
85
  the current-user SID without requiring a separate account-name lookup.
86
86
  The advanced options retain `currentUserSid` as an explicit classification
87
87
  override and `principalTranslationFailed: true` as an immediate unverified
88
- result. The optional `principalSids` translation cache is still accepted but
89
- is no longer needed because the query returns SIDs directly.
88
+ result. The query returns SIDs directly; no translation cache is needed.
90
89
  Injected executors must return the same structured success JSON as the built-in
91
90
  query: valid `ownerSid` and `currentUserSid` strings, an explicit boolean
92
91
  `remote`, and complete DACL facts (`complete`, `daclPresent`, and `aces`).
@@ -24,8 +24,7 @@ The handle resolver verifies exact descriptor and pathname identities, with one
24
24
  bounded retry for unknown Windows observations. It borrows the handle without
25
25
  reading, reopening, closing it, or changing its cursor.
26
26
 
27
- The error helpers are `categorizeFsSafeError` and `FsSafeErrorDetails`. The
28
- deprecated native-configuration bridge retains the `FsSafePythonConfig` type.
27
+ The error helpers are `categorizeFsSafeError` and `FsSafeErrorDetails`.
29
28
 
30
29
  ## `path` and `advanced`
31
30
 
@@ -99,14 +99,13 @@ try {
99
99
  type RootReadOptions = {
100
100
  hardlinks?: "reject" | "allow"; // override defaults.hardlinks
101
101
  maxBytes?: number; // refuse reads larger than this many bytes
102
- nonBlockingRead?: boolean; // compatibility hint; safe opens are already nonblocking where supported
103
102
  symlinks?: "reject" | "follow-within-root" | "follow-parents-within-root"; // override defaults.symlinks
104
103
  };
105
104
  ```
106
105
 
107
106
  `maxBytes` is enforced eagerly: the library reads up to `maxBytes + 1` and throws `too-large` if there is more, so a hostile target cannot silently exhaust memory. Values must be non-negative safe integers or positive `Infinity`; zero is an active cap, while `Infinity` disables it. Explicitly forwarding `undefined` preserves the Root default.
108
107
 
109
- `nonBlockingRead` remains as a compatibility hint. Safe reads always add the platform's nonblocking open flag where available so a raced FIFO cannot pin a worker indefinitely; regular-file descriptor reads retain normal Node behavior.
108
+ Safe reads always add the platform's nonblocking open flag where available so a raced FIFO cannot pin a worker indefinitely; regular-file descriptor reads retain normal Node behavior.
110
109
 
111
110
  ## `readAbsolute()` and `reader()`
112
111
 
@@ -25,7 +25,6 @@ type RootDefaults = {
25
25
  maxBytes?: number; // refuse reads larger than this many bytes; defaults to 16 MiB
26
26
  mkdir?: boolean; // create missing parent dirs on write/openWritable/append; default true
27
27
  mode?: number; // requested file mode; per-call override available
28
- nonBlockingRead?: boolean; // compatibility hint; safe opens are already nonblocking where supported
29
28
  renameIdentity?: "strict" | "verify-content-with-lock"; // default "strict"
30
29
  symlinks?: "reject" | "follow-within-root" | "follow-parents-within-root"; // read policy
31
30
  mutationSymlinks?: "reject" | "follow-parents-within-root"; // opt-in mutation policy
@@ -66,39 +65,11 @@ fs.reader(options?) // (path) => Promise<Buffer>; useful for loader A
66
65
  fs.walk(rel, options) // root-bounded AsyncIterable<{ relativePath, kind, size }>
67
66
  ```
68
67
 
69
- `walk()` is the incremental, root-bounded recursive scan. It supports entry and
70
- depth budgets, cancellation, and `symlinkPolicy: "skip" |
71
- "follow-within-root" | "include"`. Include mode returns links as
72
- `{ relativePath, kind: "symlink", size }` without resolving or entering their
73
- targets, including dangling and outside-root links. `size` describes the link,
74
- not its target. With an entry budget, sorted walks prepare small metadata
75
- batches within the remaining budget; unbounded sorted walks reuse the full
76
- directory snapshot.
77
- The default `order: "sorted"` enumerates and sorts each directory's names;
78
- `order: "filesystem"` streams names in filesystem order for bounded work in
79
- wide directories. Budget exhaustion yields a `"truncated"` marker by
80
- default or throws `FsSafeError("too-large")` with `limitBehavior: "throw"`.
81
- Use `entryFilter(entry)` to return `"include"`, `"skip"`, or
82
- `"skip-subtree"`, directly or through a Promise. `"skip"` omits the current
83
- entry but still descends into a directory; `"skip-subtree"` omits a directory
84
- and all of its descendants.
85
- Filters run serially outside metadata batches, with the options object as their
86
- `this` receiver. After an awaited filter resolves, the walk checks cancellation
87
- and revalidates the current listing directory and Root identities before using
88
- the decision. Captured entry metadata retains its snapshot semantics.
89
-
90
- Cancellation and iterator disposal wait for a pending filter to settle; they do
91
- not race the callback or close its directory while it is running. Callback
92
- throws and promise rejections reject the walk through normal cleanup.
93
- Directory reads remain fail-fast by default. With
94
- `onDirectoryError: "skip-and-report"`, the iterator instead yields
95
- `{ relativePath, kind: "directory-error", size: 0, error }` and continues with
96
- the remaining tree. That policy also covers identity-check failures after an
97
- awaited filter, while callback failures always reject.
98
- In include mode, a directory that becomes a symlink before descent is a
99
- `path-mismatch` directory error; it is never silently omitted or followed.
100
- See [Directory walking](walk.md) for the pure-Node guarantees and the contrast
101
- with the standalone best-effort walkers.
68
+ `walk()` is the root-bounded recursive iterator, with budgets, cancellation,
69
+ and symlink/filter policies. Default `order: "sorted"` enumerates and sorts all
70
+ names in each directory even with an entry budget; use `order: "filesystem"`
71
+ for bounded memory in wide directories. See [Root-bounded walking](walk.md#root-bounded-async-iteration)
72
+ for truncation, callback, and directory-error contracts.
102
73
 
103
74
  `open()` returns a Node `FileHandle` for streaming. Prefer `await using` for cleanup:
104
75
 
@@ -143,136 +114,22 @@ fs.mkdir(rel, options?) // mkdir -p (creates missing parents)
143
114
  fs.ensureRoot(options?) // accepts "" / "." as the root itself
144
115
  ```
145
116
 
146
- `mkdir`, `ensureRoot`, `create`, and `createJson` accept `private: true`.
147
- Missing directories are created with private permissions, and an existing
148
- requested directory must already be private. Existing ancestors are not
149
- chmodded or assigned new ACLs. Private files use owner-only POSIX permissions
150
- or a protected Windows DACL granting access to the current user, System, and
151
- Administrators. On macOS, private directories and files must also have no ACL;
152
- creation rejects relevant inheritable parent ACLs, while noninheriting parent
153
- ACLs remain allowed. A native helper with `inspectDarwinAcl` is required. Native
154
- `off`, a missing helper, or an older helper without that capability rejects with
155
- `helper-unavailable` before creating parents or stages. See [creation](creation.md)
156
- for platform support, synchronous leaf creation, and failure handling.
117
+ Mutation options control parent creation, modes, durability, and publication.
118
+ `write`, `create`, `append`, `writeJson`, `createJson`, and `copyIn` inherit
119
+ `durable` from Root defaults (normally `true`); `false` skips synchronization
120
+ without changing publication or identity checks. See [write options](writing.md#write-options)
121
+ for precedence and platform behavior, and [append](writing.md#write-verbs)
122
+ for newline handling and creation modes.
157
123
 
158
- ```ts
159
- await fs.mkdir("private-data", { private: true });
160
- await fs.create("private-data/credential", "synthetic credential", { private: true });
161
- ```
162
-
163
- `write`, `create`, `append`, `writeJson`, and `createJson` accept `mode?: number`; use `0o600` for credentials and other private state. `writeJson` also accepts the same options as `JSON.stringify` plus `trailingNewline?: boolean` (defaults `true` so the file ends in `\n`).
164
-
165
- For `append` and `openWritable`, `mode` only affects new-file creation: POSIX
166
- permissions remain subject to the process umask. These methods do not chmod
167
- existing files. Replacement and create-only writes apply their final mode
168
- through the retained descriptor; see [Writing](writing.md#write-options).
169
-
170
- Buffered `create` and `createJson` also accept `atomic?: boolean`. With `true`,
171
- complete content is staged before exclusive publication even in native-off mode;
172
- the fallback requires hardlinks. Omitted or `false` keeps the existing buffered
173
- publication behavior. Streamed creates always stage complete content. The flag
174
- does not change `durable` or promise stronger containment or crash durability.
175
- See [atomic creation and settlement](writing.md#atomic-buffered-creation).
176
-
177
- `create` also accepts `AsyncIterable<Uint8Array>` with `RootCreateStreamOptions`:
178
- the same path, authority, mode, and durability options, plus `maxBytes` and
179
- `signal`, without `encoding` or `renameIdentity`. It consumes one chunk at a
180
- time and publishes the completed file exclusively. The byte cap inherits an
181
- explicit `Root.defaults.maxBytes`; without either cap, consumption is unlimited.
182
- See [streamed creation](writing.md#streamed-creation) for cancellation,
183
- cleanup, and filesystem requirements.
184
-
185
- `append` accepts `prependNewlineIfNeeded: true` to separate text from existing
186
- content when neither side supplies a newline. String data uses its `encoding`
187
- for the newline check, including UTF-16LE; Buffer data uses a single LF byte.
188
- Empty strings and Buffers add no separator; an empty append still creates a
189
- missing file.
190
-
191
- These five methods and `copyIn` also accept `durable?: boolean`: the per-call value overrides
192
- `Root.defaults.durable`, which defaults to `true` when omitted. An explicitly
193
- `undefined` per-call value preserves the root default. `durable: false` keeps
194
- the existing publication behavior, modes, and identity checks but skips file
195
- and parent-directory fsync calls. Use it only for reconstructible data: a crash
196
- may lose the write or leave the previous file. See [Writing](writing.md#write-options)
197
- for platform details.
198
-
199
- `create` and `createJson` additionally accept `durable: "file"` to require file
200
- synchronization, including propagating `EPERM`. Parent-directory synchronization
201
- retains its existing best-effort behavior. This option applies to buffered and
202
- streamed creation and does not select a publication strategy.
203
-
204
- `copyIn` accepts a `RootCopySource`: a trusted absolute source path or a file
205
- within another Root. The guarded form supplies `root` with only its `open` and
206
- `stat` read capabilities, plus `relativePath`:
124
+ `mkdir`, `ensureRoot`, `create`, and `createJson` accept `private: true`; see
125
+ [Creation](creation.md) for permission checks and native requirements.
126
+ Buffered `create` and `createJson` support [atomic publication](writing.md#atomic-buffered-creation)
127
+ and `durable: "file"`; `create` also supports [streamed input](writing.md#streamed-creation).
207
128
 
208
- ```ts
209
- const source = await root("/srv/templates");
210
- const destination = await root("/srv/workspace");
211
- await destination.copyIn("config/settings.json", {
212
- root: source,
213
- relativePath: "config/settings.json",
214
- }, {
215
- overwrite: false,
216
- clone: "auto",
217
- mode: 0o600,
218
- signal: AbortSignal.timeout(30_000),
219
- });
220
- ```
221
-
222
- The source Root applies its read policies, including confinement and symlink
223
- handling. `sourceHardlinks` overrides its hardlink policy only when supplied;
224
- otherwise the source Root default is retained. The admitted source
225
- descriptor stays open through copying and source-identity verification; copying
226
- does not consume its current file position. Both forms enforce `maxBytes` while
227
- reading, including when a file grows after admission, and use bounded buffers.
228
- Copies have independent file data; changing either file cannot change the other.
229
- Set `preserveSourceMode: true` to select the mode from the admitted source
230
- descriptor. An explicit numeric `mode`, including `Root.defaults.mode`, takes
231
- precedence. By default, copying retains the existing destination-mode rules.
232
- The operation verifies source identity, not a coherent snapshot of concurrent
233
- in-place edits. Keep the source unchanged when snapshot consistency is required.
234
-
235
- `overwrite` defaults to `true`, preserving the existing replacement behavior.
236
- With `overwrite: false`, an existing destination produces `already-exists` and
237
- is never altered. Copying prepares a private sibling file before publishing its
238
- completed contents. Native mode uses no-replace rename. The guarded JavaScript
239
- fallback links the completed stage and removes its temporary name in the same
240
- JavaScript turn; the filesystem must support hardlinks. Other processes can
241
- briefly observe both names. The source is never hardlinked to the destination.
242
-
243
- `clone` chooses the file-data transfer strategy through `CopyCloneMode`, shared
244
- with [`copyTree`](copy.md#api). File copies default to `"never"`; tree copies
245
- default to `"auto"`:
246
-
247
- | Value | Behavior |
248
- | --- | --- |
249
- | `never` | Copy regular file bytes using reads and writes, without explicit cloning or copy offload. |
250
- | `auto` | Try native file cloning, then copy offload or ordinary byte copying when cloning is unavailable. |
251
- | `always` | Require native cloning; fail when the binding or filesystem cannot provide it. |
252
-
253
- Native file cloning supports APFS and supported Linux filesystems. Windows
254
- currently uses byte copying for `never` and `auto`; `always` fails. Clone choice
255
- does not change modes, durability, root confinement, or source and publication
256
- identity checks. The shared strategy does not replace Root's guarded regular-file
257
- contract with `copyTree`'s caller-owned immutable-tree and metadata contract.
258
-
259
- An already aborted `signal` prevents I/O. Cancellation during copying waits for
260
- admitted reads and native work to settle, then cleans only the owned unpublished
261
- stage. The final authority check runs before publication. Once publication has
262
- occurred, later cancellation or verification failure preserves the destination.
263
- The synchronous optional `onDestinationPublished` callback receives a frozen
264
- `RootCopyPublicationReceipt` containing `{ path, dev, ino }`, with exact bigint identity immediately after
265
- publication, before later checks can fail. Callback errors also preserve the
266
- published file. Promise, thenable, and synchronous or asynchronous generator
267
- results reject with `TypeError`; returned generators are never advanced. Other
268
- synchronous return values are ignored. This receipt records an outcome; it does
269
- not authorize removing a file that another actor may have edited. Application recovery and cooperative
270
- locking remain caller-owned.
271
-
272
- Existing `copyIn` callers must account for completed destinations retained after
273
- a post-publication source-verification failure, even without the new options.
274
- Recovery must inspect current destination state rather than assume a rejected
275
- copy left no file.
129
+ `copyIn` accepts a trusted absolute path or another Root as its source, with
130
+ byte limits, cloning, cancellation, and publication receipts. See the complete
131
+ [copy contract](writing.md#write-verbs); a rejected copy
132
+ can still leave a completed destination after publication.
276
133
 
277
134
  Root operations that choose a new destination reject a leading Windows
278
135
  drive-relative spelling such as `C:name` on every platform. This applies to
@@ -294,17 +151,11 @@ does not otherwise reject them.
294
151
 
295
152
  `openWritable` opens a writable file with options `mode?: number` and `writeMode?: "replace" | "append" | "update"`. `replace` truncates existing files and is the default; `update` keeps existing contents. Before truncation or handle return, descriptor and pathname identities are compared with lossless bigint metadata; persistently unknown Windows identities fail closed. The returned `stat` remains an ordinary numeric Node `Stats` object. Use it for streaming output. Prefer `await using` for cleanup.
296
153
 
297
- `remove` leaves non-empty directories unchanged unless `recursive: true` is
298
- provided. Recursive removal defaults to streaming entries in filesystem order;
299
- `order: "sorted"` processes each directory's children lexicographically. The
300
- `maxEntries` (100,000 by default) and `maxDepth` (64 by default) budgets accept
301
- explicit `Infinity` when the caller needs unlimited traversal. It never
302
- follows discovered symlinks; an explicit `mutationSymlinks` policy rejects them,
303
- while the omitted policy unlinks them. `force: true` ignores missing targets,
304
- and `signal` stops further work after admitted I/O and resource cleanup settle.
305
- Removal is not transactional: a budget, cancellation, policy, or identity
306
- failure can leave a partially removed tree. See [removal](writing.md)
307
- for the full counting and failure contract.
154
+ `remove` leaves non-empty directories unchanged unless `recursive: true`.
155
+ Recursive removal defaults to filesystem order, `maxEntries: 100_000`, and
156
+ `maxDepth: 64`. It is incremental: budget, cancellation, policy, or identity
157
+ failures can leave a partially removed tree. See [removal](writing.md#write-verbs)
158
+ for ordering, symlink, and failure semantics.
308
159
 
309
160
  ### Live mutation authority
310
161
 
@@ -540,17 +391,8 @@ const b = await load("/srv/workspace/state.bin"); // absolute, but inside the ro
540
391
 
541
392
  ### "Touch only if missing" seeding
542
393
 
543
- ```ts
544
- try {
545
- await fs.create("config/seed.json", initialJson);
546
- } catch (err) {
547
- if (err instanceof FsSafeError && err.code === "already-exists") {
548
- // existing config wins
549
- } else {
550
- throw err;
551
- }
552
- }
553
- ```
394
+ Use `create()` and handle `already-exists` so existing configuration wins;
395
+ see the [seeding example](writing.md#write-verbs).
554
396
 
555
397
  ### Replace + verify
556
398
 
@@ -314,5 +314,5 @@ await withTimeout(
314
314
 
315
315
  - [JSON files](json.md) — `writeJson` accepts `mode: 0o600` for non-secret JSON state.
316
316
  - [Atomic writes](atomic.md) — the lower-level `replaceFileAtomic` used by these helpers.
317
- - [Private file-store mode](private-file-store.md) — root-bounded JSON+text stores using secret-file write policy.
317
+ - [Private file-store mode](file-store.md#private-mode) — root-bounded JSON+text stores using secret-file write policy.
318
318
  - [Migrating to 0.5](migrating-to-0.5.md) — strict/try reads and create-only adoption checklist.