@openclaw/openshell-sandbox 2026.10.5-beta.3 → 2026.10.10-beta.1

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,3 +1,4 @@
1
+ import { numberStats } from "./exact-identity.js";
1
2
  import fsSync from "node:fs";
2
3
  import fs from "node:fs/promises";
3
4
  import { FsSafeError } from "./errors.js";
@@ -5,7 +6,7 @@ import { recordFileOpenFailure, recordPreOpenFileChange } from "./opened-file-fa
5
6
  import { assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
6
7
  import { resolveReadOpenFlags } from "./read-open-flags.js";
7
8
  import { fileNotFoundError, hardlinkedPathNotAllowedError } from "./root-errors.js";
8
- import { inspectFileIdentity } from "./strict-file-identity.js";
9
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
9
10
  import { getFsSafeTestHooks } from "./test-hooks.js";
10
11
  const OPEN_READ_FLAGS = resolveReadOpenFlags();
11
12
  const OPEN_READ_FOLLOW_FLAGS = resolveReadOpenFlags({ followSymlinks: true });
@@ -19,7 +20,7 @@ export async function openLocalFileDescriptor(filePath, options) {
19
20
  // Reject directories before opening so we never surface EISDIR to callers (e.g. tool
20
21
  // results that get sent to messaging channels). See openclaw/openclaw#31186.
21
22
  try {
22
- preOpenStat = await inspectFileIdentity(() => {
23
+ preOpenStat = inspectFileIdentitySync(() => {
23
24
  const stat = fsSync.lstatSync(filePath, { bigint: true });
24
25
  observedBeforeOpen = true;
25
26
  if (stat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
@@ -37,13 +38,14 @@ export async function openLocalFileDescriptor(filePath, options) {
37
38
  }
38
39
  // Only an initial lookup failure falls through; a failed re-inspection aborts.
39
40
  }
40
- if (preOpenStat) {
41
- await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
41
+ if (preOpenStat && fsSafeTestHooks?.afterPreOpenLstat) {
42
+ await fsSafeTestHooks.afterPreOpenLstat(filePath);
42
43
  }
43
44
  const openFlags = (options?.symlinks === "follow-within-root"
44
45
  ? OPEN_READ_FOLLOW_FLAGS
45
46
  : OPEN_READ_FLAGS) | (options?.readWrite ? fsSync.constants.O_RDWR : 0);
46
- await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
47
+ if (fsSafeTestHooks?.beforeOpen)
48
+ await fsSafeTestHooks.beforeOpen(filePath, openFlags);
47
49
  let handle;
48
50
  try {
49
51
  handle = await fs.open(filePath, openFlags).catch((error) => recordFileOpenFailure(isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
@@ -59,19 +61,28 @@ export async function openLocalFileDescriptor(filePath, options) {
59
61
  throw err;
60
62
  }
61
63
  try {
62
- await fsSafeTestHooks?.afterOpen?.(filePath, handle);
63
- const stat = fsSync.fstatSync(handle.fd);
64
- if (!stat.isFile()) {
65
- throw new FsSafeError("not-file", "not a file");
66
- }
64
+ if (fsSafeTestHooks?.afterOpen)
65
+ await fsSafeTestHooks.afterOpen(filePath, handle);
67
66
  // Keep numeric Stats for the public receipt, never for identity verification.
68
67
  let openedIdentity;
69
- const identity = await inspectFileIdentity(() => (openedIdentity = fsSync.fstatSync(handle.fd, { bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
70
- if ([0, 1].includes(stat.nlink)) {
68
+ let identity;
69
+ try {
70
+ identity = inspectFileIdentitySync(() => {
71
+ openedIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
72
+ if (!openedIdentity.isFile())
73
+ throw new FsSafeError("not-file", "not a file");
74
+ return openedIdentity;
75
+ }, preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
76
+ }
77
+ catch (error) {
78
+ if (openedIdentity && [0n, 1n].includes(openedIdentity.nlink)) {
71
79
  await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
72
80
  }
73
81
  throw error;
74
- });
82
+ }
83
+ if (!identity.isFile())
84
+ throw new FsSafeError("not-file", "not a file");
85
+ const stat = numberStats(identity);
75
86
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
76
87
  throw hardlinkedPathNotAllowedError();
77
88
  }
@@ -81,6 +81,9 @@ export interface NativeOwnedTreeRemovalResult {
81
81
  errorCode?: string;
82
82
  errorMessage?: string;
83
83
  }
84
+ export interface NativeEmptyTreeRemovalResult extends Omit<NativeOwnedTreeRemovalResult, "outcome"> {
85
+ outcome?: "removed" | "preserved" | "not-empty";
86
+ }
84
87
  export interface NativeRootRemovalEntry {
85
88
  dev: bigint;
86
89
  ino: bigint;
@@ -101,6 +104,7 @@ export interface NativeWindowsAccessControlEntry {
101
104
  export interface NativeWindowsSecurityFacts {
102
105
  ownerSid: string;
103
106
  currentUserSid: string;
107
+ currentAppContainerSid?: string | null;
104
108
  ownerClass: string;
105
109
  worldWritable: boolean;
106
110
  groupWritable: boolean;
@@ -124,6 +128,8 @@ export interface NativeWindowsDirectoryReceipt {
124
128
  }
125
129
  export interface NativeDarwinAclFacts {
126
130
  state: "absent" | "empty" | "present";
131
+ inheritsToFiles?: boolean;
132
+ inheritsToDirectories?: boolean;
127
133
  }
128
134
  type NativeTwoPathArgs = [
129
135
  sourceRootFd: number,
@@ -132,6 +138,37 @@ type NativeTwoPathArgs = [
132
138
  targetRelPath: string
133
139
  ];
134
140
  export interface NativeBinding {
141
+ readCopyMetadata?(fd: number): Buffer;
142
+ restoreCopyMetadata?(fd: number, snapshot: Buffer): void;
143
+ restoreCopyFileTimes?(fd: number, atimeNs: bigint, mtimeNs: bigint): void;
144
+ copyLinkExclusive?(sourceParent: number, sourceName: string, parent: number, name: string, dev: bigint, ino: bigint, preserveMetadata: boolean, mode: number): {
145
+ dev: bigint;
146
+ ino: bigint;
147
+ };
148
+ publishCopyLink?(parent: number, name: string, destination: string, dev: bigint, ino: bigint): void;
149
+ removeCopyLink?(parent: number, name: string, dev: bigint, ino: bigint): void;
150
+ createCopySymlink?(parent: number, name: string, target: Buffer, mode: number, atimeNs?: bigint, mtimeNs?: bigint): {
151
+ dev: bigint;
152
+ ino: bigint;
153
+ errorCode?: string;
154
+ errorMessage?: string;
155
+ };
156
+ tryAcquireWriteLease?(fd: number): boolean;
157
+ isFileWriteLeaseHeld?(fd: number): boolean;
158
+ releaseFileWriteLease?(fd: number): void;
159
+ holdWindowsSharingLock?(path: string): {
160
+ close(): void;
161
+ };
162
+ setWindowsFileAttributes?(path: string, attrs: {
163
+ readOnly?: boolean;
164
+ hidden?: boolean;
165
+ system?: boolean;
166
+ }): void;
167
+ readWindowsFileExtents?(path: string): {
168
+ vcn: bigint;
169
+ lcn: bigint;
170
+ clusters: bigint;
171
+ }[];
135
172
  rootRemovalStat?(parent: number, name: string): NativeRootRemovalEntry;
136
173
  rootRemovalUnlink?(parent: number, name: string, dev: bigint, ino: bigint, directory: boolean): void;
137
174
  openRootRemovalDirectory?(parent: number, name: string, dev: bigint, ino: bigint): NativeRootRemovalDirectory;
@@ -169,6 +206,11 @@ export interface NativeBinding {
169
206
  };
170
207
  /** Internal: consumes only a descriptor returned by this binding. */
171
208
  closeOwnedFd(fd: number): void;
209
+ createPipe?(): {
210
+ reader: number;
211
+ writer: number;
212
+ atomicCloseOnExec: boolean;
213
+ };
172
214
  /** Internal Darwin-only synchronous inspection; the caller retains its fd. */
173
215
  inspectDarwinAcl?(fd: number, inheritanceTarget?: "file" | "directory"): NativeDarwinAclFacts;
174
216
  /** POSIX system canonicalization; confinement and identity policy stay with callers. */
@@ -176,6 +218,10 @@ export interface NativeBinding {
176
218
  path?: string;
177
219
  errno?: number;
178
220
  };
221
+ /** Internal Windows normalized name resolution after a denied DOS-name query. */
222
+ resolveWindowsCanonicalPath?(path: string): string | null;
223
+ /** Internal immutable primary-token membership, cached across Workers. */
224
+ isWindowsAppContainer?(): boolean;
179
225
  /** Internal: exact directory identity and canonical path from one no-follow handle. */
180
226
  observeDirectory?(path: string): NativeDirectoryObservation;
181
227
  /** Internal POSIX-only exact name/descriptor observation; the caller retains its fd. */
@@ -183,66 +229,87 @@ export interface NativeBinding {
183
229
  /** Linux/Windows byte transfer; callers retain both admitted descriptors until settlement. */
184
230
  copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
185
231
  /** Internal: same private, immutable input ownership as the ZIP buffer reader. */
186
- openTarBufferNative(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
232
+ openTarBufferNative?(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
187
233
  readonly entries: NativeArchiveEntry[];
188
234
  readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
189
235
  }>;
190
236
  /** Internal: input remains private, unpooled and immutable until the reader is released. */
191
- openZipBufferNative(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
237
+ openZipBufferNative?(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
192
238
  readonly entries: NativeArchiveEntry[];
193
239
  readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
194
240
  }>;
195
- readCloneFileMetadata(paths: string[]): Promise<(Buffer | null)[]>;
196
- probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
197
- cloneTree(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
241
+ readCloneFileMetadata?(paths: string[]): Promise<(Buffer | null)[]>;
242
+ probeTreeClone?(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
243
+ cloneTree?(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
198
244
  openStagedSymlink?(parentFd: number, basename: string): number;
199
245
  stagedSymlinkTarget?(parentFd: number, basename: string, linkFd: number): string;
200
246
  stagedSymlinkMatches?(parentFd: number, basename: string, linkFd: number): boolean;
201
247
  publishStagedSymlink?(parentFd: number, basename: string, linkFd: number, destination: string): void;
202
248
  removeStagedSymlink?(parentFd: number, basename: string, linkFd: number): "removed" | "name-absent" | "preserved";
203
249
  createStagedFile?(parentFd: number, basename: string): number;
250
+ stagedFileMetadata?(parentFd: number, basename: string, fd: number): {
251
+ mode: bigint;
252
+ nlink: bigint;
253
+ } | null;
204
254
  stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
255
+ stagedDirectoryMatches?(fd: number, path: string, dev: bigint, ino: bigint): boolean;
205
256
  removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
206
257
  copyFileExclusive?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined, signal: AbortSignal | undefined): Promise<NativeFileCopyResult>;
207
258
  copyFileExclusiveSync?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined): NativeFileCopyResult;
208
- cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
209
- copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
210
- createPrivateDirectory(path: string): void;
259
+ cloneFileExclusive?(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
260
+ copyFileRangeExclusive?(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
261
+ createPrivateDirectory?(path: string): void;
211
262
  /** Internal Windows creation receipts use full FILE_ID_INFO, never Node's projection. */
212
263
  inspectWindowsDirectory?(path: string, requirePrivate: boolean): NativeWindowsDirectoryReceipt;
213
264
  createPrivateDirectoryWithParentIdentity?(path: string, expectedParentIdentity: string): NativeWindowsDirectoryReceipt;
214
265
  /** Protects an already-private borrowed Node file; descriptor ownership stays with Node. */
215
266
  protectPrivateWindowsFile?(fd: number, path: string, expectedParentIdentity: string): NativeWindowsDirectoryReceipt;
216
267
  verifyPrivateWindowsFile?(fd: number, path: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks: number): void;
217
- extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
218
- fstatIdentity(fd: number): NativeFileIdentity;
219
- inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
220
- linkBeneath(...args: NativeTwoPathArgs): void;
268
+ extractArchiveNative?(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
269
+ fstatIdentity?(fd: number): NativeFileIdentity;
270
+ inspectArchiveNative?(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
271
+ linkBeneath?(...args: NativeTwoPathArgs): void;
221
272
  /** Direct-child mkdir; true is receipt provenance only, never cleanup ownership. */
222
273
  mkdirChildBeneath?(parentFd: number, basename: string, mode: number): boolean;
223
274
  mkdirOpenChildBeneath?(parentFd: number, basename: string, mode: number, flags: number): {
224
275
  fd: number;
225
276
  created: boolean;
226
277
  };
227
- mkdirBeneath(rootFd: number, relPath: string, mode: number): void;
228
- openBeneath(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
229
- readOwnerAndDacl(path: string): NativeWindowsSecurityFacts;
278
+ createTempDirectoryBeneath?(parentFd: number, basename: string): {
279
+ fd: number;
280
+ searchOnly: boolean;
281
+ };
282
+ verifyRootDirectory?(pathname: string, dev: bigint, ino: bigint): null | {
283
+ errorCode?: string;
284
+ errorMessage?: string;
285
+ errorErrno?: number;
286
+ closeErrorCode?: string;
287
+ closeErrorMessage?: string;
288
+ closeErrorErrno?: number;
289
+ fallbackFd?: number;
290
+ };
291
+ mkdirBeneath?(rootFd: number, relPath: string, mode: number): void;
292
+ openBeneath?(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
293
+ openPublicationMetadata?(parentFd: number, basename: string): number;
294
+ readOwnerAndDacl?(path: string): NativeWindowsSecurityFacts;
230
295
  /** Internal Windows-only inspection of the exact borrowed Node descriptor. */
231
296
  inspectWindowsSecureFileHandle?(fd: number): NativeWindowsDescriptorSecurityFacts;
232
297
  ownedTreeRemovalAvailable?(parentFd: number): boolean;
298
+ tryRemoveEmptyOwnedTree?(parentFd: number, basename: string, directoryFd: number): NativeEmptyTreeRemovalResult;
233
299
  removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
234
300
  removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
235
301
  /** Local filesystem admission for one-way cooperative entry publication. */
236
302
  retainWindowsEntryPublication?(sourcePath: string, name: string, sourceDev: bigint, sourceIno: bigint, targetPath: string, targetName: string, targetDev: bigint, targetIno: bigint, dev: bigint, ino: bigint, kind: "file" | "directory" | "symlink"): NativeWindowsEntryPublication;
237
303
  entryPublicationFilesystem?(parentFd: number): string;
238
304
  publishRetainedEntryNoReplace?(sourceParentFd: number, sourceBasename: string, sourceFd: number, targetParentFd: number, targetBasename: string): NativePublicationTransition;
239
- renameNoReplace(...args: NativeTwoPathArgs): void;
305
+ renameNoReplace?(...args: NativeTwoPathArgs): void;
240
306
  /** Linux auto-mode fallback through already-admitted direct-child parents. */
241
307
  moveNoReplaceFallback?(...args: [...paths: NativeTwoPathArgs, expectedSourceDev: bigint, expectedSourceIno: bigint]): void;
242
308
  /** Identity-fenced retained-directory rename capability. */
243
309
  renameNoReplaceWithIdentity?(...args: [...paths: NativeTwoPathArgs, expectedSourceDev: bigint, expectedSourceIno: bigint]): void;
244
- renameReplace(...args: NativeTwoPathArgs): void;
245
- sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
310
+ renameReplace?(...args: NativeTwoPathArgs): void;
311
+ sha256File?(fd: number, maxBytes?: number, signal?: AbortSignal, ownsPosition?: boolean): Promise<NativeFileHash>;
246
312
  }
313
+ export type CapableNativeBinding<K extends keyof NativeBinding> = NativeBinding & Required<Pick<NativeBinding, K>>;
247
314
  export declare function captureNativeFdClose(binding: NativeBinding): (fd: number) => void;
248
315
  export {};
@@ -4,5 +4,10 @@ export type FsSafeNativeConfig = {
4
4
  };
5
5
  export declare function configureFsSafeNative(config: Partial<FsSafeNativeConfig>): void;
6
6
  export declare function getFsSafeNativeConfig(): FsSafeNativeConfig;
7
+ /** Internal provenance; keep the public configuration shape unchanged. */
8
+ export declare function getNativeModeSetting(): {
9
+ mode: FsSafeNativeMode;
10
+ source: "programmatic configuration" | "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default";
11
+ };
7
12
  export declare function isFsSafeNativeRequired(): boolean;
8
13
  export declare function __resetFsSafeNativeConfigForTest(): void;
@@ -26,6 +26,17 @@ export function getFsSafeNativeConfig() {
26
26
  "auto",
27
27
  };
28
28
  }
29
+ /** Internal provenance; keep the public configuration shape unchanged. */
30
+ export function getNativeModeSetting() {
31
+ if (overrideConfig.mode !== undefined)
32
+ return { mode: overrideConfig.mode, source: "programmatic configuration" };
33
+ for (const source of ["FS_SAFE_NATIVE_MODE", "OPENCLAW_FS_SAFE_NATIVE_MODE"]) {
34
+ const mode = parseMode(process.env[source]);
35
+ if (mode !== undefined)
36
+ return { mode, source };
37
+ }
38
+ return { mode: "auto", source: "default" };
39
+ }
29
40
  export function isFsSafeNativeRequired() {
30
41
  return (overrideConfig.mode ??
31
42
  parseMode(process.env.FS_SAFE_NATIVE_MODE) ??
@@ -2,23 +2,25 @@ import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { getNativeBinding } from "./native.js";
4
4
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
5
+ import { isNativeStatIdentity, statIdentityFromNative } from "./native-stat-identity.js";
5
6
  export function getNativeDirectoryObservationBackend(enabled = true) {
6
7
  if (!enabled)
7
8
  return undefined;
8
- const binding = getNativeBinding();
9
+ const binding = getNativeBinding("observeDirectory");
9
10
  return binding && typeof binding.observeDirectory === "function"
10
11
  ? binding
11
12
  : undefined;
12
13
  }
13
14
  function validateNativeDirectoryObservation(observation) {
14
- if (!observation || typeof observation.dev !== "bigint" || observation.dev < 0n ||
15
- typeof observation.ino !== "bigint" || observation.ino < 0n ||
15
+ if (!observation || !isNativeStatIdentity(observation.dev) ||
16
+ !isNativeStatIdentity(observation.ino) ||
16
17
  typeof observation.realPath !== "string" ||
17
18
  observation.realPath.includes("\0") ||
18
19
  (!path.isAbsolute(observation.realPath) && !path.win32.isAbsolute(observation.realPath))) {
19
20
  throw new FsSafeError("path-mismatch", "native directory observation is invalid");
20
21
  }
21
- return observation;
22
+ const dev = statIdentityFromNative(observation.dev), ino = statIdentityFromNative(observation.ino);
23
+ return dev === observation.dev && ino === observation.ino ? observation : { ...observation, dev, ino };
22
24
  }
23
25
  /** Exact identity, type, and canonical path captured from one retained handle. */
24
26
  export function inspectNativeDirectoryObservation(backend, pathname, expected, platform = process.platform) {
@@ -1,3 +1,4 @@
1
+ import { type NativeBinding } from "./native.js";
1
2
  /** Warn once per capability, without including caller paths or native error text. */
2
- export declare function warnNativeFallback(feature: string, limitation: string): void;
3
+ export declare function warnNativeFallback(feature: string, limitation: string, ...capabilities: (keyof NativeBinding)[]): void;
3
4
  export declare function __resetNativeFallbackWarningsForTest(): void;
@@ -1,10 +1,34 @@
1
+ import { getNativeFallbackDiagnostic } from "./native.js";
2
+ import { getNativeModeSetting } from "./native-config.js";
3
+ import { nativeGuidance } from "./native-guidance.js";
1
4
  const warnedFeatures = new Set();
5
+ function fallbackReason(capabilities) {
6
+ const diagnostic = getNativeFallbackDiagnostic(...capabilities);
7
+ const expected = diagnostic.packageName === undefined
8
+ ? `No native package is published for ${diagnostic.target}.`
9
+ : `Expected ${diagnostic.packageName}@${diagnostic.packageVersion}.`;
10
+ const guidance = nativeGuidance(diagnostic.reason, getNativeModeSetting().source);
11
+ switch (diagnostic.reason) {
12
+ case "disabled":
13
+ return `native support is disabled by ${diagnostic.source} (disabled). ${expected} ${guidance}`;
14
+ case "unsupported-target":
15
+ return `the target ${diagnostic.target} is unsupported (unsupported-target). ${expected} ${guidance}`;
16
+ case "package-missing":
17
+ return `the selected native package could not be resolved (package-missing). ${expected} ${guidance}`;
18
+ case "load-failed":
19
+ return `the native package could not be loaded (load-failed: ${diagnostic.code}). ${expected} ${guidance}`;
20
+ case "capability-missing":
21
+ return `the native package lacks requested functions (capability-missing: ${diagnostic.capabilities.join(", ")}). ${expected} ${guidance}`;
22
+ default:
23
+ return `the requested native operation is unavailable (${diagnostic.reason}). ${expected}`;
24
+ }
25
+ }
2
26
  /** Warn once per capability, without including caller paths or native error text. */
3
- export function warnNativeFallback(feature, limitation) {
27
+ export function warnNativeFallback(feature, limitation, ...capabilities) {
4
28
  if (warnedFeatures.has(feature))
5
29
  return;
6
30
  warnedFeatures.add(feature);
7
- process.emitWarning(`${feature} is using its portable fallback because native support is unavailable or disabled. ${limitation}`, { code: "FS_SAFE_NATIVE_FALLBACK", type: "FsSafeWarning" });
31
+ process.emitWarning(`${feature} is using its portable fallback because ${fallbackReason(capabilities)} ${limitation}`, { code: "FS_SAFE_NATIVE_FALLBACK", type: "FsSafeWarning" });
8
32
  }
9
33
  export function __resetNativeFallbackWarningsForTest() {
10
34
  warnedFeatures.clear();
@@ -0,0 +1,3 @@
1
+ import type { getNativeModeSetting } from "./native-config.js";
2
+ /** Shared, path-free next steps for status reports and fallback warnings. */
3
+ export declare function nativeGuidance(reason: string, modeSource: ReturnType<typeof getNativeModeSetting>["source"]): string | undefined;
@@ -0,0 +1,21 @@
1
+ /** Shared, path-free next steps for status reports and fallback warnings. */
2
+ export function nativeGuidance(reason, modeSource) {
3
+ switch (reason) {
4
+ case "disabled": {
5
+ const enable = modeSource === "programmatic configuration"
6
+ ? 'call configureFsSafeNative({ mode: "auto" }) or configureFsSafeNative({ mode: "require" })'
7
+ : `set ${modeSource}=auto or ${modeSource}=require`;
8
+ return `If native loading is intended, ${enable}; keep intentional off settings.`;
9
+ }
10
+ case "unsupported-target":
11
+ return "Portable mode is expected on this target.";
12
+ case "package-missing":
13
+ return "Reinstall with optional dependencies included: npm --include=optional, or pnpm without --no-optional / optional=false. Check the lockfile and registry mirror.";
14
+ case "load-failed":
15
+ return "Check Node architecture, Linux libc compatibility, package dependencies, and OS code-integrity policy. Reinstalling identical bytes may not help.";
16
+ case "capability-missing":
17
+ return "Use matching fs-safe and platform-package versions; some targets intentionally provide only a subset of capabilities.";
18
+ default:
19
+ return undefined;
20
+ }
21
+ }
@@ -1,3 +1,3 @@
1
1
  import type { BigIntStats } from "node:fs";
2
2
  import type { NativeBinding } from "./native-binding.js";
3
- export declare function nativeMoveNoReplace(binding: NativeBinding, paths: Parameters<NativeBinding["renameNoReplace"]>, expected: BigIntStats | undefined, allowFallback: boolean, operation?: string): "link-unlink" | undefined;
3
+ export declare function nativeMoveNoReplace(binding: NativeBinding, paths: Parameters<NonNullable<NativeBinding["renameNoReplace"]>>, expected: BigIntStats | undefined, allowFallback: boolean, operation?: string): "link-unlink" | undefined;
@@ -1,4 +1,5 @@
1
1
  import { FsSafeError } from "./errors.js";
2
+ import { assertNativeCapabilities } from "./native.js";
2
3
  import { cachedNoReplaceUnavailable, noReplaceUnavailable, rememberNoReplaceUnavailable, } from "./native-noreplace.js";
3
4
  function fallbackError(error, operation) {
4
5
  const code = error?.code;
@@ -24,6 +25,7 @@ function fallbackError(error, operation) {
24
25
  return error;
25
26
  }
26
27
  export function nativeMoveNoReplace(binding, paths, expected, allowFallback, operation = "move") {
28
+ assertNativeCapabilities(binding, "renameNoReplace");
27
29
  const [sourceFd, source, targetFd, target] = paths;
28
30
  let unavailable = cachedNoReplaceUnavailable(binding, sourceFd, operation);
29
31
  if (!unavailable) {
@@ -1,5 +1,4 @@
1
1
  import fsSync, {} from "node:fs";
2
- import fs from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import { sameFileIdentityForCleanup } from "./file-identity.js";
5
4
  import { getNativeBinding } from "./native.js";
@@ -32,21 +31,21 @@ export function removeNativeCreatedFileIfStillPinned(params) {
32
31
  }
33
32
  }
34
33
  export async function createNativeExclusiveFile(targetPath, mode) {
35
- const binding = getNativeBinding();
34
+ const binding = getNativeBinding("openBeneath");
36
35
  if (!binding) {
37
36
  return undefined;
38
37
  }
39
38
  const closeFd = captureNativeFdClose(binding);
40
39
  const parentPath = path.dirname(targetPath);
41
40
  const basename = path.basename(targetPath);
42
- const parent = await fs.open(parentPath, fsSync.constants.O_RDONLY |
41
+ const parentFd = fsSync.openSync(parentPath, fsSync.constants.O_RDONLY |
43
42
  (typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
44
43
  let fd;
45
44
  let created;
46
45
  try {
47
46
  let opened;
48
47
  try {
49
- opened = binding.openBeneath(parent.fd, basename, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL));
48
+ opened = binding.openBeneath(parentFd, basename, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL));
50
49
  }
51
50
  catch (error) {
52
51
  // The parent open above and every post-create operation remain untagged.
@@ -94,7 +93,7 @@ export async function createNativeExclusiveFile(targetPath, mode) {
94
93
  }
95
94
  removeNativeCreatedFileIfStillPinned({
96
95
  parentPath,
97
- parentFd: parent.fd,
96
+ parentFd,
98
97
  basename,
99
98
  created,
100
99
  });
@@ -102,6 +101,9 @@ export async function createNativeExclusiveFile(targetPath, mode) {
102
101
  throw error;
103
102
  }
104
103
  finally {
105
- await parent.close().catch(() => undefined);
104
+ try {
105
+ fsSync.closeSync(parentFd);
106
+ }
107
+ catch { /* Preserve the creation outcome. */ }
106
108
  }
107
109
  }
@@ -1,15 +1,20 @@
1
1
  import { type BigIntStats, type Stats } from "node:fs";
2
2
  import type { FileHandle } from "node:fs/promises";
3
- import type { FileIdentityStat } from "./file-identity.js";
4
- import type { NativeBinding } from "./native.js";
3
+ import { type FileIdentityStat } from "./file-identity.js";
4
+ import { type NativeBinding } from "./native.js";
5
5
  import { describeStagedDirectory } from "./staged-directory.js";
6
6
  type RootIdentity = Pick<FileIdentityStat, "dev" | "ino">;
7
+ type NativeRootHandle = {
8
+ readonly fd: number;
9
+ close(): Promise<void>;
10
+ [Symbol.asyncDispose](): Promise<void>;
11
+ };
7
12
  export type NativeRootAdmission = {
8
13
  directoryFlags?: number;
9
14
  exactRoot: boolean;
10
15
  reportCloseErrors?: boolean;
11
16
  operation: string;
12
- root: FileHandle;
17
+ root: NativeRootHandle;
13
18
  rootPath: string;
14
19
  };
15
20
  export type NativeParentAdmission = {
@@ -23,6 +28,41 @@ export type NativeParentAdmission = {
23
28
  stagedDirectory?: ReturnType<typeof describeStagedDirectory>;
24
29
  };
25
30
  export declare function sameNativeIdentity(left: RootIdentity, right: RootIdentity): boolean;
31
+ /** Short native mutations need a retained descriptor, not a Node FileHandle. */
32
+ export declare function openExactNativeRootSync(binding: NativeBinding, params: {
33
+ rootPath: string;
34
+ rootIdentity: {
35
+ dev: bigint;
36
+ ino: bigint;
37
+ };
38
+ }): {
39
+ fd: number;
40
+ stat: BigIntStats;
41
+ close(): void;
42
+ [Symbol.dispose](): void;
43
+ };
44
+ export declare function openExactNativeRootAdmissionSync(binding: NativeBinding, params: {
45
+ rootPath: string;
46
+ rootIdentity: {
47
+ dev: bigint;
48
+ ino: bigint;
49
+ };
50
+ operation: string;
51
+ reportCloseErrors?: boolean;
52
+ }): {
53
+ directoryFlags: number;
54
+ exactRoot: boolean;
55
+ reportCloseErrors: boolean | undefined;
56
+ operation: string;
57
+ root: {
58
+ fd: number;
59
+ stat: BigIntStats;
60
+ close(): void;
61
+ [Symbol.dispose](): void;
62
+ };
63
+ rootPath: string;
64
+ };
65
+ export declare function borrowNativeRootParent(admission: ReturnType<typeof openExactNativeRootAdmissionSync>): NativeParentAdmission;
26
66
  export declare function openNativeRootAdmission(binding: NativeBinding, params: {
27
67
  rootPath: string;
28
68
  rootIdentity?: RootIdentity;
@@ -32,5 +72,5 @@ export declare function openNativeRootAdmission(binding: NativeBinding, params:
32
72
  }): Promise<NativeRootAdmission>;
33
73
  export declare function openNativeParentAdmission(binding: NativeBinding, rootAdmission: Omit<NativeRootAdmission, "root"> & {
34
74
  root: Pick<FileHandle, "fd">;
35
- }, relativeParentPath: string, observation?: "native-directory"): Promise<NativeParentAdmission>;
75
+ }, relativeParentPath: string, observation?: "native-directory" | "native-publication"): Promise<NativeParentAdmission>;
36
76
  export {};