@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,18 +1,20 @@
1
1
  import fsSync, {} from "node:fs";
2
- import fs from "node:fs/promises";
3
2
  import path from "node:path";
4
- import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
3
+ import { assertSyncDirectoryGuard, inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
5
4
  import { nodeDirectorySearchOnlyFlags } from "./directory-mode-node.js";
6
5
  import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
7
6
  import { FsSafeError } from "./errors.js";
7
+ import { hasSafeFileIdentity } from "./file-identity.js";
8
+ import { assertNativeCapabilities } from "./native.js";
8
9
  import { captureNativeFdClose } from "./native-binding.js";
9
10
  import { realpathSync } from "./realpath.js";
10
- import { describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
11
+ import { describeNativeStagedDirectory, describePolicyStagedDirectory, describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
11
12
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
12
13
  import { createSuppressedError } from "./suppressed-error.js";
13
14
  import { assertNoWindowsPathAlias, pathForWindowsFilesystem } from "./windows-path-alias.js";
14
15
  export function sameNativeIdentity(left, right) {
15
- return left.dev === right.dev && left.ino === right.ino;
16
+ return hasSafeFileIdentity(left) && hasSafeFileIdentity(right) &&
17
+ BigInt(left.dev) === BigInt(right.dev) && BigInt(left.ino) === BigInt(right.ino);
16
18
  }
17
19
  function unavailable(message) {
18
20
  return new FsSafeError("helper-unavailable", message);
@@ -22,13 +24,67 @@ function assertParentAdmissionAvailable(binding) {
22
24
  throw unavailable("native parent directory admission is unavailable");
23
25
  }
24
26
  captureNativeFdClose(binding);
27
+ assertNativeCapabilities(binding, "openBeneath");
28
+ }
29
+ /** Short native mutations need a retained descriptor, not a Node FileHandle. */
30
+ export function openExactNativeRootSync(binding, params) {
31
+ assertParentAdmissionAvailable(binding);
32
+ assertNoWindowsPathAlias(params.rootPath, "filesystem", "native root uses a Windows filesystem namespace alias");
33
+ const flags = (nodeDirectorySearchOnlyFlags()?.flags ?? fsSync.constants.O_RDONLY) |
34
+ (fsSync.constants.O_DIRECTORY ?? 0);
35
+ const fd = fsSync.openSync(pathForWindowsFilesystem(params.rootPath), flags);
36
+ let held = true;
37
+ const close = () => {
38
+ if (!held)
39
+ return;
40
+ held = false;
41
+ fsSync.closeSync(fd);
42
+ };
43
+ try {
44
+ const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), params.rootIdentity);
45
+ if (!stat.isDirectory())
46
+ throw new FsSafeError("path-mismatch", "native root is not a directory");
47
+ return { fd, stat, close, [Symbol.dispose]: close };
48
+ }
49
+ catch (error) {
50
+ try {
51
+ close();
52
+ }
53
+ catch (closeError) {
54
+ throw createSuppressedError(closeError, error, "native root admission and close failed");
55
+ }
56
+ throw error;
57
+ }
58
+ }
59
+ export function openExactNativeRootAdmissionSync(binding, params) {
60
+ return {
61
+ directoryFlags: (nodeDirectorySearchOnlyFlags()?.flags ?? fsSync.constants.O_RDONLY) | (fsSync.constants.O_DIRECTORY ?? 0),
62
+ exactRoot: true,
63
+ reportCloseErrors: params.reportCloseErrors,
64
+ operation: params.operation,
65
+ root: openExactNativeRootSync(binding, params),
66
+ rootPath: params.rootPath,
67
+ };
68
+ }
69
+ export function borrowNativeRootParent(admission) {
70
+ const guard = { dir: admission.rootPath, realPath: admission.rootPath, stat: admission.root.stat };
71
+ assertSyncDirectoryGuard(guard);
72
+ return { fd: admission.root.fd, guard, close() { } };
25
73
  }
26
74
  export async function openNativeRootAdmission(binding, params) {
27
75
  assertParentAdmissionAvailable(binding);
28
76
  const directoryFlags = (params.searchOnly ? nodeDirectorySearchOnlyFlags()?.flags ?? fsSync.constants.O_RDONLY : fsSync.constants.O_RDONLY) |
29
77
  (fsSync.constants.O_DIRECTORY ?? 0);
30
78
  assertNoWindowsPathAlias(params.rootPath, "filesystem", "native root uses a Windows filesystem namespace alias");
31
- const root = await fs.open(pathForWindowsFilesystem(params.rootPath), directoryFlags);
79
+ let fd = fsSync.openSync(pathForWindowsFilesystem(params.rootPath), directoryFlags);
80
+ const close = async () => {
81
+ if (fd < 0)
82
+ return;
83
+ const owned = fd;
84
+ fd = -1;
85
+ fsSync.closeSync(owned);
86
+ };
87
+ const root = { get fd() { return fd; }, close, [Symbol.asyncDispose]: close };
32
88
  try {
33
89
  const exactRoot = typeof params.rootIdentity?.dev === "bigint" &&
34
90
  typeof params.rootIdentity.ino === "bigint";
@@ -38,10 +94,7 @@ export async function openNativeRootAdmission(binding, params) {
38
94
  rootMatches = true;
39
95
  }
40
96
  else if (process.platform === "win32") {
41
- if (typeof binding.fstatIdentity !== "function") {
42
- throw unavailable("native root directory identity checks are unavailable");
43
- }
44
- const identity = binding.fstatIdentity(root.fd);
97
+ const identity = fsSync.fstatSync(root.fd, { bigint: true });
45
98
  rootMatches = !params.rootIdentity || sameNativeIdentity(params.rootIdentity, identity);
46
99
  }
47
100
  else {
@@ -87,7 +140,8 @@ export async function openNativeParentAdmission(binding, rootAdmission, relative
87
140
  const parentInput = relativeParentPath
88
141
  ? path.join(rootAdmission.rootPath, ...relativeParentPath.split("/"))
89
142
  : rootAdmission.rootPath;
90
- if (observation === "native-directory" && process.platform === "win32" && rootAdmission.exactRoot &&
143
+ if ((observation === "native-directory" || observation === "native-publication") &&
144
+ process.platform === "win32" && rootAdmission.exactRoot &&
91
145
  typeof binding.observeDirectory === "function") {
92
146
  const stat = inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true }));
93
147
  let observed;
@@ -110,17 +164,33 @@ export async function openNativeParentAdmission(binding, rootAdmission, relative
110
164
  }
111
165
  const parentPath = realpathSync.native(pathForWindowsFilesystem(parentInput));
112
166
  assertNoWindowsPathAlias(parentPath, "filesystem", "native parent uses a Windows filesystem namespace alias");
167
+ if (observation === "native-publication" && process.platform !== "win32" && rootAdmission.exactRoot) {
168
+ const captured = describeNativeStagedDirectory(binding, parentFd, parentPath);
169
+ return {
170
+ fd: parentFd,
171
+ close: () => closeFd(parentFd),
172
+ guard: { dir: parentPath, realPath: parentPath, stat: captured.stat },
173
+ stagedDirectory: captured.directory,
174
+ };
175
+ }
176
+ if (observation === "native-directory" && process.platform !== "win32" && rootAdmission.exactRoot) {
177
+ const captured = describePolicyStagedDirectory(parentFd, parentPath, binding);
178
+ captured.disposeObservation?.();
179
+ return {
180
+ fd: parentFd,
181
+ close: () => closeFd(parentFd),
182
+ guard: { dir: parentPath, realPath: parentPath, stat: captured.stat },
183
+ stagedDirectory: captured.directory,
184
+ };
185
+ }
113
186
  const stagedDirectory = process.platform === "win32"
114
187
  ? undefined
115
188
  : describeStagedDirectory(parentFd, parentPath);
116
189
  const parentPathStat = rootAdmission.exactRoot
117
190
  ? await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })))
118
- : fsSync.lstatSync(pathForWindowsFilesystem(parentPath));
191
+ : fsSync.lstatSync(pathForWindowsFilesystem(parentPath), { bigint: true });
119
192
  if (process.platform === "win32" && !rootAdmission.exactRoot) {
120
- if (typeof binding.fstatIdentity !== "function") {
121
- throw unavailable("native parent directory identity checks are unavailable");
122
- }
123
- const parentIdentity = binding.fstatIdentity(parentFd);
193
+ const parentIdentity = fsSync.fstatSync(parentFd, { bigint: true });
124
194
  if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
125
195
  throw new FsSafeError("path-mismatch", `${rootAdmission.operation} parent changed during resolution`);
126
196
  }
@@ -1,6 +1,6 @@
1
- import type { FileHandle } from "node:fs/promises";
1
+ import type { NativeRootAdmission } from "./native-parent-admission.js";
2
2
  import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import type { FileIdentityStat } from "./file-identity.js";
4
- import type { NativeBinding } from "./native.js";
4
+ import { type NativeBinding } from "./native.js";
5
5
  import type { PinnedWriteParams } from "./pinned-write-types.js";
6
- export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: FileHandle, parentFd: number, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
6
+ export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: NativeRootAdmission["root"], parentFd: number, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
@@ -8,6 +8,8 @@ import { assertSyncDirectoryGuard } from "./directory-guard.js";
8
8
  import { FsSafeError } from "./errors.js";
9
9
  import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, } from "./native-operations.js";
10
10
  import { writePinnedInput } from "./pinned-write-input.js";
11
+ import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
12
+ import { assertNativeCapabilities } from "./native.js";
11
13
  import { captureNativeFdClose } from "./native-binding.js";
12
14
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
13
15
  import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
@@ -15,6 +17,7 @@ import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
15
17
  import { cleanupPinnedFilePath } from "./file-cleanup.js";
16
18
  import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
17
19
  import { exactIdentityMatches } from "./staged-directory.js";
20
+ import { restoreCopyMetadata } from "./copy-metadata.js";
18
21
  import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
19
22
  function closeWriteFd(closeFd, fd) {
20
23
  if (fd === undefined)
@@ -50,6 +53,7 @@ function assertNamedParentCurrent(binding, guard, identity) {
50
53
  assertSyncDirectoryGuard({ ...guard, stat: identity });
51
54
  }
52
55
  export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
56
+ assertNativeCapabilities(binding, "openBeneath", "renameNoReplace", "renameReplace", "fstatIdentity");
53
57
  const closeFd = captureNativeFdClose(binding);
54
58
  const parentPath = parentGuard.realPath;
55
59
  let tempFd;
@@ -78,6 +82,9 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
78
82
  try {
79
83
  tempName = `.fs-safe-${randomUUID()}.tmp`;
80
84
  params.assertBeforeMutation?.();
85
+ const copied = params.input.kind === "file" && !params.private
86
+ ? await createNativeCopyFile(binding, params.input, parentFd, tempName, params.maxBytes)
87
+ : undefined;
81
88
  if (params.private) {
82
89
  const parentIdentity = fsSync.fstatSync(parentFd, { bigint: true });
83
90
  privateHandle = await createFileHandle(path.join(parentPath, tempName), {
@@ -91,16 +98,25 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
91
98
  });
92
99
  tempFd = privateHandle.fd;
93
100
  }
101
+ else if (copied) {
102
+ tempFd = copied.fd;
103
+ }
94
104
  else {
95
105
  tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
96
106
  }
97
107
  const verificationIdentity = inspectFileIdentitySync(() => fsSync.fstatSync(tempFd, { bigint: true }));
98
108
  tempIdentity = verificationIdentity;
109
+ if (params.input.kind === "file")
110
+ assertNativeCopyCompleted(params.input, copied);
99
111
  // Creation is requested at 0600 in the binding, but a restrictive umask
100
112
  // can remove owner access. Keep the unpublished inode private and
101
113
  // reopenable until the published name has been identity-fenced.
102
- fsSync.fchmodSync(tempFd, 0o600);
103
- await writePinnedInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
114
+ if ((verificationIdentity.mode & 128n) === 0n)
115
+ fsSync.fchmodSync(tempFd, 0o600);
116
+ if (!copied)
117
+ await writePinnedInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
118
+ if (params.input.kind === "file")
119
+ restoreCopyMetadata(tempFd, params.input.metadata);
104
120
  if (params.sync !== false) {
105
121
  if (params.strictFileSync)
106
122
  fsSync.fsyncSync(tempFd);
@@ -132,6 +148,9 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
132
148
  }
133
149
  assertFinalSymlinkRejected(path.join(parentPath, params.basename), params.rejectFinalSymlink);
134
150
  }
151
+ // The authority callback itself may have cancelled an otherwise completed copy.
152
+ if (params.input.kind === "file")
153
+ params.input.signal?.throwIfAborted();
135
154
  try {
136
155
  if (params.overwrite === false) {
137
156
  binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
@@ -150,17 +169,25 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
150
169
  if (receipt)
151
170
  publication = Object.freeze({ status: "published", staged: receipt, basename: params.basename, overwrite: false });
152
171
  params.onPublished?.(verificationIdentity);
153
- targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
172
+ const metadataOnly = params.overwrite === false && params.input.kind === "buffer" &&
173
+ params.input.stageBeforePublish !== false && !params.private &&
174
+ !params.assertBeforeMutation && binding.openPublicationMetadata;
175
+ targetFd = metadataOnly
176
+ ? metadataOnly(parentFd, params.basename)
177
+ : binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
154
178
  const targetStat = inspectFileIdentitySync(() => fsSync.fstatSync(targetFd, { bigint: true }), verificationIdentity);
155
- const targetIdentity = binding.fstatIdentity(targetFd);
156
179
  if (!targetStat.isFile()) {
157
180
  throw new FsSafeError("path-mismatch", "native write target changed after rename");
158
181
  }
159
182
  // Native exclusive creation starts at 0600. Apply the requested mode only
160
183
  // after reopening and fencing the published name, both so mode 000 stays
161
184
  // verifiable and so broader modes are never exposed before that fence.
185
+ // Windows chmod only changes the read-only attribute; use the fresh target
186
+ // observation to avoid rewriting an unchanged attribute.
187
+ const modeChange = (targetStat.mode & 128n) !== BigInt(params.mode & 0o200);
162
188
  try {
163
- fsSync.fchmodSync(targetFd, params.mode);
189
+ if (modeChange)
190
+ fsSync.fchmodSync(targetFd, params.mode);
164
191
  // Windows FlushFileBuffers needs write access; the verification reopen is read-only.
165
192
  if (params.sync !== false) {
166
193
  if (params.strictFileSync)
@@ -194,7 +221,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
194
221
  else
195
222
  await params.verifyPublished?.(targetFd, verificationIdentity, parentGuard);
196
223
  completed = true;
197
- return { dev: targetIdentity.dev, ino: targetIdentity.ino };
224
+ return { dev: targetStat.dev, ino: targetStat.ino };
198
225
  }
199
226
  catch (error) {
200
227
  failure = { error };
@@ -1,4 +1,7 @@
1
1
  import type { FileIdentityStat } from "./file-identity.js";
2
- import type { NativeBinding } from "./native.js";
2
+ import { type NativeBinding } from "./native.js";
3
3
  import type { PinnedWriteParams } from "./pinned-write-types.js";
4
+ type PinnedWriteCapabilitiesInput = Pick<PinnedWriteParams, "input" | "private" | "verifyPosixMode">;
5
+ export declare function getPinnedWriteNativeBinding(params: PinnedWriteCapabilitiesInput): NativeBinding | undefined;
4
6
  export declare function runPinnedWriteNative(binding: NativeBinding, params: PinnedWriteParams, fallback: (input: PinnedWriteParams["input"]) => Promise<FileIdentityStat>): Promise<FileIdentityStat>;
7
+ export {};
@@ -56,12 +56,27 @@ import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
56
56
  import { capturePolicyAwareNativeParent } from "./native-policy-parent.js";
57
57
  import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
58
58
  import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
59
+ import { assertNativeCapabilities, getNativeBinding } from "./native.js";
59
60
  import { captureNativeFdClose } from "./native-binding.js";
60
61
  import { cachedNoReplaceUnavailable } from "./native-noreplace.js";
61
62
  import { isFsSafeNativeRequired } from "./native-config.js";
63
+ import { copyLinkAtParent } from "./copy-link.js";
64
+ const writeCapabilities = ["openBeneath", "mkdirBeneath", "renameNoReplace", "renameReplace"];
65
+ const posixWriteCapabilities = [...writeCapabilities, "createStagedFile", "stagedFileMatches", "removeStagedFile"];
66
+ function pinnedWriteCapabilities(params) {
67
+ const windows = process.platform === "win32";
68
+ const capabilities = windows ? [...writeCapabilities, "fstatIdentity"] : posixWriteCapabilities;
69
+ const nativeCopy = params.input.kind === "file" && !params.private &&
70
+ (windows || !params.verifyPosixMode);
71
+ return nativeCopy ? [...capabilities, "copyFileExclusive"] : capabilities;
72
+ }
73
+ export function getPinnedWriteNativeBinding(params) {
74
+ return getNativeBinding(...pinnedWriteCapabilities(params));
75
+ }
62
76
  export async function runPinnedWriteNative(binding, params, fallback) {
63
77
  const env_1 = { stack: [], error: void 0, hasError: false };
64
78
  try {
79
+ assertNativeCapabilities(binding, ...pinnedWriteCapabilities(params));
65
80
  const closeFd = captureNativeFdClose(binding);
66
81
  const windows = process.platform === "win32";
67
82
  if (!windows) {
@@ -105,17 +120,21 @@ export async function runPinnedWriteNative(binding, params, fallback) {
105
120
  params.assertBeforeMutation?.();
106
121
  binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
107
122
  }
108
- const admitted = await openNativeParentAdmission(binding, rootAdmission, params.relativeParentPath);
123
+ const admitted = await openNativeParentAdmission(binding, rootAdmission, params.relativeParentPath, "native-publication");
109
124
  parentFd = admitted.fd;
110
125
  parentPath = admitted.guard.realPath;
111
126
  directory = admitted.stagedDirectory;
112
127
  parentPathStat = admitted.guard.stat;
113
128
  }
114
129
  const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
130
+ if (params.input.kind === "link") {
131
+ return await copyLinkAtParent(params, params.input, verificationGuard, { binding, fd: parentFd });
132
+ }
115
133
  if (params.overwrite === false) {
116
134
  try {
117
- fsSync.lstatSync(path.join(parentPath, params.basename));
118
- throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
135
+ if (fsSync.lstatSync(path.join(parentPath, params.basename), { throwIfNoEntry: false })) {
136
+ throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
137
+ }
119
138
  }
120
139
  catch (error) {
121
140
  if (error.code !== "ENOENT") {
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { getFsSafeNativeConfig } from "./native-config.js";
4
+ import { isNativeStatIdentity, statIdentityFromNative } from "./native-stat-identity.js";
4
5
  export class NativePolicyDirectoryMismatch extends FsSafeError {
5
6
  constructor(cause) {
6
7
  super("path-mismatch", "staging directory pathname changed", { cause });
@@ -39,8 +40,8 @@ export function nativePolicyDirectoryObserver(binding, fd, pathname) {
39
40
  }
40
41
  if (getFsSafeNativeConfig().mode !== mode)
41
42
  return undefined;
42
- if (!observed || typeof observed.dev !== "bigint" || observed.dev < 0n || observed.dev > 0xffffffffffffffffn ||
43
- typeof observed.ino !== "bigint" || observed.ino < 0n || observed.ino > 0xffffffffffffffffn ||
43
+ if (!observed || !isNativeStatIdentity(observed.dev) ||
44
+ !isNativeStatIdentity(observed.ino) ||
44
45
  typeof observed.mode !== "bigint" || observed.mode < 0n || observed.mode > 0xffffffffn ||
45
46
  typeof observed.nlink !== "bigint" || observed.nlink < 0n || observed.nlink > 0xffffffffffffffffn ||
46
47
  typeof observed.realPath !== "string" || observed.realPath.includes("\0") ||
@@ -53,7 +54,7 @@ export function nativePolicyDirectoryObserver(binding, fd, pathname) {
53
54
  return Object.freeze({
54
55
  canonicalPath: observed.realPath,
55
56
  identity: Object.freeze({
56
- dev: observed.dev, ino: observed.ino, mode: observed.mode, nlink: observed.nlink,
57
+ dev: statIdentityFromNative(observed.dev), ino: statIdentityFromNative(observed.ino), mode: observed.mode, nlink: observed.nlink,
57
58
  }),
58
59
  });
59
60
  };
@@ -1,6 +1,6 @@
1
1
  import { type BigIntStats } from "node:fs";
2
2
  import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
- import type { NativeBinding } from "./native.js";
3
+ import { type NativeBinding } from "./native.js";
4
4
  import type { NativeRootAdmission } from "./native-parent-admission.js";
5
5
  import type { PinnedWriteParams } from "./pinned-write-types.js";
6
6
  import { describeStagedDirectory, type PolicyStagedDirectory } from "./staged-directory.js";
@@ -54,6 +54,7 @@ import fsSync, {} from "node:fs";
54
54
  import path from "node:path";
55
55
  import { inspectDirectoryIdentity, inspectDirectoryIdentitySync, } from "./directory-guard.js";
56
56
  import { FsSafeError } from "./errors.js";
57
+ import { assertNativeCapabilities } from "./native.js";
57
58
  import { captureNativeFdClose } from "./native-binding.js";
58
59
  import { NativePolicyDirectoryMismatch } from "./native-policy-directory-observation.js";
59
60
  import { assertWindowsPolicyParentCurrent, closeWindowsPolicyParentAfterFailure, openWindowsPolicyParent, windowsParentObservation, } from "./native-policy-parent-windows.js";
@@ -76,6 +77,7 @@ function mkdirPosixPolicyChild(binding, parentFd, basename) {
76
77
  if (typeof created === "boolean")
77
78
  return created;
78
79
  }
80
+ assertNativeCapabilities(binding, "mkdirBeneath");
79
81
  binding.mkdirBeneath(parentFd, basename, 0o777);
80
82
  return false;
81
83
  }
@@ -122,6 +124,7 @@ function tryDescribePolicyPosixParent(binding, fd, pathname) {
122
124
  export async function capturePolicyAwareNativeParent(binding, params, rootAdmission, windows, directoryFlags, fuseChildCreation = false) {
123
125
  const env_1 = { stack: [], error: void 0, hasError: false };
124
126
  try {
127
+ assertNativeCapabilities(binding, "openBeneath");
125
128
  const rootFd = rootAdmission.root.fd;
126
129
  const closeFd = captureNativeFdClose(binding);
127
130
  const observationDisposers = windows ? undefined : new Map();
@@ -0,0 +1,3 @@
1
+ import type { NativeBinding } from "./native-binding.js";
2
+ /** Exact nonprivate stage facts from one fresh named/retained observation. */
3
+ export declare function nativeStagedFileMode(binding: NativeBinding, parentFd: number, name: string, fd: number, expectedMode?: number): number | undefined;
@@ -0,0 +1,16 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ /** Exact nonprivate stage facts from one fresh named/retained observation. */
3
+ export function nativeStagedFileMode(binding, parentFd, name, fd, expectedMode) {
4
+ if (typeof binding.stagedFileMetadata !== "function")
5
+ return undefined;
6
+ const stat = binding.stagedFileMetadata(parentFd, name, fd);
7
+ if (!stat || typeof stat.mode !== "bigint" || stat.mode < 0n || stat.mode > 0xffffffffn ||
8
+ typeof stat.nlink !== "bigint" || stat.nlink !== 1n || (stat.mode & 61440n) !== 32768n) {
9
+ throw new FsSafeError("path-mismatch", "staged entry no longer names the exclusive created file");
10
+ }
11
+ const mode = Number(stat.mode & 4095n);
12
+ if (expectedMode !== undefined && mode !== expectedMode) {
13
+ throw new FsSafeError("insecure-permissions", "filesystem did not enforce the staged file mode");
14
+ }
15
+ return mode;
16
+ }
@@ -5,7 +5,7 @@ import type { FileIdentityStat } from "./file-identity.js";
5
5
  import { type NativeBinding } from "./native-binding.js";
6
6
  import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write-types.js";
7
7
  import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
8
- export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
8
+ export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile" | "renameReplace" | "renameNoReplace">>;
9
9
  export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
10
10
  type StagedPermissionPolicy = "private-creation" | "mode-only";
11
11
  type NativeStageOwner = Readonly<{
@@ -60,13 +60,15 @@ import { FsSafeError } from "./errors.js";
60
60
  import { assertSynchronousCallbackResult, isMutationAuthorityError } from "./mutation-authority.js";
61
61
  import { captureNativeFdClose } from "./native-binding.js";
62
62
  import { replayPinnedInput, writePinnedInput } from "./pinned-write-input.js";
63
+ import { nativeStagedFileMode } from "./native-staged-file-metadata.js";
63
64
  import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
64
- import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
65
+ import { assertNativeStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
65
66
  import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
66
67
  import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
67
68
  import { createStagedFileReceipt, stagedFailure } from "./staged-file-settlement.js";
68
69
  import { noReplaceUnavailable, rememberNoReplaceUnavailable } from "./native-noreplace.js";
69
70
  import { isFsSafeNativeRequired } from "./native-config.js";
71
+ import { restoreCopyMetadata } from "./copy-metadata.js";
70
72
  export function assertNativeStaging(binding) {
71
73
  if ([
72
74
  binding.closeOwnedFd,
@@ -221,6 +223,9 @@ class NativeStagedFile {
221
223
  }
222
224
  #assertNamed(name, expectedMode) {
223
225
  const fd = this.#file();
226
+ const mode = !this.#private ? nativeStagedFileMode(this.#owner.binding, this.#owner.parentFd, name, fd, this.#verifyMode ? expectedMode : undefined) : undefined;
227
+ if (mode !== undefined)
228
+ return mode;
224
229
  const stat = this.#owner.binding.stagedFileMatches(this.#owner.parentFd, name, fd)
225
230
  ? fs.fstatSync(fd, { bigint: true }) : undefined;
226
231
  if (!stat || stat.nlink !== 1n) {
@@ -233,9 +238,12 @@ class NativeStagedFile {
233
238
  if (this.#open().publication.status !== "not-published") {
234
239
  throw new FsSafeError("helper-failed", "staged file publication has already been attempted");
235
240
  }
236
- assertStagedDirectoryCurrent(this.#owner.directory);
241
+ this.#assertDirectory();
237
242
  this.#assertNamed(this.#owner.name, 0o600);
238
243
  }
244
+ #assertDirectory() {
245
+ assertNativeStagedDirectoryCurrent(this.#owner.binding, this.#owner.parentFd, this.#owner.directory);
246
+ }
239
247
  #assertPermissions(fd, stat, expectedMode) {
240
248
  if (this.#private)
241
249
  assertPrivateCreationFile(stat, fd);
@@ -259,7 +267,7 @@ class NativeStagedFile {
259
267
  throw new FsSafeError("helper-unavailable", "required cloning cannot verify staged permissions before copying");
260
268
  }
261
269
  }
262
- assertStagedDirectoryCurrent(this.#owner.directory);
270
+ this.#assertDirectory();
263
271
  // The exclusive open performs no fallible post-open checks. Store its fd
264
272
  // before every subsequent operation, including the first metadata read.
265
273
  const state = this.#open();
@@ -299,6 +307,8 @@ class NativeStagedFile {
299
307
  } : this.#assertBeforeMutation;
300
308
  if (!copied)
301
309
  await writePinnedInput(fd, input, maxBytes, assertBeforeMutation);
310
+ if (input.kind === "file")
311
+ restoreCopyMetadata(fd, input.metadata);
302
312
  if (this.#verifyMode)
303
313
  this.#assertStagePermissions(fd);
304
314
  if (this.#owner.sync !== false) {
@@ -386,7 +396,7 @@ class NativeStagedFile {
386
396
  this.#open().publication = receipt;
387
397
  onPublished?.(receipt.staged.identity);
388
398
  const stagedMode = this.#assertNamed(basename, 0o600);
389
- assertStagedDirectoryCurrent(this.#owner.directory);
399
+ this.#assertDirectory();
390
400
  // Keep contents private until the name passes its identity fence. Mode
391
401
  // changes use the owned fd, including for final mode 000.
392
402
  const fd = this.#file();
@@ -403,7 +413,7 @@ class NativeStagedFile {
403
413
  if (this.#owner.sync !== false)
404
414
  syncFileBestEffortSync(this.#owner.parentFd);
405
415
  this.#assertNamed(basename, this.#owner.mode);
406
- assertStagedDirectoryCurrent(this.#owner.directory);
416
+ this.#assertDirectory();
407
417
  return receipt;
408
418
  }
409
419
  #finalize() {
@@ -0,0 +1,3 @@
1
+ /** Native stat identities are unsigned; Node BigIntStats preserves them as signed bits. */
2
+ export declare function statIdentityFromNative(value: bigint): bigint;
3
+ export declare function isNativeStatIdentity(value: unknown): value is bigint;
@@ -0,0 +1,7 @@
1
+ /** Native stat identities are unsigned; Node BigIntStats preserves them as signed bits. */
2
+ export function statIdentityFromNative(value) {
3
+ return value <= 0x7fffffffffffffffn || process.versions.bun ? value : BigInt.asIntN(64, value);
4
+ }
5
+ export function isNativeStatIdentity(value) {
6
+ return typeof value === "bigint" && value >= 0n && value <= 0xffffffffffffffffn;
7
+ }
@@ -0,0 +1,17 @@
1
+ import { type FsSafeNativeMode } from "./native-config.js";
2
+ export type FsSafeNativeStatusReason = "not-probed" | "loaded" | "disabled" | "unsupported-target" | "package-missing" | "load-failed" | "capability-missing";
3
+ export type FsSafeNativeStatus = {
4
+ mode: FsSafeNativeMode;
5
+ modeSource: "programmatic configuration" | "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default";
6
+ reason: FsSafeNativeStatusReason;
7
+ loaded: boolean;
8
+ target: string;
9
+ packageName?: string;
10
+ packageVersion?: string;
11
+ loadErrorCode?: string;
12
+ guidance?: string;
13
+ };
14
+ /** Read this module instance's cached native outcome, optionally probing once. */
15
+ export declare function readFsSafeNativeStatus(options?: {
16
+ probe?: boolean;
17
+ }): FsSafeNativeStatus;
@@ -0,0 +1,30 @@
1
+ import { getNativeModeSetting } from "./native-config.js";
2
+ import { nativeGuidance } from "./native-guidance.js";
3
+ import { getNativeBinding, getNativeFallbackDiagnostic } from "./native.js";
4
+ /** Read this module instance's cached native outcome, optionally probing once. */
5
+ export function readFsSafeNativeStatus(options) {
6
+ const { mode, source: modeSource } = getNativeModeSetting();
7
+ if (options?.probe && mode !== "off") {
8
+ try {
9
+ getNativeBinding();
10
+ }
11
+ catch {
12
+ // Required-mode load failures are cached; health checks report them.
13
+ }
14
+ }
15
+ const diagnostic = getNativeFallbackDiagnostic();
16
+ const guidance = nativeGuidance(diagnostic.reason, modeSource);
17
+ return {
18
+ mode,
19
+ modeSource,
20
+ reason: diagnostic.reason,
21
+ loaded: diagnostic.reason === "loaded",
22
+ target: diagnostic.target,
23
+ ...(diagnostic.packageName === undefined ? {} : {
24
+ packageName: diagnostic.packageName,
25
+ packageVersion: diagnostic.packageVersion,
26
+ }),
27
+ ...(diagnostic.reason === "load-failed" ? { loadErrorCode: diagnostic.code } : {}),
28
+ ...(guidance === undefined ? {} : { guidance }),
29
+ };
30
+ }
@@ -1,5 +1,30 @@
1
- import { type NativeBinding } from "./native-binding.js";
1
+ import { type NativeBinding, type CapableNativeBinding } from "./native-binding.js";
2
2
  export type { NativeBinding } from "./native-binding.js";
3
+ /** Internal view of the cached load; never attempts to load an addon. */
4
+ export declare function getNativeFallbackDiagnostic(...capabilities: (keyof NativeBinding)[]): {
5
+ target: string;
6
+ packageName: string | undefined;
7
+ packageVersion: string | undefined;
8
+ reason: "disabled";
9
+ source: "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default" | "programmatic configuration";
10
+ } | {
11
+ target: string;
12
+ packageName: string | undefined;
13
+ packageVersion: string | undefined;
14
+ reason: "capability-missing";
15
+ capabilities: (keyof NativeBinding)[];
16
+ } | {
17
+ reason: "not-probed" | "loaded" | "unsupported-target" | "package-missing";
18
+ target: string;
19
+ packageName: string | undefined;
20
+ packageVersion: string | undefined;
21
+ } | {
22
+ reason: "load-failed";
23
+ code: string;
24
+ target: string;
25
+ packageName: string | undefined;
26
+ packageVersion: string | undefined;
27
+ };
3
28
  export declare function __loadBundledNativeForTest(): NativeBinding;
4
29
  export declare function __nativeLoaderDetectorsForTest(): {
5
30
  report: boolean | undefined;
@@ -7,7 +32,9 @@ export declare function __nativeLoaderDetectorsForTest(): {
7
32
  elfInterpreter: boolean | undefined;
8
33
  };
9
34
  export declare function __nativeTargetForTest(platform: NodeJS.Platform, arch: string, musl?: boolean): string | undefined;
10
- export declare function getNativeBinding(): NativeBinding | undefined;
11
- export declare function requireNativeBinding(): NativeBinding;
12
- export declare function __setNativeLoaderForTest(loader: () => NativeBinding): void;
35
+ export declare function selectNativeBinding<K extends keyof NativeBinding>(native: NativeBinding | undefined, ...capabilities: K[]): CapableNativeBinding<K> | undefined;
36
+ export declare function assertNativeCapabilities<K extends keyof NativeBinding>(native: NativeBinding, ...capabilities: K[]): asserts native is CapableNativeBinding<K>;
37
+ export declare function getNativeBinding<K extends keyof NativeBinding = never>(...capabilities: K[]): CapableNativeBinding<K> | undefined;
38
+ export declare function requireNativeBinding<K extends keyof NativeBinding = never>(...capabilities: K[]): CapableNativeBinding<K>;
39
+ export declare function __setNativeLoaderForTest(loader: () => NativeBinding, resolver?: (name: string) => string): void;
13
40
  export declare function __resetNativeLoaderForTest(): void;