@openclaw/fs-safe 0.12.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 (383) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +12 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +32 -11
  5. package/dist/advanced.d.ts +2 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -0
  8. package/dist/archive-input.d.ts.map +1 -1
  9. package/dist/archive-input.js +6 -2
  10. package/dist/archive-merge.d.ts.map +1 -1
  11. package/dist/archive-merge.js +15 -2
  12. package/dist/archive-native.d.ts.map +1 -1
  13. package/dist/archive-native.js +7 -19
  14. package/dist/archive-plan.js +1 -1
  15. package/dist/archive-read.d.ts.map +1 -1
  16. package/dist/archive-read.js +18 -13
  17. package/dist/archive-staging.d.ts.map +1 -1
  18. package/dist/archive-staging.js +43 -10
  19. package/dist/archive-tar-inspect.d.ts.map +1 -1
  20. package/dist/archive-tar-inspect.js +4 -1
  21. package/dist/archive-zip-admission.d.ts +1 -1
  22. package/dist/archive-zip-admission.d.ts.map +1 -1
  23. package/dist/archive-zip-admission.js +2 -2
  24. package/dist/archive-zip-directory.d.ts +3 -0
  25. package/dist/archive-zip-directory.d.ts.map +1 -1
  26. package/dist/archive-zip-directory.js +13 -2
  27. package/dist/archive-zip-loader.d.ts +3 -2
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +30 -4
  30. package/dist/archive-zip-manifest.d.ts +5 -0
  31. package/dist/archive-zip-manifest.d.ts.map +1 -0
  32. package/dist/archive-zip-manifest.js +22 -0
  33. package/dist/archive-zip-names.d.ts +6 -1
  34. package/dist/archive-zip-names.d.ts.map +1 -1
  35. package/dist/archive-zip-names.js +35 -14
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +3 -2
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -4
  40. package/dist/darwin-acl.d.ts +4 -0
  41. package/dist/darwin-acl.d.ts.map +1 -0
  42. package/dist/darwin-acl.js +24 -0
  43. package/dist/deny-mutations.d.ts.map +1 -1
  44. package/dist/deny-mutations.js +8 -2
  45. package/dist/directory-durability.d.ts.map +1 -1
  46. package/dist/directory-durability.js +67 -23
  47. package/dist/directory-entry-path.d.ts +3 -0
  48. package/dist/directory-entry-path.d.ts.map +1 -0
  49. package/dist/directory-entry-path.js +21 -0
  50. package/dist/directory-guard.d.ts +17 -1
  51. package/dist/directory-guard.d.ts.map +1 -1
  52. package/dist/directory-guard.js +134 -48
  53. package/dist/directory-mode-node.d.ts +12 -0
  54. package/dist/directory-mode-node.d.ts.map +1 -1
  55. package/dist/directory-mode-node.js +102 -4
  56. package/dist/effective-uid.d.ts +2 -0
  57. package/dist/effective-uid.d.ts.map +1 -0
  58. package/dist/effective-uid.js +25 -0
  59. package/dist/file-handle-transfer.d.ts.map +1 -1
  60. package/dist/file-handle-transfer.js +98 -27
  61. package/dist/file-hash.d.ts.map +1 -1
  62. package/dist/file-hash.js +3 -0
  63. package/dist/file-lock-sync.d.ts.map +1 -1
  64. package/dist/file-lock-sync.js +36 -6
  65. package/dist/file-lock.d.ts.map +1 -1
  66. package/dist/file-lock.js +29 -9
  67. package/dist/file-observation.d.ts +1 -1
  68. package/dist/file-observation.d.ts.map +1 -1
  69. package/dist/file-store-boundary.d.ts +6 -2
  70. package/dist/file-store-boundary.d.ts.map +1 -1
  71. package/dist/file-store-boundary.js +20 -65
  72. package/dist/file-store-copy-source.d.ts +5 -0
  73. package/dist/file-store-copy-source.d.ts.map +1 -0
  74. package/dist/file-store-copy-source.js +31 -0
  75. package/dist/file-store-path.d.ts.map +1 -1
  76. package/dist/file-store-path.js +4 -1
  77. package/dist/file-store-sync-directory.d.ts +16 -0
  78. package/dist/file-store-sync-directory.d.ts.map +1 -0
  79. package/dist/file-store-sync-directory.js +349 -0
  80. package/dist/file-store.d.ts.map +1 -1
  81. package/dist/file-store.js +11 -28
  82. package/dist/filename.d.ts.map +1 -1
  83. package/dist/filename.js +65 -20
  84. package/dist/fs.d.ts.map +1 -1
  85. package/dist/fs.js +3 -2
  86. package/dist/guarded-mkdir.d.ts +8 -0
  87. package/dist/guarded-mkdir.d.ts.map +1 -1
  88. package/dist/guarded-mkdir.js +176 -27
  89. package/dist/guest.d.ts.map +1 -1
  90. package/dist/guest.js +4 -1
  91. package/dist/home-dir.d.ts.map +1 -1
  92. package/dist/home-dir.js +73 -10
  93. package/dist/install-path.d.ts.map +1 -1
  94. package/dist/install-path.js +54 -19
  95. package/dist/json-durable-queue-ownership.d.ts +6 -0
  96. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  97. package/dist/json-durable-queue-ownership.js +89 -42
  98. package/dist/json-durable-queue-paths.d.ts +11 -0
  99. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  100. package/dist/json-durable-queue-paths.js +42 -0
  101. package/dist/json-durable-queue-read.d.ts.map +1 -1
  102. package/dist/json-durable-queue-read.js +2 -0
  103. package/dist/json-durable-queue.d.ts.map +1 -1
  104. package/dist/json-durable-queue.js +50 -58
  105. package/dist/json-store.d.ts.map +1 -1
  106. package/dist/json-store.js +5 -1
  107. package/dist/json.d.ts.map +1 -1
  108. package/dist/json.js +54 -22
  109. package/dist/local-file-access.d.ts.map +1 -1
  110. package/dist/local-file-access.js +4 -0
  111. package/dist/local-file-descriptor.d.ts +17 -0
  112. package/dist/local-file-descriptor.d.ts.map +1 -0
  113. package/dist/local-file-descriptor.js +84 -0
  114. package/dist/local-roots.d.ts.map +1 -1
  115. package/dist/local-roots.js +35 -7
  116. package/dist/move-path-cleanup.d.ts +2 -0
  117. package/dist/move-path-cleanup.d.ts.map +1 -1
  118. package/dist/move-path-cleanup.js +44 -18
  119. package/dist/move-path.d.ts.map +1 -1
  120. package/dist/move-path.js +31 -6
  121. package/dist/native-binding.d.ts +14 -0
  122. package/dist/native-binding.d.ts.map +1 -1
  123. package/dist/native-directory-observation.d.ts +17 -0
  124. package/dist/native-directory-observation.d.ts.map +1 -0
  125. package/dist/native-directory-observation.js +37 -0
  126. package/dist/native-parent-admission.d.ts +31 -0
  127. package/dist/native-parent-admission.d.ts.map +1 -0
  128. package/dist/native-parent-admission.js +124 -0
  129. package/dist/native-pinned-write-windows.d.ts +0 -1
  130. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  131. package/dist/native-pinned-write-windows.js +0 -3
  132. package/dist/native-pinned-write.d.ts.map +1 -1
  133. package/dist/native-pinned-write.js +309 -40
  134. package/dist/output.d.ts.map +1 -1
  135. package/dist/output.js +15 -5
  136. package/dist/overwrite-file-handle.d.ts.map +1 -1
  137. package/dist/overwrite-file-handle.js +5 -1
  138. package/dist/owner-dacl.d.ts.map +1 -1
  139. package/dist/owner-dacl.js +2 -0
  140. package/dist/path-policy.d.ts.map +1 -1
  141. package/dist/path-policy.js +7 -2
  142. package/dist/path-prefix.d.ts +7 -0
  143. package/dist/path-prefix.d.ts.map +1 -0
  144. package/dist/path-prefix.js +82 -0
  145. package/dist/path-scope-lexical.d.ts.map +1 -1
  146. package/dist/path-scope-lexical.js +18 -8
  147. package/dist/path-segment-route.d.ts +7 -0
  148. package/dist/path-segment-route.d.ts.map +1 -0
  149. package/dist/path-segment-route.js +24 -0
  150. package/dist/path-suffix-aliases.d.ts +10 -0
  151. package/dist/path-suffix-aliases.d.ts.map +1 -0
  152. package/dist/path-suffix-aliases.js +386 -0
  153. package/dist/path.d.ts.map +1 -1
  154. package/dist/path.js +16 -5
  155. package/dist/permissions-windows.d.ts.map +1 -1
  156. package/dist/permissions-windows.js +14 -3
  157. package/dist/permissions.d.ts.map +1 -1
  158. package/dist/permissions.js +37 -8
  159. package/dist/pinned-mutation-admission.d.ts +25 -0
  160. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  161. package/dist/pinned-mutation-admission.js +425 -0
  162. package/dist/pinned-mutation-observation.d.ts +34 -0
  163. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  164. package/dist/pinned-mutation-observation.js +142 -0
  165. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  166. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  167. package/dist/pinned-mutation-shared-route.js +70 -0
  168. package/dist/pinned-open.d.ts +6 -0
  169. package/dist/pinned-open.d.ts.map +1 -1
  170. package/dist/pinned-open.js +28 -10
  171. package/dist/pinned-write-types.d.ts +75 -0
  172. package/dist/pinned-write-types.d.ts.map +1 -0
  173. package/dist/pinned-write-types.js +1 -0
  174. package/dist/pinned-write.d.ts +7 -33
  175. package/dist/pinned-write.d.ts.map +1 -1
  176. package/dist/pinned-write.js +151 -17
  177. package/dist/private-directory.d.ts.map +1 -1
  178. package/dist/private-directory.js +2 -0
  179. package/dist/private-producer-handoff.d.ts +16 -0
  180. package/dist/private-producer-handoff.d.ts.map +1 -0
  181. package/dist/private-producer-handoff.js +272 -0
  182. package/dist/private-temp-workspace.d.ts +2 -39
  183. package/dist/private-temp-workspace.d.ts.map +1 -1
  184. package/dist/private-temp-workspace.js +183 -77
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +33 -29
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +56 -45
  189. package/dist/replace-directory.d.ts.map +1 -1
  190. package/dist/replace-directory.js +12 -5
  191. package/dist/replace-file-temp-owner.d.ts +1 -0
  192. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  193. package/dist/replace-file-temp-owner.js +12 -1
  194. package/dist/replace-file.d.ts.map +1 -1
  195. package/dist/replace-file.js +24 -24
  196. package/dist/root-boundary.d.ts +4 -0
  197. package/dist/root-boundary.d.ts.map +1 -1
  198. package/dist/root-boundary.js +6 -1
  199. package/dist/root-context.d.ts +12 -1
  200. package/dist/root-context.d.ts.map +1 -1
  201. package/dist/root-context.js +57 -11
  202. package/dist/root-directory-creation.d.ts +13 -0
  203. package/dist/root-directory-creation.d.ts.map +1 -0
  204. package/dist/root-directory-creation.js +212 -0
  205. package/dist/root-directory-list.d.ts +16 -4
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +180 -39
  208. package/dist/root-directory.d.ts +23 -0
  209. package/dist/root-directory.d.ts.map +1 -0
  210. package/dist/root-directory.js +141 -0
  211. package/dist/root-errors.d.ts.map +1 -1
  212. package/dist/root-errors.js +12 -1
  213. package/dist/root-file-final-admission.d.ts +21 -0
  214. package/dist/root-file-final-admission.d.ts.map +1 -0
  215. package/dist/root-file-final-admission.js +83 -0
  216. package/dist/root-file.d.ts.map +1 -1
  217. package/dist/root-file.js +103 -24
  218. package/dist/root-impl.d.ts +1 -0
  219. package/dist/root-impl.d.ts.map +1 -1
  220. package/dist/root-impl.js +289 -317
  221. package/dist/root-move-noreplace.d.ts +14 -0
  222. package/dist/root-move-noreplace.d.ts.map +1 -0
  223. package/dist/root-move-noreplace.js +202 -0
  224. package/dist/root-observed-path.d.ts +9 -0
  225. package/dist/root-observed-path.d.ts.map +1 -0
  226. package/dist/root-observed-path.js +95 -0
  227. package/dist/root-path-errors.d.ts +12 -0
  228. package/dist/root-path-errors.d.ts.map +1 -0
  229. package/dist/root-path-errors.js +13 -0
  230. package/dist/root-path-existing.d.ts.map +1 -1
  231. package/dist/root-path-existing.js +53 -20
  232. package/dist/root-path-observation.d.ts +63 -0
  233. package/dist/root-path-observation.d.ts.map +1 -0
  234. package/dist/root-path-observation.js +180 -0
  235. package/dist/root-path-stat.d.ts +5 -0
  236. package/dist/root-path-stat.d.ts.map +1 -0
  237. package/dist/root-path-stat.js +101 -0
  238. package/dist/root-path-symlink.d.ts.map +1 -1
  239. package/dist/root-path-symlink.js +23 -4
  240. package/dist/root-path.d.ts +11 -0
  241. package/dist/root-path.d.ts.map +1 -1
  242. package/dist/root-path.js +215 -53
  243. package/dist/root-paths-lexical.d.ts +9 -0
  244. package/dist/root-paths-lexical.d.ts.map +1 -0
  245. package/dist/root-paths-lexical.js +22 -0
  246. package/dist/root-paths.d.ts +3 -25
  247. package/dist/root-paths.d.ts.map +1 -1
  248. package/dist/root-paths.js +77 -154
  249. package/dist/root-read-admission.d.ts +26 -0
  250. package/dist/root-read-admission.d.ts.map +1 -0
  251. package/dist/root-read-admission.js +94 -0
  252. package/dist/root-remove-identity.d.ts +15 -0
  253. package/dist/root-remove-identity.d.ts.map +1 -0
  254. package/dist/root-remove-identity.js +89 -0
  255. package/dist/root-remove-receipt.d.ts +17 -0
  256. package/dist/root-remove-receipt.d.ts.map +1 -0
  257. package/dist/root-remove-receipt.js +37 -0
  258. package/dist/root-remove.d.ts +2 -1
  259. package/dist/root-remove.d.ts.map +1 -1
  260. package/dist/root-remove.js +172 -10
  261. package/dist/root-write-admission.d.ts +68 -0
  262. package/dist/root-write-admission.d.ts.map +1 -0
  263. package/dist/root-write-admission.js +322 -0
  264. package/dist/root-write-compatibility.d.ts +13 -0
  265. package/dist/root-write-compatibility.d.ts.map +1 -0
  266. package/dist/root-write-compatibility.js +74 -0
  267. package/dist/root-write-complete-parent.d.ts +42 -0
  268. package/dist/root-write-complete-parent.d.ts.map +1 -0
  269. package/dist/root-write-complete-parent.js +195 -0
  270. package/dist/root-write-publication.d.ts +24 -0
  271. package/dist/root-write-publication.d.ts.map +1 -0
  272. package/dist/root-write-publication.js +85 -0
  273. package/dist/root-write-verification.d.ts.map +1 -1
  274. package/dist/root-write-verification.js +6 -4
  275. package/dist/secret-file.d.ts.map +1 -1
  276. package/dist/secret-file.js +62 -24
  277. package/dist/secret-read-async.d.ts.map +1 -1
  278. package/dist/secret-read-async.js +18 -13
  279. package/dist/secret-read-policy.d.ts.map +1 -1
  280. package/dist/secret-read-policy.js +5 -1
  281. package/dist/secure-file.d.ts.map +1 -1
  282. package/dist/secure-file.js +92 -7
  283. package/dist/secure-temp-dir.d.ts +6 -3
  284. package/dist/secure-temp-dir.d.ts.map +1 -1
  285. package/dist/secure-temp-dir.js +121 -101
  286. package/dist/secure-temp-repair.d.ts +35 -0
  287. package/dist/secure-temp-repair.d.ts.map +1 -0
  288. package/dist/secure-temp-repair.js +106 -0
  289. package/dist/sibling-staged-file.d.ts +3 -2
  290. package/dist/sibling-staged-file.d.ts.map +1 -1
  291. package/dist/sibling-staged-file.js +179 -55
  292. package/dist/sibling-temp.d.ts.map +1 -1
  293. package/dist/sibling-temp.js +27 -13
  294. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  295. package/dist/sidecar-lock-acquire.js +44 -14
  296. package/dist/sidecar-lock-root.d.ts.map +1 -1
  297. package/dist/sidecar-lock-root.js +4 -2
  298. package/dist/staged-directory.d.ts +14 -5
  299. package/dist/staged-directory.d.ts.map +1 -1
  300. package/dist/staged-directory.js +54 -3
  301. package/dist/standalone-publication-path.d.ts +2 -0
  302. package/dist/standalone-publication-path.d.ts.map +1 -0
  303. package/dist/standalone-publication-path.js +6 -0
  304. package/dist/stat-observation.d.ts +11 -0
  305. package/dist/stat-observation.d.ts.map +1 -0
  306. package/dist/stat-observation.js +64 -0
  307. package/dist/strict-file-identity.d.ts.map +1 -1
  308. package/dist/strict-file-identity.js +39 -2
  309. package/dist/symlink-parents.d.ts.map +1 -1
  310. package/dist/symlink-parents.js +7 -2
  311. package/dist/temp-target.d.ts +2 -0
  312. package/dist/temp-target.d.ts.map +1 -1
  313. package/dist/temp-target.js +130 -3
  314. package/dist/temp-workspace-admission.d.ts +22 -0
  315. package/dist/temp-workspace-admission.d.ts.map +1 -0
  316. package/dist/temp-workspace-admission.js +374 -0
  317. package/dist/temp-workspace-child-admission.d.ts +13 -0
  318. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  319. package/dist/temp-workspace-child-admission.js +95 -0
  320. package/dist/temp-workspace-descriptor.d.ts +48 -0
  321. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  322. package/dist/temp-workspace-descriptor.js +363 -0
  323. package/dist/temp-workspace-identity.d.ts +15 -0
  324. package/dist/temp-workspace-identity.d.ts.map +1 -0
  325. package/dist/temp-workspace-identity.js +41 -0
  326. package/dist/temp-workspace-owner.d.ts +7 -6
  327. package/dist/temp-workspace-owner.d.ts.map +1 -1
  328. package/dist/temp-workspace-owner.js +121 -61
  329. package/dist/temp-workspace-permissions.d.ts +4 -0
  330. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  331. package/dist/temp-workspace-permissions.js +32 -0
  332. package/dist/temp-workspace-types.d.ts +40 -0
  333. package/dist/temp-workspace-types.d.ts.map +1 -0
  334. package/dist/temp-workspace-types.js +1 -0
  335. package/dist/temp.d.ts +1 -1
  336. package/dist/temp.d.ts.map +1 -1
  337. package/dist/temp.js +1 -1
  338. package/dist/test-hooks.d.ts +7 -0
  339. package/dist/test-hooks.d.ts.map +1 -1
  340. package/dist/text-atomic.d.ts.map +1 -1
  341. package/dist/text-atomic.js +3 -1
  342. package/dist/trash.d.ts.map +1 -1
  343. package/dist/trash.js +54 -8
  344. package/dist/walk.d.ts.map +1 -1
  345. package/dist/walk.js +9 -6
  346. package/dist/windows-owner.d.ts.map +1 -1
  347. package/dist/windows-owner.js +5 -0
  348. package/dist/windows-path-alias.d.ts +39 -0
  349. package/dist/windows-path-alias.d.ts.map +1 -0
  350. package/dist/windows-path-alias.js +153 -0
  351. package/docs/advanced.md +25 -0
  352. package/docs/archive.md +23 -2
  353. package/docs/atomic.md +27 -3
  354. package/docs/copy.md +3 -0
  355. package/docs/durability.md +14 -0
  356. package/docs/errors.md +14 -6
  357. package/docs/file-store.md +24 -3
  358. package/docs/filename.md +14 -7
  359. package/docs/install-path.md +2 -2
  360. package/docs/install.md +8 -7
  361. package/docs/json-store.md +5 -1
  362. package/docs/json.md +11 -0
  363. package/docs/mutation-policy-proof.md +65 -0
  364. package/docs/native-helper.md +26 -9
  365. package/docs/native.md +32 -8
  366. package/docs/output.md +33 -11
  367. package/docs/path-prefix.md +64 -0
  368. package/docs/path-suffix-aliases.md +159 -0
  369. package/docs/path.md +11 -0
  370. package/docs/private-file-store.md +14 -0
  371. package/docs/public-api.md +9 -0
  372. package/docs/quickstart.md +1 -1
  373. package/docs/reading.md +8 -1
  374. package/docs/root.md +18 -3
  375. package/docs/secret-file.md +3 -0
  376. package/docs/secure-file.md +6 -2
  377. package/docs/security-model.md +75 -8
  378. package/docs/sidecar-lock.md +28 -1
  379. package/docs/store.md +5 -1
  380. package/docs/temp.md +260 -36
  381. package/docs/test-hooks.md +14 -0
  382. package/docs/writing.md +38 -8
  383. package/package.json +10 -10
@@ -1,5 +1,6 @@
1
1
  import { type RootContext } from "./root-context.js";
2
2
  import type { RootRemoveOptions } from "./root-options.js";
3
+ import type { RemovalPathReceipts } from "./root-remove-receipt.js";
3
4
  export declare function validateRemoveOptions(options: RootRemoveOptions): void;
4
- export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions): Promise<void>;
5
+ export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions, receipts?: RemovalPathReceipts): Promise<void>;
5
6
  //# sourceMappingURL=root-remove.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAS3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AA0CD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
1
+ {"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AAsOD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
@@ -1,7 +1,7 @@
1
1
  import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
4
+ import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
5
5
  import { assertMutationNotDenied } from "./deny-mutations.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { sameFileIdentityForCleanup } from "./file-identity.js";
@@ -9,6 +9,7 @@ import { MutationAuthorityError } from "./mutation-authority.js";
9
9
  import { isNotFoundPathError, isPathInside } from "./path.js";
10
10
  import { assertRootIdentityCurrent } from "./root-context.js";
11
11
  import { normalizeRemoveGuardError, normalizeRemovePathError, rootPathChangedError } from "./root-errors.js";
12
+ import { assertRemovalDirectoryCurrent, createRemovalDirectoryAssertion, } from "./root-remove-identity.js";
12
13
  import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
13
14
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
14
15
  import { createSuppressedError } from "./suppressed-error.js";
@@ -41,22 +42,178 @@ function assertNotAborted(signal) {
41
42
  throw new MutationAuthorityError(error);
42
43
  }
43
44
  }
44
- async function removeOne(root, targetPath, options) {
45
- let guard;
45
+ function removalAncestorChanged(error) {
46
+ return new FsSafeError("path-mismatch", "removal ancestor changed during operation", {
47
+ cause: error instanceof Error ? error : undefined,
48
+ });
49
+ }
50
+ function sameCanonicalDirectory(left, right) {
51
+ return left === right || (isPathInside(left, right) && isPathInside(right, left));
52
+ }
53
+ function assertExactRemovalReceiptPrefixCurrent(rootGuard, intermediates) {
54
+ try {
55
+ assertSyncDirectoryGuard(rootGuard);
56
+ for (const receipt of intermediates) {
57
+ inspectDirectoryIdentitySync(receipt.path, receipt);
58
+ }
59
+ }
60
+ catch (error) {
61
+ // force tolerates a vanished leaf, never a lost traversal boundary.
62
+ throw removalAncestorChanged(error);
63
+ }
64
+ }
65
+ async function assertMissingRemovalPrefixCurrent(rootGuard, intermediates) {
66
+ if (intermediates.length === 0) {
67
+ assertExactRemovalReceiptPrefixCurrent(rootGuard, intermediates);
68
+ return;
69
+ }
70
+ const deepest = intermediates[intermediates.length - 1];
71
+ assertExactRemovalReceiptPrefixCurrent(rootGuard, intermediates.slice(0, -1));
72
+ let deepestGuard;
73
+ try {
74
+ deepestGuard = await createAsyncDirectoryGuard(deepest.path, { bigint: true });
75
+ }
76
+ catch (error) {
77
+ throw removalAncestorChanged(error);
78
+ }
79
+ if (!sameFileIdentityForCleanup(deepestGuard.stat, deepest) ||
80
+ !sameCanonicalDirectory(deepestGuard.realPath, deepest.path)) {
81
+ throw removalAncestorChanged();
82
+ }
83
+ }
84
+ async function captureNonrecursiveRemovalAdmission(root, targetPath, options, receipts) {
85
+ const parentPath = path.dirname(targetPath);
86
+ if (!isPathInside(root.rootReal, parentPath)) {
87
+ throw new FsSafeError("path-mismatch", "removal parent is outside the retained root");
88
+ }
89
+ const retained = receipts?.complete(root.rootReal, targetPath);
90
+ let rootGuard;
46
91
  try {
47
- guard = await createAsyncDirectoryGuard(path.dirname(targetPath));
92
+ // A complete, exactly spelled parent canonicalization below also proves
93
+ // Root's canonical route. Retain Root's earlier exact identity either way.
94
+ rootGuard = retained && parentPath !== root.rootReal
95
+ ? { dir: root.rootReal, realPath: root.rootReal, stat: retained.rootStat }
96
+ : await createAsyncDirectoryGuard(root.rootReal, { bigint: true, initial: retained?.rootStat });
48
97
  }
49
98
  catch (error) {
50
- if (options.force && isNotFoundPathError(error)) {
99
+ // force may tolerate a missing target or parent, never a missing Root.
100
+ if (options.force && isNotFoundPathError(error))
51
101
  await assertRootIdentityCurrent(root);
102
+ throw normalizeRemoveGuardError(error);
103
+ }
104
+ if (!sameFileIdentityForCleanup(rootGuard.stat, root.rootIdentity))
105
+ throw rootPathChangedError();
106
+ if (parentPath === root.rootReal) {
107
+ const rootAssertion = createRemovalDirectoryAssertion(rootGuard.dir, rootGuard.stat, rootGuard.realPath);
108
+ return Object.freeze({
109
+ assertAfterMutation() {
110
+ // Here Root is also the immediate parent, so retain its established
111
+ // post-dispatch error classification while checking it only once.
112
+ assertRemovalDirectoryCurrent(rootAssertion);
113
+ },
114
+ assertCurrent() {
115
+ try {
116
+ assertRemovalDirectoryCurrent(rootAssertion);
117
+ }
118
+ catch (error) {
119
+ throw removalAncestorChanged(error);
120
+ }
121
+ },
122
+ });
123
+ }
124
+ const intermediates = [];
125
+ const retainedDirectories = retained?.directories;
126
+ const freshSegments = retainedDirectories
127
+ ? undefined
128
+ : path.relative(root.rootReal, parentPath).split(path.sep).filter(Boolean).slice(0, -1);
129
+ const intermediateCount = retainedDirectories ? Math.max(0, retainedDirectories.length - 1) : freshSegments.length;
130
+ let freshAncestor = root.rootReal;
131
+ for (let index = 0; index < intermediateCount; index += 1) {
132
+ const retainedDirectory = retainedDirectories?.[index];
133
+ const ancestor = retainedDirectory?.path ?? (freshAncestor = path.join(freshAncestor, freshSegments[index]));
134
+ try {
135
+ const stat = inspectDirectoryIdentitySync(ancestor, undefined, retainedDirectory?.stat);
136
+ intermediates.push(createRemovalDirectoryAssertion(ancestor, stat));
137
+ }
138
+ catch (error) {
139
+ if (isNotFoundPathError(error)) {
140
+ await assertMissingRemovalPrefixCurrent(rootGuard, intermediates);
141
+ assertNotAborted(options.signal);
142
+ if (options.force)
143
+ return undefined;
144
+ }
145
+ throw normalizeRemoveGuardError(error);
146
+ }
147
+ }
148
+ let parentGuard;
149
+ try {
150
+ parentGuard = await createAsyncDirectoryGuard(retained?.parent?.path ?? parentPath, {
151
+ bigint: true,
152
+ initial: retained?.parent?.stat,
153
+ });
154
+ }
155
+ catch (error) {
156
+ if (isNotFoundPathError(error)) {
157
+ await assertMissingRemovalPrefixCurrent(rootGuard, intermediates);
52
158
  assertNotAborted(options.signal);
53
- return;
159
+ if (options.force)
160
+ return undefined;
54
161
  }
55
162
  throw normalizeRemoveGuardError(error);
56
163
  }
164
+ if (!sameCanonicalDirectory(parentGuard.realPath, parentPath)) {
165
+ throw removalAncestorChanged();
166
+ }
167
+ const canonicalParentCoversRoot = retained !== undefined && parentGuard.realPath === parentPath;
168
+ if (retained && !canonicalParentCoversRoot) {
169
+ try {
170
+ rootGuard = await createAsyncDirectoryGuard(root.rootReal, { bigint: true, initial: retained.rootStat });
171
+ }
172
+ catch (error) {
173
+ if (options.force && isNotFoundPathError(error))
174
+ await assertRootIdentityCurrent(root);
175
+ throw normalizeRemoveGuardError(error);
176
+ }
177
+ }
178
+ const rootAssertion = createRemovalDirectoryAssertion(rootGuard.dir, rootGuard.stat, canonicalParentCoversRoot ? undefined : rootGuard.realPath);
179
+ const parentAssertion = createRemovalDirectoryAssertion(parentGuard.dir, parentGuard.stat, parentGuard.realPath);
180
+ const assertPrefixCurrent = () => {
181
+ try {
182
+ assertRemovalDirectoryCurrent(rootAssertion);
183
+ for (const assertion of intermediates)
184
+ assertRemovalDirectoryCurrent(assertion);
185
+ }
186
+ catch (error) {
187
+ throw removalAncestorChanged(error);
188
+ }
189
+ };
190
+ return Object.freeze({
191
+ assertAfterMutation() {
192
+ assertPrefixCurrent();
193
+ // Preserve the established nonrecursive post-dispatch mapping for the
194
+ // immediate parent: disappearance is `not-found`, while replacements
195
+ // retain their directory-guard classification. Earlier lost ancestry
196
+ // remains a fail-closed `path-mismatch`.
197
+ assertRemovalDirectoryCurrent(parentAssertion);
198
+ },
199
+ assertCurrent() {
200
+ assertPrefixCurrent();
201
+ try {
202
+ assertRemovalDirectoryCurrent(parentAssertion);
203
+ }
204
+ catch (error) {
205
+ throw removalAncestorChanged(error);
206
+ }
207
+ },
208
+ });
209
+ }
210
+ async function removeOne(root, targetPath, options, receipts) {
211
+ const admission = await captureNonrecursiveRemovalAdmission(root, targetPath, options, receipts);
212
+ if (!admission)
213
+ return;
57
214
  try {
58
215
  await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", targetPath);
59
- await assertAsyncDirectoryGuard(guard);
216
+ admission.assertCurrent();
60
217
  }
61
218
  catch (error) {
62
219
  throw normalizeRemoveGuardError(error);
@@ -72,13 +229,18 @@ async function removeOne(root, targetPath, options) {
72
229
  if (!(options.force && isNotFoundPathError(error)))
73
230
  throw normalizeRemovePathError(error);
74
231
  }
75
- await assertAsyncDirectoryGuard(guard).catch(error => { throw normalizeRemoveGuardError(error); });
232
+ try {
233
+ admission.assertAfterMutation();
234
+ }
235
+ catch (error) {
236
+ throw normalizeRemoveGuardError(error);
237
+ }
76
238
  assertNotAborted(options.signal);
77
239
  }
78
- export async function removePathInRootFallback(root, targetPath, options) {
240
+ export async function removePathInRootFallback(root, targetPath, options, receipts) {
79
241
  assertNotAborted(options.signal);
80
242
  if (!options.recursive) {
81
- await removeOne(root, targetPath, options);
243
+ await removeOne(root, targetPath, options, receipts);
82
244
  return;
83
245
  }
84
246
  const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
@@ -0,0 +1,68 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ import { type DenyMutationPolicy } from "./deny-mutations.js";
3
+ import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
5
+ import type { RootContext } from "./root-context.js";
6
+ import { resolvePathInRoot } from "./root-context.js";
7
+ import { type MutationSymlinkPolicy } from "./root-symlink-policy.js";
8
+ import { type PreparedRootWriteParent } from "./root-write-complete-parent.js";
9
+ export type PinnedWriteTarget = Readonly<{
10
+ rootReal: string;
11
+ targetPath: string;
12
+ relativeParentPath: string;
13
+ basename: string;
14
+ mode: number;
15
+ mutationAdmission?: PinnedWriteMutationAdmission;
16
+ }>;
17
+ export type GuardedWritePath = Awaited<ReturnType<typeof resolvePathInRoot>>;
18
+ export type SelectedRootWriteTargetAdmission = Readonly<{
19
+ authorize(selectedTargetPath: string): Promise<void>;
20
+ }>;
21
+ export type GuardedRootWriteTarget = Readonly<{
22
+ resolvedPath: GuardedWritePath;
23
+ targetPath: string;
24
+ mutationAdmission?: PinnedWriteMutationAdmission;
25
+ selectedTargetAdmission?: SelectedRootWriteTargetAdmission;
26
+ }>;
27
+ export type RootWritePathSelection = Readonly<{
28
+ mutationAdmission: PinnedWriteMutationAdmission;
29
+ selectedTargetAdmission: SelectedRootWriteTargetAdmission;
30
+ operationTargetPath: string;
31
+ selectedPath: string;
32
+ parentGuard: AnyAsyncDirectoryGuard;
33
+ }>;
34
+ export type RetainedRootWriteSelection = RootWritePathSelection & Readonly<{
35
+ identity: Readonly<Pick<BigIntStats, "dev" | "ino">>;
36
+ }>;
37
+ export declare function retainRootWriteSelection(owner: object, selection: RetainedRootWriteSelection): void;
38
+ export declare function createRootWriteSelectionForFd(selection: RootWritePathSelection, fd: number): RetainedRootWriteSelection;
39
+ export declare function takeRootWriteSelection(owner: object): RetainedRootWriteSelection | undefined;
40
+ export declare function assertRootWritePathSelectionSync(root: RootContext, selection: RootWritePathSelection): void;
41
+ export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): void;
42
+ export declare function prepareRootWritePathSelection(params: Omit<RootWritePathSelection, "parentGuard"> & Readonly<{
43
+ preparedParent?: PreparedRootWriteParent;
44
+ }>): Promise<RootWritePathSelection>;
45
+ export declare function prepareGuardedRootWritePathSelection(guarded: {
46
+ mutationAdmission?: PinnedWriteMutationAdmission;
47
+ selectedTargetAdmission?: SelectedRootWriteTargetAdmission;
48
+ } | undefined, selectedPath: string, operationTargetPath?: string, preparedParent?: PreparedRootWriteParent): Promise<RootWritePathSelection | undefined>;
49
+ export declare function refreshRootWritePathSelection(selection: RootWritePathSelection): Promise<void>;
50
+ /**
51
+ * Re-authorize a retained shared-JavaScript destination without recapturing
52
+ * either its pathname binding or its exact parent fence.
53
+ */
54
+ export declare function refreshRootWriteSelection(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): Promise<void>;
55
+ export declare function refreshRetainedRootWriteAdmission(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): Promise<void>;
56
+ type GuardedWritePathOptions = {
57
+ relativePath: string;
58
+ denyMutations?: DenyMutationPolicy;
59
+ mutationSymlinks?: MutationSymlinkPolicy;
60
+ allowFinalSymlink?: boolean;
61
+ protectDeniedAncestors?: boolean;
62
+ shouldAssertNoPathAlias?: (resolved: GuardedWritePath) => Promise<boolean> | boolean;
63
+ };
64
+ export declare function resolveGuardedWritePathInRoot(root: RootContext, params: GuardedWritePathOptions): Promise<GuardedWritePath>;
65
+ export declare function resolveGuardedWriteTargetInRoot(root: RootContext, params: GuardedWritePathOptions): Promise<GuardedRootWriteTarget>;
66
+ export declare function resolvePinnedWriteTargetInRoot(root: RootContext, relativePath: string, requestedMode?: number, denyMutations?: DenyMutationPolicy, overwrite?: boolean, mutationSymlinks?: MutationSymlinkPolicy): Promise<PinnedWriteTarget>;
67
+ export {};
68
+ //# sourceMappingURL=root-write-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"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"}
@@ -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"}