@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,182 @@
1
+ import path from "node:path";
2
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
3
+ import { isPathRelativeEscape } from "./path.js";
4
+ import { realpathSync } from "./realpath.js";
5
+ function windowsComparablePath(value, normalize) {
6
+ const original = normalize
7
+ ? path.win32.normalize(value.replaceAll("/", "\\"))
8
+ : value.replaceAll("/", "\\");
9
+ if (/^\\\\\?\\UNC\\/i.test(original)) {
10
+ return {
11
+ original,
12
+ comparison: `\\\\${original.slice(8)}`,
13
+ originalIndex: (comparisonIndex) => comparisonIndex + 6,
14
+ };
15
+ }
16
+ if (original.startsWith("\\\\?\\")) {
17
+ return {
18
+ original,
19
+ comparison: original.slice(4),
20
+ originalIndex: (comparisonIndex) => comparisonIndex + 4,
21
+ };
22
+ }
23
+ return { original, comparison: original, originalIndex: (comparisonIndex) => comparisonIndex };
24
+ }
25
+ function trimWindowsTrailingSeparators(value) {
26
+ const rootLength = path.win32.parse(value).root.length;
27
+ let end = value.length;
28
+ while (end > rootLength && value[end - 1] === "\\")
29
+ end -= 1;
30
+ return value.slice(0, end);
31
+ }
32
+ function trimWindowsLeadingSeparators(value) {
33
+ let start = 0;
34
+ while (value[start] === "\\")
35
+ start += 1;
36
+ return value.slice(start);
37
+ }
38
+ function exactWindowsRootPrefix(rootPath, candidatePath) {
39
+ const root = trimWindowsTrailingSeparators(rootPath.replaceAll("/", "\\"));
40
+ const candidate = candidatePath.replaceAll("/", "\\");
41
+ let relativePath;
42
+ if (candidate === root) {
43
+ relativePath = "";
44
+ }
45
+ else {
46
+ const rootWithSep = root.endsWith("\\") ? root : `${root}\\`;
47
+ if (!candidate.startsWith(rootWithSep))
48
+ return undefined;
49
+ relativePath = trimWindowsLeadingSeparators(candidate.slice(rootWithSep.length));
50
+ }
51
+ return {
52
+ admission: "exact",
53
+ candidateRootPath: root,
54
+ path: relativePath === "" ? root : `${root}${root.endsWith("\\") ? "" : "\\"}${relativePath}`,
55
+ relativePath,
56
+ };
57
+ }
58
+ function windowsRootPrefix(rootPath, candidatePath) {
59
+ const exact = exactWindowsRootPrefix(rootPath, candidatePath);
60
+ if (exact)
61
+ return exact;
62
+ const trusted = windowsComparablePath(rootPath, true);
63
+ const supplied = windowsComparablePath(candidatePath, false);
64
+ const root = trimWindowsTrailingSeparators(trusted.original);
65
+ const rootComparison = trimWindowsTrailingSeparators(trusted.comparison);
66
+ const candidate = supplied.comparison;
67
+ if (!path.win32.isAbsolute(supplied.original))
68
+ return undefined;
69
+ let relativePath;
70
+ let candidateRootPath;
71
+ let admission;
72
+ if (candidate === rootComparison) {
73
+ relativePath = "";
74
+ candidateRootPath = supplied.original;
75
+ admission = "exact";
76
+ }
77
+ else if (candidate.toLowerCase() === rootComparison.toLowerCase()) {
78
+ relativePath = "";
79
+ candidateRootPath = supplied.original;
80
+ admission = "identity";
81
+ }
82
+ else {
83
+ const rootWithSep = rootComparison.endsWith("\\") ? rootComparison : `${rootComparison}\\`;
84
+ const candidatePrefix = candidate.slice(0, rootWithSep.length);
85
+ if (candidatePrefix === rootWithSep) {
86
+ admission = "exact";
87
+ }
88
+ else if (candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()) {
89
+ admission = "identity";
90
+ }
91
+ else {
92
+ return undefined;
93
+ }
94
+ relativePath = trimWindowsLeadingSeparators(candidate.slice(rootWithSep.length));
95
+ candidateRootPath = supplied.original.slice(0, supplied.originalIndex(rootComparison.length));
96
+ }
97
+ const rebasedPath = relativePath === ""
98
+ ? root
99
+ : `${root}${root.endsWith("\\") ? "" : "\\"}${relativePath}`;
100
+ return { candidateRootPath, path: rebasedPath, relativePath, admission };
101
+ }
102
+ function exactRootIdentity(rootPath, expected) {
103
+ if (typeof expected?.dev === "bigint" && typeof expected.ino === "bigint") {
104
+ return { dev: expected.dev, ino: expected.ino };
105
+ }
106
+ const observed = inspectDirectoryIdentitySync(rootPath);
107
+ return { dev: observed.dev, ino: observed.ino };
108
+ }
109
+ /**
110
+ * Admit a path at a trusted directory boundary.
111
+ *
112
+ * On Windows, an exact structural prefix is allocation-only and performs no
113
+ * filesystem observation. A match which depends on case folding is accepted
114
+ * only when the candidate prefix names the exact trusted root object. Accepted
115
+ * paths are always returned under the trusted root spelling.
116
+ */
117
+ export function admitPathInsideRoot(params) {
118
+ // Some platform-fallback tests intentionally spoof process.platform while
119
+ // retaining Node's POSIX path module. A single-slash absolute root remains
120
+ // unambiguously POSIX; double-slash and slash-backslash roots are reserved
121
+ // for the synthetic Windows namespace cases below.
122
+ const usesPosixBoundary = process.platform !== "win32" || (path.sep === "/" &&
123
+ params.rootPath.startsWith("/") &&
124
+ params.rootPath[1] !== "/" &&
125
+ params.rootPath[1] !== "\\");
126
+ if (usesPosixBoundary) {
127
+ if (params.rootPath.startsWith("/") &&
128
+ params.candidatePath.startsWith("/") &&
129
+ !params.candidatePath.includes("/..") &&
130
+ (params.candidatePath === params.rootPath ||
131
+ (params.candidatePath.startsWith(params.rootPath) &&
132
+ (params.rootPath.endsWith("/") || params.candidatePath[params.rootPath.length] === "/")))) {
133
+ const relativeStart = params.rootPath.endsWith("/")
134
+ ? params.rootPath.length
135
+ : params.rootPath.length + 1;
136
+ return {
137
+ admission: "exact",
138
+ path: params.candidatePath,
139
+ relativePath: params.candidatePath === params.rootPath
140
+ ? ""
141
+ : params.candidatePath.slice(relativeStart),
142
+ };
143
+ }
144
+ const root = path.resolve(params.rootPath);
145
+ const candidate = path.resolve(params.candidatePath);
146
+ const relativePath = path.relative(root, candidate);
147
+ if (isPathRelativeEscape(relativePath))
148
+ return undefined;
149
+ return { path: candidate, relativePath: relativePath === "." ? "" : relativePath, admission: "exact" };
150
+ }
151
+ const match = windowsRootPrefix(params.rootPath, params.candidatePath);
152
+ if (!match)
153
+ return undefined;
154
+ const admitted = () => ({
155
+ admission: match.admission,
156
+ path: match.path,
157
+ relativePath: match.relativePath,
158
+ });
159
+ if (match.admission === "exact")
160
+ return admitted();
161
+ const cached = params.identityCache?.get(match.candidateRootPath);
162
+ if (cached !== undefined)
163
+ return cached ? admitted() : undefined;
164
+ try {
165
+ const expected = exactRootIdentity(params.rootPath, params.rootIdentity);
166
+ const candidateRootPath = params.resolveCandidateRoot
167
+ ? realpathSync.native(match.candidateRootPath)
168
+ : match.candidateRootPath;
169
+ if (params.inspectCandidateRoot) {
170
+ params.inspectCandidateRoot(candidateRootPath, expected);
171
+ }
172
+ else {
173
+ inspectDirectoryIdentitySync(candidateRootPath, expected);
174
+ }
175
+ params.identityCache?.set(match.candidateRootPath, true);
176
+ return admitted();
177
+ }
178
+ catch {
179
+ params.identityCache?.set(match.candidateRootPath, false);
180
+ return undefined;
181
+ }
182
+ }
@@ -1,5 +1,8 @@
1
+ import fs, { type BigIntStats } from "node:fs";
2
+ import { type AsyncDirectoryGuard, type DirectoryObservationGuard } from "./directory-guard.js";
1
3
  export type RootContext = {
2
4
  rootDir: string;
5
+ rootGuard?: AsyncDirectoryGuard<BigIntStats>;
3
6
  rootIdentity: {
4
7
  dev: number;
5
8
  ino: number;
@@ -16,7 +19,15 @@ export declare function assertValidRootDestinationPath(relativePath: string): vo
16
19
  export declare function expandRelativePathWithHome(relativePath: string): Promise<string>;
17
20
  export declare function resolveRootContext(rootDir: string): Promise<RootContext>;
18
21
  export declare function rootRelativeReadPath(root: RootContext, filePath: string): string;
19
- export declare function assertRootIdentityCurrent(root: RootContext): Promise<void>;
22
+ export declare function assertRootIdentityCurrentSync(root: RootContext, observe?: (stat: fs.BigIntStats) => void): void;
23
+ export declare function assertRootIdentityCurrent(root: RootContext, observe?: (stat: fs.BigIntStats) => void): Promise<void>;
24
+ /**
25
+ * Observe the current Root with an exact, operation-local receipt.
26
+ *
27
+ * This is deliberately separate from {@link assertRootIdentityCurrent}: callers
28
+ * must not retain the returned guard beyond the operation that requested it.
29
+ */
30
+ export declare function createRootObservationGuard(root: RootContext): Promise<DirectoryObservationGuard | undefined>;
20
31
  export declare function resolvePathInRoot(root: RootContext, relativePath: string, options?: {
21
32
  aliasErrorCode?: "outside-workspace" | "path-alias";
22
33
  allowFinalSymlink?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAetF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA4B9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBhF;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhF;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAqDtE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEvE"}
1
+ {"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7C,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAQF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,IAAI,CAmBN;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAYhD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuEtE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEvE"}
@@ -1,18 +1,24 @@
1
- import fs from "node:fs";
1
+ import fs, {} from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { inspectDirectoryIdentity } from "./directory-guard.js";
4
+ import { extendDirectoryObservationGuard, inspectDirectoryIdentitySync, inspectDirectoryObservationSync, } from "./directory-guard.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { sameFileIdentity } from "./file-identity.js";
7
7
  import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isPathInside, } from "./path.js";
8
8
  import { ROOT_PATH_ALIAS_POLICIES, resolveRootPath } from "./root-path.js";
9
+ import { admitPathInsideRoot } from "./root-boundary.js";
9
10
  import { outsideWorkspaceError, rootPathChangedError } from "./root-errors.js";
10
11
  import { isDriveRelativePath } from "./safe-path-segment.js";
11
12
  import { realpathSync } from "./realpath.js";
12
13
  import { inspectFileIdentity } from "./strict-file-identity.js";
14
+ import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathFromBasePreservingWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
15
+ function hasExactRootIdentity(identity) {
16
+ return typeof identity.dev === "bigint" && typeof identity.ino === "bigint";
17
+ }
13
18
  export const ensureTrailingSep = (value) => value.endsWith(path.sep) ? value : value + path.sep;
14
19
  export function assertValidRootRelativePath(relativePath) {
15
20
  assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
21
+ assertNoWindowsPathAlias(relativePath, "filesystem", "relative path uses a Windows filesystem namespace alias");
16
22
  }
17
23
  export function assertValidRootDestinationPath(relativePath) {
18
24
  assertValidRootRelativePath(relativePath);
@@ -26,29 +32,42 @@ const WINDOWS_PARENT_COMPONENT = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
26
32
  export async function expandRelativePathWithHome(relativePath) {
27
33
  const homeOnly = relativePath === "~";
28
34
  const homePrefix = relativePath.startsWith("~/") || (path.sep === "\\" && relativePath.startsWith("~\\"));
29
- if (!homeOnly && !homePrefix)
35
+ if (!homeOnly && !homePrefix) {
36
+ assertNoWindowsPathAlias(relativePath, "filesystem", "relative path uses a Windows filesystem namespace alias");
30
37
  return relativePath;
38
+ }
31
39
  const rawHome = process.env.HOME || process.env.USERPROFILE || os.homedir();
40
+ assertNoWindowsPathAlias(rawHome, "filesystem", "home path uses a Windows filesystem namespace alias");
32
41
  if (cachedHomePath?.raw !== rawHome) {
33
42
  let realHome = rawHome;
34
43
  try {
35
- realHome = realpathSync.native(rawHome);
44
+ realHome = realpathSync.native(pathForWindowsFilesystem(rawHome));
36
45
  }
37
46
  catch {
38
47
  // If the home dir cannot be canonicalized, keep lexical expansion behavior.
39
48
  }
40
49
  cachedHomePath = { raw: rawHome, real: realHome };
41
50
  }
42
- return homeOnly ? cachedHomePath.real : `${ensureTrailingSep(cachedHomePath.real)}${relativePath.slice(2)}`;
51
+ if (homeOnly) {
52
+ assertNoWindowsPathAlias(cachedHomePath.real, "filesystem", "home path uses a Windows filesystem namespace alias");
53
+ return cachedHomePath.real;
54
+ }
55
+ const expanded = `${ensureTrailingSep(cachedHomePath.real)}${relativePath.slice(2)}`;
56
+ assertNoWindowsPathAlias(expanded, "filesystem", "expanded path uses a Windows filesystem namespace alias");
57
+ return expanded;
43
58
  }
44
59
  export async function resolveRootContext(rootDir) {
45
60
  assertNoNulPathInput(rootDir, "root dir contains a NUL byte");
46
- const lexicalRoot = path.resolve(rootDir);
61
+ assertNoWindowsPathAlias(rootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
62
+ const lexicalRoot = resolvePathPreservingWindowsRoot(rootDir);
63
+ assertNoWindowsPathAlias(lexicalRoot, "filesystem", "root dir uses a Windows filesystem namespace alias");
47
64
  let rootReal;
65
+ let rootStat;
48
66
  let rootIdentity;
49
67
  try {
50
- rootReal = realpathSync.native(rootDir);
51
- const rootStat = await inspectFileIdentity(() => {
68
+ rootReal = realpathSync.native(pathForWindowsFilesystem(rootDir));
69
+ assertNoWindowsPathAlias(rootReal, "filesystem", "canonical root path uses a Windows filesystem namespace alias");
70
+ rootStat = await inspectFileIdentity(() => {
52
71
  const stat = fs.statSync(rootReal, { bigint: true });
53
72
  if (!stat.isDirectory())
54
73
  throw new FsSafeError("invalid-path", "root dir is not a directory");
@@ -67,6 +86,7 @@ export async function resolveRootContext(rootDir) {
67
86
  }
68
87
  return {
69
88
  rootDir: lexicalRoot,
89
+ rootGuard: { dir: rootReal, realPath: rootReal, stat: rootStat },
70
90
  rootIdentity,
71
91
  rootReal,
72
92
  rootWithSep: ensureTrailingSep(rootReal),
@@ -77,11 +97,21 @@ export function rootRelativeReadPath(root, filePath) {
77
97
  if (!absoluteInput)
78
98
  return filePath;
79
99
  const raw = process.platform === "win32" ? filePath.replaceAll("/", path.sep) : filePath;
80
- for (const base of [root.rootDir, root.rootReal]) {
100
+ const bases = root.rootDir === root.rootReal ? [root.rootReal] : [root.rootDir, root.rootReal];
101
+ for (const base of bases) {
102
+ if (process.platform === "win32") {
103
+ const admitted = admitPathInsideRoot({
104
+ rootPath: base,
105
+ candidatePath: raw,
106
+ rootIdentity: root.rootIdentity,
107
+ resolveCandidateRoot: base === root.rootDir && root.rootDir !== root.rootReal,
108
+ });
109
+ if (admitted)
110
+ return admitted.relativePath;
111
+ continue;
112
+ }
81
113
  const prefix = ensureTrailingSep(base);
82
- const matches = process.platform === "win32"
83
- ? raw.slice(0, prefix.length).toLowerCase() === prefix.toLowerCase()
84
- : raw.startsWith(prefix);
114
+ const matches = raw.startsWith(prefix);
85
115
  if (matches) {
86
116
  let start = prefix.length;
87
117
  while (raw[start] === path.sep)
@@ -91,11 +121,12 @@ export function rootRelativeReadPath(root, filePath) {
91
121
  }
92
122
  return raw;
93
123
  }
94
- export async function assertRootIdentityCurrent(root) {
124
+ export function assertRootIdentityCurrentSync(root, observe) {
95
125
  let current;
96
126
  try {
97
127
  if (typeof root.rootIdentity.dev === "bigint" && typeof root.rootIdentity.ino === "bigint") {
98
- await inspectDirectoryIdentity(root.rootReal, { dev: root.rootIdentity.dev, ino: root.rootIdentity.ino });
128
+ const stat = inspectDirectoryIdentitySync(root.rootReal, { dev: root.rootIdentity.dev, ino: root.rootIdentity.ino });
129
+ observe?.(stat);
99
130
  return;
100
131
  }
101
132
  current = fs.lstatSync(root.rootReal);
@@ -109,11 +140,36 @@ export async function assertRootIdentityCurrent(root) {
109
140
  throw rootPathChangedError();
110
141
  }
111
142
  }
143
+ export async function assertRootIdentityCurrent(root, observe) {
144
+ assertRootIdentityCurrentSync(root, observe);
145
+ }
146
+ /**
147
+ * Observe the current Root with an exact, operation-local receipt.
148
+ *
149
+ * This is deliberately separate from {@link assertRootIdentityCurrent}: callers
150
+ * must not retain the returned guard beyond the operation that requested it.
151
+ */
152
+ export async function createRootObservationGuard(root) {
153
+ const rootIdentity = root.rootIdentity;
154
+ if (!hasExactRootIdentity(rootIdentity)) {
155
+ await assertRootIdentityCurrent(root);
156
+ return undefined;
157
+ }
158
+ try {
159
+ const observed = await inspectDirectoryObservationSync(root.rootReal, rootIdentity);
160
+ return extendDirectoryObservationGuard(observed, root.rootReal, root.rootReal);
161
+ }
162
+ catch (error) {
163
+ throw rootPathChangedError(error instanceof Error ? error : undefined);
164
+ }
165
+ }
112
166
  export async function resolvePathInRoot(root, relativePath, options) {
113
167
  assertValidRootRelativePath(relativePath);
114
168
  await assertRootIdentityCurrent(root);
115
169
  const expanded = await expandRelativePathWithHome(relativePath);
116
- let resolved = path.resolve(root.rootWithSep, expanded);
170
+ assertNoWindowsPathAlias(expanded, "filesystem", "expanded path uses a Windows filesystem namespace alias");
171
+ let resolved = resolvePathFromBasePreservingWindowsRoot(root.rootWithSep, expanded);
172
+ assertNoWindowsPathAlias(resolved, "filesystem", "resolved path uses a Windows filesystem namespace alias");
117
173
  if (!options?.resolveCanonical && !isPathInside(root.rootWithSep, resolved)) {
118
174
  throw outsideWorkspaceError();
119
175
  }
@@ -123,11 +179,13 @@ export async function resolvePathInRoot(root, relativePath, options) {
123
179
  const rawAbsolutePath = path.isAbsolute(expanded)
124
180
  ? expanded
125
181
  : `${root.rootWithSep}${expanded}`;
182
+ assertNoWindowsPathAlias(rawAbsolutePath, "filesystem", "resolved path uses a Windows filesystem namespace alias");
126
183
  try {
127
184
  const resolution = {
128
185
  absolutePath: rawAbsolutePath,
129
186
  rootPath: root.rootReal,
130
187
  rootCanonicalPath: root.rootReal,
188
+ rootIdentity: root.rootIdentity,
131
189
  boundaryLabel: "root",
132
190
  policy: options?.allowFinalSymlink ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
133
191
  rejectSymlinks: options?.rejectSymlinks,
@@ -142,8 +200,23 @@ export async function resolvePathInRoot(root, relativePath, options) {
142
200
  throw new FsSafeError("path-alias", "parent traversal resolves differently through a symlink");
143
201
  }
144
202
  }
145
- if (options?.resolveCanonical)
203
+ if (options?.resolveCanonical) {
146
204
  resolved = checked.canonicalPath;
205
+ }
206
+ else {
207
+ // resolveRootPath preserves ordinary caller spelling as part of its
208
+ // public receipt. Re-admit the exact normalized spelling before it is
209
+ // reused for Root I/O: on a case-sensitive Windows directory it may
210
+ // otherwise name a distinct case-folded sibling after raw traversal.
211
+ const admitted = admitPathInsideRoot({
212
+ rootPath: root.rootReal,
213
+ candidatePath: checked.absolutePath,
214
+ rootIdentity: root.rootIdentity,
215
+ });
216
+ if (!admitted)
217
+ throw outsideWorkspaceError();
218
+ resolved = admitted.path;
219
+ }
147
220
  }
148
221
  catch (error) {
149
222
  if (error instanceof FsSafeError && error.code === "symlink") {
@@ -0,0 +1,13 @@
1
+ import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
2
+ import type { RootContext } from "./root-context.js";
3
+ /**
4
+ * Complete the common Root.mkdir case with one exact existing parent. This is
5
+ * optional evidence: every uncertain probe returns to the ordered root walk.
6
+ */
7
+ export declare function tryMkdirAtExactParent(root: RootContext, targetPath: string, mutationAdmission: PinnedWriteMutationAdmission): Promise<boolean>;
8
+ export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission): Promise<string>;
9
+ export declare function mkdirPathFallback(root: RootContext, resolved: {
10
+ rootReal: string;
11
+ resolved: string;
12
+ }, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission): Promise<void>;
13
+ //# sourceMappingURL=root-directory-creation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-directory-creation.d.ts","sourceRoot":"","sources":["../src/root-directory-creation.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAoDrD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,4BAA4B,GAC9C,OAAO,CAAC,OAAO,CAAC,CAoGlB;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,iBAAiB,CAAC,EAAE,4BAA4B,GAC/C,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,cAAc,UAAQ,EACtB,iBAAiB,CAAC,EAAE,4BAA4B,GAC/C,OAAO,CAAC,IAAI,CAAC,CAuBf"}
@@ -0,0 +1,212 @@
1
+ import fsSync, {} from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
7
+ import { hasNodeErrorCode } from "./path.js";
8
+ import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
9
+ import { admitPathInsideRoot } from "./root-boundary.js";
10
+ import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
11
+ import { getFsSafeTestHooks } from "./test-hooks.js";
12
+ function mutationWalkOptions(mutationAdmission, operationTarget, assertBeforeMutation) {
13
+ const session = assertBeforeMutation === undefined
14
+ ? mutationAdmission.beginSharedParentWalk?.()
15
+ : undefined;
16
+ return {
17
+ dispose: () => session?.dispose(),
18
+ options: {
19
+ rejectSymlinks: mutationAdmission.rejectParentSymlinks,
20
+ revalidateParentAfterBeforeComponent: true,
21
+ retainedTargetPath: session?.retainedTargetPath,
22
+ synchronousAuthorizationIncludesFence: session !== undefined,
23
+ afterCreateComponent: session?.advanceCreatedDirectory,
24
+ beforeCreateComponent: (componentPath, prospectiveDirectory, retainedTargetPath, parent) => {
25
+ const targetPath = retainedTargetPath ?? operationTarget(prospectiveDirectory);
26
+ const request = Object.freeze({
27
+ targetPath,
28
+ mutationPath: componentPath,
29
+ phase: "parent-create",
30
+ });
31
+ return session?.tryAuthorizeAtParent(request, parent) ??
32
+ (session?.authorize(request) ?? mutationAdmission.authorize(request));
33
+ },
34
+ beforeUseComponent: async (_componentPath, prospectiveDirectory, retainedTargetPath) => {
35
+ const targetPath = retainedTargetPath ?? operationTarget(prospectiveDirectory);
36
+ const request = Object.freeze({
37
+ targetPath,
38
+ mutationPath: targetPath,
39
+ phase: "parent",
40
+ });
41
+ await (session?.authorize(request) ?? mutationAdmission.authorize(request));
42
+ },
43
+ },
44
+ };
45
+ }
46
+ /**
47
+ * Complete the common Root.mkdir case with one exact existing parent. This is
48
+ * optional evidence: every uncertain probe returns to the ordered root walk.
49
+ */
50
+ export async function tryMkdirAtExactParent(root, targetPath, mutationAdmission) {
51
+ if (getFsSafeTestHooks()?.beforeRootFallbackMutation)
52
+ return false;
53
+ if (typeof root.rootIdentity.dev !== "bigint" ||
54
+ typeof root.rootIdentity.ino !== "bigint" ||
55
+ root.rootIdentity.dev === 0n || root.rootIdentity.ino === 0n)
56
+ return false;
57
+ const rootIdentity = Object.freeze({
58
+ dev: root.rootIdentity.dev,
59
+ ino: root.rootIdentity.ino,
60
+ });
61
+ const session = mutationAdmission.beginSharedParentWalk?.();
62
+ if (!session)
63
+ return false;
64
+ try {
65
+ if (path.resolve(session.retainedTargetPath) !== path.resolve(targetPath))
66
+ return false;
67
+ const parentPath = path.dirname(targetPath);
68
+ let parentGuard;
69
+ try {
70
+ parentGuard = await createAsyncDirectoryGuard(parentPath, { bigint: true });
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ try {
76
+ fsSync.lstatSync(targetPath, { bigint: true });
77
+ return false;
78
+ }
79
+ catch (error) {
80
+ if (!hasNodeErrorCode(error, "ENOENT"))
81
+ return false;
82
+ }
83
+ const admittedParent = admitPathInsideRoot({
84
+ rootPath: root.rootReal,
85
+ candidatePath: parentPath,
86
+ rootIdentity,
87
+ });
88
+ if (admittedParent?.admission !== "exact" || admittedParent.path !== parentPath ||
89
+ parentGuard.realPath !== parentPath)
90
+ return false;
91
+ await session.authorize(Object.freeze({
92
+ targetPath,
93
+ mutationPath: targetPath,
94
+ phase: "parent",
95
+ }));
96
+ await assertAsyncDirectoryGuard(parentGuard);
97
+ try {
98
+ fsSync.lstatSync(targetPath, { bigint: true });
99
+ return false;
100
+ }
101
+ catch (error) {
102
+ if (!hasNodeErrorCode(error, "ENOENT"))
103
+ return false;
104
+ }
105
+ assertSyncDirectoryGuard(parentGuard);
106
+ const createAdmission = session.tryAuthorizeAtParent(Object.freeze({
107
+ targetPath,
108
+ mutationPath: targetPath,
109
+ phase: "parent-create",
110
+ }), checkedMutationDirectory(parentPath, parentGuard.realPath, parentGuard.stat));
111
+ if (!createAdmission)
112
+ return false;
113
+ try {
114
+ await fs.mkdir(targetPath);
115
+ }
116
+ catch (error) {
117
+ if (hasNodeErrorCode(error, "EEXIST"))
118
+ return false;
119
+ throw error;
120
+ }
121
+ const observeCreated = async () => {
122
+ const childGuard = await createAsyncDirectoryGuard(targetPath, { bigint: true });
123
+ const parentAfter = inspectDirectoryIdentitySync(parentPath, parentGuard.stat);
124
+ assertDirectoryIdentitySync(root.rootReal, rootIdentity);
125
+ const admittedChild = admitPathInsideRoot({
126
+ rootPath: root.rootReal,
127
+ candidatePath: childGuard.realPath,
128
+ rootIdentity,
129
+ });
130
+ if (childGuard.realPath !== targetPath || childGuard.dir !== targetPath ||
131
+ admittedChild?.admission !== "exact" || admittedChild.path !== targetPath) {
132
+ throw new FsSafeError("path-mismatch", "created directory changed during operation");
133
+ }
134
+ return { childGuard, parentAfter };
135
+ };
136
+ try {
137
+ const created = await observeCreated();
138
+ if (session.advanceCreatedDirectory(Object.freeze({
139
+ admission: createAdmission,
140
+ parent: checkedMutationDirectory(parentPath, parentGuard.realPath, created.parentAfter),
141
+ child: checkedMutationDirectory(targetPath, created.childGuard.realPath, created.childGuard.stat),
142
+ })))
143
+ return true;
144
+ }
145
+ catch {
146
+ // Post-create observations are optional receipt evidence. The directory
147
+ // was already created, so renew ordered admission without a second mkdir.
148
+ }
149
+ await session.authorize(Object.freeze({
150
+ targetPath,
151
+ mutationPath: targetPath,
152
+ phase: "parent",
153
+ }));
154
+ await assertAsyncDirectoryGuard(parentGuard);
155
+ assertDirectoryIdentitySync(root.rootReal, rootIdentity);
156
+ assertFinalSymlinkRejected(targetPath, true);
157
+ const refreshed = await observeCreated();
158
+ await assertAsyncDirectoryGuard(parentGuard);
159
+ await assertAsyncDirectoryGuard(refreshed.childGuard);
160
+ assertDirectoryIdentitySync(root.rootReal, rootIdentity);
161
+ return true;
162
+ }
163
+ finally {
164
+ session.dispose();
165
+ }
166
+ }
167
+ export async function prepareRootWriteTarget(root, targetPath, assertBeforeMutation, mutationAdmission) {
168
+ const basename = path.basename(targetPath);
169
+ const baseParams = {
170
+ rootReal: root.rootReal,
171
+ rootIdentity: root.rootIdentity,
172
+ targetPath: path.dirname(targetPath),
173
+ assertBeforeMutation,
174
+ };
175
+ const mutationWalk = mutationAdmission
176
+ ? mutationWalkOptions(mutationAdmission, (prospectiveParent) => path.join(prospectiveParent, basename), assertBeforeMutation)
177
+ : undefined;
178
+ let parentPath;
179
+ try {
180
+ parentPath = await mkdirPathComponentsWithGuards(mutationWalk ? {
181
+ ...baseParams,
182
+ ...mutationWalk.options,
183
+ } : baseParams);
184
+ }
185
+ finally {
186
+ mutationWalk?.dispose();
187
+ }
188
+ // Continue through the guarded walk's real parent instead of re-entering
189
+ // the original path through a symlinked component.
190
+ return path.join(parentPath, basename);
191
+ }
192
+ export async function mkdirPathFallback(root, resolved, assertBeforeMutation, rejectSymlinks = false, mutationAdmission) {
193
+ const baseParams = {
194
+ rootReal: resolved.rootReal, targetPath: resolved.resolved, assertBeforeMutation,
195
+ rootIdentity: root.rootIdentity,
196
+ rejectSymlinks,
197
+ beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
198
+ };
199
+ const mutationWalk = mutationAdmission
200
+ ? mutationWalkOptions(mutationAdmission, (prospectiveDirectory) => prospectiveDirectory, assertBeforeMutation)
201
+ : undefined;
202
+ try {
203
+ await mkdirPathComponentsWithGuards(mutationWalk ? {
204
+ ...baseParams,
205
+ ...mutationWalk.options,
206
+ rejectSymlinks: rejectSymlinks || mutationAdmission.rejectParentSymlinks,
207
+ } : baseParams);
208
+ }
209
+ finally {
210
+ mutationWalk?.dispose();
211
+ }
212
+ }