@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
@@ -0,0 +1,322 @@
1
+ import fsSync from "node:fs";
2
+ import path from "node:path";
3
+ import { assertMutationNotDenied } from "./deny-mutations.js";
4
+ import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { isNotFoundPathError } from "./path.js";
7
+ import { assertNoPathAliasEscape } from "./path-policy.js";
8
+ import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy, } from "./pinned-mutation-admission.js";
9
+ import { admitPathInsideRoot } from "./root-boundary.js";
10
+ import { resolvePathInRoot } from "./root-context.js";
11
+ import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
12
+ import { inheritWriteTargetMode } from "./root-write-mode.js";
13
+ import { mutationSymlinkResolution, } from "./root-symlink-policy.js";
14
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
15
+ import { realpathSync } from "./realpath.js";
16
+ import { getFsSafeTestHooks } from "./test-hooks.js";
17
+ import { assertPreparedRootWriteParentCurrent, } from "./root-write-complete-parent.js";
18
+ const retainedRootWriteSelections = new WeakMap();
19
+ export function retainRootWriteSelection(owner, selection) {
20
+ retainedRootWriteSelections.set(owner, selection);
21
+ }
22
+ export function createRootWriteSelectionForFd(selection, fd) {
23
+ const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
24
+ return Object.freeze({
25
+ ...selection,
26
+ identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
27
+ });
28
+ }
29
+ export function takeRootWriteSelection(owner) {
30
+ const selection = retainedRootWriteSelections.get(owner);
31
+ retainedRootWriteSelections.delete(owner);
32
+ return selection;
33
+ }
34
+ function writeSelectionChanged(cause) {
35
+ return new FsSafeError("path-mismatch", "write target changed during operation", {
36
+ cause: cause instanceof Error ? cause : undefined,
37
+ });
38
+ }
39
+ function inspectRegularSelectionPath(pathname, expected, follow) {
40
+ let observed;
41
+ try {
42
+ observed = inspectFileIdentitySync(() => follow
43
+ ? fsSync.statSync(pathname, { bigint: true })
44
+ : fsSync.lstatSync(pathname, { bigint: true }), expected);
45
+ }
46
+ catch (error) {
47
+ if (error instanceof FsSafeError && error.code === "path-mismatch")
48
+ throw error;
49
+ throw writeSelectionChanged(error);
50
+ }
51
+ if ((!follow && observed.isSymbolicLink()) || !observed.isFile()) {
52
+ throw writeSelectionChanged();
53
+ }
54
+ if (observed.nlink > 1n)
55
+ throw hardlinkedPathNotAllowedError();
56
+ return observed;
57
+ }
58
+ function sameNormalizedPathSpelling(left, right) {
59
+ // Windows can expose case-sensitive directories, so normalize separators
60
+ // and roots without using path.relative's case-folding comparison.
61
+ return path.resolve(left) === path.resolve(right);
62
+ }
63
+ function inspectSelectionBindingSync(root, selection, fd) {
64
+ inspectRegularSelectionPath(selection.selectedPath, selection.identity, false);
65
+ if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
66
+ let operationRealPath;
67
+ try {
68
+ operationRealPath = realpathSync.native(selection.operationTargetPath);
69
+ }
70
+ catch (error) {
71
+ throw writeSelectionChanged(error);
72
+ }
73
+ const admitted = admitPathInsideRoot({
74
+ rootPath: root.rootReal,
75
+ candidatePath: operationRealPath,
76
+ rootIdentity: root.rootIdentity,
77
+ });
78
+ if (!admitted)
79
+ throw outsideWorkspaceError();
80
+ inspectRegularSelectionPath(admitted.path, selection.identity, true);
81
+ }
82
+ if (fd !== undefined) {
83
+ try {
84
+ inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), selection.identity);
85
+ }
86
+ catch (error) {
87
+ if (error instanceof FsSafeError && error.code === "path-mismatch")
88
+ throw error;
89
+ throw writeSelectionChanged(error);
90
+ }
91
+ }
92
+ }
93
+ export function assertRootWritePathSelectionSync(root, selection) {
94
+ if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
95
+ let operationRealPath;
96
+ try {
97
+ operationRealPath = realpathSync.native(selection.operationTargetPath);
98
+ }
99
+ catch (error) {
100
+ throw writeSelectionChanged(error);
101
+ }
102
+ const admitted = admitPathInsideRoot({
103
+ rootPath: root.rootReal,
104
+ candidatePath: operationRealPath,
105
+ rootIdentity: root.rootIdentity,
106
+ });
107
+ if (!admitted)
108
+ throw outsideWorkspaceError();
109
+ const selected = inspectRegularSelectionPath(selection.selectedPath, undefined, false);
110
+ inspectRegularSelectionPath(admitted.path, selected, true);
111
+ }
112
+ assertSyncDirectoryGuard(selection.parentGuard);
113
+ }
114
+ export function assertRootWriteSelectionSync(root, selection, verifyBinding, fd) {
115
+ if (verifyBinding)
116
+ inspectSelectionBindingSync(root, selection, fd);
117
+ assertSyncDirectoryGuard(selection.parentGuard);
118
+ }
119
+ async function authorizeRootWritePathSelection(selection, afterAwait) {
120
+ const selectedAdmissionPending = !sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath);
121
+ await selection.mutationAdmission.authorize(Object.freeze({
122
+ targetPath: selection.operationTargetPath,
123
+ mutationPath: selection.operationTargetPath,
124
+ phase: "parent",
125
+ }));
126
+ afterAwait?.(selectedAdmissionPending);
127
+ if (selectedAdmissionPending) {
128
+ await selection.selectedTargetAdmission.authorize(selection.selectedPath);
129
+ afterAwait?.(false);
130
+ }
131
+ }
132
+ export async function prepareRootWritePathSelection(params) {
133
+ const { preparedParent, ...selectionParams } = params;
134
+ if (preparedParent && (!sameNormalizedPathSpelling(preparedParent.operationTargetPath, params.operationTargetPath) ||
135
+ !sameNormalizedPathSpelling(preparedParent.parentGuard.dir, path.dirname(params.operationTargetPath))))
136
+ throw writeSelectionChanged();
137
+ const selection = Object.freeze({
138
+ ...selectionParams,
139
+ parentGuard: preparedParent?.parentGuard ??
140
+ await createAsyncDirectoryGuard(path.dirname(params.selectedPath), { bigint: true }),
141
+ });
142
+ await authorizeRootWritePathSelection(selection, preparedParent
143
+ ? (selectedAdmissionPending) => assertPreparedRootWriteParentCurrent(preparedParent, !selectedAdmissionPending)
144
+ : undefined);
145
+ if (preparedParent) {
146
+ if (!sameNormalizedPathSpelling(preparedParent.selectedTargetPath, params.selectedPath)) {
147
+ throw writeSelectionChanged();
148
+ }
149
+ }
150
+ else {
151
+ await assertAsyncDirectoryGuard(selection.parentGuard);
152
+ }
153
+ return selection;
154
+ }
155
+ export async function prepareGuardedRootWritePathSelection(guarded, selectedPath, operationTargetPath = selectedPath, preparedParent) {
156
+ if (!guarded?.mutationAdmission)
157
+ return undefined;
158
+ if (!guarded.selectedTargetAdmission)
159
+ throw writeSelectionChanged();
160
+ return await prepareRootWritePathSelection({
161
+ mutationAdmission: guarded.mutationAdmission,
162
+ selectedTargetAdmission: guarded.selectedTargetAdmission,
163
+ operationTargetPath,
164
+ selectedPath,
165
+ preparedParent,
166
+ });
167
+ }
168
+ export async function refreshRootWritePathSelection(selection) {
169
+ await authorizeRootWritePathSelection(selection);
170
+ await assertAsyncDirectoryGuard(selection.parentGuard);
171
+ }
172
+ /**
173
+ * Re-authorize a retained shared-JavaScript destination without recapturing
174
+ * either its pathname binding or its exact parent fence.
175
+ */
176
+ export async function refreshRootWriteSelection(root, selection, verifyBinding, fd) {
177
+ await authorizeRootWritePathSelection(selection);
178
+ if (verifyBinding)
179
+ inspectSelectionBindingSync(root, selection, fd);
180
+ await assertAsyncDirectoryGuard(selection.parentGuard);
181
+ }
182
+ export async function refreshRetainedRootWriteAdmission(root, selection, verifyBinding, fd) {
183
+ await getFsSafeTestHooks()?.beforePinnedWriteParentAdmission?.(selection.operationTargetPath);
184
+ await refreshRootWriteSelection(root, selection, verifyBinding, fd);
185
+ }
186
+ export async function resolveGuardedWritePathInRoot(root, params) {
187
+ const resolvedPath = await resolvePathInRoot(root, params.relativePath, {
188
+ aliasErrorCode: "path-alias",
189
+ rejectAmbiguousParents: true,
190
+ allowFinalSymlink: params.allowFinalSymlink,
191
+ ...mutationSymlinkResolution(params.mutationSymlinks),
192
+ });
193
+ await assertMutationNotDenied(resolvedPath.resolved, params.denyMutations, params.protectDeniedAncestors ? { protectAncestors: true } : undefined);
194
+ if (await (params.shouldAssertNoPathAlias?.(resolvedPath) ?? true)) {
195
+ try {
196
+ await assertNoPathAliasEscape({
197
+ absolutePath: resolvedPath.resolved,
198
+ rootPath: resolvedPath.rootReal,
199
+ boundaryLabel: "root",
200
+ });
201
+ }
202
+ catch (error) {
203
+ throw new FsSafeError("path-alias", "path alias escape blocked", {
204
+ cause: error instanceof Error ? error : undefined,
205
+ });
206
+ }
207
+ }
208
+ return resolvedPath;
209
+ }
210
+ export async function resolveGuardedWriteTargetInRoot(root, params) {
211
+ const policy = params.denyMutations === undefined && params.mutationSymlinks === undefined
212
+ ? undefined
213
+ : snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks);
214
+ const resolveCurrent = async () => await resolveGuardedWritePathInRoot(root, {
215
+ ...params,
216
+ denyMutations: policy?.denyMutations,
217
+ mutationSymlinks: policy?.mutationSymlinks,
218
+ });
219
+ const resolvedPath = await resolveCurrent();
220
+ if (!policy)
221
+ return { resolvedPath, targetPath: resolvedPath.resolved };
222
+ const relativeParent = path.relative(resolvedPath.rootReal, path.dirname(resolvedPath.resolved));
223
+ const prepared = await preparePinnedWriteMutationAdmission({
224
+ rootReal: resolvedPath.rootReal,
225
+ rootWithSep: resolvedPath.rootWithSep,
226
+ rootIdentity: root.rootIdentity,
227
+ resolvedTargetPath: resolvedPath.resolved,
228
+ originalPath: params.relativePath,
229
+ defaultRelativeParentPath: relativeParent
230
+ ? relativeParent.split(path.sep).join(path.posix.sep)
231
+ : "",
232
+ policy,
233
+ resolveCurrent,
234
+ });
235
+ const selectedTargetAdmission = Object.freeze({
236
+ async authorize(selectedTargetPath) {
237
+ const admitted = admitPathInsideRoot({
238
+ rootPath: root.rootReal,
239
+ candidatePath: selectedTargetPath,
240
+ rootIdentity: root.rootIdentity,
241
+ });
242
+ if (!admitted)
243
+ throw outsideWorkspaceError();
244
+ await resolveGuardedWritePathInRoot(root, {
245
+ relativePath: admitted.relativePath,
246
+ denyMutations: policy.denyMutations,
247
+ mutationSymlinks: policy.mutationSymlinks,
248
+ });
249
+ },
250
+ });
251
+ const targetPath = path.join(resolvedPath.rootReal, ...prepared.relativeParentPath.split("/").filter(Boolean), path.basename(resolvedPath.resolved));
252
+ return {
253
+ resolvedPath,
254
+ targetPath,
255
+ mutationAdmission: prepared.mutationAdmission,
256
+ selectedTargetAdmission,
257
+ };
258
+ }
259
+ export async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true, mutationSymlinks) {
260
+ // Snapshot mutable caller-owned policy before preflight so every admission
261
+ // in this operation authorizes the same rules.
262
+ const policy = denyMutations === undefined && mutationSymlinks === undefined
263
+ ? undefined
264
+ : snapshotPinnedMutationPolicy(denyMutations, mutationSymlinks);
265
+ const resolveCurrent = policy
266
+ ? async () => await resolveGuardedWritePathInRoot(root, {
267
+ relativePath,
268
+ denyMutations: policy.denyMutations,
269
+ mutationSymlinks: policy.mutationSymlinks,
270
+ })
271
+ : undefined;
272
+ const { rootReal, rootWithSep, resolved } = resolveCurrent
273
+ ? await resolveCurrent()
274
+ : await resolveGuardedWritePathInRoot(root, { relativePath });
275
+ const relativeResolved = path.relative(rootReal, resolved);
276
+ if (path.isAbsolute(relativeResolved))
277
+ throw outsideWorkspaceError();
278
+ const relativePosix = relativeResolved
279
+ ? relativeResolved.split(path.sep).join(path.posix.sep)
280
+ : "";
281
+ const basename = path.posix.basename(relativePosix);
282
+ if (!basename || basename === "." || basename === "/") {
283
+ throw new FsSafeError("invalid-path", "invalid target path");
284
+ }
285
+ if (!overwrite) {
286
+ try {
287
+ const existing = fsSync.statSync(resolved);
288
+ if (!existing.isFile())
289
+ throw new FsSafeError("not-file", "not a file");
290
+ if (existing.nlink > 1)
291
+ throw hardlinkedPathNotAllowedError();
292
+ throw new FsSafeError("already-exists", "file already exists");
293
+ }
294
+ catch (error) {
295
+ if (!isNotFoundPathError(error))
296
+ throw error;
297
+ }
298
+ }
299
+ const mode = overwrite
300
+ ? await inheritWriteTargetMode({
301
+ targetPath: resolved,
302
+ rootWithSep,
303
+ rootIdentity: root.rootIdentity,
304
+ requestedMode,
305
+ })
306
+ : requestedMode ?? 0o600;
307
+ let relativeParentPath = path.posix.dirname(relativePosix) === "." ? "" : path.posix.dirname(relativePosix);
308
+ let mutationAdmission;
309
+ if (policy) {
310
+ ({ relativeParentPath, mutationAdmission } = await preparePinnedWriteMutationAdmission({
311
+ rootReal,
312
+ rootWithSep,
313
+ rootIdentity: root.rootIdentity,
314
+ resolvedTargetPath: resolved,
315
+ originalPath: relativePath,
316
+ defaultRelativeParentPath: relativeParentPath,
317
+ policy,
318
+ resolveCurrent: resolveCurrent,
319
+ }));
320
+ }
321
+ return { rootReal, targetPath: resolved, relativeParentPath, basename, mode, mutationAdmission };
322
+ }
@@ -0,0 +1,13 @@
1
+ import { type RootBoundaryIdentity } from "./root-boundary.js";
2
+ export declare function assertRootFallbackWritePath(expected: string | undefined, actual: string): void;
3
+ export declare function withRootFallbackCompatibilityLock<T>(params: {
4
+ rootPath: string;
5
+ targetPath: string;
6
+ rootIdentity?: RootBoundaryIdentity;
7
+ assertBeforeMutation?: () => void;
8
+ }, run: (binding: {
9
+ targetPath: string;
10
+ relativePath: string;
11
+ assertBeforeMutation: () => void;
12
+ }) => Promise<T>): Promise<T>;
13
+ //# sourceMappingURL=root-write-compatibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-write-compatibility.d.ts","sourceRoot":"","sources":["../src/root-write-compatibility.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA6CpF,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAI9F;AAED,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,EACxH,GAAG,EAAE,CAAC,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,IAAI,CAAA;CAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3G,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { isNotFoundPathError } from "./path.js";
5
+ import { admitPathInsideRoot } from "./root-boundary.js";
6
+ import { withPinnedWriteRenameIdentityLock } from "./pinned-write.js";
7
+ import { realpathSync } from "./realpath.js";
8
+ import { serializePathWrite } from "./write-queue.js";
9
+ function unsupportedSpelling() {
10
+ return new FsSafeError("path-alias", "Windows compatibility writes require lower-case ASCII destination components");
11
+ }
12
+ // The caller first applies the Root's ordinary path and mutation policies. This
13
+ // extra admission rule is deliberately narrower than general Windows filenames:
14
+ // the existing lock protocol has no portable case/Unicode equivalence key for a
15
+ // missing name. Do not guess one, or switch keys when a placeholder appears.
16
+ function effectiveDestination(rootPath, targetPath, rootIdentity) {
17
+ let effective;
18
+ try {
19
+ effective = realpathSync.native(targetPath);
20
+ }
21
+ catch (error) {
22
+ if (!isNotFoundPathError(error))
23
+ throw error;
24
+ let cursor = targetPath;
25
+ const missing = [];
26
+ for (;;) {
27
+ try {
28
+ fs.lstatSync(cursor);
29
+ break;
30
+ }
31
+ catch (error) {
32
+ if (!isNotFoundPathError(error) || cursor === rootPath)
33
+ throw error;
34
+ const parent = path.dirname(cursor);
35
+ if (parent === cursor)
36
+ throw error;
37
+ missing.unshift(path.basename(cursor));
38
+ cursor = parent;
39
+ }
40
+ }
41
+ // An existing dangling link must fail resolution, not become a missing
42
+ // lexical component. Keep the fallback bounded by the retained Root.
43
+ effective = path.join(realpathSync.native(cursor), ...missing);
44
+ }
45
+ const admitted = admitPathInsideRoot({ rootPath, candidatePath: effective, rootIdentity });
46
+ if (!admitted)
47
+ throw unsupportedSpelling();
48
+ const relative = admitted.relativePath;
49
+ if (!relative || !relative.split(path.sep).every(component => /^[a-z0-9._-]+$/.test(component) && !component.endsWith(".")))
50
+ throw unsupportedSpelling();
51
+ return admitted.path;
52
+ }
53
+ export function assertRootFallbackWritePath(expected, actual) {
54
+ if (expected !== undefined && actual !== expected) {
55
+ throw new FsSafeError("path-mismatch", "compatibility write destination changed after lock selection");
56
+ }
57
+ }
58
+ export async function withRootFallbackCompatibilityLock(params, run) {
59
+ const targetPath = effectiveDestination(params.rootPath, params.targetPath, params.rootIdentity);
60
+ const relativePath = path.relative(params.rootPath, targetPath).split(path.sep).join("/");
61
+ const assertCurrent = () => assertRootFallbackWritePath(targetPath, effectiveDestination(params.rootPath, targetPath, params.rootIdentity));
62
+ // Sidecars are reentrant within one manager; aliases also need the same local
63
+ // queue before acquisition. The outer Root queue uses the caller's spelling.
64
+ return await serializePathWrite(targetPath, async () => await withPinnedWriteRenameIdentityLock({
65
+ rootPath: params.rootPath, targetPath, relativeTargetPath: relativePath,
66
+ }, async () => {
67
+ // The guarded open resolves again under the lock; each mutation rechecks
68
+ // below. Lock selection is never reused as post-lock filesystem authority.
69
+ return await run({
70
+ targetPath, relativePath,
71
+ assertBeforeMutation: () => { params.assertBeforeMutation?.(); assertCurrent(); },
72
+ });
73
+ }));
74
+ }
@@ -0,0 +1,42 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
+ import type { RootContext } from "./root-context.js";
4
+ import type { GuardedRootWriteTarget } from "./root-write-admission.js";
5
+ type PreparedWriteTargetObservation = Readonly<{
6
+ exists: false;
7
+ } | {
8
+ exists: true;
9
+ stat: BigIntStats;
10
+ }>;
11
+ /**
12
+ * Exact, operation-local evidence that an ordinary shared-JavaScript write
13
+ * already has a complete parent. Path selection consumes this instead of
14
+ * recapturing the same parent after a component walk.
15
+ */
16
+ export type PreparedRootWriteParent = Readonly<{
17
+ operationTargetPath: string;
18
+ selectedTargetPath: string;
19
+ parentGuard: AsyncDirectoryGuard<BigIntStats>;
20
+ rootPath: string;
21
+ rootIdentity: Readonly<{
22
+ dev: bigint;
23
+ ino: bigint;
24
+ }>;
25
+ nativeMode: string;
26
+ target: PreparedWriteTargetObservation;
27
+ }>;
28
+ type SharedRootWriteTargetParams = Readonly<{
29
+ relativePath: string;
30
+ guardedTarget: GuardedRootWriteTarget;
31
+ mkdir?: boolean;
32
+ assertBeforeMutation?: () => void;
33
+ }>;
34
+ export type SharedRootWriteTarget = Readonly<{
35
+ targetPath: string;
36
+ mutationAdmission: GuardedRootWriteTarget["mutationAdmission"];
37
+ preparedParent?: PreparedRootWriteParent;
38
+ }>;
39
+ export declare function assertPreparedRootWriteParentCurrent(prepared: PreparedRootWriteParent, verifyTarget?: boolean): void;
40
+ export declare function prepareSharedRootWriteTarget(root: RootContext, params: SharedRootWriteTargetParams): Promise<SharedRootWriteTarget>;
41
+ export {};
42
+ //# sourceMappingURL=root-write-complete-parent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAKxE,KAAK,8BAA8B,GAAG,QAAQ,CAC1C;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC,CAAC;AAEH,KAAK,2BAA2B,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,CAAC;AA2GH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,uBAAuB,EACjC,YAAY,UAAQ,GACnB,IAAI,CAqBN;AAwCD,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAqBhC"}
@@ -0,0 +1,195 @@
1
+ import fsSync from "node:fs";
2
+ import path from "node:path";
3
+ import { assertDirectoryIdentitySync, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
4
+ import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { getFsSafeNativeConfig } from "./native-config.js";
7
+ import { hasNodeErrorCode } from "./path.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { admitPathInsideRoot } from "./root-boundary.js";
10
+ import { prepareRootWriteTarget } from "./root-directory-creation.js";
11
+ import { isSafePathSegment } from "./safe-path-segment.js";
12
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
13
+ import { getFsSafeTestHooks } from "./test-hooks.js";
14
+ function writeSelectionChanged(cause) {
15
+ return new FsSafeError("path-mismatch", "write target changed during operation", {
16
+ cause: cause instanceof Error ? cause : undefined,
17
+ });
18
+ }
19
+ function sameNormalizedPathSpelling(left, right) {
20
+ // Windows can expose case-sensitive directories, so normalize separators
21
+ // and roots without using path.relative's case-folding comparison.
22
+ return path.resolve(left) === path.resolve(right);
23
+ }
24
+ function windowsReservedDeviceSegment(segment) {
25
+ const extension = segment.indexOf(".");
26
+ const stem = segment.slice(0, extension < 0 ? segment.length : extension).toUpperCase();
27
+ return WINDOWS_RESERVED_DEVICE_NAMES.has(stem);
28
+ }
29
+ function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
30
+ if (process.versions.bun || relativePath === "" || relativePath.startsWith("~") ||
31
+ relativePath.includes("\0") || path.isAbsolute(relativePath))
32
+ return false;
33
+ const separator = process.platform === "win32" ? "\\" : "/";
34
+ const alternateSeparator = process.platform === "win32" ? "/" : "\\";
35
+ if (relativePath.includes(alternateSeparator))
36
+ return false;
37
+ const segments = relativePath.split(separator);
38
+ if (segments.some((segment) => segment === "" || segment === "." || segment === ".." ||
39
+ (process.platform === "win32" &&
40
+ (!isSafePathSegment(segment, { allowDotPrefix: true }) ||
41
+ segment.endsWith(".") || segment.endsWith(" ") ||
42
+ windowsReservedDeviceSegment(segment)))))
43
+ return false;
44
+ const routed = path.resolve(root.rootReal, relativePath);
45
+ const admitted = admitPathInsideRoot({
46
+ rootPath: root.rootReal,
47
+ candidatePath: routed,
48
+ rootIdentity: root.rootIdentity,
49
+ });
50
+ return admitted?.admission === "exact" &&
51
+ sameNormalizedPathSpelling(admitted.path, routed) &&
52
+ sameNormalizedPathSpelling(routed, operationTargetPath);
53
+ }
54
+ // Preparation is optional evidence. An exact identity mismatch is conclusive
55
+ // and remains fail-closed; every other probe failure must let the established
56
+ // ordered component admission classify the same filesystem state.
57
+ function deoptPreparationFailure(error) {
58
+ if (error instanceof FsSafeError && error.code === "path-mismatch")
59
+ throw error;
60
+ return undefined;
61
+ }
62
+ function capturePreparedTargetObservation(targetPath) {
63
+ let stat;
64
+ try {
65
+ stat = inspectFileIdentitySync(() => fsSync.lstatSync(targetPath, { bigint: true }));
66
+ }
67
+ catch (error) {
68
+ if (hasNodeErrorCode(error, "ENOENT"))
69
+ return Object.freeze({ exists: false });
70
+ return deoptPreparationFailure(error);
71
+ }
72
+ // Final aliases retain the established full component walk and selected-path
73
+ // admission. The shortcut is intentionally limited to an ordinary spelling.
74
+ if (stat.isSymbolicLink())
75
+ return undefined;
76
+ let realPath;
77
+ try {
78
+ realPath = realpathSync.native(targetPath);
79
+ }
80
+ catch (error) {
81
+ return deoptPreparationFailure(error);
82
+ }
83
+ if (!sameNormalizedPathSpelling(realPath, targetPath))
84
+ return undefined;
85
+ return Object.freeze({ exists: true, stat });
86
+ }
87
+ function samePreparedTargetFacts(left, right) {
88
+ return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode &&
89
+ left.nlink === right.nlink;
90
+ }
91
+ function assertPreparedTargetCurrent(prepared) {
92
+ if (!prepared.target.exists) {
93
+ try {
94
+ fsSync.lstatSync(prepared.selectedTargetPath, { bigint: true });
95
+ }
96
+ catch (error) {
97
+ if (hasNodeErrorCode(error, "ENOENT"))
98
+ return;
99
+ throw writeSelectionChanged(error);
100
+ }
101
+ throw writeSelectionChanged();
102
+ }
103
+ try {
104
+ const current = inspectFileIdentitySync(() => fsSync.lstatSync(prepared.selectedTargetPath, { bigint: true }), prepared.target.stat);
105
+ if (current.isSymbolicLink() || !samePreparedTargetFacts(prepared.target.stat, current)) {
106
+ throw writeSelectionChanged();
107
+ }
108
+ }
109
+ catch (error) {
110
+ if (error instanceof FsSafeError && error.code === "path-mismatch")
111
+ throw error;
112
+ throw writeSelectionChanged(error);
113
+ }
114
+ }
115
+ export function assertPreparedRootWriteParentCurrent(prepared, verifyTarget = false) {
116
+ try {
117
+ if (getFsSafeNativeConfig().mode !== prepared.nativeMode)
118
+ throw writeSelectionChanged();
119
+ if (!sameNormalizedPathSpelling(prepared.parentGuard.dir, prepared.rootPath)) {
120
+ assertDirectoryIdentitySync(prepared.rootPath, prepared.rootIdentity);
121
+ }
122
+ else if (prepared.parentGuard.stat.dev !== prepared.rootIdentity.dev ||
123
+ prepared.parentGuard.stat.ino !== prepared.rootIdentity.ino) {
124
+ throw writeSelectionChanged();
125
+ }
126
+ const parent = inspectDirectoryIdentitySync(prepared.parentGuard.dir, prepared.parentGuard.stat);
127
+ if (parent.mode !== prepared.parentGuard.stat.mode ||
128
+ parent.nlink !== prepared.parentGuard.stat.nlink ||
129
+ !sameNormalizedPathSpelling(realpathSync.native(prepared.parentGuard.dir), prepared.parentGuard.realPath))
130
+ throw writeSelectionChanged();
131
+ if (verifyTarget)
132
+ assertPreparedTargetCurrent(prepared);
133
+ }
134
+ catch (error) {
135
+ if (error instanceof FsSafeError && error.code === "path-mismatch")
136
+ throw error;
137
+ throw writeSelectionChanged(error);
138
+ }
139
+ }
140
+ async function prepareCompleteRootWriteParent(root, guarded, relativePath) {
141
+ if (!guarded.mutationAdmission || !guarded.selectedTargetAdmission ||
142
+ typeof root.rootIdentity.dev !== "bigint" || typeof root.rootIdentity.ino !== "bigint" ||
143
+ !ordinarySharedWriteRoute(root, relativePath, guarded.targetPath) ||
144
+ !sameNormalizedPathSpelling(guarded.resolvedPath.resolved, guarded.targetPath))
145
+ return undefined;
146
+ const parentPath = path.dirname(guarded.targetPath);
147
+ const admittedParent = admitPathInsideRoot({
148
+ rootPath: root.rootReal,
149
+ candidatePath: parentPath,
150
+ rootIdentity: root.rootIdentity,
151
+ });
152
+ if (admittedParent?.admission !== "exact" ||
153
+ !sameNormalizedPathSpelling(admittedParent.path, parentPath))
154
+ return undefined;
155
+ let parentGuard;
156
+ try {
157
+ parentGuard = await createAsyncDirectoryGuard(parentPath, { bigint: true });
158
+ }
159
+ catch (error) {
160
+ return deoptPreparationFailure(error);
161
+ }
162
+ if (!sameNormalizedPathSpelling(parentGuard.dir, parentPath) ||
163
+ !sameNormalizedPathSpelling(parentGuard.realPath, parentPath))
164
+ return undefined;
165
+ const target = capturePreparedTargetObservation(guarded.targetPath);
166
+ if (!target)
167
+ return undefined;
168
+ return Object.freeze({
169
+ operationTargetPath: guarded.targetPath,
170
+ selectedTargetPath: guarded.targetPath,
171
+ parentGuard,
172
+ rootPath: root.rootReal,
173
+ rootIdentity: Object.freeze({ dev: root.rootIdentity.dev, ino: root.rootIdentity.ino }),
174
+ nativeMode: getFsSafeNativeConfig().mode,
175
+ target,
176
+ });
177
+ }
178
+ export async function prepareSharedRootWriteTarget(root, params) {
179
+ const { guardedTarget } = params;
180
+ const resolvedPath = guardedTarget.resolvedPath.resolved;
181
+ const mutationAdmission = guardedTarget.mutationAdmission;
182
+ const beforeParentAdmission = mutationAdmission
183
+ ? getFsSafeTestHooks()?.beforePinnedWriteParentAdmission
184
+ : undefined;
185
+ if (mutationAdmission)
186
+ await beforeParentAdmission?.(resolvedPath);
187
+ const preparedParent = params.mkdir !== false &&
188
+ params.assertBeforeMutation === undefined && beforeParentAdmission === undefined
189
+ ? await prepareCompleteRootWriteParent(root, guardedTarget, params.relativePath)
190
+ : undefined;
191
+ const targetPath = params.mkdir === false
192
+ ? guardedTarget.targetPath
193
+ : preparedParent?.selectedTargetPath ?? await prepareRootWriteTarget(root, resolvedPath, params.assertBeforeMutation, mutationAdmission);
194
+ return { targetPath, mutationAdmission, preparedParent };
195
+ }
@@ -1,6 +1,8 @@
1
+ import { type RootBoundaryIdentity } from "./root-boundary.js";
1
2
  export declare function inheritWriteTargetMode(params: {
2
3
  targetPath: string;
3
4
  rootWithSep: string;
5
+ rootIdentity?: RootBoundaryIdentity;
4
6
  requestedMode?: number;
5
7
  }): Promise<number>;
6
8
  //# sourceMappingURL=root-write-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AAUA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClB"}
1
+ {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AAQA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAGpF,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDlB"}