@openclaw/openshell-sandbox 2026.10.5-beta.4 → 2026.10.10-beta.2

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 (240) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
  3. package/node_modules/@openclaw/fs-safe/README.md +20 -4
  4. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
  5. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  6. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
  7. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  8. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
  9. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
  10. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
  11. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
  12. package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
  13. package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
  14. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
  15. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
  16. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
  17. package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
  18. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
  19. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
  20. package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
  21. package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
  22. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
  23. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
  24. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
  25. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
  26. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
  27. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
  28. package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
  29. package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
  30. package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
  31. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
  32. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
  33. package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
  34. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
  35. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
  36. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
  37. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
  38. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
  39. package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
  41. package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
  42. package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
  43. package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
  44. package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
  45. package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
  46. package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
  47. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
  48. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
  52. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
  53. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
  54. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
  55. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
  56. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  57. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
  58. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
  59. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
  60. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
  61. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
  62. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
  63. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
  64. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
  65. package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
  66. package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
  67. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
  68. package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
  69. package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
  70. package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
  71. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
  72. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
  73. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
  77. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
  78. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
  79. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
  81. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
  82. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
  84. package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
  85. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
  86. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
  87. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
  88. package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
  89. package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
  90. package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
  91. package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
  92. package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
  93. package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
  94. package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
  95. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
  96. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
  97. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
  98. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
  99. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  100. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
  101. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
  102. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
  103. package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
  104. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
  105. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
  106. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
  107. package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
  108. package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
  109. package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
  110. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
  111. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
  112. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
  113. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
  114. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
  115. package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
  116. package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
  117. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
  118. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
  119. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
  120. package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
  121. package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
  122. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
  123. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
  124. package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
  125. package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
  126. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
  127. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
  128. package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
  129. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
  130. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
  131. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
  132. package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
  134. package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
  135. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
  137. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
  139. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
  140. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
  141. package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
  142. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
  143. package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
  144. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
  145. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
  146. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
  147. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
  148. package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
  149. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
  150. package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
  151. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
  152. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
  153. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
  154. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
  155. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
  156. package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
  157. package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
  158. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
  159. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
  160. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
  161. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
  162. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
  163. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
  164. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
  165. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
  166. package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
  167. package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
  168. package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
  170. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
  172. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
  174. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
  175. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
  176. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
  177. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
  178. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
  179. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
  180. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
  181. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
  182. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
  183. package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
  184. package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
  185. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  186. package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
  187. package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
  188. package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
  189. package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
  190. package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
  191. package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
  192. package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
  193. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
  194. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
  196. package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
  197. package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
  198. package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
  199. package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
  200. package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
  201. package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
  202. package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
  203. package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
  204. package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
  205. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
  206. package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
  207. package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
  208. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
  209. package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
  210. package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
  211. package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
  212. package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
  213. package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
  214. package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
  215. package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
  216. package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
  217. package/node_modules/@openclaw/fs-safe/package.json +21 -12
  218. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  219. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  220. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  221. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  222. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  223. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
  224. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  225. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
  226. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  227. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  228. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  229. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  230. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  231. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  232. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  233. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  234. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  235. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
  236. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  237. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  238. package/package.json +5 -5
  239. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
  240. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
@@ -1,6 +1,6 @@
1
1
  import { type BigIntStats, type Stats } from "node:fs";
2
- import type { DirectoryReceipt } from "./directory-durability.js";
3
- import type { FileIdentityStat } from "./file-identity.js";
2
+ import type { DirectoryReceipt, ExactDirectoryReceipt } from "./directory-durability.js";
3
+ import { type FileIdentityInput } from "./file-identity.js";
4
4
  type ExactIdentity = Readonly<Pick<BigIntStats, "dev" | "ino">>;
5
5
  declare const numericFields: readonly ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"];
6
6
  declare const timeFields: readonly ["atime", "mtime", "ctime", "birthtime"];
@@ -13,11 +13,11 @@ type DirectoryAuthority = DirectoryProvenance & Readonly<{
13
13
  path: string;
14
14
  realPath: string;
15
15
  }>;
16
- export declare function directoryReceiptIdentity(identity: FileIdentityStat): ExactIdentity;
16
+ export declare function directoryReceiptIdentity(input: FileIdentityInput): ExactIdentity;
17
17
  export declare function directoryReceiptAuthority(receipt: DirectoryReceipt<Stats | BigIntStats>): DirectoryAuthority;
18
- export declare function ownDirectoryReceipt(receipt: DirectoryReceipt<Stats | BigIntStats>): DirectoryReceipt;
19
- export declare function copyRetainedDirectoryReceipt(receipt: DirectoryReceipt): DirectoryReceipt;
20
- export declare function createDirectoryReceiptFromIdentity(pathname: string, realPath: string, exactStat: BigIntStats): DirectoryReceipt;
21
- export declare function createDirectoryReceiptSync(directoryPath: string, label: string, canonicalize?: (pathname: string) => string): DirectoryReceipt;
18
+ export declare function ownDirectoryReceipt(receipt: DirectoryReceipt<Stats | BigIntStats>): ExactDirectoryReceipt;
19
+ export declare function copyRetainedDirectoryReceipt(receipt: DirectoryReceipt): ExactDirectoryReceipt;
20
+ export declare function createDirectoryReceiptFromIdentity(pathname: string, realPath: string, exactStat: BigIntStats): ExactDirectoryReceipt;
21
+ export declare function createDirectoryReceiptSync(directoryPath: string, label: string, canonicalize?: (pathname: string) => string): ExactDirectoryReceipt;
22
22
  export declare function assertDirectoryReceiptCurrentSync(receipt: DirectoryReceipt, label: string, canonicalize?: (pathname: string) => string): void;
23
23
  export {};
@@ -1,6 +1,7 @@
1
1
  import fs, {} from "node:fs";
2
2
  import { inspectDirectoryIdentitySync } from "./directory-guard.js";
3
3
  import { FsSafeError } from "./errors.js";
4
+ import { identityFromReceipt } from "./file-identity.js";
4
5
  import { realpathSync } from "./realpath.js";
5
6
  import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
6
7
  const numericFields = ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"];
@@ -9,10 +10,11 @@ const timeFields = ["atime", "mtime", "ctime", "birthtime"];
9
10
  // fields and accept exact provenance only while its public identity is intact.
10
11
  const authorities = new WeakMap();
11
12
  const identities = new WeakMap();
12
- export function directoryReceiptIdentity(identity) {
13
- if (!identity) {
13
+ export function directoryReceiptIdentity(input) {
14
+ if (!input) {
14
15
  throw new FsSafeError("path-mismatch", "directory receipt identity is missing");
15
16
  }
17
+ const identity = identityFromReceipt(input);
16
18
  const known = identities.get(identity);
17
19
  const dev = identity.dev;
18
20
  const ino = identity.ino;
@@ -41,9 +43,11 @@ function snapshotDirectoryReceipt(receipt) {
41
43
  }
42
44
  assertNoWindowsPathAlias(pathname, "filesystem");
43
45
  assertNoWindowsPathAlias(realPath, "filesystem");
44
- const identity = directoryReceiptIdentity(stat);
46
+ const identity = directoryReceiptIdentity(receipt.exactIdentity ?? stat);
45
47
  const known = authorities.get(receipt);
46
- if (known && (pathname !== known.path || realPath !== known.realPath ||
48
+ const matches = (value, expected) => typeof value === "bigint" ? value === expected : value === Number(expected);
49
+ if (known && (!matches(stat.dev, known.identity.dev) || !matches(stat.ino, known.identity.ino) ||
50
+ pathname !== known.path || realPath !== known.realPath ||
47
51
  identity.dev !== known.identity.dev || identity.ino !== known.identity.ino)) {
48
52
  throw new FsSafeError("path-mismatch", "directory receipt changed before admission");
49
53
  }
@@ -79,6 +83,7 @@ export function copyRetainedDirectoryReceipt(receipt) {
79
83
  }
80
84
  function receiptFromAuthority(authority) {
81
85
  const receipt = {
86
+ exactIdentity: authority.identity,
82
87
  path: authority.path,
83
88
  realPath: authority.realPath,
84
89
  identity: Object.assign(Object.create(fs.Stats.prototype), authority.metadata),
@@ -1,4 +1,5 @@
1
- export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
1
+ export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type ExactDirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
2
2
  export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, } from "./publish-file.js";
3
3
  export type { PublishFileExclusiveCleanup, PublishFileExclusiveDirectorySyncFailure, PublishFileExclusiveFailureDetails, PublishFileExclusiveFailurePhase, PublishFileExclusiveSyncFailurePolicy, } from "./publish-file-failure.js";
4
4
  export { sha256File, sha256FileSync, type Sha256FileInput, type Sha256FileSyncInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
5
+ export type { ExactFileIdentity, ExactIdentityReceipt } from "./exact-identity.js";
@@ -18,7 +18,7 @@ function basename(name) {
18
18
  }
19
19
  function identity(value) {
20
20
  const { dev, ino } = value;
21
- if (typeof dev !== "bigint" || typeof ino !== "bigint" || dev < 0n || ino <= 0n ||
21
+ if (typeof dev !== "bigint" || typeof ino !== "bigint" || dev < -(1n << 63n) || ino < -(1n << 63n) || ino === 0n ||
22
22
  dev > 0xffffffffffffffffn || ino > 0xffffffffffffffffn) {
23
23
  throw new FsSafeError("path-mismatch", "publication requires an exact known identity");
24
24
  }
@@ -230,7 +230,7 @@ export function retainEntryForPublication(options) {
230
230
  inspectEntry(sourcePath, source.expected);
231
231
  const sourceFd = kind === "symlink"
232
232
  ? binding.openStagedSymlink(sourceParent.fd, source.basename)
233
- : binding.openBeneath(sourceParent.fd, source.basename, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK |
233
+ : native.openBeneath(sourceParent.fd, source.basename, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK |
234
234
  (kind === "directory" ? fs.constants.O_DIRECTORY : 0))?.fd;
235
235
  if (!Number.isInteger(sourceFd) || sourceFd < 0) {
236
236
  throw new FsSafeError("helper-unavailable", "publication source descriptor is unavailable");
@@ -0,0 +1,17 @@
1
+ import { type BigIntStats, type Stats } from "node:fs";
2
+ export type ExactFileIdentity = Readonly<{
3
+ dev: bigint;
4
+ ino: bigint;
5
+ }>;
6
+ export type ExactIdentityReceipt = {
7
+ readonly exactIdentity: ExactFileIdentity;
8
+ };
9
+ /** Copy scalars from one observation; never try to recover rounded identity bits. */
10
+ export declare function exactFileIdentity(stat: {
11
+ dev: number | bigint;
12
+ ino: number | bigint;
13
+ }): ExactFileIdentity;
14
+ /** Node stores unsigned libuv fields in a signed BigInt64Array. */
15
+ export declare function numberStatScalar(value: number | bigint): number;
16
+ /** Legacy metadata view of the very same bigint observation. */
17
+ export declare function numberStats(stat: BigIntStats): Stats;
@@ -0,0 +1,29 @@
1
+ import fs, {} from "node:fs";
2
+ import { FsSafeError } from "./errors.js";
3
+ /** Copy scalars from one observation; never try to recover rounded identity bits. */
4
+ export function exactFileIdentity(stat) {
5
+ const { dev, ino } = stat;
6
+ if ((typeof dev !== "bigint" && !Number.isSafeInteger(dev)) ||
7
+ (typeof ino !== "bigint" && !Number.isSafeInteger(ino))) {
8
+ throw new FsSafeError("path-mismatch", "file identity is not exact");
9
+ }
10
+ return Object.freeze({ dev: BigInt(dev), ino: BigInt(ino) });
11
+ }
12
+ /** Node stores unsigned libuv fields in a signed BigInt64Array. */
13
+ export function numberStatScalar(value) {
14
+ return typeof value === "bigint" ? Number(BigInt.asUintN(64, value)) : Number(value);
15
+ }
16
+ /** Legacy metadata view of the very same bigint observation. */
17
+ export function numberStats(stat) {
18
+ const result = Object.create(fs.Stats.prototype);
19
+ for (const field of ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"]) {
20
+ result[field] = numberStatScalar(stat[field]);
21
+ }
22
+ for (const field of ["atime", "mtime", "ctime", "birthtime"]) {
23
+ const ns = stat[`${field}Ns`];
24
+ const remainder = ((ns % 1000000000n) + 1000000000n) % 1000000000n;
25
+ result[`${field}Ms`] = Number((ns - remainder) / 1000000000n) * 1_000 + Number(remainder) / 1_000_000;
26
+ // Stats.prototype supplies Node's lazy, correctly rounded Date getters.
27
+ }
28
+ return result;
29
+ }
@@ -113,7 +113,8 @@ export async function copyFileHandle(source, target, options = {}) {
113
113
  const assertBeforeMutation = composeMutationAssertions(undefined, options.assertBeforeMutation);
114
114
  const sourceStat = await inspectFileIdentity(() => {
115
115
  signal?.throwIfAborted();
116
- return source.stat({ bigint: true });
116
+ const fd = source.fd;
117
+ return fd < 0 ? source.stat({ bigint: true }) : fs.fstatSync(fd, { bigint: true });
117
118
  });
118
119
  signal?.throwIfAborted();
119
120
  if (!sourceStat.isFile())
@@ -123,7 +124,8 @@ export async function copyFileHandle(source, target, options = {}) {
123
124
  }
124
125
  const targetStat = await inspectFileIdentity(() => {
125
126
  signal?.throwIfAborted();
126
- return target.stat({ bigint: true });
127
+ const fd = target.fd;
128
+ return fd < 0 ? target.stat({ bigint: true }) : fs.fstatSync(fd, { bigint: true });
127
129
  });
128
130
  signal?.throwIfAborted();
129
131
  if (!targetStat.isFile())
@@ -10,7 +10,7 @@ export type Sha256FileResult = {
10
10
  bytes: number;
11
11
  digest: string;
12
12
  };
13
- export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions): Promise<Sha256FileResult>;
13
+ export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions, ownsPosition?: boolean): Promise<Sha256FileResult>;
14
14
  export declare function sha256File(input: Sha256FileInput, options?: Sha256FileOptions): Promise<Sha256FileResult>;
15
15
  /** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
16
16
  export declare function sha256FileSync(input: Sha256FileSyncInput, options?: Sha256FileOptions): Sha256FileResult;
@@ -55,9 +55,9 @@ import fsSync from "node:fs";
55
55
  import fs from "node:fs/promises";
56
56
  import { normalizeMaxBytes } from "./byte-budget.js";
57
57
  import { FsSafeError } from "./errors.js";
58
- import { getNativeBinding } from "./native.js";
58
+ import { getNativeBinding, selectNativeBinding } from "./native.js";
59
59
  import { resolveReadOpenFlags } from "./read-open-flags.js";
60
- import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
60
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
61
61
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
62
62
  // Idle storage is bounded independently of caller concurrency. A pending read
63
63
  // retains its exclusive borrow until the async hasher settles.
@@ -89,7 +89,7 @@ class HashScratch {
89
89
  hashScratchBuffers.shift();
90
90
  }
91
91
  }
92
- export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
92
+ export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}, ownsPosition = false) {
93
93
  const env_1 = { stack: [], error: void 0, hasError: false };
94
94
  try {
95
95
  signal?.throwIfAborted();
@@ -100,11 +100,12 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
100
100
  if (stat.size > maxBytes) {
101
101
  throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
102
102
  }
103
- if (native) {
103
+ const hasher = selectNativeBinding(native, "sha256File");
104
+ if (hasher) {
104
105
  // A completed N-API task can mask later aborts on the same signal.
105
106
  const nativeSignal = signal ? AbortSignal.any([signal]) : undefined;
106
107
  try {
107
- const result = await native.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
108
+ const result = await hasher.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal, ownsPosition);
108
109
  signal?.throwIfAborted();
109
110
  return result;
110
111
  }
@@ -183,7 +184,7 @@ function rethrowHashOpenError(error) {
183
184
  }
184
185
  async function hashPath(filePath, options) {
185
186
  assertNoWindowsPathAlias(filePath, "filesystem", "SHA-256 path uses a Windows filesystem namespace alias");
186
- const before = await inspectFileIdentity(() => hashPathIdentity(filePath));
187
+ const before = inspectFileIdentitySync(() => hashPathIdentity(filePath));
187
188
  let handle;
188
189
  try {
189
190
  options.signal?.throwIfAborted();
@@ -195,9 +196,10 @@ async function hashPath(filePath, options) {
195
196
  let completed = false;
196
197
  try {
197
198
  options.signal?.throwIfAborted();
198
- const opened = await inspectFileIdentity(() => hashDescriptorIdentity(handle.fd), before);
199
- await inspectFileIdentity(() => hashPathIdentity(filePath, true), opened);
200
- const result = await hashFileHandle(handle, getNativeBinding(), options);
199
+ const opened = inspectFileIdentitySync(() => hashDescriptorIdentity(handle.fd), before);
200
+ inspectFileIdentitySync(() => hashPathIdentity(filePath, true), opened);
201
+ // Only this overload owns the handle and its otherwise unobservable cursor.
202
+ const result = await hashFileHandle(handle, getNativeBinding(), options, true);
201
203
  completed = true;
202
204
  return result;
203
205
  }
@@ -1,7 +1,13 @@
1
+ import type { ExactIdentityReceipt } from "./exact-identity.js";
2
+ export type { ExactFileIdentity, ExactIdentityReceipt } from "./exact-identity.js";
1
3
  export type FileIdentityStat = {
2
4
  dev: number | bigint;
3
5
  ino: number | bigint;
4
6
  };
7
+ export type FileIdentityInput = FileIdentityStat | ExactIdentityReceipt;
8
+ /** Only the explicit companion is unambiguous; nested source/target receipts are not inferred. */
9
+ export declare function identityFromReceipt(value: FileIdentityInput): FileIdentityStat;
10
+ export declare function hasSafeFileIdentity(identity: FileIdentityStat): boolean;
5
11
  export declare function sha256Hex(data: string | Buffer, encoding?: BufferEncoding): string;
6
- export declare function sameFileIdentity(left: FileIdentityStat, right: FileIdentityStat, platform?: NodeJS.Platform): boolean;
12
+ export declare function sameFileIdentity(left: FileIdentityInput, right: FileIdentityInput, platform?: NodeJS.Platform): boolean;
7
13
  export declare function sameFileIdentityForCleanup(left: FileIdentityStat, right: FileIdentityStat, platform?: NodeJS.Platform): boolean;
@@ -1,4 +1,20 @@
1
1
  import { createHash } from "node:crypto";
2
+ /** Only the explicit companion is unambiguous; nested source/target receipts are not inferred. */
3
+ export function identityFromReceipt(value) {
4
+ if ("exactIdentity" in value) {
5
+ const exact = value.exactIdentity;
6
+ const dev = exact?.dev, ino = exact?.ino;
7
+ // A malformed explicit companion cannot fall back to a lossy legacy view.
8
+ return typeof dev === "bigint" && typeof ino === "bigint" ? { dev, ino } : { dev: NaN, ino: NaN };
9
+ }
10
+ return value;
11
+ }
12
+ export function hasSafeFileIdentity(identity) {
13
+ return isExactStatValue(identity.dev) && isExactStatValue(identity.ino);
14
+ }
15
+ function isExactStatValue(value) {
16
+ return typeof value === "bigint" || Number.isSafeInteger(value);
17
+ }
2
18
  function isZero(value) {
3
19
  return value === 0 || value === 0n;
4
20
  }
@@ -16,17 +32,23 @@ export function sha256Hex(data, encoding) {
16
32
  return createHash("sha256").update(buffer).digest("hex");
17
33
  }
18
34
  export function sameFileIdentity(left, right, platform = process.platform) {
35
+ left = identityFromReceipt(left);
36
+ right = identityFromReceipt(right);
37
+ const leftDev = left.dev;
38
+ const leftIno = left.ino;
39
+ const rightDev = right.dev;
40
+ const rightIno = right.ino;
19
41
  // When Windows cannot open a path for stat, libuv's FindFirstFile fallback reports dev=0 and
20
42
  // ino=0. Treating either unknown value as a mismatch caused nondeterministic path-mismatch
21
43
  // failures on legitimate reads under antivirus or indexer contention.
22
- return (!isStatValueProvablyDifferent(left.dev, right.dev, platform) &&
23
- !isStatValueProvablyDifferent(left.ino, right.ino, platform));
44
+ return (isExactStatValue(leftDev) && isExactStatValue(leftIno) &&
45
+ isExactStatValue(rightDev) && isExactStatValue(rightIno) &&
46
+ !isStatValueProvablyDifferent(leftDev, rightDev, platform) &&
47
+ !isStatValueProvablyDifferent(leftIno, rightIno, platform));
24
48
  }
25
49
  function sameCleanupStatValue(left, right) {
26
50
  // Converting an unsafe number to bigint cannot recover an already rounded ID.
27
- if (typeof left === "number" && !Number.isSafeInteger(left))
28
- return false;
29
- if (typeof right === "number" && !Number.isSafeInteger(right))
51
+ if (!isExactStatValue(left) || !isExactStatValue(right))
30
52
  return false;
31
53
  return sameStatValue(left, right);
32
54
  }
@@ -38,8 +38,7 @@ export function defaultSyncShouldReclaim(snapshot, staleMs, nowMs) {
38
38
  }
39
39
  export function syncReclaimGuardExists(reclaimGuardPath) {
40
40
  try {
41
- fs.lstatSync(reclaimGuardPath);
42
- return true;
41
+ return fs.lstatSync(reclaimGuardPath, { throwIfNoEntry: false }) !== undefined;
43
42
  }
44
43
  catch (error) {
45
44
  if (error.code === "ENOENT")
@@ -91,7 +91,14 @@ function canonicalLockParentSync(parent) {
91
91
  function normalizeTargetPath(targetPath) {
92
92
  const resolved = path.resolve(targetPath);
93
93
  assertNoWindowsPathAlias(resolved);
94
- fs.mkdirSync(recursiveMkdirPath(path.dirname(resolved)), { recursive: true });
94
+ const directory = path.dirname(resolved);
95
+ let parentExists = false;
96
+ try {
97
+ parentExists = fs.statSync(directory).isDirectory();
98
+ }
99
+ catch { /* Preserve mkdir errors below. */ }
100
+ if (!parentExists)
101
+ fs.mkdirSync(recursiveMkdirPath(directory), { recursive: true });
95
102
  let parent;
96
103
  try {
97
104
  parent = canonicalLockParentSync(path.dirname(resolved));
@@ -1,4 +1,5 @@
1
1
  import { type SidecarLockAcquireOptions, type SidecarLockHandle, type SidecarLockHeldEntry, type SidecarLockRetryOptions, type SidecarLockStaleRecovery } from "./sidecar-lock.js";
2
+ export { tryAcquireWriteLease, type FileWriteLease } from "./file-write-lease.js";
2
3
  export { acquireFileLockSync, withFileLockSync, type FileLockSyncAcquireOptions, type FileLockSyncHandle, } from "./file-lock-sync.js";
3
4
  export type FileLockRetryOptions = SidecarLockRetryOptions;
4
5
  export type FileLockStaleRecovery = SidecarLockStaleRecovery;
@@ -1,5 +1,6 @@
1
1
  import { createSidecarLockManager, } from "./sidecar-lock.js";
2
2
  import { getFsSafeLockConfig } from "./lock-config.js";
3
+ export { tryAcquireWriteLease } from "./file-write-lease.js";
3
4
  export { acquireFileLockSync, withFileLockSync, } from "./file-lock-sync.js";
4
5
  function resolveFileLockManagerKey(targetPath, managerKey) {
5
6
  return managerKey ?? `fs-safe.file-lock:${targetPath}`;
@@ -0,0 +1,9 @@
1
+ export type FileWriteLease = {
2
+ readonly fd: number;
3
+ /** False after release or when the kernel breaks or downgrades the lease. */
4
+ isHeld(): boolean;
5
+ release(): void;
6
+ [Symbol.dispose](): void;
7
+ };
8
+ /** Linux kernel write lease. The caller retains the open fd and SIGIO handling. */
9
+ export declare function tryAcquireWriteLease(fd: number): FileWriteLease | null;
@@ -0,0 +1,42 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { requireNativeBinding } from "./native.js";
3
+ /** Linux kernel write lease. The caller retains the open fd and SIGIO handling. */
4
+ export function tryAcquireWriteLease(fd) {
5
+ if (process.platform !== "linux") {
6
+ throw new FsSafeError("unsupported-platform", "File write leases require Linux");
7
+ }
8
+ if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
9
+ throw new FsSafeError("invalid-path", "Expected an open file descriptor");
10
+ }
11
+ const native = requireNativeBinding();
12
+ const acquire = native.tryAcquireWriteLease?.bind(native);
13
+ const held = native.isFileWriteLeaseHeld?.bind(native);
14
+ const unlock = native.releaseFileWriteLease?.bind(native);
15
+ if (!acquire || !held || !unlock) {
16
+ throw new FsSafeError("helper-unavailable", "Native file write leases are unavailable");
17
+ }
18
+ const call = (operation) => {
19
+ try {
20
+ return operation();
21
+ }
22
+ catch (cause) {
23
+ throw new FsSafeError("helper-failed", "Native file write lease operation failed", { cause });
24
+ }
25
+ };
26
+ if (!call(() => acquire(fd)))
27
+ return null;
28
+ let released = false;
29
+ const release = () => {
30
+ if (released)
31
+ return;
32
+ // Never retry an ambiguous settlement against a potentially recycled fd.
33
+ released = true;
34
+ call(() => unlock(fd));
35
+ };
36
+ return Object.freeze({
37
+ fd,
38
+ isHeld: () => !released && call(() => held(fd)),
39
+ release,
40
+ [Symbol.dispose]: release,
41
+ });
42
+ }
@@ -14,6 +14,7 @@ export declare function mkdirPathComponentsWithGuards(params: {
14
14
  afterCreateComponent?: (receipt: PinnedCreatedDirectoryReceipt) => PinnedMutationAuthorizationToken | undefined;
15
15
  assertBeforeMutation?: () => void;
16
16
  createMissing?: boolean;
17
+ synchronousMkdir?: boolean;
17
18
  mode?: number;
18
19
  private?: boolean;
19
20
  rejectSymlinks?: boolean;
@@ -2,7 +2,7 @@ import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { createDirectoryWithAdmission } from "./create.js";
5
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
5
+ import { assertDirectoryGuard, captureDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { hasNodeErrorCode, isNotFoundPathError, isPathRelativeEscape } from "./path.js";
8
8
  import { directoryComponentNotDirectoryError, rootPathChangedError } from "./root-errors.js";
@@ -55,6 +55,9 @@ function createdDirectoryEvidence(admission, parent, childPath) {
55
55
  return undefined;
56
56
  }
57
57
  }
58
+ function captureExactDirectoryGuard(dir) {
59
+ return captureDirectoryGuard(dir, "native", { bigint: true });
60
+ }
58
61
  function suppliedExactRootIdentity(identity) {
59
62
  return typeof identity?.dev === "bigint" && typeof identity.ino === "bigint"
60
63
  ? { dev: identity.dev, ino: identity.ino }
@@ -68,7 +71,7 @@ function assertGuardMatchesRootIdentity(guard, expected) {
68
71
  function isPromiseLike(value) {
69
72
  return typeof value?.then === "function";
70
73
  }
71
- async function realpathOrThrowNotFile(target) {
74
+ function realpathOrThrowNotFile(target) {
72
75
  try {
73
76
  const canonical = realpathSync.native(pathForWindowsFilesystem(target));
74
77
  assertNoWindowsPathAlias(canonical, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
@@ -95,7 +98,7 @@ export async function mkdirPathComponentsWithGuards(params) {
95
98
  const root = resolvePathPreservingWindowsRoot(rawRootReal);
96
99
  const target = resolvePathPreservingWindowsRoot(rawTargetPath);
97
100
  const suppliedIdentity = suppliedExactRootIdentity(params.rootIdentity);
98
- const configuredRootGuard = await createAsyncDirectoryGuard(root, { bigint: true });
101
+ const configuredRootGuard = captureExactDirectoryGuard(root);
99
102
  const checkedRootIdentity = suppliedIdentity ?? {
100
103
  dev: configuredRootGuard.stat.dev,
101
104
  ino: configuredRootGuard.stat.ino,
@@ -105,7 +108,7 @@ export async function mkdirPathComponentsWithGuards(params) {
105
108
  const rootCanonical = resolvePathPreservingWindowsRoot(configuredRootGuard.realPath);
106
109
  const rootGuard = rootCanonical === root
107
110
  ? configuredRootGuard
108
- : await createAsyncDirectoryGuard(rootCanonical, { bigint: true });
111
+ : captureExactDirectoryGuard(rootCanonical);
109
112
  assertGuardMatchesRootIdentity(rootGuard, checkedRootIdentity);
110
113
  const admissionParams = {
111
114
  candidatePath: target,
@@ -138,11 +141,12 @@ export async function mkdirPathComponentsWithGuards(params) {
138
141
  let created = false;
139
142
  let createReceipt;
140
143
  if (!params.revalidateParentAfterBeforeComponent) {
141
- await assertAsyncDirectoryGuard(parentGuard);
144
+ assertDirectoryGuard(parentGuard, "native");
142
145
  }
143
- await params.beforeComponent?.(next);
146
+ if (params.beforeComponent)
147
+ await params.beforeComponent(next);
144
148
  if (params.revalidateParentAfterBeforeComponent) {
145
- await assertAsyncDirectoryGuard(parentGuard);
149
+ assertDirectoryGuard(parentGuard, "native");
146
150
  }
147
151
  let shouldCreate = true;
148
152
  if (params.beforeCreateComponent) {
@@ -165,7 +169,7 @@ export async function mkdirPathComponentsWithGuards(params) {
165
169
  const authorization = params.beforeCreateComponent(next, prospectiveParent, retainedTargetPath, observedGuard(parentGuard));
166
170
  if (isPromiseLike(authorization)) {
167
171
  createReceipt = await authorization;
168
- await assertAsyncDirectoryGuard(parentGuard);
172
+ assertDirectoryGuard(parentGuard, "native");
169
173
  }
170
174
  else {
171
175
  createReceipt = authorization;
@@ -193,7 +197,10 @@ export async function mkdirPathComponentsWithGuards(params) {
193
197
  });
194
198
  }
195
199
  else {
196
- await fs.mkdir(next, { mode: params.mode });
200
+ if (params.synchronousMkdir)
201
+ fsSync.mkdirSync(next, { mode: params.mode });
202
+ else
203
+ await fs.mkdir(next, { mode: params.mode });
197
204
  }
198
205
  created = true;
199
206
  }
@@ -217,7 +224,7 @@ export async function mkdirPathComponentsWithGuards(params) {
217
224
  prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
218
225
  }
219
226
  await params.beforeUseComponent(next, prospectiveParent, retainedTargetPath);
220
- await assertAsyncDirectoryGuard(parentGuard);
227
+ assertDirectoryGuard(parentGuard, "native");
221
228
  }
222
229
  const stat = createdAuthorization ? undefined : fsSync.lstatSync(next);
223
230
  if (stat && ((params.rejectSymlinks && stat.isSymbolicLink()) ||
@@ -228,7 +235,7 @@ export async function mkdirPathComponentsWithGuards(params) {
228
235
  // segment at a time and realpath-check each segment before descending.
229
236
  const nextReal = createdAuthorization && createdEvidence
230
237
  ? createdEvidence.receipt.child.canonicalPath
231
- : await realpathOrThrowNotFile(next);
238
+ : realpathOrThrowNotFile(next);
232
239
  const admittedNextReal = admitPathInsideRoot({
233
240
  rootPath: rootCanonical,
234
241
  candidatePath: nextReal,
@@ -245,9 +252,9 @@ export async function mkdirPathComponentsWithGuards(params) {
245
252
  if (!targetStat.isDirectory()) {
246
253
  throw directoryComponentNotDirectoryError();
247
254
  }
248
- currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
255
+ currentGuard = captureExactDirectoryGuard(admittedNextPath);
249
256
  assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
250
- await assertAsyncDirectoryGuard(parentGuard);
257
+ assertDirectoryGuard(parentGuard, "native");
251
258
  retainedTargetPath = undefined;
252
259
  retainedParentPath = undefined;
253
260
  current = admittedNextPath;
@@ -257,11 +264,11 @@ export async function mkdirPathComponentsWithGuards(params) {
257
264
  currentGuard = createdEvidence.guard;
258
265
  }
259
266
  else {
260
- currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
267
+ currentGuard = captureExactDirectoryGuard(admittedNextPath);
261
268
  }
262
269
  assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
263
270
  if (!createdAuthorization || !createdEvidence) {
264
- await assertAsyncDirectoryGuard(parentGuard);
271
+ assertDirectoryGuard(parentGuard, "native");
265
272
  }
266
273
  current = admittedNextPath;
267
274
  }
@@ -92,6 +92,9 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
92
92
  " try:",
93
93
  " os.rename(src_basename + '/', dst_basename, src_dir_fd=src_parent_fd, dst_dir_fd=dst_parent_fd)",
94
94
  " except OSError as error:",
95
+ " # ENOTDIR can describe the slash-checked source, not just the target.",
96
+ " if error.errno == errno.ENOTDIR:",
97
+ " verify(src_parent_fd, src_basename, 1)",
95
98
  " if error.errno in (errno.ENOTEMPTY, errno.ENOTDIR, errno.EEXIST):",
96
99
  " raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), dst_basename) from error",
97
100
  " raise",
@@ -0,0 +1 @@
1
+ export declare function isHardlinkFallbackError(error: unknown): boolean;
@@ -0,0 +1,5 @@
1
+ // These refusals leave the destination uncreated. EEXIST is deliberately excluded.
2
+ const HARDLINK_FALLBACK_CODES = new Set(["EACCES", "EPERM", "EXDEV", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
3
+ export function isHardlinkFallbackError(error) {
4
+ return HARDLINK_FALLBACK_CODES.has(error?.code ?? "");
5
+ }
@@ -3,6 +3,7 @@ export type * from "./errors.js";
3
3
  export { DEFAULT_ROOT_MAX_BYTES, root, type Root, } from "./root.js";
4
4
  export type * from "./root-public-types.js";
5
5
  export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
6
+ export { getFsSafeNativeStatus } from "./config.js";
6
7
  export type * from "./config.js";
7
8
  export { writeExternalFileWithinRoot, } from "./output.js";
8
9
  export type * from "./output.js";
@@ -1,5 +1,6 @@
1
1
  export { FsSafeError, categorizeFsSafeError, isNoReplaceUnsupported, } from "./errors.js";
2
2
  export { DEFAULT_ROOT_MAX_BYTES, root, } from "./root.js";
3
3
  export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
4
+ export { getFsSafeNativeStatus } from "./config.js";
4
5
  export { writeExternalFileWithinRoot, } from "./output.js";
5
6
  export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import { syncDirectory } from "./directory-durability.js";
7
7
  import { FsSafeError } from "./errors.js";
8
8
  import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
9
+ import { isHardlinkFallbackError } from "./hardlink-fallback.js";
9
10
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
10
11
  import { serializePathWrite } from "./write-queue.js";
11
12
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
@@ -61,6 +62,18 @@ async function withQueueEntryLock(paths, run) {
61
62
  assertNoWindowsPathAlias(resolvedJsonPath);
62
63
  return await serializePathWrite(paths.jsonPath, async () => await withQueueTransferLock(resolvedJsonPath, run));
63
64
  }
65
+ async function linkQueueGeneration(sourcePath, targetPath) {
66
+ try {
67
+ await fs.link(sourcePath, targetPath);
68
+ }
69
+ catch (error) {
70
+ // Shared inode identity is recovery evidence; a copy cannot replace it.
71
+ if (isHardlinkFallbackError(error)) {
72
+ throw new FsSafeError("helper-unavailable", "durable queue ownership requires hardlink support", { cause: error });
73
+ }
74
+ throw error;
75
+ }
76
+ }
64
77
  async function claimDurableQueueEntryUnlocked(paths, options = {}) {
65
78
  const processingPath = paths.processingPath;
66
79
  await recoverDurableQueueRetirement({ jsonPath: paths.jsonPath, processingPath });
@@ -92,7 +105,7 @@ async function claimDurableQueueEntryUnlocked(paths, options = {}) {
92
105
  throw new FsSafeError("path-mismatch", "queue entry is not exclusively owned");
93
106
  }
94
107
  try {
95
- await fs.link(paths.jsonPath, processingPath);
108
+ await linkQueueGeneration(paths.jsonPath, processingPath);
96
109
  }
97
110
  catch (error) {
98
111
  if (getErrorCode(error) === "EEXIST") {
@@ -227,7 +240,7 @@ export async function moveDurableQueueEntryToFailed(params) {
227
240
  throw new FsSafeError("already-exists", "failed queue destination already exists");
228
241
  }
229
242
  if (!failed)
230
- await fs.link(sourcePath, failedPath);
243
+ await linkQueueGeneration(sourcePath, failedPath);
231
244
  await syncDirectory(path.dirname(failedPath));
232
245
  await fs.unlink(sourcePath);
233
246
  await syncDirectory(path.dirname(sourcePath));
@@ -305,7 +318,7 @@ async function recoverDurableQueueRetirement(params) {
305
318
  if (!sameFileIdentityForCleanup(entry, processing)) {
306
319
  const pending = regularQueueFileIdentity(params.jsonPath, "retirement");
307
320
  if (!pending) {
308
- await fs.link(entryPath, params.jsonPath);
321
+ await linkQueueGeneration(entryPath, params.jsonPath);
309
322
  await syncDirectory(path.dirname(params.jsonPath));
310
323
  }
311
324
  else if (sameFileIdentityForCleanup(pending, entry)) {