@openclaw/feishu 2026.9.8 → 2026.10.1-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 (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
@@ -69,16 +69,20 @@ export function matchRootFileOpenFailure(failure, handlers) {
69
69
  }
70
70
  return handlers.fallback(failure);
71
71
  }
72
- function openRootFileResolved(params) {
72
+ function finalizeRootFileOpen(params) {
73
+ if ("ok" in params.resolved) {
74
+ return params.resolved;
75
+ }
76
+ const resolved = params.resolved;
73
77
  const rejectHardlinks = params.rejectHardlinks ?? true;
74
78
  const opened = openPinnedFileSync({
75
- filePath: params.absolutePath,
76
- resolvedPath: params.resolvedPath,
79
+ filePath: resolved.absolutePath,
80
+ resolvedPath: resolved.resolvedPath,
77
81
  rejectHardlinks,
78
82
  maxBytes: params.maxBytes,
79
83
  allowedType: params.allowedType,
80
84
  ioFs: params.ioFs,
81
- finalAdmission: createRootFileFinalAdmission(params.ioFs, params.rootObservation, params.boundaryLabel, rejectHardlinks),
85
+ finalAdmission: createRootFileFinalAdmission(params.ioFs, resolved.rootObservation, resolved.boundaryLabel, rejectHardlinks),
82
86
  });
83
87
  if (!opened.ok) {
84
88
  return opened;
@@ -88,25 +92,9 @@ function openRootFileResolved(params) {
88
92
  path: opened.path,
89
93
  fd: opened.fd,
90
94
  stat: opened.stat,
91
- rootRealPath: params.rootRealPath,
95
+ rootRealPath: resolved.rootRealPath,
92
96
  };
93
97
  }
94
- function finalizeRootFileOpen(params) {
95
- if ("ok" in params.resolved) {
96
- return params.resolved;
97
- }
98
- return openRootFileResolved({
99
- absolutePath: params.resolved.absolutePath,
100
- resolvedPath: params.resolved.resolvedPath,
101
- rootRealPath: params.resolved.rootRealPath,
102
- boundaryLabel: params.resolved.boundaryLabel,
103
- rootObservation: params.resolved.rootObservation,
104
- maxBytes: params.maxBytes,
105
- rejectHardlinks: params.rejectHardlinks,
106
- allowedType: params.allowedType,
107
- ioFs: params.ioFs,
108
- });
109
- }
110
98
  export async function openRootFile(params) {
111
99
  const ioFs = params.ioFs ?? fs;
112
100
  const rawAbsolutePath = params.absolutePath;
@@ -50,7 +50,6 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import { randomUUID } from "node:crypto";
54
53
  import { registerRootHandleContext } from "./root-handle-context.js";
55
54
  import fsSync, { constants as fsConstants } from "node:fs";
56
55
  import fs from "node:fs/promises";
@@ -69,6 +68,7 @@ import { openedPathResolutionError, recordExclusiveCreateFailure, recordOpenedFi
69
68
  import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy } from "./pinned-write.js";
70
69
  import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy } from "./pinned-mutation-admission.js";
71
70
  import { getNativeBinding } from "./native.js";
71
+ import { isFsSafeNativeRequired } from "./native-config.js";
72
72
  import { validatePinnedRelativePath } from "./pinned-operation.js";
73
73
  import { PATH_ALIAS_POLICIES } from "./path-policy.js";
74
74
  import { assertNoNulPathInput, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
@@ -78,6 +78,7 @@ import { sameFileIdentity } from "./file-identity.js";
78
78
  import { removePathIfIdentityUnchanged } from "./replace-file-temp-owner.js";
79
79
  import { realpathSync } from "./realpath.js";
80
80
  import { mkdirPathFallback, prepareRootWriteTarget, tryMkdirAtExactParent } from "./root-directory-creation.js";
81
+ import { buildAtomicWriteTempPath, rootWriteQueueKey, tryMkdirRootNative, tryOpenCreateRootNative } from "./root-create-native.js";
81
82
  import { isNonRegularWriteOpenError, resolveNonblockingWriteFlag } from "./write-open-flags.js";
82
83
  import { resolveRootPath, resolveRootPathSync, resolveRootPathForRemoval } from "./root-path.js";
83
84
  import { RemovalPathReceipts } from "./root-remove-receipt.js";
@@ -93,6 +94,7 @@ import { stringifyJsonDocument } from "./json-stringify.js";
93
94
  import { walkRoot } from "./root-walk.js";
94
95
  import { registerTempPathForExit } from "./temp-cleanup.js";
95
96
  import { removePathInRootFallback, validateRemoveOptions } from "./root-remove.js";
97
+ import { removePathInRootNative } from "./root-remove-native.js";
96
98
  import { serializePathWrite } from "./write-queue.js";
97
99
  import { verifyAtomicWriteResult } from "./root-write-verification.js";
98
100
  import { assertRootWritePathSelectionSync, assertRootWriteSelectionSync, createRootWriteSelectionForFd, prepareGuardedRootWritePathSelection, resolveGuardedWritePathInRoot, resolveGuardedWriteTargetInRoot, resolvePinnedWriteTargetInRoot, refreshRetainedRootWriteAdmission, refreshRootWritePathSelection, } from "./root-write-admission.js";
@@ -101,12 +103,13 @@ import { finishRootFallbackWrite } from "./root-write-publication.js";
101
103
  import { withRootFallbackCompatibilityLock } from "./root-write-compatibility.js";
102
104
  import { assertRootFallbackWritePath } from "./root-write-lock-binding.js";
103
105
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
104
- import { admitMoveSourceStat, movePathNoReplaceNative } from "./root-move-noreplace.js";
106
+ import { admitMoveSourceStat, movePathNative } from "./root-move-noreplace.js";
105
107
  import { admitRootReadHandle, inspectOpenedPathIdentitySync } from "./root-read-admission.js";
106
108
  import { createCopyPublicationObserver, onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
107
109
  import { writeAllToFile } from "./write-file-handle.js";
108
110
  import { createInputOptions, rethrowCreateInputError, rootWriteInput } from "./root-create-input.js";
109
111
  import { assertFinalSymlinkRejected, mutationSymlinkResolution, readSymlinkResolution } from "./root-symlink-policy.js";
112
+ import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
110
113
  import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
111
114
  import { resolvePinnedObservedPathInRoot } from "./root-observed-path.js";
112
115
  import { registerFileLockSyncRootAdapter } from "./file-lock-sync-root.js";
@@ -386,12 +389,14 @@ export class RootHandle {
386
389
  const { assertBeforeMutation } = mutationOptions;
387
390
  const { denyMutations, mutationSymlinks } = snapshotPinnedMutationPolicy(mutationOptions.denyMutations, mutationOptions.mutationSymlinks) ?? {};
388
391
  const overwrite = options.overwrite ?? false;
392
+ const requireNative = isFsSafeNativeRequired();
389
393
  await assertMoveMutationAllowed(this.context, {
390
394
  fromRelative,
391
395
  toRelative,
392
396
  denyMutations,
393
397
  });
394
398
  await movePathFallback(this.context, {
399
+ requireNative,
395
400
  fromRelative,
396
401
  denyMutations,
397
402
  assertBeforeMutation,
@@ -492,13 +497,11 @@ export async function openLocalFileSafely(params) {
492
497
  function emitWriteBoundaryWarning(reason) {
493
498
  logWarn(`security: fs-safe write boundary warning (${reason})`);
494
499
  }
495
- function buildAtomicWriteTempPath(targetPath) {
496
- return path.join(path.dirname(targetPath), `.fs-safe-${randomUUID()}.tmp`);
497
- }
498
- function rootWriteQueueKey(root, relativePath) {
499
- return `${root.rootReal}\0${relativePath}`;
500
- }
501
500
  async function openWritableFileInRoot(root, params, options) {
501
+ const requireNative = isFsSafeNativeRequired();
502
+ const policy = requireNative ? snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks) : undefined;
503
+ if (policy)
504
+ params = { ...params, ...policy };
502
505
  const guardedTarget = params.denyMutations === undefined && params.mutationSymlinks === undefined
503
506
  ? undefined
504
507
  : await resolveGuardedWriteTargetInRoot(root, {
@@ -509,12 +512,20 @@ async function openWritableFileInRoot(root, params, options) {
509
512
  const { resolved } = guardedTarget?.resolvedPath ?? await resolveGuardedWritePathInRoot(root, {
510
513
  relativePath: params.relativePath,
511
514
  });
512
- const prepared = guardedTarget ? await prepareSharedRootWriteTarget(root, {
515
+ const resolveCurrent = async () => (await resolveGuardedWritePathInRoot(root, {
516
+ relativePath: params.relativePath, ...policy,
517
+ })).resolved;
518
+ const nativeParent = requireNative && params.mkdir !== false && await tryMkdirRootNative({
519
+ originalPath: params.relativePath,
520
+ root, directory: path.dirname(resolved), target: resolved, mkdir: true, policy,
521
+ resolveCurrent, assertBeforeMutation: params.assertBeforeMutation,
522
+ });
523
+ const prepared = !nativeParent && guardedTarget ? await prepareSharedRootWriteTarget(root, {
513
524
  relativePath: params.relativePath, guardedTarget, mkdir: params.mkdir,
514
525
  assertBeforeMutation: params.assertBeforeMutation,
515
526
  }) : undefined;
516
527
  const preparedParent = prepared?.preparedParent;
517
- let ioPath = prepared?.targetPath ?? (params.mkdir === false ? resolved :
528
+ let ioPath = nativeParent ? resolved : prepared?.targetPath ?? (params.mkdir === false ? resolved :
518
529
  await prepareRootWriteTarget(root, resolved, params.assertBeforeMutation));
519
530
  const operationTargetPath = ioPath;
520
531
  try {
@@ -542,6 +553,8 @@ async function openWritableFileInRoot(root, params, options) {
542
553
  const mode = params.mode ?? 0o600;
543
554
  let handle;
544
555
  let createdForWrite = false;
556
+ let cleanupCreated;
557
+ let releaseCreationParent;
545
558
  let writePathSelection = undefined;
546
559
  const existingFlags = params.append ? OPEN_APPEND_EXISTING_FLAGS : OPEN_WRITE_EXISTING_FLAGS;
547
560
  const createFlags = params.append ? OPEN_APPEND_CREATE_FLAGS : OPEN_WRITE_CREATE_FLAGS;
@@ -574,10 +587,29 @@ async function openWritableFileInRoot(root, params, options) {
574
587
  }
575
588
  if (writePathSelection)
576
589
  await refreshRootWritePathSelection(writePathSelection);
577
- params.assertBeforeMutation?.();
578
- if (writePathSelection)
579
- assertRootWritePathSelectionSync(root, writePathSelection);
580
- handle = await fs.open(ioPath, createFlags, mode);
590
+ const nativeCreated = requireNative ? await tryOpenCreateRootNative({
591
+ originalPath: params.relativePath,
592
+ root, directory: path.dirname(ioPath), target: ioPath, mkdir: false, policy,
593
+ resolveCurrent, assertBeforeMutation() {
594
+ params.assertBeforeMutation?.();
595
+ if (writePathSelection)
596
+ assertRootWritePathSelectionSync(root, writePathSelection);
597
+ },
598
+ flags: createFlags, existingFlags, mode,
599
+ }) : undefined;
600
+ if (nativeCreated)
601
+ handle = nativeCreated.handle;
602
+ else {
603
+ params.assertBeforeMutation?.();
604
+ if (writePathSelection)
605
+ assertRootWritePathSelectionSync(root, writePathSelection);
606
+ // Windows can follow a dangling symlink even with O_EXCL.
607
+ if (!SUPPORTS_NOFOLLOW)
608
+ assertFinalSymlinkRejected(ioPath, true);
609
+ handle = await fs.open(ioPath, createFlags, mode);
610
+ }
611
+ cleanupCreated = nativeCreated?.cleanupCreated;
612
+ releaseCreationParent = nativeCreated?.releaseCreationParent;
581
613
  createdForWrite = true;
582
614
  }
583
615
  }
@@ -670,16 +702,16 @@ async function openWritableFileInRoot(root, params, options) {
670
702
  // irreversible side effects if a symlink target changes before validation.
671
703
  if (params.append !== true && params.truncateExisting !== false && !createdForWrite) {
672
704
  if (writeSelection)
673
- await refreshRetainedRootWriteAdmission(root, writeSelection, true, handle.fd);
705
+ await refreshRetainedRootWriteAdmission(root, writeSelection, handle.fd);
674
706
  assertFinalSymlinkRejected(ioPath, params.mutationSymlinks !== undefined);
675
707
  params.assertBeforeMutation?.();
676
708
  if (writeSelection)
677
- assertRootWriteSelectionSync(root, writeSelection, true, handle.fd);
709
+ assertRootWriteSelectionSync(root, writeSelection, handle.fd);
678
710
  await handle.truncate(0);
679
711
  }
680
712
  if (writeSelection) {
681
- await refreshRetainedRootWriteAdmission(root, writeSelection, true, handle.fd);
682
- assertRootWriteSelectionSync(root, writeSelection, true, handle.fd);
713
+ await refreshRetainedRootWriteAdmission(root, writeSelection, handle.fd);
714
+ assertRootWriteSelectionSync(root, writeSelection, handle.fd);
683
715
  }
684
716
  const result = {
685
717
  handle,
@@ -689,13 +721,21 @@ async function openWritableFileInRoot(root, params, options) {
689
721
  stat,
690
722
  [Symbol.asyncDispose]: () => handle.close().catch(() => undefined),
691
723
  };
692
- return { opened: result, identity, writeSelection };
724
+ if (!params.keepCreationParent)
725
+ releaseCreationParent?.();
726
+ return { opened: result, identity, writeSelection, cleanupCreated, releaseCreationParent };
693
727
  }
694
728
  catch (err) {
695
729
  const cleanupCreatedPath = createdForWrite && err instanceof FsSafeError;
696
730
  const cleanupPath = realPathForCleanup ?? ioPath;
731
+ if (cleanupCreatedPath && cleanupCreated)
732
+ await cleanupCreated().catch(() => { });
733
+ try {
734
+ releaseCreationParent?.();
735
+ }
736
+ catch { /* Preserve the admission failure. */ }
697
737
  await handle.close().catch(() => { });
698
- if (cleanupCreatedPath && createdIdentity) {
738
+ if (cleanupCreatedPath && createdIdentity && !cleanupCreated) {
699
739
  await removePathIfIdentityUnchanged(cleanupPath, createdIdentity).catch(() => { });
700
740
  }
701
741
  throw err;
@@ -704,7 +744,7 @@ async function openWritableFileInRoot(root, params, options) {
704
744
  async function appendFileInRoot(root, params) {
705
745
  const env_1 = { stack: [], error: void 0, hasError: false };
706
746
  try {
707
- const { opened: target, identity } = await openWritableFileInRoot(root, {
747
+ const { opened: target, identity, cleanupCreated, releaseCreationParent } = await openWritableFileInRoot(root, {
708
748
  relativePath: params.relativePath,
709
749
  mkdir: params.mkdir,
710
750
  mode: params.mode,
@@ -713,17 +753,23 @@ async function appendFileInRoot(root, params) {
713
753
  mutationSymlinks: params.mutationSymlinks,
714
754
  truncateExisting: false,
715
755
  append: true,
756
+ keepCreationParent: true,
716
757
  });
758
+ const creationParent = __addDisposableResource(env_1, { [Symbol.dispose]() { releaseCreationParent?.(); } }, false);
717
759
  let dispatched = false;
718
760
  // Reverse disposal order closes the handle before path cleanup and retains both failures.
719
761
  const cleanup = __addDisposableResource(env_1, {
720
762
  async [Symbol.asyncDispose]() {
721
- if (!dispatched && target.createdForWrite) {
763
+ if (!dispatched && target.createdForWrite && !cleanupCreated) {
722
764
  await removePathIfIdentityUnchanged(target.realPath, identity);
723
765
  }
724
766
  },
725
767
  }, true);
726
768
  const handle = __addDisposableResource(env_1, target.handle, true);
769
+ const nativeCleanup = __addDisposableResource(env_1, { async [Symbol.asyncDispose]() {
770
+ if (!dispatched && target.createdForWrite)
771
+ await cleanupCreated?.();
772
+ } }, true);
727
773
  try {
728
774
  let prefix = "";
729
775
  if (params.prependNewlineIfNeeded === true &&
@@ -773,6 +819,9 @@ async function appendFileInRoot(root, params) {
773
819
  }
774
820
  async function removePathInRoot(root, params) {
775
821
  validatePinnedRelativePath(params.relativePath);
822
+ const requireNative = isFsSafeNativeRequired();
823
+ if (requireNative)
824
+ params = { ...params, ...snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks) };
776
825
  const removalReceipts = params.recursive ? undefined : new RemovalPathReceipts();
777
826
  const resolved = await resolvePinnedPathInRoot(root, {
778
827
  relativePath: params.relativePath,
@@ -782,7 +831,10 @@ async function removePathInRoot(root, params) {
782
831
  removalReceipts,
783
832
  });
784
833
  try {
785
- await removePathInRootFallback(root, resolved.resolved, params, removalReceipts);
834
+ if (requireNative)
835
+ await removePathInRootNative(root, resolved.resolved, params, removalReceipts);
836
+ else
837
+ await removePathInRootFallback(root, resolved.resolved, params, removalReceipts);
786
838
  }
787
839
  catch (error) {
788
840
  if (params.recursive)
@@ -791,6 +843,7 @@ async function removePathInRoot(root, params) {
791
843
  }
792
844
  }
793
845
  async function mkdirPathInRoot(root, params) {
846
+ const requireNative = isFsSafeNativeRequired();
794
847
  const privateMode = resolveCreationPermissions(params, true).private;
795
848
  validatePinnedRelativePath(params.relativePath);
796
849
  const policy = params.denyMutations === undefined && params.mutationSymlinks === undefined
@@ -801,6 +854,13 @@ async function mkdirPathInRoot(root, params) {
801
854
  ? async () => await resolvePinnedPathInRoot(root, resolution)
802
855
  : undefined;
803
856
  const resolved = await resolvePinnedPathInRoot(root, resolution);
857
+ if (requireNative && await tryMkdirRootNative({
858
+ originalPath: params.relativePath,
859
+ root, directory: resolved.resolved, target: resolved.resolved, mkdir: true, private: privateMode,
860
+ policy, resolveCurrent: async () => (await resolvePinnedPathInRoot(root, resolution)).resolved,
861
+ assertBeforeMutation: params.assertBeforeMutation,
862
+ }))
863
+ return;
804
864
  const prepared = policy && resolved.relativePosix !== ""
805
865
  ? await preparePinnedWriteMutationAdmission({
806
866
  rootReal: resolved.rootReal,
@@ -1057,17 +1117,13 @@ async function resolvePinnedRootPathInRoot(root, params) {
1057
1117
  }
1058
1118
  async function statPathFallback(root, relativePath) {
1059
1119
  const initialObservationHook = getFsSafeTestHooks()?.beforeRootStatInitialObservation;
1060
- const observed = initialObservationHook
1061
- ? undefined
1120
+ const resolved = initialObservationHook
1121
+ ? await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true })
1062
1122
  : await resolvePinnedObservedPathInRoot(root, relativePath, "stat");
1063
- const resolved = observed ??
1064
- await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true });
1065
1123
  return await statResolvedPathInRoot(root, resolved.resolved, resolved.receipt);
1066
1124
  }
1067
1125
  async function listPathFallback(root, relativePath, withFileTypes) {
1068
- const observed = await resolvePinnedObservedPathInRoot(root, relativePath, "directory");
1069
- const resolved = observed ??
1070
- await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true });
1126
+ const resolved = await resolvePinnedObservedPathInRoot(root, relativePath, "directory");
1071
1127
  return await listDirectoryPath(root, resolved.resolved, withFileTypes, resolved.receipt);
1072
1128
  }
1073
1129
  async function movePathFallback(root, params) {
@@ -1126,13 +1182,23 @@ async function movePathFallback(root, params) {
1126
1182
  if (!pinnedTarget) {
1127
1183
  throw new FsSafeError("path-mismatch", "destination admission was not completed");
1128
1184
  }
1129
- if (!params.overwrite) {
1130
- await movePathNoReplaceNative(root, params, {
1185
+ const nativeReplace = params.overwrite && params.requireNative
1186
+ ? getNativeBinding()?.renameReplaceWithIdentity : undefined;
1187
+ if (params.overwrite && !nativeReplace && params.requireNative) {
1188
+ throw new FsSafeError("helper-unavailable", "native overwrite move is unavailable");
1189
+ }
1190
+ if (!params.overwrite || nativeReplace) {
1191
+ await movePathNative(root, params, {
1131
1192
  sourcePath: source.resolved,
1132
1193
  sourceParentPath: path.dirname(pinnedSource.canonicalPath),
1133
1194
  targetPath: target.resolved,
1134
1195
  targetParentPath: path.dirname(pinnedTarget.canonicalPath),
1135
- });
1196
+ sourceOriginalPath: originalRoutes?.[0],
1197
+ targetOriginalPath: originalRoutes?.[1],
1198
+ sourceCanonicalPath: pinnedSource.canonicalPath,
1199
+ targetCanonicalPath: pinnedTarget.canonicalPath,
1200
+ expectedSourceIdentity: sourceIdentity,
1201
+ }, params.overwrite);
1136
1202
  return;
1137
1203
  }
1138
1204
  const guardOptions = { bigint: sourceIdentity !== undefined };
@@ -1244,20 +1310,21 @@ async function writeFileFallbackUnlocked(root, params, expectedWritePath) {
1244
1310
  try {
1245
1311
  tempPath = buildAtomicWriteTempPath(destinationPath);
1246
1312
  if (retainedSelection) {
1247
- await refreshRetainedRootWriteAdmission(root, retainedSelection, true, target?.handle.fd);
1313
+ await refreshRetainedRootWriteAdmission(root, retainedSelection, target?.handle.fd);
1248
1314
  }
1249
1315
  else if (missingSelection) {
1250
1316
  await refreshRootWritePathSelection(missingSelection);
1251
1317
  }
1252
1318
  params.assertBeforeMutation?.();
1253
1319
  if (retainedSelection) {
1254
- assertRootWriteSelectionSync(root, retainedSelection, true, target?.handle.fd);
1320
+ assertRootWriteSelectionSync(root, retainedSelection, target?.handle.fd);
1255
1321
  }
1256
1322
  else if (missingSelection) {
1257
1323
  assertRootWritePathSelectionSync(root, missingSelection);
1258
1324
  }
1259
1325
  assertRootIdentityCurrentSync(root);
1260
1326
  assertSyncDirectoryGuard(destinationGuard);
1327
+ assertExclusiveCreateLeaf(tempPath);
1261
1328
  writtenHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
1262
1329
  writtenIdentity = fsSync.fstatSync(writtenHandle.fd, { bigint: true });
1263
1330
  // Preserve the creation mask when no existing destination supplies a mode.
@@ -1291,7 +1358,7 @@ async function writeFileFallbackUnlocked(root, params, expectedWritePath) {
1291
1358
  // Windows cannot replace a destination while its old handle remains open.
1292
1359
  await target?.handle.close();
1293
1360
  if (retainedSelection) {
1294
- await refreshRetainedRootWriteAdmission(root, retainedSelection, true);
1361
+ await refreshRetainedRootWriteAdmission(root, retainedSelection);
1295
1362
  }
1296
1363
  else if (missingSelection) {
1297
1364
  await refreshRootWritePathSelection(missingSelection);
@@ -1299,7 +1366,7 @@ async function writeFileFallbackUnlocked(root, params, expectedWritePath) {
1299
1366
  assertFinalSymlinkRejected(destinationPath, params.mutationSymlinks !== undefined);
1300
1367
  params.assertBeforeMutation?.();
1301
1368
  if (retainedSelection) {
1302
- assertRootWriteSelectionSync(root, retainedSelection, true);
1369
+ assertRootWriteSelectionSync(root, retainedSelection);
1303
1370
  }
1304
1371
  else if (missingSelection) {
1305
1372
  assertRootWritePathSelectionSync(root, missingSelection);
@@ -1388,6 +1455,7 @@ async function writeMissingFileFallback(root, params, expectedWritePath) {
1388
1455
  params.assertBeforeMutation?.();
1389
1456
  if (mutationAdmission)
1390
1457
  assertSyncDirectoryGuard(parentGuard);
1458
+ assertExclusiveCreateLeaf(targetPath);
1391
1459
  const handle = await fs.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600).catch((error) => recordExclusiveCreateFailure(error, targetPath));
1392
1460
  writtenHandle = handle;
1393
1461
  created = true;
@@ -2,9 +2,14 @@ import { type BigIntStats, type Stats } from "node:fs";
2
2
  import type { RootMoveOptions } from "./root-options.js";
3
3
  import { type RootContext } from "./root-context.js";
4
4
  export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T, overwrite?: boolean): T;
5
- export declare function movePathNoReplaceNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks">, paths: {
5
+ export declare function movePathNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks">, paths: {
6
6
  sourcePath: string;
7
7
  sourceParentPath: string;
8
8
  targetPath: string;
9
9
  targetParentPath: string;
10
- }): Promise<void>;
10
+ sourceOriginalPath?: string;
11
+ targetOriginalPath?: string;
12
+ sourceCanonicalPath?: string;
13
+ targetCanonicalPath?: string;
14
+ expectedSourceIdentity?: BigIntStats;
15
+ }, overwrite?: boolean): Promise<void>;
@@ -10,7 +10,8 @@ import { assertRootIdentityCurrent, assertRootIdentityCurrentSync } from "./root
10
10
  import { resolveRootPathSync } from "./root-path.js";
11
11
  import { admitPathInsideRoot } from "./root-boundary.js";
12
12
  import { errorCauseOptions, fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, outsideWorkspaceError, } from "./root-errors.js";
13
- import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
13
+ import { assertFinalSymlinkRejected, mutationSymlinkResolution } from "./root-symlink-policy.js";
14
+ import { PATH_ALIAS_POLICIES } from "./path-policy.js";
14
15
  import { createSuppressedError } from "./suppressed-error.js";
15
16
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
16
17
  import { getFsSafeTestHooks } from "./test-hooks.js";
@@ -40,11 +41,19 @@ function nativePrimitiveUnavailable(error) {
40
41
  return code === "ENOSYS" || code === "ENOTSUP" || code === "EOPNOTSUPP";
41
42
  }
42
43
  function normalizeMoveError(error) {
44
+ if (error instanceof FsSafeError)
45
+ return error;
46
+ const code = error?.code;
47
+ if (code === "path-mismatch" || code === "hardlink") {
48
+ return new FsSafeError(code, "native move source changed", errorCauseOptions(error));
49
+ }
50
+ if (code === "ELOOP")
51
+ return new FsSafeError("symlink", "native move source is a symlink", errorCauseOptions(error));
43
52
  if (isNotFoundPathError(error)) {
44
53
  return fileNotFoundError(error instanceof Error ? error : undefined);
45
54
  }
46
55
  if (nativePrimitiveUnavailable(error)) {
47
- return new FsSafeError("helper-unavailable", "native no-replace move is unavailable", errorCauseOptions(error));
56
+ return new FsSafeError("helper-unavailable", "native move is unavailable", errorCauseOptions(error));
48
57
  }
49
58
  return error;
50
59
  }
@@ -66,26 +75,28 @@ export function admitMoveSourceStat(stat, overwrite = false) {
66
75
  }
67
76
  return stat;
68
77
  }
69
- export async function movePathNoReplaceNative(root, params, paths) {
70
- try {
71
- fsSync.lstatSync(paths.targetPath);
72
- throw new FsSafeError("already-exists", "destination exists");
73
- }
74
- catch (error) {
75
- if (error instanceof FsSafeError)
76
- throw error;
77
- if (!isNotFoundPathError(error))
78
- throw error;
79
- // Advisory fast rejection only. renameNoReplace owns the collision decision.
80
- }
78
+ export async function movePathNative(root, params, paths, overwrite = false) {
79
+ if (!overwrite)
80
+ try {
81
+ fsSync.lstatSync(paths.targetPath);
82
+ throw new FsSafeError("already-exists", "destination exists");
83
+ }
84
+ catch (error) {
85
+ if (error instanceof FsSafeError)
86
+ throw error;
87
+ if (!isNotFoundPathError(error))
88
+ throw error;
89
+ // Advisory fast rejection only. renameNoReplace owns the collision decision.
90
+ }
81
91
  const binding = requireNativeBinding();
82
- if (typeof binding.renameNoReplace !== "function") {
83
- throw new FsSafeError("helper-unavailable", "native no-replace move is unavailable");
92
+ if (typeof (overwrite ? binding.renameReplaceWithIdentity : binding.renameNoReplace) !== "function") {
93
+ throw new FsSafeError("helper-unavailable", "native move is unavailable");
84
94
  }
85
95
  const rootAdmission = await openNativeRootAdmission(binding, {
86
96
  rootPath: root.rootReal,
87
97
  rootIdentity: root.rootIdentity,
88
98
  operation: "native move",
99
+ searchOnly: true,
89
100
  });
90
101
  let sourceParent;
91
102
  let targetParent;
@@ -113,14 +124,13 @@ export async function movePathNoReplaceNative(root, params, paths) {
113
124
  await assertRootIdentityCurrent(root);
114
125
  for (const admission of parentAdmissions)
115
126
  assertSyncDirectoryGuard(admission.guard);
116
- // Capture an exact receipt only when admission must survive a callback.
117
- let sourceStat;
118
- if (params.assertBeforeMutation) {
119
- sourceStat = inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })));
120
- }
121
- else {
127
+ // Overwrite publication always binds an exact source; no-clobber retains
128
+ // its existing callback-dependent identity contract.
129
+ const sourceStat = overwrite || params.assertBeforeMutation
130
+ ? inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true }), overwrite), paths.expectedSourceIdentity)
131
+ : undefined;
132
+ if (!sourceStat)
122
133
  admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath));
123
- }
124
134
  assertFinalSymlinkRejected(admittedTargetPath, params.mutationSymlinks !== undefined);
125
135
  params.assertBeforeMutation?.();
126
136
  if (params.assertBeforeMutation || params.mutationSymlinks === "reject") {
@@ -145,21 +155,43 @@ export async function movePathNoReplaceNative(root, params, paths) {
145
155
  assertSyncDirectoryGuard(admission.guard);
146
156
  }
147
157
  // The callback can change a leaf without replacing its admitted parent.
158
+ if (overwrite && params.assertBeforeMutation) {
159
+ for (const [original, selected, expected, policy] of [
160
+ [paths.sourceOriginalPath, admittedSourcePath, paths.sourceCanonicalPath, PATH_ALIAS_POLICIES.strict],
161
+ [paths.targetOriginalPath, admittedTargetPath, paths.targetCanonicalPath, PATH_ALIAS_POLICIES.unlinkTarget],
162
+ ]) {
163
+ if (original === undefined || expected === undefined)
164
+ continue;
165
+ for (const absolutePath of new Set([path.isAbsolute(original) ? original : `${root.rootWithSep}${original}`, selected])) {
166
+ const current = resolveRootPathSync({ absolutePath, rootPath: root.rootReal,
167
+ rootCanonicalPath: root.rootReal, rootIdentity: root.rootIdentity, boundaryLabel: "root", policy,
168
+ ...mutationSymlinkResolution(params.mutationSymlinks) });
169
+ if (current.canonicalPath !== expected)
170
+ throw new FsSafeError("path-mismatch", "move route changed during authorization");
171
+ }
172
+ }
173
+ }
148
174
  if (sourceStat !== undefined) {
149
- inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })), sourceStat);
175
+ inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true }), overwrite), sourceStat);
150
176
  }
151
177
  try {
152
- binding.renameNoReplace(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath));
178
+ assertFinalSymlinkRejected(admittedTargetPath, params.mutationSymlinks !== undefined);
179
+ if (overwrite)
180
+ binding.renameReplaceWithIdentity(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath), sourceStat.dev, sourceStat.ino);
181
+ else
182
+ binding.renameNoReplace(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath));
153
183
  }
154
184
  catch (error) {
155
- if (isAlreadyExistsError(error) || error?.code === "ENOTEMPTY") {
185
+ if (isAlreadyExistsError(error) || (!overwrite && error?.code === "ENOTEMPTY")) {
156
186
  throw new FsSafeError("already-exists", "destination exists", errorCauseOptions(error));
157
187
  }
158
- throw normalizeRenameNoReplaceError(error);
188
+ throw overwrite ? normalizeMoveError(error) : normalizeRenameNoReplaceError(error);
159
189
  }
160
190
  try {
161
191
  for (const admission of parentAdmissions)
162
192
  await assertAsyncDirectoryGuard(admission.guard);
193
+ if (overwrite)
194
+ inspectFileIdentitySync(() => fsSync.lstatSync(admittedTargetPath, { bigint: true }), sourceStat);
163
195
  }
164
196
  catch (error) {
165
197
  throw normalizePinnedPathError(error);
@@ -188,18 +220,15 @@ export async function movePathNoReplaceNative(root, params, paths) {
188
220
  catch (error) {
189
221
  closeErrors.push(error);
190
222
  }
191
- if (failed) {
192
- if (closeErrors.length > 0) {
193
- const closeError = closeErrors.length === 1
194
- ? closeErrors[0]
195
- : new AggregateError(closeErrors, "native move descriptor closes failed");
223
+ if (closeErrors.length > 0) {
224
+ const closeError = closeErrors.length === 1
225
+ ? closeErrors[0]
226
+ : new AggregateError(closeErrors, "native move descriptor closes failed");
227
+ if (failed) {
196
228
  throw createSuppressedError(closeError, operationError, "native move and descriptor close failed");
197
229
  }
198
- throw operationError;
199
- }
200
- if (closeErrors.length === 1)
201
- throw closeErrors[0];
202
- if (closeErrors.length > 1) {
203
- throw new AggregateError(closeErrors, "native move descriptor closes failed");
230
+ throw closeError;
204
231
  }
232
+ if (failed)
233
+ throw operationError;
205
234
  }
@@ -4,4 +4,4 @@ export type PinnedObservedPath = {
4
4
  resolved: string;
5
5
  receipt?: RootPathObservationReceipt;
6
6
  };
7
- export declare function resolvePinnedObservedPathInRoot(root: RootContext, relativePath: string, kind: RootPathObservationKind): Promise<PinnedObservedPath | undefined>;
7
+ export declare function resolvePinnedObservedPathInRoot(root: RootContext, relativePath: string, kind: RootPathObservationKind): Promise<PinnedObservedPath>;
@@ -11,8 +11,6 @@ import { errorCauseOptions, fileNotFoundError, outsideWorkspaceError } from "./r
11
11
  import { getFsSafeTestHooks } from "./test-hooks.js";
12
12
  export async function resolvePinnedObservedPathInRoot(root, relativePath, kind) {
13
13
  const rootGuard = await createRootObservationGuard(root);
14
- if (!rootGuard)
15
- return undefined;
16
14
  // Race hooks exercise the established JavaScript observation points. Do not
17
15
  // silently move those points into the native helper in test configurations.
18
16
  const directoryObserver = getNativeDirectoryObservationBackend(getFsSafeTestHooks() === undefined);
@@ -19,12 +19,11 @@ export type RootDefaults = {
19
19
  mkdir?: boolean;
20
20
  mode?: number;
21
21
  denyMutations?: DenyMutationPolicy;
22
- nonBlockingRead?: boolean;
23
22
  renameIdentity?: RenameIdentityPolicy;
24
23
  symlinks?: SymlinkPolicy;
25
24
  mutationSymlinks?: MutationSymlinkPolicy;
26
25
  };
27
- export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
26
+ export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "symlinks">;
28
27
  export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
29
28
  export type RootWriteOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks" | "durable" | "mkdir" | "mode" | "renameIdentity"> & {
30
29
  encoding?: BufferEncoding;
@@ -79,6 +78,6 @@ export type RootRemoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "den
79
78
  export type RootMkdirOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
80
79
  private?: boolean;
81
80
  };
82
- export type RootReadParams = Omit<RootReadOptions, "nonBlockingRead">;
81
+ export type RootReadParams = RootReadOptions;
83
82
  export declare function readDefaults(defaults: RootDefaults): RootReadParams;
84
83
  export declare function mergeReadOptions(defaults: RootDefaults, options: RootReadOptions): RootReadParams;