@openclaw/fs-safe 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +38 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -1
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-merge.d.ts.map +1 -1
  10. package/dist/archive-merge.js +113 -46
  11. package/dist/archive-parser.wasm +0 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +10 -11
  14. package/dist/archive-tar-stream.d.ts +3 -0
  15. package/dist/archive-tar-stream.d.ts.map +1 -1
  16. package/dist/archive-tar-stream.js +56 -37
  17. package/dist/archive-tar-wasm.d.ts +16 -4
  18. package/dist/archive-tar-wasm.d.ts.map +1 -1
  19. package/dist/archive-tar-wasm.js +134 -34
  20. package/dist/archive-zip-directory.d.ts +4 -0
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +2 -0
  23. package/dist/archive-zip-entry.d.ts +6 -2
  24. package/dist/archive-zip-entry.d.ts.map +1 -1
  25. package/dist/archive-zip-entry.js +23 -8
  26. package/dist/archive-zip-integrity.d.ts.map +1 -1
  27. package/dist/archive-zip-integrity.js +3 -4
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +107 -31
  30. package/dist/archive-zip-names.d.ts +1 -0
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +6 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +11 -11
  35. package/dist/bounded-read-stream.d.ts +0 -1
  36. package/dist/bounded-read-stream.d.ts.map +1 -1
  37. package/dist/bounded-read-stream.js +0 -6
  38. package/dist/clone-metadata.d.ts +1 -0
  39. package/dist/clone-metadata.d.ts.map +1 -1
  40. package/dist/clone-metadata.js +6 -2
  41. package/dist/copy-publication.d.ts +6 -0
  42. package/dist/copy-publication.d.ts.map +1 -1
  43. package/dist/copy-publication.js +3 -0
  44. package/dist/copy-tree-portable.d.ts.map +1 -1
  45. package/dist/copy-tree-portable.js +44 -24
  46. package/dist/copy.d.ts.map +1 -1
  47. package/dist/copy.js +29 -11
  48. package/dist/create-directory.d.ts +20 -0
  49. package/dist/create-directory.d.ts.map +1 -0
  50. package/dist/create-directory.js +130 -0
  51. package/dist/create-file-async.d.ts +7 -0
  52. package/dist/create-file-async.d.ts.map +1 -0
  53. package/dist/create-file-async.js +121 -0
  54. package/dist/create-file.d.ts +8 -0
  55. package/dist/create-file.d.ts.map +1 -0
  56. package/dist/create-file.js +190 -0
  57. package/dist/create-owned-file.d.ts +8 -0
  58. package/dist/create-owned-file.d.ts.map +1 -0
  59. package/dist/create-owned-file.js +16 -0
  60. package/dist/create.d.ts +4 -0
  61. package/dist/create.d.ts.map +1 -0
  62. package/dist/create.js +2 -0
  63. package/dist/creation-darwin.d.ts +7 -0
  64. package/dist/creation-darwin.d.ts.map +1 -0
  65. package/dist/creation-darwin.js +79 -0
  66. package/dist/creation-file-state.d.ts +19 -0
  67. package/dist/creation-file-state.d.ts.map +1 -0
  68. package/dist/creation-file-state.js +118 -0
  69. package/dist/creation-path.d.ts +21 -0
  70. package/dist/creation-path.d.ts.map +1 -0
  71. package/dist/creation-path.js +71 -0
  72. package/dist/creation-permissions.d.ts +19 -0
  73. package/dist/creation-permissions.d.ts.map +1 -0
  74. package/dist/creation-permissions.js +125 -0
  75. package/dist/directory-durability.d.ts +1 -1
  76. package/dist/directory-durability.d.ts.map +1 -1
  77. package/dist/directory-durability.js +22 -80
  78. package/dist/directory-guard.d.ts +3 -0
  79. package/dist/directory-guard.d.ts.map +1 -1
  80. package/dist/directory-mode-node.d.ts +2 -0
  81. package/dist/directory-mode-node.d.ts.map +1 -1
  82. package/dist/directory-mode-node.js +8 -0
  83. package/dist/directory-mode-owner.js +5 -5
  84. package/dist/directory-receipt.d.ts +24 -0
  85. package/dist/directory-receipt.d.ts.map +1 -0
  86. package/dist/directory-receipt.js +127 -0
  87. package/dist/file-cleanup.d.ts +19 -0
  88. package/dist/file-cleanup.d.ts.map +1 -0
  89. package/dist/file-cleanup.js +78 -0
  90. package/dist/file-handle-transfer.d.ts +2 -0
  91. package/dist/file-handle-transfer.d.ts.map +1 -1
  92. package/dist/file-handle-transfer.js +57 -2
  93. package/dist/file-identity.d.ts.map +1 -1
  94. package/dist/file-identity.js +18 -4
  95. package/dist/file-lock-sync-admission.d.ts +19 -0
  96. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  97. package/dist/file-lock-sync-admission.js +93 -0
  98. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  99. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  100. package/dist/file-lock-sync-root-acquire.js +370 -0
  101. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  102. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  103. package/dist/file-lock-sync-root-arbitration.js +66 -0
  104. package/dist/file-lock-sync-root-held.d.ts +34 -0
  105. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  106. package/dist/file-lock-sync-root-held.js +393 -0
  107. package/dist/file-lock-sync-root-io.d.ts +44 -0
  108. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  109. package/dist/file-lock-sync-root-io.js +209 -0
  110. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  111. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  112. package/dist/file-lock-sync-root-mutation.js +277 -0
  113. package/dist/file-lock-sync-root-options.d.ts +20 -0
  114. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  115. package/dist/file-lock-sync-root-options.js +58 -0
  116. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  117. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  118. package/dist/file-lock-sync-root-registration.js +90 -0
  119. package/dist/file-lock-sync-root.d.ts +36 -0
  120. package/dist/file-lock-sync-root.d.ts.map +1 -0
  121. package/dist/file-lock-sync-root.js +361 -0
  122. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  123. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  124. package/dist/file-lock-sync-stale-admission.js +205 -0
  125. package/dist/file-lock-sync.d.ts.map +1 -1
  126. package/dist/file-lock-sync.js +245 -205
  127. package/dist/file-observation.d.ts +1 -1
  128. package/dist/file-observation.d.ts.map +1 -1
  129. package/dist/file-store-boundary.d.ts +2 -6
  130. package/dist/file-store-boundary.d.ts.map +1 -1
  131. package/dist/file-store-boundary.js +3 -9
  132. package/dist/file-store-prune.d.ts.map +1 -1
  133. package/dist/file-store-prune.js +5 -1
  134. package/dist/file-store-sync-write.d.ts.map +1 -1
  135. package/dist/file-store-sync-write.js +5 -8
  136. package/dist/file-store.d.ts.map +1 -1
  137. package/dist/file-store.js +47 -12
  138. package/dist/guarded-mkdir.d.ts +1 -0
  139. package/dist/guarded-mkdir.d.ts.map +1 -1
  140. package/dist/guarded-mkdir.js +36 -7
  141. package/dist/json-document-store.d.ts.map +1 -1
  142. package/dist/json-document-store.js +22 -15
  143. package/dist/json-durable-queue-ownership.d.ts +0 -1
  144. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  145. package/dist/json-durable-queue-ownership.js +0 -6
  146. package/dist/move-path.js +1 -1
  147. package/dist/native-binding.d.ts +13 -1
  148. package/dist/native-binding.d.ts.map +1 -1
  149. package/dist/native-fallback-warning.d.ts +4 -0
  150. package/dist/native-fallback-warning.d.ts.map +1 -0
  151. package/dist/native-fallback-warning.js +11 -0
  152. package/dist/native-operations.d.ts +0 -2
  153. package/dist/native-operations.d.ts.map +1 -1
  154. package/dist/native-operations.js +0 -24
  155. package/dist/native-parent-admission.d.ts +5 -2
  156. package/dist/native-parent-admission.d.ts.map +1 -1
  157. package/dist/native-parent-admission.js +27 -7
  158. package/dist/native-pinned-write-windows.d.ts +1 -1
  159. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  160. package/dist/native-pinned-write-windows.js +174 -29
  161. package/dist/native-pinned-write.d.ts.map +1 -1
  162. package/dist/native-pinned-write.js +26 -14
  163. package/dist/native-policy-parent-windows.d.ts +14 -0
  164. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  165. package/dist/native-policy-parent-windows.js +209 -0
  166. package/dist/native-rename-outcome.d.ts +4 -0
  167. package/dist/native-rename-outcome.d.ts.map +1 -0
  168. package/dist/native-rename-outcome.js +8 -0
  169. package/dist/native-staged-file.d.ts +3 -2
  170. package/dist/native-staged-file.d.ts.map +1 -1
  171. package/dist/native-staged-file.js +121 -72
  172. package/dist/output.d.ts.map +1 -1
  173. package/dist/output.js +12 -8
  174. package/dist/owner-dacl.d.ts.map +1 -1
  175. package/dist/owner-dacl.js +10 -4
  176. package/dist/path-prefix.d.ts.map +1 -1
  177. package/dist/path-prefix.js +30 -8
  178. package/dist/path-suffix-aliases.d.ts +2 -0
  179. package/dist/path-suffix-aliases.d.ts.map +1 -1
  180. package/dist/path-suffix-aliases.js +25 -17
  181. package/dist/permission-exec.d.ts +2 -0
  182. package/dist/permission-exec.d.ts.map +1 -1
  183. package/dist/permission-exec.js +150 -21
  184. package/dist/permissions-windows.js +1 -1
  185. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  186. package/dist/pinned-mutation-admission.js +10 -5
  187. package/dist/pinned-mutation-observation.d.ts +0 -1
  188. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  189. package/dist/pinned-mutation-observation.js +0 -19
  190. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  191. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  192. package/dist/pinned-mutation-shared-route.js +1 -1
  193. package/dist/pinned-write-input.d.ts +4 -0
  194. package/dist/pinned-write-input.d.ts.map +1 -0
  195. package/dist/pinned-write-input.js +25 -0
  196. package/dist/pinned-write-mode.d.ts +5 -0
  197. package/dist/pinned-write-mode.d.ts.map +1 -0
  198. package/dist/pinned-write-mode.js +24 -0
  199. package/dist/pinned-write-staged.d.ts +6 -0
  200. package/dist/pinned-write-staged.d.ts.map +1 -0
  201. package/dist/pinned-write-staged.js +187 -0
  202. package/dist/pinned-write-types.d.ts +5 -0
  203. package/dist/pinned-write-types.d.ts.map +1 -1
  204. package/dist/pinned-write.d.ts.map +1 -1
  205. package/dist/pinned-write.js +35 -145
  206. package/dist/private-directory.d.ts.map +1 -1
  207. package/dist/private-directory.js +18 -4
  208. package/dist/private-producer-handoff-sync.d.ts +14 -0
  209. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  210. package/dist/private-producer-handoff-sync.js +114 -0
  211. package/dist/private-producer-handoff.d.ts +22 -4
  212. package/dist/private-producer-handoff.d.ts.map +1 -1
  213. package/dist/private-producer-handoff.js +140 -77
  214. package/dist/private-temp-workspace.d.ts.map +1 -1
  215. package/dist/private-temp-workspace.js +75 -121
  216. package/dist/publish-copy-stage.d.ts +2 -1
  217. package/dist/publish-copy-stage.d.ts.map +1 -1
  218. package/dist/publish-copy-stage.js +16 -7
  219. package/dist/publish-file.d.ts.map +1 -1
  220. package/dist/publish-file.js +2 -2
  221. package/dist/regular-file.d.ts.map +1 -1
  222. package/dist/regular-file.js +1 -15
  223. package/dist/replace-directory.d.ts.map +1 -1
  224. package/dist/replace-directory.js +256 -18
  225. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  226. package/dist/replace-file-copy-fallback.js +62 -70
  227. package/dist/replace-file-copy-source.d.ts.map +1 -1
  228. package/dist/replace-file-copy-source.js +10 -12
  229. package/dist/replace-file-temp-owner.d.ts +5 -9
  230. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  231. package/dist/replace-file-temp-owner.js +56 -72
  232. package/dist/replace-file.js +6 -6
  233. package/dist/retained-directory-replacement.d.ts +26 -0
  234. package/dist/retained-directory-replacement.d.ts.map +1 -0
  235. package/dist/retained-directory-replacement.js +193 -0
  236. package/dist/root-boundary.d.ts +1 -0
  237. package/dist/root-boundary.d.ts.map +1 -1
  238. package/dist/root-boundary.js +4 -0
  239. package/dist/root-context.d.ts +0 -8
  240. package/dist/root-context.d.ts.map +1 -1
  241. package/dist/root-context.js +0 -3
  242. package/dist/root-create-input.d.ts +8 -1
  243. package/dist/root-create-input.d.ts.map +1 -1
  244. package/dist/root-create-input.js +17 -4
  245. package/dist/root-directory-creation.d.ts +3 -3
  246. package/dist/root-directory-creation.d.ts.map +1 -1
  247. package/dist/root-directory-creation.js +15 -3
  248. package/dist/root-directory-list.d.ts +1 -0
  249. package/dist/root-directory-list.d.ts.map +1 -1
  250. package/dist/root-directory-list.js +1 -0
  251. package/dist/root-impl.d.ts.map +1 -1
  252. package/dist/root-impl.js +46 -17
  253. package/dist/root-move-noreplace.d.ts.map +1 -1
  254. package/dist/root-move-noreplace.js +24 -15
  255. package/dist/root-options.d.ts +12 -4
  256. package/dist/root-options.d.ts.map +1 -1
  257. package/dist/root-path-errors.d.ts +1 -0
  258. package/dist/root-path-errors.d.ts.map +1 -1
  259. package/dist/root-path-errors.js +11 -2
  260. package/dist/root-path-existing.d.ts.map +1 -1
  261. package/dist/root-path-existing.js +11 -35
  262. package/dist/root-path-stat.d.ts.map +1 -1
  263. package/dist/root-path-stat.js +59 -7
  264. package/dist/root-path.js +1 -13
  265. package/dist/root-remove.d.ts +1 -0
  266. package/dist/root-remove.d.ts.map +1 -1
  267. package/dist/root-remove.js +4 -0
  268. package/dist/root-walk.d.ts +1 -1
  269. package/dist/root-walk.d.ts.map +1 -1
  270. package/dist/root-walk.js +17 -2
  271. package/dist/root-write-admission.d.ts +0 -2
  272. package/dist/root-write-admission.d.ts.map +1 -1
  273. package/dist/root-write-admission.js +1 -15
  274. package/dist/root-write-complete-parent.d.ts.map +1 -1
  275. package/dist/root-write-complete-parent.js +7 -23
  276. package/dist/root-write-publication.js +1 -1
  277. package/dist/root-write-verification.d.ts.map +1 -1
  278. package/dist/root-write-verification.js +29 -42
  279. package/dist/secret-file.d.ts.map +1 -1
  280. package/dist/secret-file.js +3 -24
  281. package/dist/secret-read-async.d.ts.map +1 -1
  282. package/dist/secret-read-async.js +3 -24
  283. package/dist/secret-read-policy.d.ts +6 -2
  284. package/dist/secret-read-policy.d.ts.map +1 -1
  285. package/dist/secret-read-policy.js +26 -2
  286. package/dist/secure-file-windows.d.ts +6 -0
  287. package/dist/secure-file-windows.d.ts.map +1 -1
  288. package/dist/secure-file-windows.js +34 -117
  289. package/dist/secure-file.js +2 -2
  290. package/dist/sibling-temp.d.ts.map +1 -1
  291. package/dist/sibling-temp.js +23 -12
  292. package/dist/sidecar-lock-acquire.d.ts +2 -28
  293. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  294. package/dist/sidecar-lock-acquire.js +288 -199
  295. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  296. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  297. package/dist/sidecar-lock-admission-context.js +60 -0
  298. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  299. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  300. package/dist/sidecar-lock-admission-parser.js +113 -0
  301. package/dist/sidecar-lock-admission.d.ts +35 -0
  302. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  303. package/dist/sidecar-lock-admission.js +7 -0
  304. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  305. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  306. package/dist/sidecar-lock-reclaim.js +80 -25
  307. package/dist/sidecar-lock-root.d.ts.map +1 -1
  308. package/dist/sidecar-lock-root.js +2 -1
  309. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  310. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  311. package/dist/sidecar-lock-stale-admission.js +232 -0
  312. package/dist/sidecar-lock-target.d.ts +8 -0
  313. package/dist/sidecar-lock-target.d.ts.map +1 -0
  314. package/dist/sidecar-lock-target.js +55 -0
  315. package/dist/sidecar-lock.d.ts.map +1 -1
  316. package/dist/sidecar-lock.js +100 -16
  317. package/dist/staged-directory.d.ts.map +1 -1
  318. package/dist/staged-directory.js +6 -6
  319. package/dist/staged-file-settlement.d.ts +17 -0
  320. package/dist/staged-file-settlement.d.ts.map +1 -0
  321. package/dist/staged-file-settlement.js +57 -0
  322. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  323. package/dist/temp-workspace-descriptor.js +9 -27
  324. package/dist/temp-workspace-owner.d.ts.map +1 -1
  325. package/dist/temp-workspace-owner.js +8 -8
  326. package/dist/walk.d.ts +5 -1
  327. package/dist/walk.d.ts.map +1 -1
  328. package/dist/walk.js +19 -6
  329. package/dist/windows-owner.d.ts.map +1 -1
  330. package/dist/windows-owner.js +4 -3
  331. package/dist/windows-security-bridge.cs +336 -0
  332. package/dist/windows-security-bridge.ps1 +15 -0
  333. package/dist/windows-security-command.d.ts +26 -0
  334. package/dist/windows-security-command.d.ts.map +1 -0
  335. package/dist/windows-security-command.js +363 -0
  336. package/dist/windows-security-facts.d.ts +6 -0
  337. package/dist/windows-security-facts.d.ts.map +1 -0
  338. package/dist/windows-security-facts.js +108 -0
  339. package/docs/advanced.md +4 -2
  340. package/docs/archive.md +97 -46
  341. package/docs/atomic.md +85 -8
  342. package/docs/config.md +6 -2
  343. package/docs/contributing.md +44 -4
  344. package/docs/copy.md +37 -0
  345. package/docs/creation.md +128 -0
  346. package/docs/durability.md +24 -0
  347. package/docs/file-store.md +19 -0
  348. package/docs/install.md +31 -7
  349. package/docs/json-store.md +5 -0
  350. package/docs/migrating-to-0.5.md +15 -6
  351. package/docs/migrating-to-0.6.md +9 -4
  352. package/docs/native-helper.md +32 -12
  353. package/docs/native.md +38 -7
  354. package/docs/output.md +6 -0
  355. package/docs/path-prefix.md +10 -0
  356. package/docs/path-suffix-aliases.md +51 -6
  357. package/docs/permissions.md +50 -14
  358. package/docs/public-api.md +3 -2
  359. package/docs/root.md +56 -3
  360. package/docs/secret-file.md +11 -2
  361. package/docs/secure-file.md +9 -4
  362. package/docs/sidecar-lock.md +114 -8
  363. package/docs/staged-file.md +12 -3
  364. package/docs/temp.md +20 -3
  365. package/docs/walk.md +67 -1
  366. package/docs/writing.md +76 -6
  367. package/package.json +19 -16
  368. package/dist/darwin-acl.d.ts +0 -4
  369. package/dist/darwin-acl.d.ts.map +0 -1
  370. package/dist/darwin-acl.js +0 -24
@@ -1,14 +1,37 @@
1
1
  import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { recordFileObservationFailure } from "./file-observation.js";
4
+ import { fileObservation, recordFileObservationFailure } from "./file-observation.js";
5
5
  import { hasNodeErrorCode, isNotFoundPathError } from "./path.js";
6
6
  import { assertRootDirectoryObservationGuard, assertRootPathObservationReceiptCurrent, createRootDirectoryObservationGuard, pathStatFromStats, } from "./root-directory-list.js";
7
7
  import { fileNotFoundError } from "./root-errors.js";
8
8
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
9
9
  import { assertStatObservationSync } from "./stat-observation.js";
10
10
  import { getFsSafeTestHooks } from "./test-hooks.js";
11
- async function missingObservedFileError(cause, pathname, before, assertParents) {
11
+ function singleLinkFileIdentity(stat) {
12
+ if (!stat?.isFile() || stat.isSymbolicLink() || (stat.nlink !== 1 && stat.nlink !== 1n))
13
+ return;
14
+ const exact = (value) => typeof value === "bigint" ? value : Number.isSafeInteger(value) ? BigInt(value) : undefined;
15
+ const dev = exact(stat.dev), ino = exact(stat.ino);
16
+ if (dev === undefined || ino === undefined || dev < 0n || ino < 0n ||
17
+ (process.platform === "win32" && (dev === 0n || ino === 0n)))
18
+ return;
19
+ return { dev, ino };
20
+ }
21
+ async function captureChangedObservedFile(error, pathname, before, observed, assertParents, proof) {
22
+ try {
23
+ const previous = singleLinkFileIdentity(before), current = singleLinkFileIdentity(observed);
24
+ if (!previous || !current || (previous.dev === current.dev && previous.ino === current.ino))
25
+ return;
26
+ await assertParents();
27
+ // A changed metadata probe grants only a fresh acquisition attempt, never ownership.
28
+ proof.failure = { error, kind: `stat-leaf-changed:${pathname}` };
29
+ }
30
+ catch {
31
+ // Ambiguous file or ancestry observations retain the original public error.
32
+ }
33
+ }
34
+ async function missingObservedFileError(cause, pathname, before, assertParents, proof) {
12
35
  const failure = new FsSafeError("path-mismatch", "file changed during operation", {
13
36
  cause: cause instanceof Error ? cause : undefined,
14
37
  });
@@ -17,7 +40,7 @@ async function missingObservedFileError(cause, pathname, before, assertParents)
17
40
  try {
18
41
  await assertParents();
19
42
  // Metadata loss permits discarding a probe, never reading or owning a file.
20
- recordFileObservationFailure(failure, `stat-leaf-missing:${pathname}`);
43
+ proof.failure = { error: failure, kind: `stat-leaf-missing:${pathname}` };
21
44
  }
22
45
  catch {
23
46
  // Keep the public mismatch and withhold provenance when ancestry changed.
@@ -26,6 +49,19 @@ async function missingObservedFileError(cause, pathname, before, assertParents)
26
49
  return failure;
27
50
  }
28
51
  export async function statResolvedPathInRoot(root, resolvedPath, receipt) {
52
+ const proof = {};
53
+ try {
54
+ // Nested observations in a hook cannot grant this invocation discard authority.
55
+ return await fileObservation().run(() => statObservedPathInRoot(root, resolvedPath, proof, receipt));
56
+ }
57
+ catch (error) {
58
+ const failure = proof.failure;
59
+ if (failure && failure.error === error)
60
+ recordFileObservationFailure(error, failure.kind);
61
+ throw error;
62
+ }
63
+ }
64
+ async function statObservedPathInRoot(root, resolvedPath, proof, receipt) {
29
65
  let admittedTarget = false;
30
66
  try {
31
67
  if (receipt) {
@@ -37,13 +73,21 @@ export async function statResolvedPathInRoot(root, resolvedPath, receipt) {
37
73
  if (beforeObservation)
38
74
  await beforeObservation(resolvedPath);
39
75
  let observed;
76
+ let sample, samples = 0;
77
+ const comparison = fileObservation();
40
78
  try {
41
- observed = assertStatObservationSync(bigint => bigint ? fsSync.lstatSync(resolvedPath, { bigint: true }) : fsSync.lstatSync(resolvedPath), receipt.target.identity);
79
+ observed = comparison.run(() => assertStatObservationSync(bigint => {
80
+ sample = bigint ? fsSync.lstatSync(resolvedPath, { bigint: true }) : fsSync.lstatSync(resolvedPath);
81
+ samples += 1;
82
+ return sample;
83
+ }, receipt.target.identity));
42
84
  }
43
85
  catch (error) {
44
86
  if (isNotFoundPathError(error)) {
45
- throw await missingObservedFileError(error, resolvedPath, "stat" in receipt.target ? receipt.target.stat : undefined, () => assertRootPathObservationReceiptCurrent(root, receipt));
87
+ throw await missingObservedFileError(error, resolvedPath, "stat" in receipt.target ? receipt.target.stat : undefined, () => assertRootPathObservationReceiptCurrent(root, receipt), proof);
46
88
  }
89
+ if (comparison.has(error, "identity"))
90
+ await captureChangedObservedFile(error, resolvedPath, "stat" in receipt.target ? receipt.target.stat : undefined, samples === 1 ? sample : undefined, () => assertRootPathObservationReceiptCurrent(root, receipt), proof);
47
91
  throw error;
48
92
  }
49
93
  if (observed.isSymbolicLink()) {
@@ -77,13 +121,21 @@ export async function statResolvedPathInRoot(root, resolvedPath, receipt) {
77
121
  if (beforeObservation)
78
122
  await beforeObservation(resolvedPath);
79
123
  let observed;
124
+ let sample, samples = 0;
125
+ const comparison = fileObservation();
80
126
  try {
81
- observed = inspectFileIdentitySync(() => fsSync.lstatSync(resolvedPath, { bigint: true }), expected);
127
+ observed = comparison.run(() => inspectFileIdentitySync(() => {
128
+ sample = fsSync.lstatSync(resolvedPath, { bigint: true });
129
+ samples += 1;
130
+ return sample;
131
+ }, expected));
82
132
  }
83
133
  catch (error) {
84
134
  if (isNotFoundPathError(error)) {
85
- throw await missingObservedFileError(error, resolvedPath, expected, () => assertRootDirectoryObservationGuard(root, parentGuard));
135
+ throw await missingObservedFileError(error, resolvedPath, expected, () => assertRootDirectoryObservationGuard(root, parentGuard), proof);
86
136
  }
137
+ if (comparison.has(error, "identity"))
138
+ await captureChangedObservedFile(error, resolvedPath, expected, samples === 1 ? sample : undefined, () => assertRootDirectoryObservationGuard(root, parentGuard), proof);
87
139
  throw error;
88
140
  }
89
141
  if (observed.isSymbolicLink()) {
package/dist/root-path.js CHANGED
@@ -519,19 +519,7 @@ function buildResolvedRootPath(params) {
519
519
  };
520
520
  }
521
521
  async function getPathKind(absolutePath, preserveFinalSymlink) {
522
- try {
523
- const operationPath = pathForWindowsFilesystem(absolutePath);
524
- const stat = preserveFinalSymlink
525
- ? fs.lstatSync(operationPath)
526
- : fs.statSync(operationPath);
527
- return { exists: true, kind: toResolvedKind(stat) };
528
- }
529
- catch (error) {
530
- if (isNotFoundPathError(error)) {
531
- return { exists: false, kind: "missing" };
532
- }
533
- throw error;
534
- }
522
+ return getPathKindSync(absolutePath, preserveFinalSymlink);
535
523
  }
536
524
  function getPathKindSync(absolutePath, preserveFinalSymlink) {
537
525
  try {
@@ -1,6 +1,7 @@
1
1
  import { type RootContext } from "./root-context.js";
2
2
  import type { RootRemoveOptions } from "./root-options.js";
3
3
  import type { RemovalPathReceipts } from "./root-remove-receipt.js";
4
+ export declare const nonrecursiveRemovalKind: unique symbol;
4
5
  export declare function validateRemoveOptions(options: RootRemoveOptions): void;
5
6
  export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions, receipts?: RemovalPathReceipts): Promise<void>;
6
7
  //# 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;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
+ {"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,eAAO,MAAM,uBAAuB,EAAE,OAAO,MAA4C,CAAC;AAK1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AAyOD,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"}
@@ -16,6 +16,7 @@ import { createSuppressedError } from "./suppressed-error.js";
16
16
  import { getFsSafeTestHooks } from "./test-hooks.js";
17
17
  const DEFAULT_MAX_ENTRIES = 100_000;
18
18
  const DEFAULT_MAX_DEPTH = 64;
19
+ export const nonrecursiveRemovalKind = Symbol("nonrecursive removal kind");
19
20
  export function validateRemoveOptions(options) {
20
21
  if (options.order !== undefined && options.order !== "filesystem" && options.order !== "sorted") {
21
22
  throw new TypeError("remove order must be filesystem or sorted");
@@ -220,6 +221,9 @@ async function removeOne(root, targetPath, options, receipts) {
220
221
  }
221
222
  try {
222
223
  const isDirectory = fsSync.lstatSync(targetPath).isDirectory();
224
+ if (!isDirectory && options[nonrecursiveRemovalKind] === "directory") {
225
+ throw new FsSafeError("path-mismatch", "store entry is no longer a directory");
226
+ }
223
227
  assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
224
228
  assertNotAborted(options.signal);
225
229
  options.assertBeforeMutation?.();
@@ -21,7 +21,7 @@ export type RootWalkEntry = RootWalkDataEntry | {
21
21
  size: 0;
22
22
  error: unknown;
23
23
  };
24
- export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult;
24
+ export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
25
25
  export type RootWalkOptions = {
26
26
  maxDepth?: number;
27
27
  maxEntries?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAsK/B"}
1
+ {"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,iBAAiB,KACrB,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAmL/B"}
package/dist/root-walk.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
- import { resolveRootPath } from "./root-path.js";
3
+ import { resolveRootPath, ROOT_PATH_ALIAS_POLICIES } from "./root-path.js";
4
4
  import { createSuppressedError } from "./suppressed-error.js";
5
5
  function validateBudget(name, value) {
6
6
  if (value === undefined)
@@ -65,12 +65,16 @@ export async function* walkRoot(root, relativePath, options) {
65
65
  options.signal?.throwIfAborted();
66
66
  let listing;
67
67
  try {
68
+ const skipChildSymlinks = depth > 0 && options.symlinkPolicy === "skip";
68
69
  const resolvedDirectory = await resolveRootPath({
69
70
  absolutePath: path.resolve(root.rootReal, directory),
70
71
  rootPath: root.rootReal,
71
72
  rootCanonicalPath: root.rootReal,
72
73
  boundaryLabel: "root walk",
74
+ policy: skipChildSymlinks ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
73
75
  });
76
+ if (skipChildSymlinks && resolvedDirectory.kind === "symlink")
77
+ return;
74
78
  if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
75
79
  throw new FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
76
80
  }
@@ -140,7 +144,18 @@ export async function* walkRoot(root, relativePath, options) {
140
144
  size = target.size;
141
145
  }
142
146
  const walkEntry = { relativePath: child, kind, size };
143
- const filterResult = options.entryFilter?.(walkEntry) ?? "include";
147
+ let filterResult = options.entryFilter?.(walkEntry) ?? "include";
148
+ if (typeof filterResult !== "string") {
149
+ filterResult = (await filterResult) ?? "include";
150
+ options.signal?.throwIfAborted();
151
+ try {
152
+ await listing.assertCurrent();
153
+ }
154
+ catch (error) {
155
+ yield onDirectoryError(directory, error);
156
+ return;
157
+ }
158
+ }
144
159
  if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
145
160
  throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
146
161
  }
@@ -34,9 +34,7 @@ export type RootWritePathSelection = Readonly<{
34
34
  export type RetainedRootWriteSelection = RootWritePathSelection & Readonly<{
35
35
  identity: Readonly<Pick<BigIntStats, "dev" | "ino">>;
36
36
  }>;
37
- export declare function retainRootWriteSelection(owner: object, selection: RetainedRootWriteSelection): void;
38
37
  export declare function createRootWriteSelectionForFd(selection: RootWritePathSelection, fd: number): RetainedRootWriteSelection;
39
- export declare function takeRootWriteSelection(owner: object): RetainedRootWriteSelection | undefined;
40
38
  export declare function assertRootWritePathSelectionSync(root: RootContext, selection: RootWritePathSelection): void;
41
39
  export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): void;
42
40
  export declare function prepareRootWritePathSelection(params: Omit<RootWritePathSelection, "parentGuard"> & Readonly<{
@@ -1 +1 @@
1
- {"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,iCAAiC,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;CAClD,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAE7E,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,SAAS,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,uBAAuB,CAAC,EAAE,gCAAgC,CAAC;CAC5D,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,iBAAiB,EAAE,4BAA4B,CAAC;IAChD,uBAAuB,EAAE,gCAAgC,CAAC;IAC1D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,sBAAsB,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,sBAAsB,GAAG,QAAQ,CAAC;IACzE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CACtD,CAAC,CAAC;AAIH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,0BAA0B,GACpC,IAAI,CAEN;AAED,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,sBAAsB,EACjC,EAAE,EAAE,MAAM,GACT,0BAA0B,CAM5B;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAI5F;AAqED,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,sBAAsB,GAChC,IAAI,CAkBN;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,0BAA0B,EACrC,aAAa,EAAE,OAAO,EACtB,EAAE,CAAC,EAAE,MAAM,GACV,IAAI,CAGN;AAsBD,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,IAAI,CAC9D,sBAAsB,EACtB,aAAa,CACd,GAAG,QAAQ,CAAC;IACX,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4BnC;AAED,wBAAsB,oCAAoC,CACxD,OAAO,EAAE;IACP,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,uBAAuB,CAAC,EAAE,gCAAgC,CAAC;CAC5D,GAAG,SAAS,EACb,YAAY,EAAE,MAAM,EACpB,mBAAmB,SAAe,EAClC,cAAc,CAAC,EAAE,uBAAuB,GACvC,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAU7C;AAED,wBAAsB,6BAA6B,CACjD,SAAS,EAAE,sBAAsB,GAChC,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,0BAA0B,EACrC,aAAa,EAAE,OAAO,EACtB,EAAE,CAAC,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,0BAA0B,EACrC,aAAa,EAAE,OAAO,EACtB,EAAE,CAAC,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,KAAK,uBAAuB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,uBAAuB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACtF,CAAC;AAEF,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA0B3B;AAED,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,sBAAsB,CAAC,CAkDjC;AAED,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,kBAAkB,EAClC,SAAS,UAAO,EAChB,gBAAgB,CAAC,EAAE,qBAAqB,GACvC,OAAO,CAAC,iBAAiB,CAAC,CA8D5B"}
1
+ {"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import { FsSafeError } from "./errors.js";
6
6
  import { isNotFoundPathError } from "./path.js";
7
7
  import { assertNoPathAliasEscape } from "./path-policy.js";
8
8
  import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy, } from "./pinned-mutation-admission.js";
9
- import { admitPathInsideRoot } from "./root-boundary.js";
9
+ import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
10
10
  import { resolvePathInRoot } from "./root-context.js";
11
11
  import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
12
12
  import { inheritWriteTargetMode } from "./root-write-mode.js";
@@ -15,10 +15,6 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
15
15
  import { realpathSync } from "./realpath.js";
16
16
  import { getFsSafeTestHooks } from "./test-hooks.js";
17
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
18
  export function createRootWriteSelectionForFd(selection, fd) {
23
19
  const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
24
20
  return Object.freeze({
@@ -26,11 +22,6 @@ export function createRootWriteSelectionForFd(selection, fd) {
26
22
  identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
27
23
  });
28
24
  }
29
- export function takeRootWriteSelection(owner) {
30
- const selection = retainedRootWriteSelections.get(owner);
31
- retainedRootWriteSelections.delete(owner);
32
- return selection;
33
- }
34
25
  function writeSelectionChanged(cause) {
35
26
  return new FsSafeError("path-mismatch", "write target changed during operation", {
36
27
  cause: cause instanceof Error ? cause : undefined,
@@ -55,11 +46,6 @@ function inspectRegularSelectionPath(pathname, expected, follow) {
55
46
  throw hardlinkedPathNotAllowedError();
56
47
  return observed;
57
48
  }
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
49
  function inspectSelectionBindingSync(root, selection, fd) {
64
50
  inspectRegularSelectionPath(selection.selectedPath, selection.identity, false);
65
51
  if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
@@ -1 +1 @@
1
- {"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAMxE,KAAK,8BAA8B,GAAG,QAAQ,CAC1C;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC,CAAC;AAEH,KAAK,2BAA2B,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,CAAC;AA2GH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,uBAAuB,EACjC,YAAY,UAAQ,GACnB,IAAI,CAqBN;AAwCD,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAsBhC"}
1
+ {"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAKxE,KAAK,8BAA8B,GAAG,QAAQ,CAC1C;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC,CAAC;AAEH,KAAK,2BAA2B,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,CAAC;AA2FH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,uBAAuB,EACjC,YAAY,UAAQ,GACnB,IAAI,CAqBN;AAwCD,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAsBhC"}
@@ -1,15 +1,14 @@
1
1
  import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { assertDirectoryIdentitySync, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
4
- import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
5
4
  import { FsSafeError } from "./errors.js";
6
5
  import { getFsSafeNativeConfig } from "./native-config.js";
7
6
  import { hasNodeErrorCode } from "./path.js";
8
7
  import { realpathSync } from "./realpath.js";
9
- import { admitPathInsideRoot } from "./root-boundary.js";
8
+ import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
9
+ import { ordinaryWindowsSegments } from "./pinned-mutation-shared-route.js";
10
10
  import { prepareRootWriteTarget } from "./root-directory-creation.js";
11
11
  import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
12
- import { isSafePathSegment } from "./safe-path-segment.js";
13
12
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
14
13
  import { getFsSafeTestHooks } from "./test-hooks.js";
15
14
  function writeSelectionChanged(cause) {
@@ -17,30 +16,15 @@ function writeSelectionChanged(cause) {
17
16
  cause: cause instanceof Error ? cause : undefined,
18
17
  });
19
18
  }
20
- function sameNormalizedPathSpelling(left, right) {
21
- // Windows can expose case-sensitive directories, so normalize separators
22
- // and roots without using path.relative's case-folding comparison.
23
- return path.resolve(left) === path.resolve(right);
24
- }
25
- function windowsReservedDeviceSegment(segment) {
26
- const extension = segment.indexOf(".");
27
- const stem = segment.slice(0, extension < 0 ? segment.length : extension).toUpperCase();
28
- return WINDOWS_RESERVED_DEVICE_NAMES.has(stem);
29
- }
30
19
  function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
31
20
  if (process.versions.bun || relativePath === "" || relativePath.startsWith("~") ||
32
21
  relativePath.includes("\0") || path.isAbsolute(relativePath))
33
22
  return false;
34
- const separator = process.platform === "win32" ? "\\" : "/";
35
- const alternateSeparator = process.platform === "win32" ? "/" : "\\";
36
- if (relativePath.includes(alternateSeparator))
37
- return false;
38
- const segments = relativePath.split(separator);
39
- if (segments.some((segment) => segment === "" || segment === "." || segment === ".." ||
40
- (process.platform === "win32" &&
41
- (!isSafePathSegment(segment, { allowDotPrefix: true }) ||
42
- segment.endsWith(".") || segment.endsWith(" ") ||
43
- windowsReservedDeviceSegment(segment)))))
23
+ if (process.platform === "win32") {
24
+ if (!ordinaryWindowsSegments(relativePath))
25
+ return false;
26
+ }
27
+ else if (relativePath.includes("\\") || relativePath.split("/").some(segment => segment === "" || segment === "." || segment === ".."))
44
28
  return false;
45
29
  const routed = path.resolve(root.rootReal, relativePath);
46
30
  const admitted = admitPathInsideRoot({
@@ -4,7 +4,7 @@ import { readFileHandleBounded } from "./bounded-read.js";
4
4
  import { syncDirectoryBestEffort } from "./directory-durability.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
7
- import { cleanupPinnedFilePath } from "./replace-file-temp-owner.js";
7
+ import { cleanupPinnedFilePath } from "./file-cleanup.js";
8
8
  import { verifyAtomicWriteResult } from "./root-write-verification.js";
9
9
  // Compatibility callers hold the lock throughout this operation. The caller
10
10
  // owns the original write handle; this helper owns a content-verified reopen.
@@ -1 +1 @@
1
- {"version":3,"file":"root-write-verification.d.ts","sourceRoot":"","sources":["../src/root-write-verification.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAK9F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhF,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;CACrC,GAAG,OAAO,CAAC,IAAI,CAAC,CAgGhB"}
1
+ {"version":3,"file":"root-write-verification.d.ts","sourceRoot":"","sources":["../src/root-write-verification.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAK9F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhF,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;CACrC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EhB"}
@@ -1,5 +1,5 @@
1
1
  import fsSync, {} from "node:fs";
2
- import fs from "node:fs/promises";
2
+ import fs, {} from "node:fs/promises";
3
3
  import { assertAsyncDirectoryGuard } from "./directory-guard.js";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { sameFileIdentity } from "./file-identity.js";
@@ -45,59 +45,46 @@ export async function verifyAtomicWriteResult(params) {
45
45
  return stat;
46
46
  };
47
47
  try {
48
- // This descriptor remains owned by the writer, even when final mode forbids opens.
49
- const stat = assertDescriptor();
50
- assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
51
- const { realPath, stat: resolvedStat } = await resolveOpenedFileRealPathForFd(params.fd, stat, params.targetPath);
52
- // Consume the resolver's observation only in this pass; the checks after
53
- // the directory guards must still sample the current path and descriptor.
54
- assertPath(resolvedStat);
55
- if (!admitPathInsideRoot({
56
- rootPath: params.root.rootReal,
57
- candidatePath: realPath,
58
- rootIdentity: params.root.rootIdentity,
59
- })) {
60
- throw outsideWorkspaceError();
61
- }
62
- await assertAsyncDirectoryGuard(params.parentGuard);
63
- await assertRootIdentityCurrent(params.root);
64
- // Recheck after canonical resolution and directory checks, including late links.
65
- assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
66
- assertDescriptor();
67
- if (needsPathOpen) {
68
- // A retained fd cannot prove that an opaque Windows pathname still names it.
69
- // Reopen only for publication verification: the writer's exact identity is
70
- // independent proof unavailable to ordinary readers. Never read any bytes.
71
- assertNoUnsafeDeviceReadPath(params.targetPath);
72
- const opened = await fs.open(params.targetPath, resolveReadOpenFlags()).catch((error) => {
73
- if (isSymlinkOpenError(error)) {
74
- throw new FsSafeError("symlink", "symlink open blocked", { cause: error });
75
- }
76
- if (hasNodeErrorCode(error, "EISDIR")) {
77
- throw new FsSafeError("not-file", "not a file");
78
- }
79
- throw error;
80
- });
81
- try {
82
- const reopenedStat = assertDescriptor(opened.fd);
48
+ let opened;
49
+ try {
50
+ while (true) {
51
+ // The original descriptor remains borrowed; only a verification reopen is owned here.
52
+ const fd = opened?.fd ?? params.fd;
53
+ const stat = assertDescriptor(fd);
83
54
  assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
84
- const { realPath: reopenedPath, stat: reopenedPathStat } = await resolveOpenedFileRealPathForFd(opened.fd, reopenedStat, params.targetPath);
85
- assertPath(reopenedPathStat);
55
+ const { realPath, stat: resolvedStat } = await resolveOpenedFileRealPathForFd(fd, stat, params.targetPath);
56
+ // Consume the resolver's observation only in this pass; the checks after
57
+ // the directory guards must still sample the current path and descriptor.
58
+ assertPath(resolvedStat);
86
59
  if (!admitPathInsideRoot({
87
60
  rootPath: params.root.rootReal,
88
- candidatePath: reopenedPath,
61
+ candidatePath: realPath,
89
62
  rootIdentity: params.root.rootIdentity,
90
63
  })) {
91
64
  throw outsideWorkspaceError();
92
65
  }
93
66
  await assertAsyncDirectoryGuard(params.parentGuard);
94
67
  await assertRootIdentityCurrent(params.root);
68
+ // Recheck after canonical resolution and directory checks, including late links.
95
69
  assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
96
- assertDescriptor(opened.fd);
70
+ assertDescriptor(fd);
71
+ if (opened || !needsPathOpen)
72
+ break;
73
+ // Opaque Windows pathname identity needs one independent, read-free reopen.
74
+ assertNoUnsafeDeviceReadPath(params.targetPath);
75
+ opened = await fs.open(params.targetPath, resolveReadOpenFlags()).catch((error) => {
76
+ if (isSymlinkOpenError(error)) {
77
+ throw new FsSafeError("symlink", "symlink open blocked", { cause: error });
78
+ }
79
+ if (hasNodeErrorCode(error, "EISDIR"))
80
+ throw new FsSafeError("not-file", "not a file");
81
+ throw error;
82
+ });
97
83
  }
98
- finally {
84
+ }
85
+ finally {
86
+ if (opened)
99
87
  await opened.close().catch(() => undefined);
100
- }
101
88
  }
102
89
  }
103
90
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUhJ,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAQjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAmER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAgCD;AAwDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzF"}
1
+ {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAShJ,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAQjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CA2CR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAgCD;AAyDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzF"}
@@ -7,42 +7,20 @@ import { readFileDescriptorBoundedSync } from "./bounded-read.js";
7
7
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
8
8
  import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
9
9
  import { assertOwnedDirectory } from "./directory-mode-owner.js";
10
- import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
11
10
  import { FsSafeError } from "./errors.js";
12
11
  import { resolveHomeRelativePath } from "./home-dir.js";
13
12
  import { openPinnedFileSync } from "./pinned-open.js";
14
13
  import { runPinnedWriteHelper } from "./pinned-write.js";
15
14
  import { ensureTrailingSep } from "./root-context.js";
16
15
  import { verifyAtomicWriteResult } from "./root-write-verification.js";
17
- import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
16
+ import { prepareSecretRead, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
18
17
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
19
18
  import { serializePathWrite } from "./write-queue.js";
20
19
  import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
21
20
  export const PRIVATE_SECRET_DIR_MODE = 0o700;
22
21
  export const PRIVATE_SECRET_FILE_MODE = 0o600;
23
22
  export function readSecretFileSync(filePath, label, options = {}) {
24
- const { resolvedPath, maxBytes } = resolveSecretReadPolicy(filePath, label, options);
25
- let rejectSymlink;
26
- let previewStat;
27
- try {
28
- assertNoUnsafeDeviceReadPath(resolvedPath);
29
- rejectSymlink = Boolean(options.rejectSymlink);
30
- previewStat = inspectFileIdentitySync(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
31
- }
32
- catch (error) {
33
- throw secretReadError(error instanceof FsSafeError ? error.code : secretPathErrorCode(error), "inspect", label, resolvedPath, error);
34
- }
35
- function inspectInput(symlinkMessage) {
36
- const stat = rejectSymlink
37
- ? fs.lstatSync(resolvedPath, { bigint: true })
38
- : fs.statSync(resolvedPath, { bigint: true });
39
- if (rejectSymlink && stat.isSymbolicLink()) {
40
- throw new FsSafeError("symlink", symlinkMessage);
41
- }
42
- return stat;
43
- }
44
- const rejectHardlinks = options.rejectHardlinks !== false;
45
- assertSecretFilePreview(previewStat, label, resolvedPath, maxBytes, rejectHardlinks);
23
+ const { resolvedPath, maxBytes, rejectSymlink, rejectHardlinks, previewStat, inspectInput } = prepareSecretRead(filePath, label, options);
46
24
  const opened = openPinnedFileSync({
47
25
  filePath: resolvedPath,
48
26
  rejectPathSymlink: rejectSymlink,
@@ -313,6 +291,7 @@ async function materializeSecretFileAtomic(params, createOnly) {
313
291
  basename: fileName,
314
292
  mkdir: false,
315
293
  mode,
294
+ verifyPosixMode: true,
316
295
  sync: params.durable !== false,
317
296
  overwrite: !createOnly,
318
297
  input: { kind: "buffer", data: typeof params.content === "string" ? params.content : Buffer.from(params.content) },
@@ -1 +1 @@
1
- {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAGjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA6DjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
1
+ {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAGjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAqCjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
@@ -5,32 +5,11 @@ import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { resolveReadOpenFlags } from "./read-open-flags.js";
7
7
  import { realpathSync } from "./realpath.js";
8
- import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
9
- import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
8
+ import { inspectFileIdentity } from "./strict-file-identity.js";
9
+ import { prepareSecretRead, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
10
10
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
11
11
  export async function readSecretFile(filePath, label, options = {}) {
12
- const { resolvedPath, maxBytes } = resolveSecretReadPolicy(filePath, label, options);
13
- let rejectSymlink;
14
- let previewStat;
15
- try {
16
- assertNoUnsafeDeviceReadPath(resolvedPath);
17
- rejectSymlink = Boolean(options.rejectSymlink);
18
- previewStat = inspectFileIdentitySync(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
19
- }
20
- catch (error) {
21
- throw secretReadError(error instanceof FsSafeError ? error.code : secretPathErrorCode(error), "inspect", label, resolvedPath, error);
22
- }
23
- function inspectInput(symlinkMessage) {
24
- const stat = rejectSymlink
25
- ? fsSync.lstatSync(resolvedPath, { bigint: true })
26
- : fsSync.statSync(resolvedPath, { bigint: true });
27
- if (rejectSymlink && stat.isSymbolicLink()) {
28
- throw new FsSafeError("symlink", symlinkMessage);
29
- }
30
- return stat;
31
- }
32
- const rejectHardlinks = options.rejectHardlinks !== false;
33
- assertSecretFilePreview(previewStat, label, resolvedPath, maxBytes, rejectHardlinks);
12
+ const { resolvedPath, maxBytes, rejectSymlink, rejectHardlinks, previewStat, inspectInput } = prepareSecretRead(filePath, label, options);
34
13
  let handle;
35
14
  let raw;
36
15
  try {
@@ -1,4 +1,4 @@
1
- import type { BigIntStats } from "node:fs";
1
+ import { type BigIntStats } from "node:fs";
2
2
  import { FsSafeError, type FsSafeErrorCode } from "./errors.js";
3
3
  export declare const DEFAULT_SECRET_FILE_MAX_BYTES: number;
4
4
  export type SecretFileReadOptions = {
@@ -10,8 +10,12 @@ export declare function secretPathErrorCode(error: unknown): FsSafeErrorCode;
10
10
  export declare function secretReadError(code: FsSafeErrorCode, action: "inspect" | "read", label: string, resolvedPath: string, error: unknown): FsSafeError;
11
11
  export declare function assertSecretFilePreview(stat: BigIntStats, label: string, resolvedPath: string, maxBytes: number, rejectHardlinks: boolean): void;
12
12
  export declare function trimSecretFileContent(raw: string, label: string, resolvedPath: string): string;
13
- export declare function resolveSecretReadPolicy(filePath: string, label: string, options: SecretFileReadOptions): {
13
+ export declare function prepareSecretRead(filePath: string, label: string, options: SecretFileReadOptions): {
14
14
  resolvedPath: string;
15
15
  maxBytes: number;
16
+ rejectSymlink: boolean;
17
+ rejectHardlinks: boolean;
18
+ previewStat: BigIntStats;
19
+ inspectInput: (symlinkMessage: string) => BigIntStats;
16
20
  };
17
21
  //# sourceMappingURL=secret-read-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-read-policy.d.ts","sourceRoot":"","sources":["../src/secret-read-policy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAGhE,eAAO,MAAM,6BAA6B,QAAY,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAGnE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,OAAO,GACb,WAAW,CAGb;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,OAAO,GACvB,IAAI,CAUN;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM9F;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG;IACxG,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAYA"}
1
+ {"version":3,"file":"secret-read-policy.d.ts","sourceRoot":"","sources":["../src/secret-read-policy.ts"],"names":[],"mappings":"AAEA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAIhE,eAAO,MAAM,6BAA6B,QAAY,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAGnE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,OAAO,GACb,WAAW,CAGb;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,OAAO,GACvB,IAAI,CAUN;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM9F;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;;;;;;mCAyBzD,MAAM,KAAG,WAAW;EAY3D"}