@openclaw/openshell-sandbox 2026.9.6 → 2026.9.7

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 (244) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/index.js +75 -229
  3. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
  4. package/node_modules/@openclaw/fs-safe/README.md +24 -6
  5. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
  6. package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
  7. package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
  8. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
  9. package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
  10. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
  11. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
  12. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
  13. package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
  14. package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
  15. package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
  16. package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
  17. package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
  18. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
  19. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
  20. package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
  21. package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
  22. package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
  23. package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
  24. package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
  25. package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
  26. package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
  27. package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
  28. package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
  29. package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
  30. package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
  31. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
  32. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
  33. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
  34. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
  35. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
  36. package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
  37. package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
  38. package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
  39. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
  40. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
  41. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
  42. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
  43. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
  44. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
  45. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
  46. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
  47. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
  48. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
  49. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
  50. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
  51. package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
  53. package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
  54. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
  55. package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
  56. package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
  57. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
  58. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
  59. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
  60. package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
  61. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
  62. package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
  63. package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
  64. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
  65. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
  66. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
  67. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
  68. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
  69. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
  71. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
  72. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
  73. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
  74. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
  75. package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
  76. package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
  77. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
  78. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
  79. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  80. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
  81. package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
  82. package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
  83. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
  84. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
  85. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
  86. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
  87. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
  88. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
  89. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
  90. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
  91. package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
  92. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
  93. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
  94. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
  95. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
  96. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
  108. package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
  109. package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
  110. package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
  111. package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
  112. package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
  113. package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
  114. package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
  115. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
  119. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
  120. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
  121. package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
  122. package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
  123. package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
  124. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
  125. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
  126. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
  127. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
  128. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
  129. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
  133. package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
  134. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
  135. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
  136. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
  137. package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
  139. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
  140. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
  141. package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
  142. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
  143. package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
  144. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
  145. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
  147. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
  148. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
  152. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
  153. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
  154. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
  155. package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
  156. package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
  157. package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
  158. package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
  159. package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
  160. package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
  161. package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
  162. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
  163. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  164. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
  165. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
  166. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
  167. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
  168. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
  169. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
  170. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
  171. package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
  172. package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
  173. package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
  174. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
  175. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
  176. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
  177. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
  178. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
  179. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
  180. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
  181. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
  182. package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
  183. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
  184. package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
  185. package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
  186. package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
  187. package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
  188. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
  189. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
  190. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
  191. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
  192. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
  193. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
  194. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
  195. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
  196. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
  197. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  198. package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
  199. package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
  200. package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
  201. package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
  202. package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
  203. package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
  204. package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
  205. package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
  206. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
  207. package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
  208. package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
  209. package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
  210. package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
  211. package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
  212. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
  213. package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
  214. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
  215. package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
  216. package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
  217. package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
  218. package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
  219. package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
  220. package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
  221. package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
  222. package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
  223. package/node_modules/@openclaw/fs-safe/package.json +13 -9
  224. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  225. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  226. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  227. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  228. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  229. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  230. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  231. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  232. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  233. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  234. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  235. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  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/node_modules/p-limit/index.d.ts +2 -0
  239. package/node_modules/p-limit/index.js +17 -1
  240. package/node_modules/p-limit/package.json +1 -1
  241. package/node_modules/p-limit/readme.md +2 -0
  242. package/package.json +6 -6
  243. package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
  244. package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
@@ -3,7 +3,7 @@ import syncFs, {} from "node:fs";
3
3
  import fs, {} from "node:fs/promises";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
- import { ownDirectoryMode } from "./directory-mode-owner.js";
6
+ import { ownDirectoryMode } from "./directory-mode-node.js";
7
7
  export async function syncDirectoryBestEffort(fsModule, dirPath) {
8
8
  let handle;
9
9
  try {
@@ -73,7 +73,10 @@ export async function pinDirectoryForMode(params) {
73
73
  : await inspectFileIdentity(async () => assertDirectory(await handle.stat({ bigint: true }), params.dirPath), expected);
74
74
  return Number(opened.mode & 4095n);
75
75
  },
76
- chmod: (mode) => handle.chmod(mode),
76
+ chmod: (mode) => {
77
+ params.mutation?.assert();
78
+ return handle.chmod(mode);
79
+ },
77
80
  close: () => handle.close(),
78
81
  ignoreChmodError: params.ignoreChmodError,
79
82
  });
@@ -103,6 +106,7 @@ export function applyDirectoryModeSync(params) {
103
106
  try {
104
107
  inspectFileIdentitySync(() => assertDirectory(params.fsModule.fstatSync(fd, { bigint: true }), params.dirPath), expected);
105
108
  // chmod ignores file-type bits; mask so raw stat modes are tolerated.
109
+ params.mutation?.assert();
106
110
  params.fchmodSync?.(fd, params.mode & 0o7777);
107
111
  }
108
112
  finally {
@@ -110,6 +114,7 @@ export function applyDirectoryModeSync(params) {
110
114
  }
111
115
  }
112
116
  export async function writeTempFile(params) {
117
+ params.mutation?.assert();
113
118
  const handle = await params.fsModule.open(params.tempPath, "wx", params.mode);
114
119
  try {
115
120
  // Custom adapters retain their async-only metadata contract.
@@ -117,6 +122,7 @@ export async function writeTempFile(params) {
117
122
  ? syncFs.fstatSync(handle.fd, { bigint: true }) : handle.stat({ bigint: true });
118
123
  const identity = await inspectFileIdentity(inspect);
119
124
  params.onIdentity?.(identity);
125
+ params.mutation?.assert();
120
126
  await params.fsModule.writeFile(handle, params.content);
121
127
  await handle.chmod(params.mode);
122
128
  if (params.sync) {
@@ -136,10 +142,12 @@ export async function writeTempFile(params) {
136
142
  }
137
143
  }
138
144
  export function writeTempFileSync(params) {
145
+ params.mutation?.assert();
139
146
  const fd = params.fsModule.openSync(params.tempPath, "wx", params.mode);
140
147
  try {
141
148
  const identity = inspectFileIdentitySync(() => params.fsModule.fstatSync(fd, { bigint: true }));
142
149
  params.onIdentity?.(identity);
150
+ params.mutation?.assert();
143
151
  params.fsModule.writeFileSync(fd, params.content);
144
152
  params.fchmodSync?.(fd, params.mode);
145
153
  if (params.sync) {
@@ -0,0 +1,21 @@
1
+ import fsSync from "node:fs";
2
+ import fs, { type FileHandle } from "node:fs/promises";
3
+ import type { AtomicMutation } from "./replace-file-mutation.js";
4
+ /** Borrow the writer's descriptor; never adopt an identity from a later pathname open. */
5
+ export declare function captureAtomicDestination(fsModule: Pick<typeof fs, "lstat">, handle: FileHandle, pathname: string, mutation: AtomicMutation, rejectHardlinks: boolean): Promise<{
6
+ verify: () => Promise<void>;
7
+ writing: () => void;
8
+ beforeWrite: () => Promise<void>;
9
+ beforeRestore: () => Promise<void>;
10
+ verifyRestore: () => Promise<void>;
11
+ assertBeforeMutation: () => void;
12
+ published: () => void;
13
+ }>;
14
+ export declare function captureAtomicDestinationSync(fsModule: Pick<typeof fsSync, "lstatSync" | "fstatSync">, fd: number, pathname: string, mutation: AtomicMutation, rejectHardlinks: boolean): {
15
+ verify: () => void;
16
+ writing: () => void;
17
+ beforeWrite: () => void;
18
+ beforeRestore: () => void;
19
+ verifyRestore: () => void;
20
+ published: () => void;
21
+ };
@@ -0,0 +1,123 @@
1
+ import fsSync, {} from "node:fs";
2
+ import fs, {} from "node:fs/promises";
3
+ import { hasErrorCode } from "./file-cleanup.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
+ function regular(stat, pathname, rejectHardlinks) {
7
+ if (stat.isSymbolicLink() || !stat.isFile()) {
8
+ throw new FsSafeError("path-mismatch", `Atomic replace destination changed: ${pathname}`);
9
+ }
10
+ if (rejectHardlinks && stat.nlink !== 1n) {
11
+ throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${pathname}`);
12
+ }
13
+ return stat;
14
+ }
15
+ /** Borrow the writer's descriptor; never adopt an identity from a later pathname open. */
16
+ export async function captureAtomicDestination(fsModule, handle, pathname, mutation, rejectHardlinks) {
17
+ const inspect = () => fsModule === fs
18
+ ? fsSync.fstatSync(handle.fd, { bigint: true }) : handle.stat({ bigint: true });
19
+ const identity = await inspectFileIdentity(inspect);
20
+ regular(identity, pathname, rejectHardlinks);
21
+ let writing = false;
22
+ let restoreDescriptorOnly = false;
23
+ const verifyDescriptor = async () => {
24
+ await inspectFileIdentity(async () => regular(await inspect(), pathname, rejectHardlinks), identity);
25
+ };
26
+ const verify = async () => {
27
+ let metadataFailure = false;
28
+ const observe = async (read) => {
29
+ try {
30
+ return await read();
31
+ }
32
+ catch (error) {
33
+ metadataFailure = hasErrorCode(error, "EIO");
34
+ throw error;
35
+ }
36
+ };
37
+ try {
38
+ await inspectFileIdentity(async () => regular(await observe(inspect), pathname, rejectHardlinks), identity);
39
+ await inspectFileIdentity(async () => regular(await observe(() => fsModule.lstat(pathname, { bigint: true })), pathname, rejectHardlinks), identity);
40
+ }
41
+ catch (error) {
42
+ if (writing && metadataFailure) {
43
+ // A metadata observation failed, but the already-owned descriptor can
44
+ // still restore its bytes after fresh exact descriptor verification.
45
+ restoreDescriptorOnly = true;
46
+ throw error;
47
+ }
48
+ mutation.refuse(error);
49
+ }
50
+ };
51
+ const verifyRestore = async () => {
52
+ if (!restoreDescriptorOnly)
53
+ return await verify();
54
+ try {
55
+ await verifyDescriptor();
56
+ }
57
+ catch (error) {
58
+ mutation.refuse(error);
59
+ }
60
+ };
61
+ return {
62
+ verify,
63
+ writing: () => { writing = true; mutation.destination("writing", pathname, identity); },
64
+ beforeWrite: async () => {
65
+ mutation.assert();
66
+ await verify();
67
+ },
68
+ beforeRestore: async () => { mutation.assert(); await verifyRestore(); },
69
+ verifyRestore,
70
+ assertBeforeMutation: () => mutation.assert(),
71
+ published: () => mutation.destination("published", pathname, identity),
72
+ };
73
+ }
74
+ export function captureAtomicDestinationSync(fsModule, fd, pathname, mutation, rejectHardlinks) {
75
+ const identity = inspectFileIdentitySync(() => fsModule.fstatSync(fd, { bigint: true }));
76
+ regular(identity, pathname, rejectHardlinks);
77
+ let writing = false;
78
+ let restoreDescriptorOnly = false;
79
+ const verify = () => {
80
+ let metadataFailure = false;
81
+ const observe = (read) => {
82
+ try {
83
+ return read();
84
+ }
85
+ catch (error) {
86
+ metadataFailure = hasErrorCode(error, "EIO");
87
+ throw error;
88
+ }
89
+ };
90
+ try {
91
+ inspectFileIdentitySync(() => regular(observe(() => fsModule.fstatSync(fd, { bigint: true })), pathname, rejectHardlinks), identity);
92
+ inspectFileIdentitySync(() => regular(observe(() => fsModule.lstatSync(pathname, { bigint: true })), pathname, rejectHardlinks), identity);
93
+ }
94
+ catch (error) {
95
+ if (writing && metadataFailure) {
96
+ restoreDescriptorOnly = true;
97
+ throw error;
98
+ }
99
+ mutation.refuse(error);
100
+ }
101
+ };
102
+ const verifyRestore = () => {
103
+ if (!restoreDescriptorOnly)
104
+ return verify();
105
+ try {
106
+ inspectFileIdentitySync(() => regular(fsModule.fstatSync(fd, { bigint: true }), pathname, rejectHardlinks), identity);
107
+ }
108
+ catch (error) {
109
+ mutation.refuse(error);
110
+ }
111
+ };
112
+ return {
113
+ verify,
114
+ writing: () => { writing = true; mutation.destination("writing", pathname, identity); },
115
+ beforeWrite: () => {
116
+ mutation.assert();
117
+ verify();
118
+ },
119
+ beforeRestore: () => { mutation.assert(); verifyRestore(); },
120
+ verifyRestore,
121
+ published: () => mutation.destination("published", pathname, identity),
122
+ };
123
+ }
@@ -0,0 +1,26 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ export type ReplaceFileAtomicDestinationState = Readonly<{
3
+ state: "removed";
4
+ path: string;
5
+ }> | Readonly<{
6
+ state: "writing" | "published";
7
+ path: string;
8
+ dev: bigint;
9
+ ino: bigint;
10
+ }>;
11
+ export type AtomicMutationOptions = {
12
+ /** Recheck live authority before new effects; retained descriptor completion may settle. */
13
+ assertBeforeMutation?: () => void;
14
+ /** Retain observed destination facts even when later completion fails. */
15
+ onDestinationState?: (state: ReplaceFileAtomicDestinationState) => void;
16
+ };
17
+ export declare class AtomicMutation {
18
+ #private;
19
+ readonly active: boolean;
20
+ constructor(options: AtomicMutationOptions);
21
+ rethrowRefusal(): void;
22
+ refuse(error: unknown): never;
23
+ assert(): void;
24
+ destination(state: "writing" | "published", path: string, identity: BigIntStats): void;
25
+ removed(path: string): void;
26
+ }
@@ -0,0 +1,47 @@
1
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
2
+ export class AtomicMutation {
3
+ active;
4
+ #assertion;
5
+ #observer;
6
+ #refusal;
7
+ #writingReported = false;
8
+ constructor(options) {
9
+ this.#assertion = options.assertBeforeMutation;
10
+ this.#observer = options.onDestinationState;
11
+ this.active = Boolean(this.#assertion || this.#observer);
12
+ }
13
+ rethrowRefusal() {
14
+ if (this.#refusal)
15
+ throw this.#refusal.error;
16
+ }
17
+ refuse(error) {
18
+ this.#refusal ??= { error };
19
+ throw this.#refusal.error;
20
+ }
21
+ #invoke(operation, name) {
22
+ this.rethrowRefusal();
23
+ try {
24
+ const result = operation();
25
+ assertSynchronousCallbackResult(result, name);
26
+ }
27
+ catch (error) {
28
+ this.refuse(error);
29
+ }
30
+ }
31
+ assert() {
32
+ this.#invoke(() => this.#assertion?.(), "assertBeforeMutation");
33
+ }
34
+ destination(state, path, identity) {
35
+ if (state === "writing") {
36
+ if (this.#writingReported)
37
+ return;
38
+ this.#writingReported = true;
39
+ }
40
+ this.#invoke(() => this.#observer?.(Object.freeze({
41
+ state, path, dev: identity.dev, ino: identity.ino,
42
+ })), "onDestinationState");
43
+ }
44
+ removed(path) {
45
+ this.#invoke(() => this.#observer?.(Object.freeze({ state: "removed", path })), "onDestinationState");
46
+ }
47
+ }
@@ -26,7 +26,7 @@ export declare class AsyncAtomicTempOwner extends AtomicTempOwner<FileHandle> {
26
26
  identity: BigIntStats;
27
27
  }): void;
28
28
  assertCurrent(fsModule: AsyncOwnerFileSystem, pathname?: string): Promise<void>;
29
- assertPublished(fsModule: AsyncOwnerFileSystem, pathname: string, expectedHash?: string): Promise<void>;
29
+ assertPublished(fsModule: AsyncOwnerFileSystem, pathname: string, expectedHash?: string, onVerified?: (identity: BigIntStats) => void): Promise<void>;
30
30
  finish(params: {
31
31
  fsModule: AsyncOwnerFileSystem;
32
32
  originalFailure?: AtomicTempFailure;
@@ -39,7 +39,7 @@ export declare class SyncAtomicTempOwner extends AtomicTempOwner<number> {
39
39
  identity: BigIntStats;
40
40
  }): void;
41
41
  assertCurrent(fsModule: SyncOwnerFileSystem, pathname?: string): void;
42
- assertPublished(fsModule: SyncOwnerFileSystem, pathname: string, expectedHash?: string): void;
42
+ assertPublished(fsModule: SyncOwnerFileSystem, pathname: string, expectedHash?: string, onVerified?: (identity: BigIntStats) => void): void;
43
43
  finish(params: {
44
44
  fsModule: SyncOwnerFileSystem;
45
45
  originalFailure?: AtomicTempFailure;
@@ -161,16 +161,21 @@ export class AsyncAtomicTempOwner extends AtomicTempOwner {
161
161
  throw error;
162
162
  }
163
163
  }
164
- async assertPublished(fsModule, pathname, expectedHash) {
164
+ async assertPublished(fsModule, pathname, expectedHash, onVerified) {
165
+ let identityCurrent = false;
165
166
  try {
166
167
  await this.assertCurrent(fsModule, pathname);
167
- return;
168
+ identityCurrent = true;
168
169
  }
169
170
  catch (error) {
170
171
  if (!(error instanceof FsSafeError) || !hasErrorCode(error, "path-mismatch") || !expectedHash) {
171
172
  throw error;
172
173
  }
173
174
  }
175
+ if (identityCurrent) {
176
+ onVerified?.(this.identity);
177
+ return;
178
+ }
174
179
  let published;
175
180
  try {
176
181
  try {
@@ -199,6 +204,7 @@ export class AsyncAtomicTempOwner extends AtomicTempOwner {
199
204
  if (sha256Hex(await published.readFile()) !== expectedHash) {
200
205
  throw new FsSafeError("path-mismatch", `Atomic replace published content changed: ${pathname}`);
201
206
  }
207
+ onVerified?.(identity);
202
208
  const previousHandle = this.takeResource();
203
209
  await previousHandle?.close();
204
210
  this.resource = published;
@@ -269,16 +275,21 @@ export class SyncAtomicTempOwner extends AtomicTempOwner {
269
275
  throw error;
270
276
  }
271
277
  }
272
- assertPublished(fsModule, pathname, expectedHash) {
278
+ assertPublished(fsModule, pathname, expectedHash, onVerified) {
279
+ let identityCurrent = false;
273
280
  try {
274
281
  this.assertCurrent(fsModule, pathname);
275
- return;
282
+ identityCurrent = true;
276
283
  }
277
284
  catch (error) {
278
285
  if (!(error instanceof FsSafeError) || !hasErrorCode(error, "path-mismatch") || !expectedHash) {
279
286
  throw error;
280
287
  }
281
288
  }
289
+ if (identityCurrent) {
290
+ onVerified?.(this.identity);
291
+ return;
292
+ }
282
293
  let publishedFd;
283
294
  try {
284
295
  try {
@@ -305,6 +316,7 @@ export class SyncAtomicTempOwner extends AtomicTempOwner {
305
316
  if (sha256Hex(fsModule.readFileSync(publishedFd)) !== expectedHash) {
306
317
  throw new FsSafeError("path-mismatch", `Atomic replace published content changed: ${pathname}`);
307
318
  }
319
+ onVerified?.(identity);
308
320
  const previousFd = this.takeResource();
309
321
  fsModule.closeSync(previousFd);
310
322
  this.resource = publishedFd;
@@ -0,0 +1,55 @@
1
+ import type syncFs from "node:fs";
2
+ import type fs from "node:fs/promises";
3
+ import type { RenameIdentityPolicy } from "./pinned-write-types.js";
4
+ import type { AtomicMutationOptions } from "./replace-file-mutation.js";
5
+ import type { ReplaceFileCopyFallbackRestorePolicy, ReplaceFileDestinationHardlinkPolicy } from "./replace-file-copy-fallback.js";
6
+ export type ReplaceFileAtomicFileSystem = {
7
+ promises: Pick<typeof fs, "mkdir" | "writeFile" | "rename" | "copyFile" | "unlink" | "rm" | "open" | "stat" | "lstat"> & {
8
+ /** @deprecated Accepted for adapter compatibility but never called. */
9
+ chmod?: typeof fs.chmod;
10
+ };
11
+ };
12
+ export type ReplaceFileAtomicSyncFileSystem = Pick<typeof syncFs, "mkdirSync" | "readFileSync" | "writeFileSync" | "renameSync" | "copyFileSync" | "unlinkSync" | "rmSync" | "openSync" | "fsyncSync" | "closeSync" | "fstatSync" | "statSync" | "lstatSync" | "ftruncateSync" | "readSync" | "writeSync"> & {
13
+ /** @deprecated Accepted for adapter compatibility but never called. */
14
+ chmodSync?: typeof syncFs.chmodSync;
15
+ fchmodSync?: typeof syncFs.fchmodSync;
16
+ };
17
+ export type ReplaceFileAtomicBaseOptions = AtomicMutationOptions & {
18
+ filePath: string;
19
+ content: string | Uint8Array;
20
+ dirMode?: number;
21
+ mode?: number;
22
+ /** Inherit only rwx bits from an existing non-symlink regular file. */
23
+ preserveExistingMode?: boolean;
24
+ tempPrefix?: string;
25
+ renameMaxRetries?: number;
26
+ renameRetryBaseDelayMs?: number;
27
+ copyFallbackOnPermissionError?: boolean;
28
+ copyFallbackRestore?: ReplaceFileCopyFallbackRestorePolicy;
29
+ maxRestoreBytes?: number;
30
+ destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
31
+ /** Strict by default; locked content verification is an explicit FUSE compatibility policy. */
32
+ renameIdentity?: RenameIdentityPolicy;
33
+ syncTempFile?: boolean;
34
+ syncParentDir?: boolean;
35
+ throwOnCleanupError?: boolean;
36
+ };
37
+ export type ReplaceFileAtomicOptions = ReplaceFileAtomicBaseOptions & {
38
+ fileSystem?: ReplaceFileAtomicFileSystem;
39
+ /** Runs while the exact staged file is retained; replacing or hardlinking it is rejected. */
40
+ beforeRename?: (params: {
41
+ filePath: string;
42
+ tempPath: string;
43
+ }) => Promise<void>;
44
+ };
45
+ export type ReplaceFileAtomicSyncOptions = ReplaceFileAtomicBaseOptions & {
46
+ fileSystem?: ReplaceFileAtomicSyncFileSystem;
47
+ /** Runs while the exact staged file is retained; replacing or hardlinking it is rejected. */
48
+ beforeRename?: (params: {
49
+ filePath: string;
50
+ tempPath: string;
51
+ }) => void;
52
+ };
53
+ export type ReplaceFileAtomicResult = {
54
+ method: "rename" | "copy-fallback";
55
+ };
@@ -1,58 +1,6 @@
1
- import syncFs from "node:fs";
2
- import fs from "node:fs/promises";
3
- import type { RenameIdentityPolicy } from "./pinned-write-types.js";
4
- import { type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy } from "./replace-file-copy-fallback.js";
5
- export type ReplaceFileAtomicFileSystem = {
6
- promises: Pick<typeof fs, "mkdir" | "writeFile" | "rename" | "copyFile" | "unlink" | "rm" | "open" | "stat" | "lstat"> & {
7
- /** @deprecated Accepted for adapter compatibility but never called. */
8
- chmod?: typeof fs.chmod;
9
- };
10
- };
11
- export type ReplaceFileAtomicSyncFileSystem = Pick<typeof syncFs, "mkdirSync" | "readFileSync" | "writeFileSync" | "renameSync" | "copyFileSync" | "unlinkSync" | "rmSync" | "openSync" | "fsyncSync" | "closeSync" | "fstatSync" | "statSync" | "lstatSync" | "ftruncateSync" | "readSync" | "writeSync"> & {
12
- /** @deprecated Accepted for adapter compatibility but never called. */
13
- chmodSync?: typeof syncFs.chmodSync;
14
- fchmodSync?: typeof syncFs.fchmodSync;
15
- };
16
- type ReplaceFileAtomicBaseOptions = {
17
- filePath: string;
18
- content: string | Uint8Array;
19
- dirMode?: number;
20
- mode?: number;
21
- /** Inherit only rwx bits from an existing non-symlink regular file. */
22
- preserveExistingMode?: boolean;
23
- tempPrefix?: string;
24
- renameMaxRetries?: number;
25
- renameRetryBaseDelayMs?: number;
26
- copyFallbackOnPermissionError?: boolean;
27
- copyFallbackRestore?: ReplaceFileCopyFallbackRestorePolicy;
28
- maxRestoreBytes?: number;
29
- destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
30
- /** Strict by default; locked content verification is an explicit FUSE compatibility policy. */
31
- renameIdentity?: RenameIdentityPolicy;
32
- syncTempFile?: boolean;
33
- syncParentDir?: boolean;
34
- throwOnCleanupError?: boolean;
35
- };
36
- export type ReplaceFileAtomicOptions = ReplaceFileAtomicBaseOptions & {
37
- fileSystem?: ReplaceFileAtomicFileSystem;
38
- /** Runs while the exact staged file is retained; replacing or hardlinking it is rejected. */
39
- beforeRename?: (params: {
40
- filePath: string;
41
- tempPath: string;
42
- }) => Promise<void>;
43
- };
44
- export type ReplaceFileAtomicSyncOptions = ReplaceFileAtomicBaseOptions & {
45
- fileSystem?: ReplaceFileAtomicSyncFileSystem;
46
- /** Runs while the exact staged file is retained; replacing or hardlinking it is rejected. */
47
- beforeRename?: (params: {
48
- filePath: string;
49
- tempPath: string;
50
- }) => void;
51
- };
52
- export type ReplaceFileAtomicResult = {
53
- method: "rename" | "copy-fallback";
54
- };
1
+ import type { ReplaceFileAtomicOptions, ReplaceFileAtomicSyncOptions, ReplaceFileAtomicResult } from "./replace-file-types.js";
2
+ export type { ReplaceFileAtomicDestinationState } from "./replace-file-mutation.js";
3
+ export type { ReplaceFileAtomicFileSystem, ReplaceFileAtomicSyncFileSystem, ReplaceFileAtomicOptions, ReplaceFileAtomicSyncOptions, ReplaceFileAtomicResult, } from "./replace-file-types.js";
55
4
  export declare function replaceFileAtomic(options: ReplaceFileAtomicOptions): Promise<ReplaceFileAtomicResult>;
56
5
  export declare function replaceFileAtomicWithDirectorySync(options: ReplaceFileAtomicOptions, syncParent?: (directoryPath: string) => Promise<unknown>): Promise<ReplaceFileAtomicResult>;
57
6
  export declare function replaceFileAtomicSync(options: ReplaceFileAtomicSyncOptions): ReplaceFileAtomicResult;
58
- export {};
@@ -13,15 +13,19 @@ import { admitStandalonePublicationPath } from "./windows-path-alias.js";
13
13
  import { sleep, sleepSync } from "./timing.js";
14
14
  import { serializePathWrite } from "./write-queue.js";
15
15
  import { hasErrorCode, readErrorCode } from "./file-cleanup.js";
16
+ import { AtomicMutation } from "./replace-file-mutation.js";
17
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
16
18
  async function renameWithRetry(params) {
17
19
  for (let attempt = 0; attempt <= params.maxRetries; attempt++) {
18
20
  if (attempt > 0)
19
21
  await params.assertSourceCurrent();
20
22
  try {
23
+ params.mutation.assert();
21
24
  await params.fsModule.rename(params.src, params.dest);
22
25
  return { method: "rename" };
23
26
  }
24
27
  catch (error) {
28
+ params.mutation.rethrowRefusal();
25
29
  const code = readErrorCode(error);
26
30
  if (code === "EBUSY" && attempt < params.maxRetries) {
27
31
  await sleep(params.baseDelayMs * 2 ** attempt);
@@ -37,6 +41,7 @@ async function renameWithRetry(params) {
37
41
  maxRestoreBytes: params.maxRestoreBytes,
38
42
  expectedSourceIdentity: params.sourceIdentity,
39
43
  sync: params.syncFallback,
44
+ mutation: params.mutation,
40
45
  });
41
46
  return { method: "copy-fallback" };
42
47
  }
@@ -50,10 +55,12 @@ function renameWithRetrySync(params) {
50
55
  if (attempt > 0)
51
56
  params.assertSourceCurrent();
52
57
  try {
58
+ params.mutation.assert();
53
59
  params.fsModule.renameSync(params.src, params.dest);
54
60
  return { method: "rename" };
55
61
  }
56
62
  catch (error) {
63
+ params.mutation.rethrowRefusal();
57
64
  const code = readErrorCode(error);
58
65
  if (code === "EBUSY" && attempt < params.maxRetries) {
59
66
  sleepSync(params.baseDelayMs * 2 ** attempt);
@@ -70,6 +77,7 @@ function renameWithRetrySync(params) {
70
77
  expectedSourceIdentity: params.sourceIdentity,
71
78
  fchmodSync: params.fchmodSync,
72
79
  sync: params.syncFallback,
80
+ mutation: params.mutation,
73
81
  });
74
82
  return { method: "copy-fallback" };
75
83
  }
@@ -142,24 +150,27 @@ export async function replaceFileAtomic(options) {
142
150
  }
143
151
  // Internal owner hook: keep directory durability inside publication verification and serialization.
144
152
  export async function replaceFileAtomicWithDirectorySync(options, syncParent) {
153
+ const mutation = new AtomicMutation(options);
145
154
  const filePath = validateReplaceFilePath(options.filePath);
146
155
  validateRestoreOptions(options);
147
156
  const renameIdentity = options.renameIdentity;
148
157
  validateRenameIdentity(renameIdentity);
149
158
  return await serializePathWrite(path.resolve(filePath), async () => {
150
159
  if (renameIdentity !== "verify-content-with-lock") {
151
- return await replaceFileAtomicUnserialized(options, filePath, renameIdentity, syncParent);
160
+ return await replaceFileAtomicUnserialized(options, filePath, renameIdentity, mutation, syncParent);
152
161
  }
153
162
  const fsModule = options.fileSystem?.promises ?? fs;
154
163
  const dir = path.dirname(filePath);
164
+ mutation.assert();
155
165
  await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, {
156
166
  recursive: true,
157
167
  mode: options.dirMode ?? 0o700,
158
168
  });
159
- return await withAtomicRenameIdentityLock(filePath, async () => await replaceFileAtomicUnserialized(options, filePath, renameIdentity, syncParent));
169
+ mutation.assert();
170
+ return await withAtomicRenameIdentityLock(filePath, async () => await replaceFileAtomicUnserialized(options, filePath, renameIdentity, mutation, syncParent));
160
171
  });
161
172
  }
162
- async function replaceFileAtomicUnserialized(options, filePath, renameIdentity, syncParent) {
173
+ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity, mutation, syncParent) {
163
174
  const fsModule = options.fileSystem?.promises ?? fs;
164
175
  const dir = path.dirname(filePath);
165
176
  const dirMode = options.dirMode ?? 0o700;
@@ -169,8 +180,9 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
169
180
  const tempOwner = new AsyncAtomicTempOwner(tempPath);
170
181
  let originalFailure;
171
182
  try {
183
+ mutation.assert();
172
184
  await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
173
- await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode });
185
+ await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode, mutation });
174
186
  tempOwner.start();
175
187
  tempOwner.adopt(await writeTempFile({
176
188
  fsModule,
@@ -179,6 +191,7 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
179
191
  mode,
180
192
  sync: options.syncTempFile === true,
181
193
  onIdentity: tempOwner.onIdentity,
194
+ mutation,
182
195
  }));
183
196
  await tempOwner.assertCurrent(fsModule);
184
197
  if (options.beforeRename) {
@@ -202,10 +215,11 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
202
215
  sourceIdentity: tempOwner.identity,
203
216
  assertSourceCurrent: () => tempOwner.assertCurrent(fsModule),
204
217
  syncFallback: options.syncTempFile === true,
218
+ mutation,
205
219
  });
206
220
  if (result.method === "rename") {
207
221
  tempOwner.markRenamed();
208
- await tempOwner.assertPublished(fsModule, filePath, expectedHash);
222
+ await tempOwner.assertPublished(fsModule, filePath, expectedHash, identity => mutation.destination("published", filePath, identity));
209
223
  }
210
224
  else {
211
225
  await tempOwner.assertCurrent(fsModule);
@@ -237,22 +251,25 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
237
251
  }
238
252
  }
239
253
  export function replaceFileAtomicSync(options) {
254
+ const mutation = new AtomicMutation(options);
240
255
  const filePath = validateReplaceFilePath(options.filePath);
241
256
  validateRestoreOptions(options);
242
257
  const renameIdentity = options.renameIdentity;
243
258
  validateRenameIdentity(renameIdentity);
244
259
  if (renameIdentity !== "verify-content-with-lock") {
245
- return replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity);
260
+ return replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity, mutation);
246
261
  }
247
262
  const fsModule = options.fileSystem ?? syncFs;
248
263
  const dir = path.dirname(filePath);
264
+ mutation.assert();
249
265
  fsModule.mkdirSync(fsModule === syncFs ? recursiveMkdirPath(dir) : dir, {
250
266
  recursive: true,
251
267
  mode: options.dirMode ?? 0o700,
252
268
  });
253
- return withAtomicRenameIdentityLockSync(filePath, () => replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity));
269
+ mutation.assert();
270
+ return withAtomicRenameIdentityLockSync(filePath, () => replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity, mutation));
254
271
  }
255
- function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
272
+ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity, mutation) {
256
273
  const fsModule = options.fileSystem ?? syncFs;
257
274
  const dir = path.dirname(filePath);
258
275
  const dirMode = options.dirMode ?? 0o700;
@@ -269,8 +286,9 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
269
286
  const tempOwner = new SyncAtomicTempOwner(tempPath);
270
287
  let originalFailure;
271
288
  try {
289
+ mutation.assert();
272
290
  fsModule.mkdirSync(fsModule === syncFs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
273
- applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync });
291
+ applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync, mutation });
274
292
  tempOwner.start();
275
293
  tempOwner.adopt(writeTempFileSync({
276
294
  fsModule,
@@ -280,10 +298,11 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
280
298
  fchmodSync,
281
299
  sync: options.syncTempFile === true,
282
300
  onIdentity: tempOwner.onIdentity,
301
+ mutation,
283
302
  }));
284
303
  tempOwner.assertCurrent(fsModule);
285
304
  if (options.beforeRename) {
286
- options.beforeRename({ filePath, tempPath });
305
+ assertSynchronousCallbackResult(options.beforeRename({ filePath, tempPath }), "beforeRename");
287
306
  tempOwner.assertCurrent(fsModule);
288
307
  }
289
308
  if (options.destinationHardlinks === "reject") {
@@ -304,10 +323,11 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
304
323
  assertSourceCurrent: () => tempOwner.assertCurrent(fsModule),
305
324
  fchmodSync,
306
325
  syncFallback: options.syncTempFile === true,
326
+ mutation,
307
327
  });
308
328
  if (result.method === "rename") {
309
329
  tempOwner.markRenamed();
310
- tempOwner.assertPublished(fsModule, filePath, expectedHash);
330
+ tempOwner.assertPublished(fsModule, filePath, expectedHash, identity => mutation.destination("published", filePath, identity));
311
331
  }
312
332
  else {
313
333
  tempOwner.assertCurrent(fsModule);
@@ -6,6 +6,7 @@ export type RetainedReplacementParent = Readonly<{
6
6
  realPath: string;
7
7
  identity: Pick<BigIntStats, "dev" | "ino">;
8
8
  }>;
9
+ export declare function copyOperationalCode(target: Error, source: unknown): void;
9
10
  export declare class RetainedDirectoryReplacement {
10
11
  #private;
11
12
  private constructor();