@openclaw/fs-safe 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +38 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -1
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-merge.d.ts.map +1 -1
  10. package/dist/archive-merge.js +113 -46
  11. package/dist/archive-parser.wasm +0 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +10 -11
  14. package/dist/archive-tar-stream.d.ts +3 -0
  15. package/dist/archive-tar-stream.d.ts.map +1 -1
  16. package/dist/archive-tar-stream.js +56 -37
  17. package/dist/archive-tar-wasm.d.ts +16 -4
  18. package/dist/archive-tar-wasm.d.ts.map +1 -1
  19. package/dist/archive-tar-wasm.js +134 -34
  20. package/dist/archive-zip-directory.d.ts +4 -0
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +2 -0
  23. package/dist/archive-zip-entry.d.ts +6 -2
  24. package/dist/archive-zip-entry.d.ts.map +1 -1
  25. package/dist/archive-zip-entry.js +23 -8
  26. package/dist/archive-zip-integrity.d.ts.map +1 -1
  27. package/dist/archive-zip-integrity.js +3 -4
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +107 -31
  30. package/dist/archive-zip-names.d.ts +1 -0
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +6 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +11 -11
  35. package/dist/bounded-read-stream.d.ts +0 -1
  36. package/dist/bounded-read-stream.d.ts.map +1 -1
  37. package/dist/bounded-read-stream.js +0 -6
  38. package/dist/clone-metadata.d.ts +1 -0
  39. package/dist/clone-metadata.d.ts.map +1 -1
  40. package/dist/clone-metadata.js +6 -2
  41. package/dist/copy-publication.d.ts +6 -0
  42. package/dist/copy-publication.d.ts.map +1 -1
  43. package/dist/copy-publication.js +3 -0
  44. package/dist/copy-tree-portable.d.ts.map +1 -1
  45. package/dist/copy-tree-portable.js +44 -24
  46. package/dist/copy.d.ts.map +1 -1
  47. package/dist/copy.js +29 -11
  48. package/dist/create-directory.d.ts +20 -0
  49. package/dist/create-directory.d.ts.map +1 -0
  50. package/dist/create-directory.js +130 -0
  51. package/dist/create-file-async.d.ts +7 -0
  52. package/dist/create-file-async.d.ts.map +1 -0
  53. package/dist/create-file-async.js +121 -0
  54. package/dist/create-file.d.ts +8 -0
  55. package/dist/create-file.d.ts.map +1 -0
  56. package/dist/create-file.js +190 -0
  57. package/dist/create-owned-file.d.ts +8 -0
  58. package/dist/create-owned-file.d.ts.map +1 -0
  59. package/dist/create-owned-file.js +16 -0
  60. package/dist/create.d.ts +4 -0
  61. package/dist/create.d.ts.map +1 -0
  62. package/dist/create.js +2 -0
  63. package/dist/creation-darwin.d.ts +7 -0
  64. package/dist/creation-darwin.d.ts.map +1 -0
  65. package/dist/creation-darwin.js +79 -0
  66. package/dist/creation-file-state.d.ts +19 -0
  67. package/dist/creation-file-state.d.ts.map +1 -0
  68. package/dist/creation-file-state.js +118 -0
  69. package/dist/creation-path.d.ts +21 -0
  70. package/dist/creation-path.d.ts.map +1 -0
  71. package/dist/creation-path.js +71 -0
  72. package/dist/creation-permissions.d.ts +19 -0
  73. package/dist/creation-permissions.d.ts.map +1 -0
  74. package/dist/creation-permissions.js +125 -0
  75. package/dist/directory-durability.d.ts +1 -1
  76. package/dist/directory-durability.d.ts.map +1 -1
  77. package/dist/directory-durability.js +22 -80
  78. package/dist/directory-guard.d.ts +3 -0
  79. package/dist/directory-guard.d.ts.map +1 -1
  80. package/dist/directory-mode-node.d.ts +2 -0
  81. package/dist/directory-mode-node.d.ts.map +1 -1
  82. package/dist/directory-mode-node.js +8 -0
  83. package/dist/directory-mode-owner.js +5 -5
  84. package/dist/directory-receipt.d.ts +24 -0
  85. package/dist/directory-receipt.d.ts.map +1 -0
  86. package/dist/directory-receipt.js +127 -0
  87. package/dist/file-cleanup.d.ts +19 -0
  88. package/dist/file-cleanup.d.ts.map +1 -0
  89. package/dist/file-cleanup.js +78 -0
  90. package/dist/file-handle-transfer.d.ts +2 -0
  91. package/dist/file-handle-transfer.d.ts.map +1 -1
  92. package/dist/file-handle-transfer.js +57 -2
  93. package/dist/file-identity.d.ts.map +1 -1
  94. package/dist/file-identity.js +18 -4
  95. package/dist/file-lock-sync-admission.d.ts +19 -0
  96. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  97. package/dist/file-lock-sync-admission.js +93 -0
  98. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  99. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  100. package/dist/file-lock-sync-root-acquire.js +370 -0
  101. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  102. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  103. package/dist/file-lock-sync-root-arbitration.js +66 -0
  104. package/dist/file-lock-sync-root-held.d.ts +34 -0
  105. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  106. package/dist/file-lock-sync-root-held.js +393 -0
  107. package/dist/file-lock-sync-root-io.d.ts +44 -0
  108. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  109. package/dist/file-lock-sync-root-io.js +209 -0
  110. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  111. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  112. package/dist/file-lock-sync-root-mutation.js +277 -0
  113. package/dist/file-lock-sync-root-options.d.ts +20 -0
  114. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  115. package/dist/file-lock-sync-root-options.js +58 -0
  116. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  117. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  118. package/dist/file-lock-sync-root-registration.js +90 -0
  119. package/dist/file-lock-sync-root.d.ts +36 -0
  120. package/dist/file-lock-sync-root.d.ts.map +1 -0
  121. package/dist/file-lock-sync-root.js +361 -0
  122. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  123. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  124. package/dist/file-lock-sync-stale-admission.js +205 -0
  125. package/dist/file-lock-sync.d.ts.map +1 -1
  126. package/dist/file-lock-sync.js +245 -205
  127. package/dist/file-observation.d.ts +1 -1
  128. package/dist/file-observation.d.ts.map +1 -1
  129. package/dist/file-store-boundary.d.ts +2 -6
  130. package/dist/file-store-boundary.d.ts.map +1 -1
  131. package/dist/file-store-boundary.js +3 -9
  132. package/dist/file-store-prune.d.ts.map +1 -1
  133. package/dist/file-store-prune.js +5 -1
  134. package/dist/file-store-sync-write.d.ts.map +1 -1
  135. package/dist/file-store-sync-write.js +5 -8
  136. package/dist/file-store.d.ts.map +1 -1
  137. package/dist/file-store.js +47 -12
  138. package/dist/guarded-mkdir.d.ts +1 -0
  139. package/dist/guarded-mkdir.d.ts.map +1 -1
  140. package/dist/guarded-mkdir.js +36 -7
  141. package/dist/json-document-store.d.ts.map +1 -1
  142. package/dist/json-document-store.js +22 -15
  143. package/dist/json-durable-queue-ownership.d.ts +0 -1
  144. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  145. package/dist/json-durable-queue-ownership.js +0 -6
  146. package/dist/move-path.js +1 -1
  147. package/dist/native-binding.d.ts +13 -1
  148. package/dist/native-binding.d.ts.map +1 -1
  149. package/dist/native-fallback-warning.d.ts +4 -0
  150. package/dist/native-fallback-warning.d.ts.map +1 -0
  151. package/dist/native-fallback-warning.js +11 -0
  152. package/dist/native-operations.d.ts +0 -2
  153. package/dist/native-operations.d.ts.map +1 -1
  154. package/dist/native-operations.js +0 -24
  155. package/dist/native-parent-admission.d.ts +5 -2
  156. package/dist/native-parent-admission.d.ts.map +1 -1
  157. package/dist/native-parent-admission.js +27 -7
  158. package/dist/native-pinned-write-windows.d.ts +1 -1
  159. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  160. package/dist/native-pinned-write-windows.js +174 -29
  161. package/dist/native-pinned-write.d.ts.map +1 -1
  162. package/dist/native-pinned-write.js +26 -14
  163. package/dist/native-policy-parent-windows.d.ts +14 -0
  164. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  165. package/dist/native-policy-parent-windows.js +209 -0
  166. package/dist/native-rename-outcome.d.ts +4 -0
  167. package/dist/native-rename-outcome.d.ts.map +1 -0
  168. package/dist/native-rename-outcome.js +8 -0
  169. package/dist/native-staged-file.d.ts +3 -2
  170. package/dist/native-staged-file.d.ts.map +1 -1
  171. package/dist/native-staged-file.js +121 -72
  172. package/dist/output.d.ts.map +1 -1
  173. package/dist/output.js +12 -8
  174. package/dist/owner-dacl.d.ts.map +1 -1
  175. package/dist/owner-dacl.js +10 -4
  176. package/dist/path-prefix.d.ts.map +1 -1
  177. package/dist/path-prefix.js +30 -8
  178. package/dist/path-suffix-aliases.d.ts +2 -0
  179. package/dist/path-suffix-aliases.d.ts.map +1 -1
  180. package/dist/path-suffix-aliases.js +25 -17
  181. package/dist/permission-exec.d.ts +2 -0
  182. package/dist/permission-exec.d.ts.map +1 -1
  183. package/dist/permission-exec.js +150 -21
  184. package/dist/permissions-windows.js +1 -1
  185. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  186. package/dist/pinned-mutation-admission.js +10 -5
  187. package/dist/pinned-mutation-observation.d.ts +0 -1
  188. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  189. package/dist/pinned-mutation-observation.js +0 -19
  190. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  191. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  192. package/dist/pinned-mutation-shared-route.js +1 -1
  193. package/dist/pinned-write-input.d.ts +4 -0
  194. package/dist/pinned-write-input.d.ts.map +1 -0
  195. package/dist/pinned-write-input.js +25 -0
  196. package/dist/pinned-write-mode.d.ts +5 -0
  197. package/dist/pinned-write-mode.d.ts.map +1 -0
  198. package/dist/pinned-write-mode.js +24 -0
  199. package/dist/pinned-write-staged.d.ts +6 -0
  200. package/dist/pinned-write-staged.d.ts.map +1 -0
  201. package/dist/pinned-write-staged.js +187 -0
  202. package/dist/pinned-write-types.d.ts +5 -0
  203. package/dist/pinned-write-types.d.ts.map +1 -1
  204. package/dist/pinned-write.d.ts.map +1 -1
  205. package/dist/pinned-write.js +35 -145
  206. package/dist/private-directory.d.ts.map +1 -1
  207. package/dist/private-directory.js +18 -4
  208. package/dist/private-producer-handoff-sync.d.ts +14 -0
  209. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  210. package/dist/private-producer-handoff-sync.js +114 -0
  211. package/dist/private-producer-handoff.d.ts +22 -4
  212. package/dist/private-producer-handoff.d.ts.map +1 -1
  213. package/dist/private-producer-handoff.js +140 -77
  214. package/dist/private-temp-workspace.d.ts.map +1 -1
  215. package/dist/private-temp-workspace.js +75 -121
  216. package/dist/publish-copy-stage.d.ts +2 -1
  217. package/dist/publish-copy-stage.d.ts.map +1 -1
  218. package/dist/publish-copy-stage.js +16 -7
  219. package/dist/publish-file.d.ts.map +1 -1
  220. package/dist/publish-file.js +2 -2
  221. package/dist/regular-file.d.ts.map +1 -1
  222. package/dist/regular-file.js +1 -15
  223. package/dist/replace-directory.d.ts.map +1 -1
  224. package/dist/replace-directory.js +256 -18
  225. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  226. package/dist/replace-file-copy-fallback.js +62 -70
  227. package/dist/replace-file-copy-source.d.ts.map +1 -1
  228. package/dist/replace-file-copy-source.js +10 -12
  229. package/dist/replace-file-temp-owner.d.ts +5 -9
  230. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  231. package/dist/replace-file-temp-owner.js +56 -72
  232. package/dist/replace-file.js +6 -6
  233. package/dist/retained-directory-replacement.d.ts +26 -0
  234. package/dist/retained-directory-replacement.d.ts.map +1 -0
  235. package/dist/retained-directory-replacement.js +193 -0
  236. package/dist/root-boundary.d.ts +1 -0
  237. package/dist/root-boundary.d.ts.map +1 -1
  238. package/dist/root-boundary.js +4 -0
  239. package/dist/root-context.d.ts +0 -8
  240. package/dist/root-context.d.ts.map +1 -1
  241. package/dist/root-context.js +0 -3
  242. package/dist/root-create-input.d.ts +8 -1
  243. package/dist/root-create-input.d.ts.map +1 -1
  244. package/dist/root-create-input.js +17 -4
  245. package/dist/root-directory-creation.d.ts +3 -3
  246. package/dist/root-directory-creation.d.ts.map +1 -1
  247. package/dist/root-directory-creation.js +15 -3
  248. package/dist/root-directory-list.d.ts +1 -0
  249. package/dist/root-directory-list.d.ts.map +1 -1
  250. package/dist/root-directory-list.js +1 -0
  251. package/dist/root-impl.d.ts.map +1 -1
  252. package/dist/root-impl.js +46 -17
  253. package/dist/root-move-noreplace.d.ts.map +1 -1
  254. package/dist/root-move-noreplace.js +24 -15
  255. package/dist/root-options.d.ts +12 -4
  256. package/dist/root-options.d.ts.map +1 -1
  257. package/dist/root-path-errors.d.ts +1 -0
  258. package/dist/root-path-errors.d.ts.map +1 -1
  259. package/dist/root-path-errors.js +11 -2
  260. package/dist/root-path-existing.d.ts.map +1 -1
  261. package/dist/root-path-existing.js +11 -35
  262. package/dist/root-path-stat.d.ts.map +1 -1
  263. package/dist/root-path-stat.js +59 -7
  264. package/dist/root-path.js +1 -13
  265. package/dist/root-remove.d.ts +1 -0
  266. package/dist/root-remove.d.ts.map +1 -1
  267. package/dist/root-remove.js +4 -0
  268. package/dist/root-walk.d.ts +1 -1
  269. package/dist/root-walk.d.ts.map +1 -1
  270. package/dist/root-walk.js +17 -2
  271. package/dist/root-write-admission.d.ts +0 -2
  272. package/dist/root-write-admission.d.ts.map +1 -1
  273. package/dist/root-write-admission.js +1 -15
  274. package/dist/root-write-complete-parent.d.ts.map +1 -1
  275. package/dist/root-write-complete-parent.js +7 -23
  276. package/dist/root-write-publication.js +1 -1
  277. package/dist/root-write-verification.d.ts.map +1 -1
  278. package/dist/root-write-verification.js +29 -42
  279. package/dist/secret-file.d.ts.map +1 -1
  280. package/dist/secret-file.js +3 -24
  281. package/dist/secret-read-async.d.ts.map +1 -1
  282. package/dist/secret-read-async.js +3 -24
  283. package/dist/secret-read-policy.d.ts +6 -2
  284. package/dist/secret-read-policy.d.ts.map +1 -1
  285. package/dist/secret-read-policy.js +26 -2
  286. package/dist/secure-file-windows.d.ts +6 -0
  287. package/dist/secure-file-windows.d.ts.map +1 -1
  288. package/dist/secure-file-windows.js +34 -117
  289. package/dist/secure-file.js +2 -2
  290. package/dist/sibling-temp.d.ts.map +1 -1
  291. package/dist/sibling-temp.js +23 -12
  292. package/dist/sidecar-lock-acquire.d.ts +2 -28
  293. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  294. package/dist/sidecar-lock-acquire.js +288 -199
  295. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  296. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  297. package/dist/sidecar-lock-admission-context.js +60 -0
  298. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  299. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  300. package/dist/sidecar-lock-admission-parser.js +113 -0
  301. package/dist/sidecar-lock-admission.d.ts +35 -0
  302. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  303. package/dist/sidecar-lock-admission.js +7 -0
  304. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  305. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  306. package/dist/sidecar-lock-reclaim.js +80 -25
  307. package/dist/sidecar-lock-root.d.ts.map +1 -1
  308. package/dist/sidecar-lock-root.js +2 -1
  309. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  310. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  311. package/dist/sidecar-lock-stale-admission.js +232 -0
  312. package/dist/sidecar-lock-target.d.ts +8 -0
  313. package/dist/sidecar-lock-target.d.ts.map +1 -0
  314. package/dist/sidecar-lock-target.js +55 -0
  315. package/dist/sidecar-lock.d.ts.map +1 -1
  316. package/dist/sidecar-lock.js +100 -16
  317. package/dist/staged-directory.d.ts.map +1 -1
  318. package/dist/staged-directory.js +6 -6
  319. package/dist/staged-file-settlement.d.ts +17 -0
  320. package/dist/staged-file-settlement.d.ts.map +1 -0
  321. package/dist/staged-file-settlement.js +57 -0
  322. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  323. package/dist/temp-workspace-descriptor.js +9 -27
  324. package/dist/temp-workspace-owner.d.ts.map +1 -1
  325. package/dist/temp-workspace-owner.js +8 -8
  326. package/dist/walk.d.ts +5 -1
  327. package/dist/walk.d.ts.map +1 -1
  328. package/dist/walk.js +19 -6
  329. package/dist/windows-owner.d.ts.map +1 -1
  330. package/dist/windows-owner.js +4 -3
  331. package/dist/windows-security-bridge.cs +336 -0
  332. package/dist/windows-security-bridge.ps1 +15 -0
  333. package/dist/windows-security-command.d.ts +26 -0
  334. package/dist/windows-security-command.d.ts.map +1 -0
  335. package/dist/windows-security-command.js +363 -0
  336. package/dist/windows-security-facts.d.ts +6 -0
  337. package/dist/windows-security-facts.d.ts.map +1 -0
  338. package/dist/windows-security-facts.js +108 -0
  339. package/docs/advanced.md +4 -2
  340. package/docs/archive.md +97 -46
  341. package/docs/atomic.md +85 -8
  342. package/docs/config.md +6 -2
  343. package/docs/contributing.md +44 -4
  344. package/docs/copy.md +37 -0
  345. package/docs/creation.md +128 -0
  346. package/docs/durability.md +24 -0
  347. package/docs/file-store.md +19 -0
  348. package/docs/install.md +31 -7
  349. package/docs/json-store.md +5 -0
  350. package/docs/migrating-to-0.5.md +15 -6
  351. package/docs/migrating-to-0.6.md +9 -4
  352. package/docs/native-helper.md +32 -12
  353. package/docs/native.md +38 -7
  354. package/docs/output.md +6 -0
  355. package/docs/path-prefix.md +10 -0
  356. package/docs/path-suffix-aliases.md +51 -6
  357. package/docs/permissions.md +50 -14
  358. package/docs/public-api.md +3 -2
  359. package/docs/root.md +56 -3
  360. package/docs/secret-file.md +11 -2
  361. package/docs/secure-file.md +9 -4
  362. package/docs/sidecar-lock.md +114 -8
  363. package/docs/staged-file.md +12 -3
  364. package/docs/temp.md +20 -3
  365. package/docs/walk.md +67 -1
  366. package/docs/writing.md +76 -6
  367. package/package.json +19 -16
  368. package/dist/darwin-acl.d.ts +0 -4
  369. package/dist/darwin-acl.d.ts.map +0 -1
  370. package/dist/darwin-acl.js +0 -24
package/docs/walk.md CHANGED
@@ -59,12 +59,43 @@ type WalkDirectoryOptions = {
59
59
  include?: (entry: WalkDirectoryEntry) => boolean;
60
60
  descend?: (entry: WalkDirectoryEntry) => boolean;
61
61
  };
62
+
63
+ type AsyncWalkDirectoryOptions = Omit<WalkDirectoryOptions, "include" | "descend"> & {
64
+ include?: (entry: WalkDirectoryEntry) => boolean | Promise<boolean>;
65
+ descend?: (entry: WalkDirectoryEntry) => boolean | Promise<boolean>;
66
+ };
62
67
  ```
63
68
 
64
69
  `symlinks` defaults to `"skip"`. `"include"` returns symlink entries without following them. `"follow"` resolves symlinks with `stat()` and may descend into linked directories, so use it only when that is intentional. Already-visited real directories are skipped so symlink cycles do not recurse forever.
65
70
 
71
+ Before descending into a child directory, `skip` and `include` recheck whether
72
+ that entry has become a symlink, including changes made while a filter waits.
73
+ The explicitly supplied walk root may still be a symlink. This best-effort
74
+ child check does not turn the standalone walker into a confinement boundary;
75
+ use `Root.walk()` when root confinement is required.
76
+
66
77
  `include` controls which entries are returned. `descend` controls which directory entries are traversed. A skipped directory can still be returned if `include` accepts it.
67
78
 
79
+ The asynchronous `walkDirectory()` accepts `AsyncWalkDirectoryOptions`. It resolves each `include` decision before calling `descend`, and resolves descent before reading the directory's children. Decisions run serially in the existing filesystem-order depth-first traversal. Both callbacks retain the supplied options object as their `this` receiver.
80
+
81
+ Absent callbacks and primitive results keep the synchronous selection path. Object and function results are awaited directly, including promises and thenables. For JavaScript callers, nullish results retain the default `true`; other resolved values use their existing truthiness. Return booleans or promises of booleans for the typed API.
82
+
83
+ ```ts
84
+ import fs from "node:fs/promises";
85
+ import path from "node:path";
86
+
87
+ const scan = await walkDirectory("/safe/workspace", {
88
+ include: (entry) => entry.kind === "file",
89
+ descend: async (entry) => {
90
+ const marked = await fs.access(path.join(entry.path, "SKILL.md"))
91
+ .then(() => true, () => false);
92
+ return !marked;
93
+ },
94
+ });
95
+ ```
96
+
97
+ This prunes a directory after finding its marker without listing that directory's children. Callback throws and promise rejections reject the walk; they are not directory failures in `failedDirs`. Filtering still consumes the examined-entry budget. `WalkDirectoryOptions` and `walkDirectorySync()` remain synchronous; the async options do not add confinement or cancellation to the standalone walker.
98
+
68
99
  Unreadable directories are skipped rather than throwing, but every skipped directory is recorded in `failedDirs`. This keeps the helper suitable for best-effort inventories while letting pruning jobs tell an incomplete scan from an empty one: a destructive reconcile that deletes state for paths missing from `entries` must first confirm `failedDirs` holds no real read failures, or a transient `EIO`/`EACCES` blip would be mistaken for mass deletion. Use a stricter root-bounded operation when every entry must be accounted for.
69
100
 
70
101
  ## Root-bounded async iteration
@@ -123,7 +154,9 @@ If a thrown walk failure and directory close both fail, disposal throws a
123
154
  `SuppressedError` with the close failure in `error` and the original failure in
124
155
  `suppressed`, preserving both causes.
125
156
 
126
- `entryFilter` is evaluated for each resolved file, directory, or other entry:
157
+ `entryFilter` is evaluated for each resolved file, directory, or other entry.
158
+ The `RootWalkEntryFilter` callback returns a `RootWalkEntryFilterResult`
159
+ or a `Promise<RootWalkEntryFilterResult>`:
127
160
 
128
161
  ```ts
129
162
  for await (const entry of capability.walk("", {
@@ -147,10 +180,43 @@ The result values are `"include"`, `"skip"`, and `"skip-subtree"`. Plain
147
180
  `"skip-subtree"` omits that directory and prunes its descendants. Returning
148
181
  `"skip-subtree"` for a non-directory is equivalent to `"skip"`.
149
182
 
183
+ An asynchronous filter can inspect a marker before deciding whether to prune:
184
+
185
+ ```ts
186
+ for await (const entry of capability.walk("", {
187
+ symlinkPolicy: "skip",
188
+ entryFilter: async (entry) => {
189
+ if (
190
+ entry.kind === "directory" &&
191
+ await capability.exists(`${entry.relativePath}/SKILL.md`)
192
+ ) {
193
+ return "skip-subtree";
194
+ }
195
+ return "include";
196
+ },
197
+ })) {
198
+ consume(entry);
199
+ }
200
+ ```
201
+
202
+ Filters run serially outside metadata batches and retain the supplied options
203
+ object as their `this` receiver. When an awaited filter resolves, the walk
204
+ checks cancellation and revalidates the current listing directory and Root
205
+ identities before using the decision. These checks do not refresh the entry's
206
+ captured metadata or pin a later operation.
207
+
208
+ Cancellation and iterator disposal wait for a pending filter to settle. The
209
+ walk does not race the callback against the abort signal or close its directory
210
+ while the callback is running; callbacks must settle their own work for
211
+ cancellation to finish. Callback throws and promise rejections reject the walk
212
+ through its normal cleanup path, even with `onDirectoryError: "skip-and-report"`.
213
+
150
214
  `onDirectoryError` defaults to `"throw"`, preserving the original fail-fast
151
215
  contract. `"skip-and-report"` yields a discriminated
152
216
  `{ kind: "directory-error", relativePath, size: 0, error }` marker for a
153
217
  directory that cannot be resolved or listed, then continues with its siblings.
218
+ This policy also applies when the directory or Root identity recheck after an
219
+ awaited filter fails; callback failures themselves are not directory errors.
154
220
  Every examined directory entry consumes `maxEntries` before filtering, so
155
221
  `"skip"` cannot turn the iterator into an unbounded traversal. Reporting and
156
222
  `"truncated"` markers describe already-reached state and do not authorize
package/docs/writing.md CHANGED
@@ -6,11 +6,11 @@ half-written replacement appears at the destination. Create-only writes
6
6
  (`create`, `createJson`, and `write` with `overwrite: false`) use sibling-temp
7
7
  staging with an atomic no-replace rename only on backends that provide one —
8
8
  the native binding, which `require` mode guarantees and `auto` mode uses when
9
- the binding loads. The pure-JavaScript fallback has no atomic no-clobber
10
- rename and does not stage: it claims the final name exclusively with `O_EXCL`
11
- and writes content in place, so a concurrent observer can see the new file
12
- before its content is complete. Use `require` mode when that visibility window
13
- matters.
9
+ the binding loads. Ordinary buffered creation in the pure-JavaScript fallback
10
+ claims the final name exclusively with `O_EXCL` and writes content in place, so
11
+ a concurrent observer can see the new file before its content is complete.
12
+ Buffered `create` and `createJson` accept `atomic: true` to stage complete content
13
+ on this fallback too. Streamed creation already stages before publication.
14
14
  `append` and `openWritable` intentionally modify an opened file in place;
15
15
  `move`, `remove`, and `mkdir` mutate directory entries rather than file bytes.
16
16
  Each verb applies the boundary checks appropriate to its operation.
@@ -113,6 +113,14 @@ Native and pure-JavaScript Windows writers honor the option. Replacement writes
113
113
  sync staged content before rename and the final mode through the retained file
114
114
  handle. Directory sync remains best-effort.
115
115
 
116
+ For `create` and `createJson`, `durable: "file"` requires each file sync to succeed,
117
+ including on `EPERM`; it overrides a disabled Root durability default. Parent
118
+ directory synchronization retains the existing best-effort policy. This also
119
+ applies to streamed creation and is independent of publication strategy. Boolean
120
+ durability options keep their existing behavior, including compatibility paths
121
+ that tolerate `EPERM`. A failed file sync before staged publication prevents
122
+ publication; a failure after publication can leave the complete file present.
123
+
116
124
  POSIX modes without read permission, including `0o000` and `0o200`, succeed:
117
125
  final verification uses a descriptor retained by the writer rather than reopening
118
126
  the published file. The requested mode is not relaxed for verification.
@@ -154,6 +162,41 @@ try {
154
162
  }
155
163
  ```
156
164
 
165
+ ### Atomic buffered creation
166
+
167
+ ```ts
168
+ await fs.create("config/seed.json", initial, { atomic: true });
169
+ await fs.createJson("config/settings.json", { enabled: true }, { atomic: true });
170
+ await fs.create("config/flushed.json", initial, { atomic: true, durable: "file" });
171
+ ```
172
+
173
+ `atomic: true` keeps the destination absent until all bytes have been written.
174
+ The native backend uses its no-replace rename; the JavaScript fallback hardlinks
175
+ the completed stage and unlinks its temporary name in the same JavaScript turn.
176
+ The fallback requires hardlink support and fails without publishing partial bytes
177
+ when that mechanism is unavailable. Other processes can briefly observe both
178
+ names. Existing and raced entries are preserved, including dangling symlinks;
179
+ ordinary confinement, type, hardlink, and symlink-policy rejections still apply.
180
+ `assertBeforeMutation` retains its live checks through content writes and publication.
181
+
182
+ Omitted or `false` preserves the existing buffered behavior. The option belongs
183
+ to buffered `create` and `createJson`, not replacement writes or Root defaults.
184
+ Streamed creation has no atomic opt-out. `atomic` changes visibility, not the
185
+ existing `durable` file/directory synchronization policy; it does not turn
186
+ best-effort synchronization into a strict crash-durability guarantee or strengthen
187
+ JavaScript pathname containment.
188
+
189
+ Atomic and streamed creates settle owned cleanup and close operations before
190
+ returning. Failed or unverifiable cleanup is reported rather than silently
191
+ discarded. Errors after publication and incomplete-settlement errors carry the
192
+ existing `StagedFileFailureDetails` publication/cleanup receipts where the writer
193
+ can establish them; native disposal can retain them inside a `SuppressedError`
194
+ cause. Preserve those details when handling errors: a rejection can follow
195
+ complete publication, and an indeterminate link or native rename must preserve names for
196
+ recovery. A cleanup or close failure also retains the original operation failure.
197
+ No later verification, mode, or synchronization failure authorizes deleting an
198
+ already published complete destination. See [receipt meanings](staged-file.md).
199
+
157
200
  ### Streamed creation
158
201
 
159
202
  Pass an `AsyncIterable<Uint8Array>` to `create()` when bytes come from a database,
@@ -202,6 +245,12 @@ forcibly interrupted, so cancellation waits for its pending work and cleanup to
202
245
  settle. Do not mutate a yielded chunk until the next pull. Producer errors retain
203
246
  their original value when cleanup succeeds.
204
247
 
248
+ Streamed creation retains the `signal` and `assertBeforeMutation` callback
249
+ selected when the call starts. Replacing or deleting those options during a
250
+ producer wait does not change the in-flight operation. Abort the original signal
251
+ or update the live authority state checked by the original callback to revoke
252
+ it; the callback continues to receive the original options object as `this`.
253
+
205
254
  An aborted or failed operation can leave created parent directories. If a
206
255
  stage's identity or parent cannot be verified during cleanup, the existing
207
256
  guarded cleanup preserves it. After publication, later verification or cleanup
@@ -268,6 +317,10 @@ await fs.move("incoming/foo.txt", "archive/foo.txt", { overwrite: true });
268
317
 
269
318
  Both `from` and `to` are bounded; `..` in either is rejected.
270
319
 
320
+ Mutation policy is captured at call start; changes to caller-owned denial arrays
321
+ apply to later moves. For live cancellation or revocation, throw from
322
+ `assertBeforeMutation` immediately before dispatch.
323
+
271
324
  The default no-clobber mode requires the native helper. It admits both parent
272
325
  directory descriptors and performs a descriptor-relative no-replace rename, so
273
326
  a competitor that creates the target first is preserved and the source remains
@@ -407,6 +460,19 @@ an atomic check-and-delete syscall. Use OS isolation for that threat model.
407
460
  await fs.mkdir("snapshots/2026/05");
408
461
  ```
409
462
 
463
+ Pass `{ private: true }` to create missing components with private permissions.
464
+ An existing requested directory must already satisfy that policy; fs-safe does
465
+ not repair it or change existing ancestor permissions. Concurrent creators may
466
+ reuse the winner only after it passes the same checks.
467
+
468
+ Buffered, streamed, and JSON `create` calls also accept `private: true`.
469
+ New POSIX directories default to `0700` and files to `0600`; conflicting
470
+ group/world or privilege bits are rejected before creation. Restrictive
471
+ owner-only file modes remain available through `mode`. On Windows, creation
472
+ uses protected ACLs rather than interpreting POSIX mode bits as access rules.
473
+ This does not change `create`'s no-overwrite behavior or select its durability
474
+ policy. See [creation](creation.md) for supported backends and owned descriptors.
475
+
410
476
  ### `fs.ensureRoot()`
411
477
 
412
478
  Treats `""` / `"."` as the root itself. Useful when a generic helper computes a relative directory and might end up at the root.
@@ -501,7 +567,11 @@ for (const file of files) await fs.write(`${stagingDir}/${file.name}`, file.body
501
567
  await fs.move(stagingDir, "snapshots/2026-05-05", { overwrite: true });
502
568
  ```
503
569
 
504
- For a true commit-or-rollback over a *directory*, use [`replaceDirectoryAtomic`](atomic.md#replacedirectoryatomic).
570
+ For guarded whole-directory publication, use
571
+ [`replaceDirectoryAtomic`](atomic.md#replacedirectoryatomic). Replacing an
572
+ existing target is a two-rename protocol with a temporary target-absence
573
+ interval and conditional no-replace rollback, not a transactional
574
+ commit-or-rollback.
505
575
 
506
576
  ### Rotate logs
507
577
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -25,6 +25,8 @@
25
25
  },
26
26
  "files": [
27
27
  "dist/archive-parser.wasm",
28
+ "dist/windows-security-bridge.cs",
29
+ "dist/windows-security-bridge.ps1",
28
30
  "dist/**/*.js",
29
31
  "dist/**/*.d.ts",
30
32
  "dist/**/*.d.ts.map",
@@ -144,10 +146,10 @@
144
146
  "test:bun": "bun node_modules/vitest/vitest.mjs run --config scripts/bun-vitest.config.ts",
145
147
  "test:bun:native": "bun scripts/bun-native-proof.mjs && bun node_modules/vitest/vitest.mjs run --config scripts/bun-native-vitest.config.ts",
146
148
  "test:coverage": "vitest run --coverage",
147
- "test:coverage:collect": "pnpm build && vitest run --coverage --coverage.reporter=json --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.statements=0 --coverage.thresholds.branches=0",
149
+ "test:coverage:collect": "pnpm build && pnpm archive:wasm:allocator-tests && vitest run --coverage --coverage.reporter=json --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.statements=0 --coverage.thresholds.branches=0",
148
150
  "test:coverage:merge": "node scripts/merge-coverage.mjs",
149
151
  "test:security": "vitest run test/fs-safe.test.ts test/read-boundary-bypass.test.ts test/write-boundary-bypass.test.ts test/additional-boundary-bypass.test.ts test/adversarial-boundary-payloads.test.ts",
150
- "check": "pnpm lint:file-size && pnpm lint:fs-boundary && pnpm build && pnpm docs:check && pnpm test && node scripts/check-pack.mjs",
152
+ "check": "pnpm lint:file-size && pnpm lint:fs-boundary && pnpm build && pnpm archive:wasm:allocator-tests && pnpm docs:check && pnpm test && node scripts/check-pack.mjs",
151
153
  "docs:check": "node scripts/check-doc-examples.mjs",
152
154
  "docs:site": "node scripts/build-docs-site.mjs",
153
155
  "native:build": "pnpm --filter @openclaw/fs-safe-native-build build",
@@ -164,24 +166,25 @@
164
166
  "crabbox:stop": "crabbox stop",
165
167
  "crabbox:warmup": "crabbox warmup",
166
168
  "archive:wasm": "node scripts/build-archive-wasm.mjs",
169
+ "archive:wasm:allocator-tests": "node scripts/build-archive-wasm.mjs --allocator-tests",
167
170
  "archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
168
171
  },
169
172
  "optionalDependencies": {
170
- "@openclaw/fs-safe-darwin-arm64": "0.14.0",
171
- "@openclaw/fs-safe-darwin-x64": "0.14.0",
172
- "@openclaw/fs-safe-linux-arm64-gnu": "0.14.0",
173
- "@openclaw/fs-safe-linux-arm64-musl": "0.14.0",
174
- "@openclaw/fs-safe-linux-x64-gnu": "0.14.0",
175
- "@openclaw/fs-safe-linux-x64-musl": "0.14.0",
176
- "@openclaw/fs-safe-win32-x64-msvc": "0.14.0",
173
+ "@openclaw/fs-safe-darwin-arm64": "0.16.0",
174
+ "@openclaw/fs-safe-darwin-x64": "0.16.0",
175
+ "@openclaw/fs-safe-linux-arm64-gnu": "0.16.0",
176
+ "@openclaw/fs-safe-linux-arm64-musl": "0.16.0",
177
+ "@openclaw/fs-safe-linux-x64-gnu": "0.16.0",
178
+ "@openclaw/fs-safe-linux-x64-musl": "0.16.0",
179
+ "@openclaw/fs-safe-win32-x64-msvc": "0.16.0",
177
180
  "jszip": "^3.10.2"
178
181
  },
179
182
  "devDependencies": {
180
183
  "@emnapi/runtime": "2.0.0-alpha.5",
181
- "@napi-rs/cli": "3.9.1",
182
- "@types/node": "^26.5.1",
183
- "@vitest/coverage-v8": "5.0.0",
184
- "fast-check": "^4.9.0",
184
+ "@napi-rs/cli": "3.10.3",
185
+ "@types/node": "^26.6.1",
186
+ "@vitest/coverage-v8": "5.0.1",
187
+ "fast-check": "^4.10.1",
185
188
  "istanbul-lib-coverage": "3.2.2",
186
189
  "istanbul-lib-report": "3.0.1",
187
190
  "istanbul-reports": "3.2.0",
@@ -189,10 +192,10 @@
189
192
  "tar": "7.5.22",
190
193
  "typescript": "^7.0.2",
191
194
  "vite": "8.3.0",
192
- "vitest": "^5.0.0"
195
+ "vitest": "^5.0.1"
193
196
  },
194
197
  "engines": {
195
198
  "node": ">=22"
196
199
  },
197
- "packageManager": "pnpm@11.25.0"
200
+ "packageManager": "pnpm@12.4.2"
198
201
  }
@@ -1,4 +0,0 @@
1
- import type { NativeDarwinAclFacts } from "./native-binding.js";
2
- /** Internal descriptor facts only; callers own identity, lifetime, and ACL policy. */
3
- export declare function inspectDarwinAcl(fd: number): NativeDarwinAclFacts;
4
- //# sourceMappingURL=darwin-acl.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"darwin-acl.d.ts","sourceRoot":"","sources":["../src/darwin-acl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,CAmBjE"}
@@ -1,24 +0,0 @@
1
- import { FsSafeError } from "./errors.js";
2
- import { getNativeBinding } from "./native.js";
3
- /** Internal descriptor facts only; callers own identity, lifetime, and ACL policy. */
4
- export function inspectDarwinAcl(fd) {
5
- if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fff_ffff) {
6
- throw new FsSafeError("permission-unverified", "Darwin ACL inspection requires a valid descriptor");
7
- }
8
- const native = getNativeBinding();
9
- if (typeof native?.inspectDarwinAcl !== "function") {
10
- throw new FsSafeError("helper-unavailable", "Darwin ACL inspection requires the matching native capability");
11
- }
12
- let facts;
13
- try {
14
- facts = native.inspectDarwinAcl(fd);
15
- }
16
- catch (cause) {
17
- throw new FsSafeError("permission-unverified", "Darwin descriptor ACL could not be inspected", { cause });
18
- }
19
- const state = facts && typeof facts === "object" && "state" in facts ? facts.state : undefined;
20
- if (state !== "absent" && state !== "empty" && state !== "present") {
21
- throw new FsSafeError("permission-unverified", "Darwin ACL inspection returned invalid facts");
22
- }
23
- return { state };
24
- }