@openclaw/fs-safe 0.11.0 → 0.13.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 (416) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +15 -8
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +32 -11
  5. package/dist/advanced.d.ts +3 -1
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -1
  8. package/dist/archive-entry.d.ts.map +1 -1
  9. package/dist/archive-entry.js +4 -3
  10. package/dist/archive-gzip-tail.d.ts.map +1 -1
  11. package/dist/archive-gzip-tail.js +13 -9
  12. package/dist/archive-input.d.ts.map +1 -1
  13. package/dist/archive-input.js +6 -2
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +15 -2
  16. package/dist/archive-native.d.ts.map +1 -1
  17. package/dist/archive-native.js +7 -19
  18. package/dist/archive-plan.js +1 -1
  19. package/dist/archive-read.d.ts.map +1 -1
  20. package/dist/archive-read.js +55 -46
  21. package/dist/archive-staging.d.ts.map +1 -1
  22. package/dist/archive-staging.js +43 -10
  23. package/dist/archive-tar-inspect.d.ts.map +1 -1
  24. package/dist/archive-tar-inspect.js +4 -1
  25. package/dist/archive-zip-admission.d.ts +1 -1
  26. package/dist/archive-zip-admission.d.ts.map +1 -1
  27. package/dist/archive-zip-admission.js +2 -2
  28. package/dist/archive-zip-directory.d.ts +3 -0
  29. package/dist/archive-zip-directory.d.ts.map +1 -1
  30. package/dist/archive-zip-directory.js +13 -2
  31. package/dist/archive-zip-loader.d.ts +3 -2
  32. package/dist/archive-zip-loader.d.ts.map +1 -1
  33. package/dist/archive-zip-loader.js +30 -4
  34. package/dist/archive-zip-manifest.d.ts +5 -0
  35. package/dist/archive-zip-manifest.d.ts.map +1 -0
  36. package/dist/archive-zip-manifest.js +22 -0
  37. package/dist/archive-zip-names.d.ts +6 -1
  38. package/dist/archive-zip-names.d.ts.map +1 -1
  39. package/dist/archive-zip-names.js +50 -17
  40. package/dist/archive-zip-preflight.d.ts.map +1 -1
  41. package/dist/archive-zip-preflight.js +3 -2
  42. package/dist/archive.d.ts.map +1 -1
  43. package/dist/archive.js +9 -4
  44. package/dist/bounded-read.js +2 -2
  45. package/dist/darwin-acl.d.ts +4 -0
  46. package/dist/darwin-acl.d.ts.map +1 -0
  47. package/dist/darwin-acl.js +24 -0
  48. package/dist/deny-mutations.d.ts.map +1 -1
  49. package/dist/deny-mutations.js +8 -2
  50. package/dist/device-path.d.ts.map +1 -1
  51. package/dist/device-path.js +5 -3
  52. package/dist/directory-durability.d.ts.map +1 -1
  53. package/dist/directory-durability.js +67 -23
  54. package/dist/directory-entry-path.d.ts +3 -0
  55. package/dist/directory-entry-path.d.ts.map +1 -0
  56. package/dist/directory-entry-path.js +21 -0
  57. package/dist/directory-guard.d.ts +17 -1
  58. package/dist/directory-guard.d.ts.map +1 -1
  59. package/dist/directory-guard.js +134 -48
  60. package/dist/directory-mode-node.d.ts +12 -0
  61. package/dist/directory-mode-node.d.ts.map +1 -1
  62. package/dist/directory-mode-node.js +102 -4
  63. package/dist/effective-uid.d.ts +2 -0
  64. package/dist/effective-uid.d.ts.map +1 -0
  65. package/dist/effective-uid.js +25 -0
  66. package/dist/file-handle-transfer.d.ts.map +1 -1
  67. package/dist/file-handle-transfer.js +98 -27
  68. package/dist/file-hash.d.ts.map +1 -1
  69. package/dist/file-hash.js +12 -2
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +36 -6
  72. package/dist/file-lock.d.ts.map +1 -1
  73. package/dist/file-lock.js +29 -9
  74. package/dist/file-observation.d.ts +1 -1
  75. package/dist/file-observation.d.ts.map +1 -1
  76. package/dist/file-store-boundary.d.ts +6 -2
  77. package/dist/file-store-boundary.d.ts.map +1 -1
  78. package/dist/file-store-boundary.js +20 -65
  79. package/dist/file-store-copy-source.d.ts +5 -0
  80. package/dist/file-store-copy-source.d.ts.map +1 -0
  81. package/dist/file-store-copy-source.js +31 -0
  82. package/dist/file-store-path.d.ts.map +1 -1
  83. package/dist/file-store-path.js +4 -1
  84. package/dist/file-store-prune.d.ts.map +1 -1
  85. package/dist/file-store-prune.js +9 -1
  86. package/dist/file-store-sync-directory.d.ts +16 -0
  87. package/dist/file-store-sync-directory.d.ts.map +1 -0
  88. package/dist/file-store-sync-directory.js +349 -0
  89. package/dist/file-store.d.ts.map +1 -1
  90. package/dist/file-store.js +11 -28
  91. package/dist/filename.d.ts +1 -0
  92. package/dist/filename.d.ts.map +1 -1
  93. package/dist/filename.js +85 -23
  94. package/dist/fs.d.ts.map +1 -1
  95. package/dist/fs.js +3 -2
  96. package/dist/guarded-mkdir.d.ts +10 -0
  97. package/dist/guarded-mkdir.d.ts.map +1 -1
  98. package/dist/guarded-mkdir.js +217 -33
  99. package/dist/guest.d.ts.map +1 -1
  100. package/dist/guest.js +16 -5
  101. package/dist/home-dir.d.ts.map +1 -1
  102. package/dist/home-dir.js +73 -10
  103. package/dist/install-path.d.ts +6 -0
  104. package/dist/install-path.d.ts.map +1 -1
  105. package/dist/install-path.js +67 -19
  106. package/dist/json-durable-queue-ownership.d.ts +8 -0
  107. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  108. package/dist/json-durable-queue-ownership.js +134 -43
  109. package/dist/json-durable-queue-paths.d.ts +11 -0
  110. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  111. package/dist/json-durable-queue-paths.js +42 -0
  112. package/dist/json-durable-queue-read.d.ts +6 -0
  113. package/dist/json-durable-queue-read.d.ts.map +1 -0
  114. package/dist/json-durable-queue-read.js +61 -0
  115. package/dist/json-durable-queue.d.ts +1 -1
  116. package/dist/json-durable-queue.d.ts.map +1 -1
  117. package/dist/json-durable-queue.js +83 -134
  118. package/dist/json-store.d.ts.map +1 -1
  119. package/dist/json-store.js +5 -1
  120. package/dist/json.d.ts.map +1 -1
  121. package/dist/json.js +54 -22
  122. package/dist/local-file-access.d.ts.map +1 -1
  123. package/dist/local-file-access.js +4 -0
  124. package/dist/local-file-descriptor.d.ts +17 -0
  125. package/dist/local-file-descriptor.d.ts.map +1 -0
  126. package/dist/local-file-descriptor.js +84 -0
  127. package/dist/local-roots.d.ts.map +1 -1
  128. package/dist/local-roots.js +35 -7
  129. package/dist/move-path-cleanup.d.ts +2 -0
  130. package/dist/move-path-cleanup.d.ts.map +1 -1
  131. package/dist/move-path-cleanup.js +44 -18
  132. package/dist/move-path.d.ts.map +1 -1
  133. package/dist/move-path.js +31 -6
  134. package/dist/native-binding.d.ts +21 -0
  135. package/dist/native-binding.d.ts.map +1 -1
  136. package/dist/native-directory-observation.d.ts +17 -0
  137. package/dist/native-directory-observation.d.ts.map +1 -0
  138. package/dist/native-directory-observation.js +37 -0
  139. package/dist/native-parent-admission.d.ts +31 -0
  140. package/dist/native-parent-admission.d.ts.map +1 -0
  141. package/dist/native-parent-admission.js +124 -0
  142. package/dist/native-pinned-write-windows.d.ts +0 -1
  143. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  144. package/dist/native-pinned-write-windows.js +0 -3
  145. package/dist/native-pinned-write.d.ts.map +1 -1
  146. package/dist/native-pinned-write.js +309 -40
  147. package/dist/output.d.ts.map +1 -1
  148. package/dist/output.js +15 -5
  149. package/dist/overwrite-file-handle.d.ts.map +1 -1
  150. package/dist/overwrite-file-handle.js +5 -1
  151. package/dist/owner-dacl.d.ts.map +1 -1
  152. package/dist/owner-dacl.js +2 -0
  153. package/dist/path-policy.d.ts.map +1 -1
  154. package/dist/path-policy.js +7 -2
  155. package/dist/path-prefix.d.ts +7 -0
  156. package/dist/path-prefix.d.ts.map +1 -0
  157. package/dist/path-prefix.js +82 -0
  158. package/dist/path-scope-lexical.d.ts +14 -0
  159. package/dist/path-scope-lexical.d.ts.map +1 -0
  160. package/dist/path-scope-lexical.js +37 -0
  161. package/dist/path-segment-route.d.ts +7 -0
  162. package/dist/path-segment-route.d.ts.map +1 -0
  163. package/dist/path-segment-route.js +24 -0
  164. package/dist/path-suffix-aliases.d.ts +10 -0
  165. package/dist/path-suffix-aliases.d.ts.map +1 -0
  166. package/dist/path-suffix-aliases.js +386 -0
  167. package/dist/path.d.ts.map +1 -1
  168. package/dist/path.js +35 -6
  169. package/dist/permissions-windows.d.ts.map +1 -1
  170. package/dist/permissions-windows.js +14 -3
  171. package/dist/permissions.d.ts.map +1 -1
  172. package/dist/permissions.js +37 -8
  173. package/dist/pinned-mutation-admission.d.ts +25 -0
  174. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  175. package/dist/pinned-mutation-admission.js +425 -0
  176. package/dist/pinned-mutation-observation.d.ts +34 -0
  177. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  178. package/dist/pinned-mutation-observation.js +142 -0
  179. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  180. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  181. package/dist/pinned-mutation-shared-route.js +70 -0
  182. package/dist/pinned-open.d.ts +6 -0
  183. package/dist/pinned-open.d.ts.map +1 -1
  184. package/dist/pinned-open.js +28 -10
  185. package/dist/pinned-write-types.d.ts +75 -0
  186. package/dist/pinned-write-types.d.ts.map +1 -0
  187. package/dist/pinned-write-types.js +1 -0
  188. package/dist/pinned-write.d.ts +7 -33
  189. package/dist/pinned-write.d.ts.map +1 -1
  190. package/dist/pinned-write.js +152 -17
  191. package/dist/private-directory.d.ts.map +1 -1
  192. package/dist/private-directory.js +2 -0
  193. package/dist/private-producer-handoff.d.ts +16 -0
  194. package/dist/private-producer-handoff.d.ts.map +1 -0
  195. package/dist/private-producer-handoff.js +272 -0
  196. package/dist/private-temp-workspace.d.ts +2 -39
  197. package/dist/private-temp-workspace.d.ts.map +1 -1
  198. package/dist/private-temp-workspace.js +183 -77
  199. package/dist/publish-file.d.ts.map +1 -1
  200. package/dist/publish-file.js +33 -29
  201. package/dist/regular-file.d.ts.map +1 -1
  202. package/dist/regular-file.js +56 -45
  203. package/dist/replace-directory.d.ts.map +1 -1
  204. package/dist/replace-directory.js +12 -5
  205. package/dist/replace-file-temp-owner.d.ts +1 -0
  206. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  207. package/dist/replace-file-temp-owner.js +12 -1
  208. package/dist/replace-file.d.ts.map +1 -1
  209. package/dist/replace-file.js +24 -24
  210. package/dist/root-boundary.d.ts +29 -0
  211. package/dist/root-boundary.d.ts.map +1 -0
  212. package/dist/root-boundary.js +182 -0
  213. package/dist/root-context.d.ts +12 -1
  214. package/dist/root-context.d.ts.map +1 -1
  215. package/dist/root-context.js +89 -16
  216. package/dist/root-directory-creation.d.ts +13 -0
  217. package/dist/root-directory-creation.d.ts.map +1 -0
  218. package/dist/root-directory-creation.js +212 -0
  219. package/dist/root-directory-list.d.ts +16 -4
  220. package/dist/root-directory-list.d.ts.map +1 -1
  221. package/dist/root-directory-list.js +182 -33
  222. package/dist/root-directory.d.ts +23 -0
  223. package/dist/root-directory.d.ts.map +1 -0
  224. package/dist/root-directory.js +141 -0
  225. package/dist/root-errors.d.ts.map +1 -1
  226. package/dist/root-errors.js +12 -1
  227. package/dist/root-file-final-admission.d.ts +21 -0
  228. package/dist/root-file-final-admission.d.ts.map +1 -0
  229. package/dist/root-file-final-admission.js +83 -0
  230. package/dist/root-file.d.ts.map +1 -1
  231. package/dist/root-file.js +103 -24
  232. package/dist/root-impl.d.ts +1 -0
  233. package/dist/root-impl.d.ts.map +1 -1
  234. package/dist/root-impl.js +316 -325
  235. package/dist/root-move-noreplace.d.ts +14 -0
  236. package/dist/root-move-noreplace.d.ts.map +1 -0
  237. package/dist/root-move-noreplace.js +202 -0
  238. package/dist/root-move-preflight.d.ts +8 -0
  239. package/dist/root-move-preflight.d.ts.map +1 -0
  240. package/dist/root-move-preflight.js +16 -0
  241. package/dist/root-observed-path.d.ts +9 -0
  242. package/dist/root-observed-path.d.ts.map +1 -0
  243. package/dist/root-observed-path.js +95 -0
  244. package/dist/root-path-errors.d.ts +12 -0
  245. package/dist/root-path-errors.d.ts.map +1 -0
  246. package/dist/root-path-errors.js +13 -0
  247. package/dist/root-path-existing.d.ts +2 -0
  248. package/dist/root-path-existing.d.ts.map +1 -1
  249. package/dist/root-path-existing.js +63 -22
  250. package/dist/root-path-observation.d.ts +63 -0
  251. package/dist/root-path-observation.d.ts.map +1 -0
  252. package/dist/root-path-observation.js +180 -0
  253. package/dist/root-path-stat.d.ts +5 -0
  254. package/dist/root-path-stat.d.ts.map +1 -0
  255. package/dist/root-path-stat.js +101 -0
  256. package/dist/root-path-symlink.d.ts.map +1 -1
  257. package/dist/root-path-symlink.js +23 -4
  258. package/dist/root-path.d.ts +13 -0
  259. package/dist/root-path.d.ts.map +1 -1
  260. package/dist/root-path.js +263 -73
  261. package/dist/root-paths-lexical.d.ts +9 -0
  262. package/dist/root-paths-lexical.d.ts.map +1 -0
  263. package/dist/root-paths-lexical.js +22 -0
  264. package/dist/root-paths.d.ts +3 -29
  265. package/dist/root-paths.d.ts.map +1 -1
  266. package/dist/root-paths.js +89 -178
  267. package/dist/root-read-admission.d.ts +26 -0
  268. package/dist/root-read-admission.d.ts.map +1 -0
  269. package/dist/root-read-admission.js +94 -0
  270. package/dist/root-remove-identity.d.ts +15 -0
  271. package/dist/root-remove-identity.d.ts.map +1 -0
  272. package/dist/root-remove-identity.js +89 -0
  273. package/dist/root-remove-receipt.d.ts +17 -0
  274. package/dist/root-remove-receipt.d.ts.map +1 -0
  275. package/dist/root-remove-receipt.js +37 -0
  276. package/dist/root-remove.d.ts +2 -1
  277. package/dist/root-remove.d.ts.map +1 -1
  278. package/dist/root-remove.js +172 -10
  279. package/dist/root-walk.d.ts.map +1 -1
  280. package/dist/root-walk.js +2 -1
  281. package/dist/root-write-admission.d.ts +68 -0
  282. package/dist/root-write-admission.d.ts.map +1 -0
  283. package/dist/root-write-admission.js +322 -0
  284. package/dist/root-write-compatibility.d.ts +13 -0
  285. package/dist/root-write-compatibility.d.ts.map +1 -0
  286. package/dist/root-write-compatibility.js +74 -0
  287. package/dist/root-write-complete-parent.d.ts +42 -0
  288. package/dist/root-write-complete-parent.d.ts.map +1 -0
  289. package/dist/root-write-complete-parent.js +195 -0
  290. package/dist/root-write-mode.d.ts +2 -0
  291. package/dist/root-write-mode.d.ts.map +1 -1
  292. package/dist/root-write-mode.js +20 -7
  293. package/dist/root-write-publication.d.ts +24 -0
  294. package/dist/root-write-publication.d.ts.map +1 -0
  295. package/dist/root-write-publication.js +85 -0
  296. package/dist/root-write-verification.d.ts.map +1 -1
  297. package/dist/root-write-verification.js +18 -7
  298. package/dist/safe-path-segment.d.ts.map +1 -1
  299. package/dist/safe-path-segment.js +3 -1
  300. package/dist/secret-file.d.ts.map +1 -1
  301. package/dist/secret-file.js +62 -24
  302. package/dist/secret-read-async.d.ts.map +1 -1
  303. package/dist/secret-read-async.js +18 -13
  304. package/dist/secret-read-policy.d.ts.map +1 -1
  305. package/dist/secret-read-policy.js +5 -1
  306. package/dist/secure-file-windows.d.ts +10 -0
  307. package/dist/secure-file-windows.d.ts.map +1 -0
  308. package/dist/secure-file-windows.js +186 -0
  309. package/dist/secure-file.d.ts.map +1 -1
  310. package/dist/secure-file.js +98 -10
  311. package/dist/secure-temp-dir.d.ts +6 -3
  312. package/dist/secure-temp-dir.d.ts.map +1 -1
  313. package/dist/secure-temp-dir.js +121 -101
  314. package/dist/secure-temp-repair.d.ts +35 -0
  315. package/dist/secure-temp-repair.d.ts.map +1 -0
  316. package/dist/secure-temp-repair.js +106 -0
  317. package/dist/sibling-staged-file.d.ts +3 -1
  318. package/dist/sibling-staged-file.d.ts.map +1 -1
  319. package/dist/sibling-staged-file.js +181 -50
  320. package/dist/sibling-temp.d.ts.map +1 -1
  321. package/dist/sibling-temp.js +27 -12
  322. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  323. package/dist/sidecar-lock-acquire.js +45 -15
  324. package/dist/sidecar-lock-root.d.ts.map +1 -1
  325. package/dist/sidecar-lock-root.js +4 -2
  326. package/dist/sidecar-lock.d.ts.map +1 -1
  327. package/dist/sidecar-lock.js +2 -3
  328. package/dist/staged-directory.d.ts +14 -5
  329. package/dist/staged-directory.d.ts.map +1 -1
  330. package/dist/staged-directory.js +54 -3
  331. package/dist/standalone-publication-path.d.ts +2 -0
  332. package/dist/standalone-publication-path.d.ts.map +1 -0
  333. package/dist/standalone-publication-path.js +6 -0
  334. package/dist/stat-observation.d.ts +11 -0
  335. package/dist/stat-observation.d.ts.map +1 -0
  336. package/dist/stat-observation.js +64 -0
  337. package/dist/strict-file-identity.d.ts.map +1 -1
  338. package/dist/strict-file-identity.js +39 -2
  339. package/dist/symlink-parents.d.ts.map +1 -1
  340. package/dist/symlink-parents.js +7 -2
  341. package/dist/temp-target.d.ts +2 -0
  342. package/dist/temp-target.d.ts.map +1 -1
  343. package/dist/temp-target.js +133 -4
  344. package/dist/temp-workspace-admission.d.ts +22 -0
  345. package/dist/temp-workspace-admission.d.ts.map +1 -0
  346. package/dist/temp-workspace-admission.js +374 -0
  347. package/dist/temp-workspace-child-admission.d.ts +13 -0
  348. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  349. package/dist/temp-workspace-child-admission.js +95 -0
  350. package/dist/temp-workspace-descriptor.d.ts +48 -0
  351. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  352. package/dist/temp-workspace-descriptor.js +363 -0
  353. package/dist/temp-workspace-identity.d.ts +15 -0
  354. package/dist/temp-workspace-identity.d.ts.map +1 -0
  355. package/dist/temp-workspace-identity.js +41 -0
  356. package/dist/temp-workspace-owner.d.ts +7 -6
  357. package/dist/temp-workspace-owner.d.ts.map +1 -1
  358. package/dist/temp-workspace-owner.js +121 -61
  359. package/dist/temp-workspace-permissions.d.ts +4 -0
  360. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  361. package/dist/temp-workspace-permissions.js +32 -0
  362. package/dist/temp-workspace-types.d.ts +40 -0
  363. package/dist/temp-workspace-types.d.ts.map +1 -0
  364. package/dist/temp-workspace-types.js +1 -0
  365. package/dist/temp.d.ts +1 -1
  366. package/dist/temp.d.ts.map +1 -1
  367. package/dist/temp.js +1 -1
  368. package/dist/test-hooks.d.ts +7 -0
  369. package/dist/test-hooks.d.ts.map +1 -1
  370. package/dist/text-atomic.d.ts.map +1 -1
  371. package/dist/text-atomic.js +3 -1
  372. package/dist/trash.d.ts.map +1 -1
  373. package/dist/trash.js +54 -8
  374. package/dist/walk.d.ts.map +1 -1
  375. package/dist/walk.js +20 -16
  376. package/dist/windows-owner.d.ts.map +1 -1
  377. package/dist/windows-owner.js +5 -0
  378. package/dist/windows-path-alias.d.ts +39 -0
  379. package/dist/windows-path-alias.d.ts.map +1 -0
  380. package/dist/windows-path-alias.js +153 -0
  381. package/docs/advanced.md +28 -1
  382. package/docs/archive.md +29 -2
  383. package/docs/atomic.md +27 -3
  384. package/docs/contributing.md +4 -1
  385. package/docs/copy.md +4 -1
  386. package/docs/durability.md +18 -2
  387. package/docs/errors.md +14 -6
  388. package/docs/file-store.md +30 -3
  389. package/docs/filename.md +21 -7
  390. package/docs/guest.md +5 -0
  391. package/docs/install-path.md +61 -15
  392. package/docs/install.md +12 -8
  393. package/docs/json-store.md +5 -1
  394. package/docs/json.md +11 -0
  395. package/docs/mutation-policy-proof.md +65 -0
  396. package/docs/native-helper.md +28 -9
  397. package/docs/native.md +46 -10
  398. package/docs/output.md +33 -11
  399. package/docs/path-prefix.md +64 -0
  400. package/docs/path-suffix-aliases.md +159 -0
  401. package/docs/path.md +12 -1
  402. package/docs/permissions.md +26 -1
  403. package/docs/private-file-store.md +14 -0
  404. package/docs/public-api.md +14 -0
  405. package/docs/quickstart.md +1 -1
  406. package/docs/reading.md +8 -1
  407. package/docs/root.md +21 -4
  408. package/docs/secret-file.md +3 -0
  409. package/docs/secure-file.md +21 -16
  410. package/docs/security-model.md +76 -9
  411. package/docs/sidecar-lock.md +28 -1
  412. package/docs/store.md +27 -2
  413. package/docs/temp.md +262 -34
  414. package/docs/test-hooks.md +14 -0
  415. package/docs/writing.md +38 -8
  416. package/package.json +10 -10
@@ -4,13 +4,43 @@ import fs, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, createAsyncDirectoryGuard, } from "./directory-guard.js";
6
6
  import { syncDirectoryBestEffort } from "./directory-durability.js";
7
+ import { isWindowsReservedDeviceName } from "./device-path.js";
7
8
  import { FsSafeError } from "./errors.js";
9
+ import { getNativeBinding } from "./native.js";
10
+ import { handoffPrivateProducerFile, } from "./private-producer-handoff.js";
8
11
  import { resolveReadOpenFlags } from "./read-open-flags.js";
9
- import { root } from "./root.js";
12
+ import { rootFromDirectoryGuard } from "./root-impl.js";
10
13
  import { inspectFileIdentity } from "./strict-file-identity.js";
11
14
  import { registerTempPathForExit } from "./temp-cleanup.js";
12
15
  import { createOwnedTempFile } from "./temp-target.js";
13
16
  import { serializePathWrite } from "./write-queue.js";
17
+ import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
18
+ const INVALID_CALLBACK_COMPONENT_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/u;
19
+ function resolveCallbackTempDirectory(workspaceDir) {
20
+ assertNoWindowsPathAlias(workspaceDir, "filesystem", "sibling temp parent uses a Windows filesystem namespace alias");
21
+ const dir = resolvePathPreservingWindowsRoot(workspaceDir);
22
+ assertNoWindowsPathAlias(dir, "filesystem", "sibling temp parent uses a Windows filesystem namespace alias");
23
+ return dir;
24
+ }
25
+ export function resolveCallbackTempPath(workspaceDir, component) {
26
+ const dir = resolveCallbackTempDirectory(workspaceDir);
27
+ if (typeof component !== "string" ||
28
+ component === "" ||
29
+ component === "." ||
30
+ component === ".." ||
31
+ INVALID_CALLBACK_COMPONENT_CHARACTERS.test(component) ||
32
+ component.endsWith(".") ||
33
+ component.endsWith(" ") ||
34
+ isWindowsReservedDeviceName(component)) {
35
+ throw new FsSafeError("invalid-path", "callback temp name must be one path component");
36
+ }
37
+ const joined = path.join(dir, component);
38
+ assertNoWindowsPathAlias(joined, "filesystem", "sibling temp path uses a Windows filesystem namespace alias");
39
+ if (path.dirname(joined) !== dir) {
40
+ throw new FsSafeError("invalid-path", "callback temp path must be a direct workspace child");
41
+ }
42
+ return joined;
43
+ }
14
44
  function assertRegularFile(stat) {
15
45
  if (stat.isSymbolicLink()) {
16
46
  throw new FsSafeError("symlink", "symlink sibling temp not allowed");
@@ -29,42 +59,102 @@ async function inspectStage(inspect, expected) {
29
59
  return stat;
30
60
  }, expected);
31
61
  }
62
+ function aggregateErrors(errors, message) {
63
+ if (errors.length === 1)
64
+ return errors[0];
65
+ return new AggregateError(errors, message);
66
+ }
32
67
  // Own the workspace before the producer can leave partial output. The finished
33
68
  // file still enters the ordinary sibling admission and publication lifecycle.
34
69
  async function writeIsolatedProducer(params) {
35
- const targetRoot = await root(path.dirname(params.tempPath));
70
+ const tempPath = params.tempPath;
71
+ const write = params.write;
72
+ const writeReceiver = params.writeReceiver;
73
+ const parentGuard = params.parentGuard;
74
+ const assertParent = params.assertParent;
75
+ const syncTempFile = params.syncTempFile;
76
+ assertParent();
77
+ const targetRoot = rootFromDirectoryGuard(parentGuard);
78
+ // Select the handoff before invoking the producer. Missing required native
79
+ // support must not leave producer output behind as a discovery side effect.
80
+ const native = getNativeBinding();
81
+ const cleanupErrors = [];
36
82
  const { target, identity } = await createOwnedTempFile({
37
83
  rootDir: targetRoot.rootReal,
38
84
  prefix: "fs-safe-output",
39
- fileName: path.basename(params.tempPath),
85
+ fileName: path.basename(tempPath),
86
+ onCleanupError: (error) => cleanupErrors.push(error),
40
87
  });
41
- const assertCurrent = () => {
42
- params.assertParent();
88
+ let cleanupStarted = false;
89
+ const cleanupWorkspace = async () => {
90
+ if (cleanupStarted)
91
+ return;
92
+ cleanupStarted = true;
93
+ try {
94
+ await target.cleanup();
95
+ }
96
+ catch (error) {
97
+ cleanupErrors.push(error);
98
+ }
99
+ if (cleanupErrors.length > 0) {
100
+ throw aggregateErrors(cleanupErrors, "isolated producer workspace cleanup failed");
101
+ }
102
+ };
103
+ const assertWorkspace = () => {
43
104
  assertDirectoryIdentitySync(target.dir, { ...identity, realPath: target.dir });
44
105
  };
106
+ const assertCurrent = () => {
107
+ assertParent();
108
+ assertWorkspace();
109
+ };
45
110
  try {
111
+ const producerPath = resolveCallbackTempPath(target.dir, path.basename(target.path));
46
112
  assertCurrent();
47
- const result = await params.write(target.path);
113
+ const result = await Reflect.apply(write, writeReceiver, [producerPath]);
48
114
  assertCurrent();
49
- await targetRoot.move(path.relative(targetRoot.rootReal, target.path), path.basename(params.tempPath), {
50
- assertBeforeMutation: assertCurrent,
115
+ if (native) {
116
+ await targetRoot.move(path.relative(targetRoot.rootReal, producerPath), path.basename(tempPath), { assertBeforeMutation: assertCurrent });
117
+ return { cleanupWorkspace, result };
118
+ }
119
+ const handoff = await handoffPrivateProducerFile({
120
+ sourcePath: producerPath,
121
+ targetPath: tempPath,
122
+ assertSourceParent: assertWorkspace,
123
+ assertTargetParent: assertParent,
124
+ readWrite: syncTempFile,
51
125
  });
52
- return result;
126
+ return { cleanupWorkspace, handoff, result };
53
127
  }
54
- finally {
55
- await target.cleanup();
128
+ catch (error) {
129
+ try {
130
+ await cleanupWorkspace();
131
+ }
132
+ catch (cleanupError) {
133
+ throw new AggregateError([error, cleanupError], "isolated producer operation and workspace cleanup failed");
134
+ }
135
+ throw error;
56
136
  }
57
137
  }
58
138
  // Callback paths are not owned until all three admission observations agree.
59
139
  // Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
60
140
  // Read/write access is needed only when the caller requests file synchronization.
61
141
  export async function writeCallbackSibling(params) {
62
- const parent = path.dirname(params.tempPath);
142
+ const parent = resolveCallbackTempDirectory(params.tempDir);
143
+ const tempPath = resolveCallbackTempPath(parent, params.tempName);
144
+ const write = params.write;
145
+ const producerIsolation = params.producerIsolation;
146
+ const resolveFinalPath = params.resolveFinalPath;
147
+ const mode = params.mode;
148
+ const ignoreModeError = params.ignoreModeError;
149
+ const maxBytes = params.maxBytes;
150
+ const syncTempFile = params.syncTempFile;
151
+ const syncParentDir = params.syncParentDir;
63
152
  const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
64
153
  const assertParent = () => assertAsyncDirectoryGuard(guard);
65
154
  let handle;
66
155
  let identity;
67
156
  let unregister;
157
+ let cleanupWorkspace;
68
158
  let renamed = false;
69
159
  let failure;
70
160
  const inspectPath = (pathname, expected) => inspectStage(() => fsSync.lstatSync(pathname, { bigint: true }), expected);
@@ -72,70 +162,98 @@ export async function writeCallbackSibling(params) {
72
162
  await assertParent();
73
163
  const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
74
164
  const current = await inspectPath(pathname, opened);
75
- if (params.maxBytes !== undefined &&
76
- (opened.size > params.maxBytes || current.size > params.maxBytes)) {
77
- throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${params.maxBytes})`);
165
+ if (maxBytes !== undefined &&
166
+ (opened.size > maxBytes || current.size > maxBytes)) {
167
+ throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${maxBytes})`);
78
168
  }
79
169
  };
80
170
  try {
81
- const result = params.producerIsolation === "private-directory"
82
- ? await writeIsolatedProducer({
83
- tempPath: params.tempPath,
84
- write: params.write,
171
+ let result;
172
+ if (producerIsolation === "private-directory") {
173
+ const isolated = await writeIsolatedProducer({
174
+ tempPath: tempPath,
175
+ write,
176
+ writeReceiver: params,
177
+ parentGuard: guard,
178
+ syncTempFile: syncTempFile,
85
179
  assertParent: () => assertDirectoryIdentitySync(parent, {
86
180
  dev: guard.stat.dev,
87
181
  ino: guard.stat.ino,
88
182
  realPath: guard.realPath,
89
183
  }),
90
- })
91
- : await params.write(params.tempPath);
184
+ });
185
+ result = isolated.result;
186
+ cleanupWorkspace = isolated.cleanupWorkspace;
187
+ if (isolated.handoff) {
188
+ handle = isolated.handoff.handle;
189
+ identity = isolated.handoff.identity;
190
+ unregister = isolated.handoff.unregister;
191
+ }
192
+ }
193
+ else {
194
+ result = await Reflect.apply(write, params, [tempPath]);
195
+ }
92
196
  await assertParent();
93
- const before = await inspectPath(params.tempPath);
94
- try {
95
- // No create/truncate flags; O_NONBLOCK also bounds a FIFO swap during open.
96
- const access = params.syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
97
- handle = await fs.open(params.tempPath, access | resolveReadOpenFlags());
197
+ if (handle) {
198
+ const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
199
+ await inspectPath(tempPath, opened);
98
200
  }
99
- catch (error) {
100
- if (error?.code === "ELOOP") {
101
- throw new FsSafeError("symlink", "symlink sibling temp not allowed", { cause: error });
201
+ else {
202
+ const before = await inspectPath(tempPath);
203
+ try {
204
+ // No create/truncate flags; O_NONBLOCK also bounds a FIFO swap during open.
205
+ const access = syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
206
+ handle = await fs.open(tempPath, access | resolveReadOpenFlags());
207
+ }
208
+ catch (error) {
209
+ if (error?.code === "ELOOP") {
210
+ throw new FsSafeError("symlink", "symlink sibling temp not allowed", { cause: error });
211
+ }
212
+ throw error;
102
213
  }
103
- throw error;
214
+ const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), before);
215
+ await inspectPath(tempPath, opened);
216
+ identity = opened;
217
+ unregister = registerTempPathForExit(tempPath, { identity, singleLinkFile: true });
104
218
  }
105
- const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), before);
106
- await inspectPath(params.tempPath, opened);
107
219
  await assertParent();
108
- identity = opened;
109
- unregister = registerTempPathForExit(params.tempPath, { identity, singleLinkFile: true });
110
- const filePath = path.resolve(params.resolveFinalPath(result));
220
+ if (cleanupWorkspace) {
221
+ const cleanup = cleanupWorkspace;
222
+ cleanupWorkspace = undefined;
223
+ await cleanup();
224
+ }
225
+ const rawFilePath = Reflect.apply(resolveFinalPath, params, [result]);
226
+ assertNoWindowsPathAlias(rawFilePath, "filesystem", "final path uses a Windows filesystem namespace alias");
227
+ const filePath = path.resolve(rawFilePath);
228
+ assertNoWindowsPathAlias(filePath, "filesystem", "final path uses a Windows filesystem namespace alias");
111
229
  if (path.dirname(filePath) !== parent) {
112
230
  throw new Error("Final path must be in the sibling temp directory.");
113
231
  }
114
- if (filePath === params.tempPath) {
232
+ if (filePath === tempPath) {
115
233
  throw new FsSafeError("invalid-path", "final path must differ from the sibling temp path");
116
234
  }
117
235
  await serializePathWrite(filePath, async () => {
118
- await assertCurrent(params.tempPath);
119
- if (params.mode !== undefined) {
236
+ await assertCurrent(tempPath);
237
+ if (mode !== undefined) {
120
238
  try {
121
- await handle.chmod(params.mode);
239
+ await handle.chmod(mode);
122
240
  }
123
241
  catch (error) {
124
- if (!params.ignoreModeError)
242
+ if (!ignoreModeError)
125
243
  throw error;
126
244
  }
127
245
  }
128
- if (params.syncTempFile) {
129
- await assertCurrent(params.tempPath);
246
+ if (syncTempFile) {
247
+ await assertCurrent(tempPath);
130
248
  await syncFileBestEffort(handle);
131
249
  }
132
- await assertCurrent(params.tempPath);
133
- await fs.rename(params.tempPath, filePath);
250
+ await assertCurrent(tempPath);
251
+ await fs.rename(tempPath, filePath);
134
252
  // A later verification failure never authorizes rollback of the final name.
135
253
  renamed = true;
136
254
  unregister();
137
255
  await assertCurrent(filePath);
138
- if (params.syncParentDir)
256
+ if (syncParentDir)
139
257
  await syncDirectoryBestEffort(parent);
140
258
  await assertCurrent(filePath);
141
259
  });
@@ -146,13 +264,22 @@ export async function writeCallbackSibling(params) {
146
264
  throw error;
147
265
  }
148
266
  finally {
267
+ const settlementErrors = [];
268
+ if (cleanupWorkspace) {
269
+ try {
270
+ await cleanupWorkspace();
271
+ }
272
+ catch (error) {
273
+ settlementErrors.push(error);
274
+ }
275
+ }
149
276
  try {
150
277
  if (!renamed && identity) {
151
278
  try {
152
279
  await assertParent();
153
280
  await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
154
- await inspectPath(params.tempPath, identity);
155
- await fs.unlink(params.tempPath);
281
+ await inspectPath(tempPath, identity);
282
+ await fs.unlink(tempPath);
156
283
  unregister?.();
157
284
  }
158
285
  catch (error) {
@@ -168,9 +295,13 @@ export async function writeCallbackSibling(params) {
168
295
  await handle?.close();
169
296
  }
170
297
  catch (error) {
171
- if (failure)
172
- throw new AggregateError([failure.error, error], "sibling publication and close failed");
173
- throw error;
298
+ settlementErrors.push(error);
299
+ }
300
+ if (settlementErrors.length > 0) {
301
+ if (failure) {
302
+ throw new AggregateError([failure.error, ...settlementErrors], "sibling publication and settlement failed");
303
+ }
304
+ throw aggregateErrors(settlementErrors, "sibling publication settlement failed");
174
305
  }
175
306
  }
176
307
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAqBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
1
+ {"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CA0BxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDhB"}
@@ -9,17 +9,22 @@ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
9
9
  import { root } from "./root.js";
10
10
  import { assertSafePathPrefix } from "./safe-path-segment.js";
11
11
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
12
- import { writeCallbackSibling } from "./sibling-staged-file.js";
12
+ import { resolveCallbackTempPath, writeCallbackSibling } from "./sibling-staged-file.js";
13
13
  import { tempFile } from "./temp-target.js";
14
14
  import { getFsSafeTestHooks } from "./test-hooks.js";
15
- function buildTempPath(dir, tempPrefix) {
15
+ import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
16
+ function buildTempName(tempPrefix) {
16
17
  const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-stream", {
17
18
  label: "sibling temp prefix",
18
19
  });
19
- return path.join(dir, `${safePrefix}.${process.pid}.${randomUUID()}.tmp`);
20
+ return `${safePrefix}.${process.pid}.${randomUUID()}.tmp`;
20
21
  }
21
22
  export async function writeSiblingTempFile(options) {
22
- const dir = path.resolve(options.dir);
23
+ const dirInput = options.dir;
24
+ assertNoWindowsPathAlias(dirInput, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
25
+ const dir = resolvePathPreservingWindowsRoot(dirInput);
26
+ assertNoWindowsPathAlias(dir, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
27
+ const producerIsolation = options.producerIsolation;
23
28
  await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: options.dirMode ?? 0o700 });
24
29
  if (options.chmodDir !== false) {
25
30
  await applyDirectoryMode({
@@ -30,9 +35,10 @@ export async function writeSiblingTempFile(options) {
30
35
  });
31
36
  }
32
37
  return await writeCallbackSibling({
33
- tempPath: buildTempPath(dir, options.tempPrefix),
38
+ tempDir: dir,
39
+ tempName: buildTempName(options.tempPrefix),
34
40
  write: options.writeTemp,
35
- producerIsolation: options.producerIsolation,
41
+ producerIsolation,
36
42
  resolveFinalPath: options.resolveFinalPath,
37
43
  mode: options.mode,
38
44
  ignoreModeError: true,
@@ -40,7 +46,7 @@ export async function writeSiblingTempFile(options) {
40
46
  syncParentDir: options.syncParentDir === true,
41
47
  });
42
48
  }
43
- function buildSiblingTempPath(params) {
49
+ function buildSiblingTempName(params) {
44
50
  const id = crypto.randomUUID();
45
51
  const safePrefix = assertSafePathPrefix(params.tempPrefix, {
46
52
  label: "sibling temp prefix",
@@ -52,17 +58,23 @@ function buildSiblingTempPath(params) {
52
58
  fileName: sanitizeUntrustedFileName(path.basename(params.targetPath), params.fallbackFileName),
53
59
  suffix,
54
60
  });
55
- return path.join(path.dirname(params.targetPath), `${prefix}${safeTail}${suffix}`);
61
+ return `${prefix}${safeTail}${suffix}`;
56
62
  }
57
63
  export async function writeViaSiblingTempPath(params) {
64
+ const rootDirInput = params.rootDir;
65
+ const targetPathInput = params.targetPath;
66
+ assertNoWindowsPathAlias(rootDirInput, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
67
+ assertNoWindowsPathAlias(targetPathInput, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
58
68
  let rootDir;
59
69
  try {
60
- rootDir = realpathSync.native(path.resolve(params.rootDir));
70
+ rootDir = realpathSync.native(resolvePathPreservingWindowsRoot(rootDirInput));
61
71
  }
62
72
  catch {
63
- rootDir = path.resolve(params.rootDir);
73
+ rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
64
74
  }
65
- const requestedTargetPath = path.resolve(params.targetPath);
75
+ assertNoWindowsPathAlias(rootDir, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
76
+ const requestedTargetPath = path.resolve(targetPathInput);
77
+ assertNoWindowsPathAlias(requestedTargetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
66
78
  let targetPath;
67
79
  try {
68
80
  const realDir = realpathSync.native(path.dirname(requestedTargetPath));
@@ -71,6 +83,7 @@ export async function writeViaSiblingTempPath(params) {
71
83
  catch {
72
84
  targetPath = requestedTargetPath;
73
85
  }
86
+ assertNoWindowsPathAlias(targetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
74
87
  const relativeTargetPath = path.relative(rootDir, targetPath);
75
88
  if (!relativeTargetPath ||
76
89
  relativeTargetPath === ".." ||
@@ -87,12 +100,14 @@ export async function writeViaSiblingTempPath(params) {
87
100
  }),
88
101
  prefix: "fs-safe-output",
89
102
  });
103
+ assertNoWindowsPathAlias(workspace.dir, "filesystem", "sibling temp workspace uses a Windows filesystem namespace alias");
90
104
  try {
91
- const tempPath = buildSiblingTempPath({
105
+ const tempName = buildSiblingTempName({
92
106
  targetPath: path.join(workspace.dir, path.basename(targetPath)),
93
107
  fallbackFileName: params.fallbackFileName ?? "output.bin",
94
108
  tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
95
109
  });
110
+ const tempPath = resolveCallbackTempPath(workspace.dir, tempName);
96
111
  await getFsSafeTestHooks()?.beforeSiblingTempWrite?.(tempPath);
97
112
  await params.writeTemp(tempPath);
98
113
  await assertAsyncDirectoryGuard(rootGuard);
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA6T5B"}
1
+ {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAK5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAoCF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA2U5B"}
@@ -13,8 +13,16 @@ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransient
13
13
  import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
14
14
  import { createSuppressedError } from "./suppressed-error.js";
15
15
  import { sleep } from "./timing.js";
16
- async function resolveNormalizedTargetPath(targetPath, lockRoot) {
17
- const resolved = path.resolve(targetPath);
16
+ import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
17
+ function isCwdIndependentAbsolutePath(filePath) {
18
+ if (!path.isAbsolute(filePath))
19
+ return false;
20
+ // A leading separator on Windows is rooted on the process's current drive.
21
+ // Drive-qualified, UNC, and namespace roots are longer and need no cwd state.
22
+ return process.platform !== "win32" || path.parse(filePath).root.length > 1;
23
+ }
24
+ async function resolveNormalizedTargetPath(resolved, lockRoot) {
25
+ assertNoWindowsPathAlias(resolved);
18
26
  const dir = path.dirname(resolved);
19
27
  if (lockRoot) {
20
28
  // The target is an arbitration key, not necessarily inside the lock Root.
@@ -22,15 +30,23 @@ async function resolveNormalizedTargetPath(targetPath, lockRoot) {
22
30
  await lockRoot.resolve(".");
23
31
  const parent = await canonicalPathFromExistingAncestor(dir);
24
32
  await lockRoot.resolve(".");
25
- return path.join(parent, path.basename(resolved));
33
+ assertNoWindowsPathAlias(parent);
34
+ const normalized = path.join(parent, path.basename(resolved));
35
+ assertNoWindowsPathAlias(normalized);
36
+ return normalized;
26
37
  }
27
38
  await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
39
+ let parent;
28
40
  try {
29
- return path.join(realpathSync.native(dir), path.basename(resolved));
41
+ parent = realpathSync.native(dir);
30
42
  }
31
43
  catch {
32
44
  return resolved;
33
45
  }
46
+ assertNoWindowsPathAlias(parent);
47
+ const normalized = path.join(parent, path.basename(resolved));
48
+ assertNoWindowsPathAlias(normalized);
49
+ return normalized;
34
50
  }
35
51
  export async function acquireSidecarLock(options, context) {
36
52
  const retry = options.retry ?? {};
@@ -38,9 +54,24 @@ export async function acquireSidecarLock(options, context) {
38
54
  validateSidecarLockTimeoutMs(options.timeoutMs);
39
55
  validateSidecarLockStaleMs(options.staleMs);
40
56
  validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
57
+ const targetPath = options.targetPath;
58
+ const explicitLockPath = options.lockPath;
59
+ const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
60
+ assertNoWindowsPathAlias(anchoredTargetPath);
61
+ const resolvedTargetPath = path.resolve(anchoredTargetPath);
62
+ let resolvedLockPath;
63
+ if (explicitLockPath !== undefined) {
64
+ const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
65
+ assertNoWindowsPathAlias(anchoredLockPath);
66
+ resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
67
+ ? explicitLockPath
68
+ : path.resolve(anchoredLockPath);
69
+ }
41
70
  context.ensureExitCleanupRegistered();
42
- const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath, options.lockRoot);
43
- const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
71
+ const lockRoot = options.lockRoot;
72
+ const normalizedTargetPath = await resolveNormalizedTargetPath(resolvedTargetPath, lockRoot);
73
+ const lockPath = resolvedLockPath ?? `${normalizedTargetPath}.lock`;
74
+ assertNoWindowsPathAlias(lockPath);
44
75
  let held = context.held.get(normalizedTargetPath);
45
76
  if (held &&
46
77
  options.reentrantOwner !== undefined &&
@@ -58,7 +89,7 @@ export async function acquireSidecarLock(options, context) {
58
89
  options.reentrantOwner !== undefined &&
59
90
  held.reentrantOwner !== undefined &&
60
91
  options.reentrantOwner === held.reentrantOwner) {
61
- held.refCount += 1;
92
+ held.refCount = (held.refCount ?? 1) + 1;
62
93
  // Retention is monotonic: any same-owner request to keep the sidecar on
63
94
  // exit upgrades the held lock; a later default acquisition never revokes it.
64
95
  if (options.retainOnExit === true) {
@@ -118,8 +149,7 @@ export async function acquireSidecarLock(options, context) {
118
149
  const payload = await options.payload();
119
150
  const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
120
151
  try {
121
- if (options.lockRoot) {
122
- const lockRoot = options.lockRoot;
152
+ if (lockRoot) {
123
153
  const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
124
154
  const observation = fileObservation();
125
155
  try {
@@ -180,7 +210,7 @@ export async function acquireSidecarLock(options, context) {
180
210
  snapshot,
181
211
  acquiredAt: Date.now(),
182
212
  metadata: options.metadata ?? {},
183
- lockRoot: options.lockRoot,
213
+ lockRoot,
184
214
  retainOnExit: options.retainOnExit,
185
215
  parsePayload: options.parsePayload,
186
216
  };
@@ -239,13 +269,13 @@ export async function acquireSidecarLock(options, context) {
239
269
  // Root-created records retain the creator's byte/token receipt;
240
270
  // partial direct writes use the exclusive descriptor's identity.
241
271
  await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
242
- lockRoot: options.lockRoot,
272
+ lockRoot,
243
273
  parsePayload: options.parsePayload,
244
274
  });
245
275
  }
246
276
  else if (createdSnapshot) {
247
277
  await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
248
- lockRoot: options.lockRoot,
278
+ lockRoot,
249
279
  parsePayload: options.parsePayload,
250
280
  });
251
281
  }
@@ -271,7 +301,7 @@ export async function acquireSidecarLock(options, context) {
271
301
  let lockFileOpenDenied = false;
272
302
  try {
273
303
  rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
274
- lockRoot: options.lockRoot,
304
+ lockRoot,
275
305
  rejectNonFile: true,
276
306
  discardObservation: "changed",
277
307
  onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
@@ -302,7 +332,7 @@ export async function acquireSidecarLock(options, context) {
302
332
  heldByThisProcess: context.held.has(normalizedTargetPath),
303
333
  })) {
304
334
  if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
305
- lockRoot: options.lockRoot,
335
+ lockRoot,
306
336
  parsePayload: options.parsePayload,
307
337
  }))) {
308
338
  await waitForRetry();
@@ -320,7 +350,7 @@ export async function acquireSidecarLock(options, context) {
320
350
  normalizedTargetPath,
321
351
  snapshot,
322
352
  shouldRemoveStaleLock: options.shouldRemoveStaleLock,
323
- lockRoot: options.lockRoot,
353
+ lockRoot,
324
354
  parsePayload: options.parsePayload,
325
355
  });
326
356
  if (removal === "removed" || removal === "changed") {
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAuD5B"}
1
+ {"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyD5B"}
@@ -53,13 +53,15 @@ export async function openSidecarRoot(lockRoot, relative, discardObservation, on
53
53
  if (!discardObservation || (!missing && (!completeParents || !discardable)))
54
54
  throw error;
55
55
  try {
56
+ const probe = fileObservation();
56
57
  try {
57
- const current = await lockRoot.stat(relative);
58
+ const current = await probe.run(() => lockRoot.stat(relative));
58
59
  if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
59
60
  throw error;
60
61
  }
61
62
  catch (probeError) {
62
- if (!(probeError instanceof FsSafeError && probeError.code === "not-found"))
63
+ if (!(probeError instanceof FsSafeError && probeError.code === "not-found") &&
64
+ !probe.has(probeError, `stat-leaf-missing:${expectedRealPath}`))
63
65
  throw probeError;
64
66
  }
65
67
  for (const { dir, stat } of parents)
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAsIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAuFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
1
+ {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAmIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAyFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
@@ -37,9 +37,6 @@ function resolveManagerState(key) {
37
37
  // Backfill state created by fs-safe versions that predate reclaim guards.
38
38
  state.reclaimCleanupRegistered ??= false;
39
39
  state.reclaimGuards ??= new Set();
40
- for (const held of state.held.values()) {
41
- held.refCount ??= 1;
42
- }
43
40
  }
44
41
  return state;
45
42
  }
@@ -163,6 +160,8 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
163
160
  await held.releasePromise;
164
161
  return true;
165
162
  }
163
+ // Older package copies can add holders after this manager was constructed.
164
+ held.refCount ??= 1;
166
165
  if (options.force) {
167
166
  held.refCount = 0;
168
167
  }