@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
@@ -1,4 +1,4 @@
1
- import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, createArchiveEntryPlanner, } from "./archive-plan.js";
1
+ import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, resolveArchiveEntryMode, createArchiveEntryPlanner, } from "./archive-plan.js";
2
2
  import { inspectTar, replayTar } from "./archive-tar-stream.js";
3
3
  import { runPinnedWriteHelper } from "./pinned-write.js";
4
4
  import { constants as fsConstants } from "node:fs";
@@ -11,9 +11,7 @@ import { assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtr
11
11
  import { resolveArchiveKind } from "./archive-kind.js";
12
12
  import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, } from "./archive-staging.js";
13
13
  import { withStagedArchivePublication } from "./archive-merge.js";
14
- import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
15
- import { admittedZipEntries } from "./archive-zip-loader.js";
16
- import { zipEntryKind, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
14
+ import { loadZipArchiveWithAdmission } from "./archive-zip-preflight.js";
17
15
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
18
16
  import { ArchiveSecurityError, ArchiveFormatError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
19
17
  import { stageArchiveFileForExtraction } from "./archive-input.js";
@@ -29,10 +27,12 @@ export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
29
27
  export { readArchiveEntry } from "./archive-read.js";
30
28
  export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
31
29
  export { ArchiveFormatError } from "./archive-errors.js";
32
- export { ArchiveSecurityError } from "./archive-staging.js";
33
- export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
30
+ export { ArchiveSecurityError } from "./archive-errors.js";
31
+ export { createArchiveSymlinkTraversalError, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
32
+ export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
34
33
  export { createTarEntryPreflightChecker } from "./archive-plan.js";
35
- export { loadZipArchiveWithPreflight, readZipCentralDirectoryEntryCount, } from "./archive-zip-preflight.js";
34
+ export { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
35
+ export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
36
36
  const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
37
37
  const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
38
38
  fsConstants.O_CREAT |
@@ -49,7 +49,7 @@ async function readZipEntryStream(entry) {
49
49
  async function writeZipFileEntry(params) {
50
50
  params.deadline.check();
51
51
  params.budget.startEntry();
52
- const readable = await readZipEntryStream(params.entry);
52
+ const readable = await readZipEntryStream(params.record.entry);
53
53
  const destinationPath = params.outPath;
54
54
  let tempHandle = null;
55
55
  let handleClosedByStream = false;
@@ -67,7 +67,7 @@ async function writeZipFileEntry(params) {
67
67
  handleClosedByStream = true;
68
68
  });
69
69
  try {
70
- await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.entry), writable, { signal: params.deadline.signal });
70
+ await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.record), writable, { signal: params.deadline.signal });
71
71
  }
72
72
  catch (err) {
73
73
  throw normalizeZipIntegrityError(createPipelineTimeoutError(err, params.deadline));
@@ -101,26 +101,29 @@ async function extractZip(params) {
101
101
  deadline.check();
102
102
  const buffer = await fs.readFile(params.archivePath, { signal: deadline.signal });
103
103
  deadline.check();
104
- const zip = await waitForDeadline(loadZipArchiveWithPreflight(buffer, limits), deadline);
104
+ const { entries } = await waitForDeadline(loadZipArchiveWithAdmission(buffer, limits), deadline);
105
105
  deadline.check();
106
- const entries = admittedZipEntries(zip);
107
- assertArchiveEntryCountWithinLimit(entries.length, limits);
106
+ assertArchiveEntryCountWithinLimit(entries.size, limits);
108
107
  const budget = createByteBudgetTracker(limits);
109
108
  await withStagedArchivePublication({ ...params, destinationGuard }, async (stagingDir) => {
110
109
  const { select } = createArchiveEntrySelector({ ...params, rootDir: stagingDir });
111
110
  const acceptedEntries = [];
112
- for (const entry of entries) {
111
+ for (const record of entries.values()) {
113
112
  deadline.check();
114
- const entryKind = zipEntryKind(entry);
115
- const relPath = select({ path: entry.name, kind: entryKind, size: zipEntryDeclaredSize(entry) });
113
+ const { entry, kind: entryKind, size, mode: archivedMode } = record;
114
+ const relPath = select({ path: record.name, kind: entryKind, size });
116
115
  if (relPath === null)
117
116
  continue;
118
117
  if (entryKind === "symlink") {
119
- throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
118
+ throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${record.name}`);
120
119
  }
121
120
  if (entryKind === "other")
122
121
  continue;
123
- const mode = zipEntryMode(entry, params.entryModes);
122
+ const mode = resolveArchiveEntryMode({
123
+ kind: entry.dir ? "directory" : "file",
124
+ archivedMode,
125
+ policy: params.entryModes,
126
+ });
124
127
  acceptedEntries.push({ path: relPath, kind: entry.dir ? "directory" : "file", mode });
125
128
  const outPath = path.join(stagingDir, relPath);
126
129
  await preparePrivateArchiveOutputPath({
@@ -128,12 +131,12 @@ async function extractZip(params) {
128
131
  destinationRealDir: stagingDir,
129
132
  relPath,
130
133
  outPath,
131
- originalPath: entry.name,
134
+ originalPath: record.name,
132
135
  isDirectory: entry.dir,
133
136
  deadline,
134
137
  });
135
138
  if (!entry.dir) {
136
- await writeZipFileEntry({ entry, outPath, budget, deadline });
139
+ await writeZipFileEntry({ record, outPath, budget, deadline });
137
140
  }
138
141
  }
139
142
  return acceptedEntries;
@@ -1,4 +1,4 @@
1
- export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
1
+ export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicDestinationState, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
2
2
  export type { RenameIdentityPolicy } from "./pinned-write-types.js";
3
3
  export type { ReplaceFileAtomicRestoreCleanup, ReplaceFileAtomicRestoreFailureDetails, ReplaceFileCopyFallbackRestorePolicy, ReplaceFileDestinationHardlinkPolicy, } from "./replace-file-copy-fallback.js";
4
4
  export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
@@ -1,9 +1,7 @@
1
1
  import type { BigIntStats } from "node:fs";
2
2
  import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write-types.js";
3
- export type RootCopyPublicationReceipt = Readonly<{
3
+ export type RootCopyPublicationReceipt = Readonly<PublishedWriteIdentity & {
4
4
  path: string;
5
- dev: bigint;
6
- ino: bigint;
7
5
  }>;
8
6
  export declare function createCopyPublicationObserver(path: string, notify?: (receipt: RootCopyPublicationReceipt) => void): {
9
7
  onPublished(identity: PublishedWriteIdentity): void;
@@ -1,16 +1,12 @@
1
1
  import { FsSafeError } from "./errors.js";
2
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
2
3
  export function createCopyPublicationObserver(path, notify) {
3
4
  let rejected;
4
5
  return {
5
6
  onPublished(identity) {
6
7
  const receipt = Object.freeze({ path, dev: identity.dev, ino: identity.ino });
7
8
  try {
8
- const result = notify?.(receipt);
9
- if (result !== null && (typeof result === "object" || typeof result === "function") &&
10
- "then" in result && typeof result.then === "function") {
11
- void Promise.resolve(result).catch(() => undefined);
12
- throw new TypeError("onDestinationPublished must be synchronous");
13
- }
9
+ assertSynchronousCallbackResult(notify?.(receipt), "onDestinationPublished");
14
10
  }
15
11
  catch (error) {
16
12
  rejected = { error };
@@ -0,0 +1,2 @@
1
+ /** Cache observations only within one synchronous policy check, never across mutations or callbacks. */
2
+ export declare function createMutationDenyMatcher(): (target: string, denied: string, prefix: boolean, protectAncestors?: boolean) => boolean;
@@ -0,0 +1,71 @@
1
+ import path from "node:path";
2
+ import { isPathInside } from "./path.js";
3
+ import { probePathCaseInsensitiveSync } from "./path-case.js";
4
+ import { observeMutationPath } from "./pinned-mutation-observation.js";
5
+ function foldedName(name) {
6
+ // Decompose before folding so combining marks precede an expanded Greek subscript.
7
+ // Default folding preserves dotless i even though its uppercase mapping is I.
8
+ return name.normalize("NFD").replace(/[^\u0131]+/gu, part => part.toLowerCase().toUpperCase().toLowerCase()).normalize("NFC");
9
+ }
10
+ function asciiCasePair(left, right) {
11
+ return /^[\x00-\x7f]*$/.test(left) && /^[\x00-\x7f]*$/.test(right) &&
12
+ left.toLowerCase() === right.toLowerCase();
13
+ }
14
+ /** Cache observations only within one synchronous policy check, never across mutations or callbacks. */
15
+ export function createMutationDenyMatcher() {
16
+ const observations = new Map();
17
+ const caseObservations = new Map();
18
+ const observe = (pathname) => {
19
+ if (!observations.has(pathname))
20
+ observations.set(pathname, observeMutationPath(pathname));
21
+ return observations.get(pathname);
22
+ };
23
+ const prospectiveInside = (parent, child) => {
24
+ const root = path.parse(parent).root;
25
+ if (root !== path.parse(child).root)
26
+ return false;
27
+ const left = parent.slice(root.length).split(path.sep);
28
+ const right = child.slice(root.length).split(path.sep).slice(0, left.length);
29
+ if (left.length !== right.length || !left.every((name, i) => foldedName(name) === foldedName(right[i]))) {
30
+ return false;
31
+ }
32
+ // Byte-identical relations were already handled without any observations.
33
+ if (left.every((name, i) => name === right[i]))
34
+ return false;
35
+ const alignedChild = path.join(root, ...right);
36
+ const a = observe(parent);
37
+ const b = observe(alignedChild);
38
+ if (!a || !b)
39
+ return true;
40
+ // Distinct existing ancestors are real objects, not prospective aliases.
41
+ if (a.canonicalAncestor !== b.canonicalAncestor ||
42
+ a.missingSegments.length === 0 || a.missingSegments.length !== b.missingSegments.length)
43
+ return false;
44
+ const missingLeft = a.missingSegments;
45
+ const missingRight = b.missingSegments;
46
+ if (!missingLeft.every((name, i) => foldedName(name) === foldedName(missingRight[i])))
47
+ return false;
48
+ const difference = missingLeft.findIndex((name, i) => name !== missingRight[i]);
49
+ if (difference === -1)
50
+ return true;
51
+ // A read-only ASCII observation can disprove aliasing at this existing parent.
52
+ // It cannot prove Unicode sensitivity or the behavior of a future directory.
53
+ if (difference === 0 && asciiCasePair(missingLeft[0], missingRight[0])) {
54
+ if (!caseObservations.has(a.canonicalAncestor)) {
55
+ caseObservations.set(a.canonicalAncestor, probePathCaseInsensitiveSync(path.join(a.canonicalAncestor, missingLeft[0]), { allowTemporaryProbe: false }));
56
+ }
57
+ if (caseObservations.get(a.canonicalAncestor) === false)
58
+ return false;
59
+ }
60
+ // Suffix probing writes directories. Policy admission has no authority for
61
+ // those side effects, so unproven case/normalization equivalence fails closed.
62
+ return true;
63
+ };
64
+ return (target, denied, prefix, protectAncestors = false) => {
65
+ if ((isPathInside(denied, target) && (prefix || isPathInside(target, denied))) ||
66
+ (protectAncestors && isPathInside(target, denied)))
67
+ return true;
68
+ return ((prefix || target.split(path.sep).length === denied.split(path.sep).length) &&
69
+ prospectiveInside(denied, target)) || (protectAncestors && prospectiveInside(target, denied));
70
+ };
71
+ }
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { createMutationDenyMatcher } from "./deny-mutation-match.js";
3
4
  import { FsSafeError } from "./errors.js";
4
- import { assertNoNulPathInput, isNotFoundPathError, isPathInside } from "./path.js";
5
+ import { assertNoNulPathInput, isNotFoundPathError } from "./path.js";
5
6
  import { realpathSync } from "./realpath.js";
6
7
  import { resolveExistingAncestor } from "./root-path-existing.js";
7
8
  import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
@@ -65,6 +66,7 @@ export function assertMutationNotDenied(filePath, policy, options = {}, mode = "
65
66
  const strict = mode === "sync-root-lock";
66
67
  const comparablePaths = strict ? strictMutationComparablePaths : resolveMutationComparablePaths;
67
68
  const targets = comparablePaths(filePath);
69
+ const matches = createMutationDenyMatcher();
68
70
  // Validate one complete phase at a time. A paths denial must not read or
69
71
  // canonicalize prefixes, and invalid later paths retain validation precedence.
70
72
  for (const kind of ["paths", "prefixes"]) {
@@ -72,8 +74,7 @@ export function assertMutationNotDenied(filePath, policy, options = {}, mode = "
72
74
  const deniedPaths = comparablePaths(entry);
73
75
  for (const target of targets) {
74
76
  for (const denied of deniedPaths) {
75
- if ((isPathInside(denied, target) && (kind === "prefixes" || isPathInside(target, denied))) ||
76
- (options.protectAncestors === true && isPathInside(target, denied))) {
77
+ if (matches(target, denied, kind === "prefixes", options.protectAncestors)) {
77
78
  throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
78
79
  }
79
80
  }
@@ -36,7 +36,6 @@ export declare function createNearestExistingDirectoryGuard(rootReal: string, ta
36
36
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
37
37
  bigint: boolean;
38
38
  }): Promise<AnyAsyncDirectoryGuard>;
39
- export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
40
39
  export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
41
40
  export declare function observeDirectoryIdentitySync(dir: string, options: {
42
41
  bigint: true;
@@ -84,9 +84,6 @@ function assertDirectoryGuard(guard, mode) {
84
84
  export async function createNearestExistingDirectoryGuard(rootReal, targetPath, options = { bigint: false }) {
85
85
  return nearestExistingDirectoryGuard(rootReal, targetPath, "native", options);
86
86
  }
87
- export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
88
- return nearestExistingDirectoryGuard(rootReal, targetPath, "normalized");
89
- }
90
87
  function nearestExistingDirectoryGuard(rootReal, targetPath, mode, options) {
91
88
  assertNoWindowsPathAlias(rootReal, "filesystem");
92
89
  assertNoWindowsPathAlias(targetPath, "filesystem");
@@ -1,5 +1,23 @@
1
- import { type BigIntStats } from "node:fs";
2
- import { type DirectoryModeOwner } from "./directory-mode-owner.js";
1
+ import { type BigIntStats, type Stats } from "node:fs";
2
+ export type DirectoryModeChecks = {
3
+ check?: () => void;
4
+ beforeChmod?: () => Promise<void>;
5
+ };
6
+ export type DirectoryModeOwner = {
7
+ verify(check?: () => void): Promise<void>;
8
+ apply(mode: number, checks?: DirectoryModeChecks): Promise<void>;
9
+ close(): Promise<void>;
10
+ };
11
+ export declare function assertOwnedDirectory(expected: Stats | BigIntStats, actual: Stats | BigIntStats): void;
12
+ /** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
13
+ export declare function ownDirectoryMode(params: {
14
+ inspect: () => Promise<number>;
15
+ chmod: (mode: number) => Promise<void>;
16
+ prepareChmod?: () => Promise<void>;
17
+ verifyChmod?: () => Promise<void>;
18
+ close: () => Promise<void>;
19
+ ignoreChmodError?: boolean;
20
+ }): DirectoryModeOwner;
3
21
  /** Darwin descriptor inspection avoids requesting directory-content reads. */
4
22
  export declare function nodeDarwinDirectoryMetadataFlags(): number;
5
23
  export declare function nodeDirectorySearchOnlyFlags(): {
@@ -3,7 +3,83 @@ import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
5
5
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
- import { assertOwnedDirectory, ownDirectoryMode } from "./directory-mode-owner.js";
6
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
7
+ export function assertOwnedDirectory(expected, actual) {
8
+ if (actual.isSymbolicLink() || !actual.isDirectory()) {
9
+ throw new FsSafeError("not-file", "directory mode target must be a real directory");
10
+ }
11
+ if (!sameFileIdentityForCleanup(expected, actual)) {
12
+ throw new FsSafeError("path-mismatch", "directory changed before its mode could be applied");
13
+ }
14
+ }
15
+ /** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
16
+ export function ownDirectoryMode(params) {
17
+ let pending = Promise.resolve();
18
+ let closing;
19
+ const enqueue = (run) => {
20
+ if (closing)
21
+ return Promise.reject(new FsSafeError("path-mismatch", "directory mode owner is closed"));
22
+ const operation = pending.then(run);
23
+ pending = operation.catch(() => undefined);
24
+ return operation;
25
+ };
26
+ return {
27
+ verify: (check) => enqueue(async () => {
28
+ check?.();
29
+ await params.inspect();
30
+ check?.();
31
+ }),
32
+ apply: (mode, checks = {}) => enqueue(async () => {
33
+ // inspect() reports permission bits only; chmod ignores file-type bits,
34
+ // so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
35
+ mode &= 0o7777;
36
+ checks.check?.();
37
+ const currentMode = await params.inspect();
38
+ checks.check?.();
39
+ if (currentMode === mode && !checks.beforeChmod && !checks.check)
40
+ return;
41
+ if (currentMode !== mode) {
42
+ await params.prepareChmod?.();
43
+ checks.check?.();
44
+ }
45
+ await checks.beforeChmod?.();
46
+ checks.check?.();
47
+ // Hooks/ancestor checks can yield; recheck the original named association.
48
+ await params.inspect();
49
+ checks.check?.();
50
+ let dispatchDeadlineFailure;
51
+ if (currentMode !== mode) {
52
+ checks.check?.();
53
+ try {
54
+ await params.chmod(mode);
55
+ }
56
+ catch (error) {
57
+ if (!params.ignoreChmodError)
58
+ throw error;
59
+ }
60
+ // Expiry cannot release the fd while post-dispatch verification is pending.
61
+ try {
62
+ checks.check?.();
63
+ }
64
+ catch (error) {
65
+ dispatchDeadlineFailure = { error };
66
+ }
67
+ await params.verifyChmod?.();
68
+ }
69
+ const finalMode = await params.inspect();
70
+ if (dispatchDeadlineFailure)
71
+ throw dispatchDeadlineFailure.error;
72
+ checks.check?.();
73
+ if (!params.ignoreChmodError && finalMode !== mode) {
74
+ throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
75
+ }
76
+ }),
77
+ close() {
78
+ closing ??= pending.then(params.close);
79
+ return closing;
80
+ },
81
+ };
82
+ }
7
83
  /** Darwin descriptor inspection avoids requesting directory-content reads. */
8
84
  export function nodeDarwinDirectoryMetadataFlags() {
9
85
  if (process.platform !== "darwin" || (process.arch !== "x64" && process.arch !== "arm64")) {
@@ -70,11 +70,6 @@ function snapshotDirectoryMetadata(stat, identity) {
70
70
  export function directoryReceiptAuthority(receipt) {
71
71
  return authorities.get(receipt) ?? snapshotDirectoryReceipt(receipt);
72
72
  }
73
- function rememberReceipt(receipt, authority) {
74
- authorities.set(receipt, authority);
75
- identities.set(receipt.identity, authority);
76
- return receipt;
77
- }
78
73
  export function ownDirectoryReceipt(receipt) {
79
74
  return receiptFromAuthority(snapshotDirectoryReceipt(receipt));
80
75
  }
@@ -83,11 +78,14 @@ export function copyRetainedDirectoryReceipt(receipt) {
83
78
  return receiptFromAuthority(authority);
84
79
  }
85
80
  function receiptFromAuthority(authority) {
86
- return rememberReceipt({
81
+ const receipt = {
87
82
  path: authority.path,
88
83
  realPath: authority.realPath,
89
84
  identity: Object.assign(Object.create(fs.Stats.prototype), authority.metadata),
90
- }, authority);
85
+ };
86
+ authorities.set(receipt, authority);
87
+ identities.set(receipt.identity, authority);
88
+ return receipt;
91
89
  }
92
90
  // The caller already admitted the exact observation and its paths.
93
91
  export function createDirectoryReceiptFromIdentity(pathname, realPath, exactStat) {
@@ -1,6 +1,3 @@
1
- function isValidUid(value) {
2
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3
- }
4
1
  function unavailable(cause) {
5
2
  return new Error("Effective user identity is unavailable.", {
6
3
  ...(cause === undefined ? {} : { cause }),
@@ -18,7 +15,7 @@ export function resolveEffectiveUid() {
18
15
  catch (cause) {
19
16
  throw unavailable(cause);
20
17
  }
21
- if (!isValidUid(uid)) {
18
+ if (!(typeof uid === "number" && Number.isSafeInteger(uid) && uid >= 0)) {
22
19
  throw unavailable();
23
20
  }
24
21
  return uid;
@@ -1,6 +1,7 @@
1
- export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "read-failed" | "secret-exists" | "store-reentrant-update" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
1
+ export type FsSafeErrorCode = (typeof OPERATIONAL_CODE_VALUES)[number] | "already-exists" | "denied-path" | "device-path" | "hardlink" | "invalid-path" | "insecure-permissions" | "not-file" | "not-owned" | "outside-workspace" | "path-alias" | "path-mismatch" | "secret-exists" | "store-reentrant-update" | "symlink" | "too-large";
2
2
  export type FsSafeErrorCategory = "policy" | "operational";
3
3
  export type FsSafeErrorDetails = Readonly<Record<string, unknown>>;
4
+ declare const OPERATIONAL_CODE_VALUES: readonly ["helper-failed", "helper-unavailable", "not-empty", "not-found", "not-removable", "permission-unverified", "read-failed", "timeout", "unsupported-platform"];
4
5
  export declare function categorizeFsSafeError(code: FsSafeErrorCode): FsSafeErrorCategory;
5
6
  export declare class FsSafeError extends Error {
6
7
  readonly code: FsSafeErrorCode;
@@ -11,3 +12,4 @@ export declare class FsSafeError extends Error {
11
12
  details?: FsSafeErrorDetails;
12
13
  });
13
14
  }
15
+ export {};
@@ -1,4 +1,4 @@
1
- const OPERATIONAL_CODES = new Set([
1
+ const OPERATIONAL_CODE_VALUES = [
2
2
  "helper-failed",
3
3
  "helper-unavailable",
4
4
  "not-empty",
@@ -8,7 +8,8 @@ const OPERATIONAL_CODES = new Set([
8
8
  "read-failed",
9
9
  "timeout",
10
10
  "unsupported-platform",
11
- ]);
11
+ ];
12
+ const OPERATIONAL_CODES = new Set(OPERATIONAL_CODE_VALUES);
12
13
  export function categorizeFsSafeError(code) {
13
14
  return OPERATIONAL_CODES.has(code) ? "operational" : "policy";
14
15
  }
@@ -22,9 +22,17 @@ export function sameFileIdentity(left, right, platform = process.platform) {
22
22
  return (!isStatValueProvablyDifferent(left.dev, right.dev, platform) &&
23
23
  !isStatValueProvablyDifferent(left.ino, right.ino, platform));
24
24
  }
25
+ function sameCleanupStatValue(left, right) {
26
+ // Converting an unsafe number to bigint cannot recover an already rounded ID.
27
+ if (typeof left === "number" && !Number.isSafeInteger(left))
28
+ return false;
29
+ if (typeof right === "number" && !Number.isSafeInteger(right))
30
+ return false;
31
+ return sameStatValue(left, right);
32
+ }
25
33
  export function sameFileIdentityForCleanup(left, right, platform = process.platform) {
26
34
  if (platform !== "win32") {
27
- return sameStatValue(left.dev, right.dev) && sameStatValue(left.ino, right.ino);
35
+ return sameCleanupStatValue(left.dev, right.dev) && sameCleanupStatValue(left.ino, right.ino);
28
36
  }
29
37
  // A zero Windows device or inode is unknown, not proof that a pathname still
30
38
  // names the object we created. Cleanup must fail closed rather than delete a
@@ -44,5 +52,5 @@ export function sameFileIdentityForCleanup(left, right, platform = process.platf
44
52
  const rightIno = right.ino;
45
53
  if (isZero(rightIno))
46
54
  return false;
47
- return sameStatValue(leftDev, rightDev) && sameStatValue(leftIno, rightIno);
55
+ return sameCleanupStatValue(leftDev, rightDev) && sameCleanupStatValue(leftIno, rightIno);
48
56
  }
@@ -66,7 +66,7 @@ export class SyncLockAcquisition {
66
66
  }
67
67
  catch (waitError) {
68
68
  // Exhausted backoff must preserve the filesystem denial diagnosis.
69
- if (waitError.code === "file_lock_timeout")
69
+ if (waitError?.code === "file_lock_timeout")
70
70
  throw error;
71
71
  throw waitError;
72
72
  }
@@ -30,7 +30,11 @@ export function defaultSyncShouldReclaim(snapshot, staleMs, nowMs) {
30
30
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
31
31
  if (createdAtMs !== null)
32
32
  return nowMs - createdAtMs > staleMs;
33
- return !snapshot.stat || nowMs - snapshot.stat.mtimeMs > staleMs;
33
+ if (!snapshot.stat)
34
+ return true;
35
+ const mtimeMs = "mtimeNs" in snapshot.stat
36
+ ? Number(snapshot.stat.mtimeNs) / 1e6 : snapshot.stat.mtimeMs;
37
+ return nowMs - mtimeMs > staleMs;
34
38
  }
35
39
  export function syncReclaimGuardExists(reclaimGuardPath) {
36
40
  try {
@@ -1,5 +1,6 @@
1
+ import { type SyncHeldLock } from "./file-lock-sync-admission.js";
2
+ import type { SidecarLockOptionFields } from "./sidecar-lock-types.js";
1
3
  import type { FileLockSyncHandle } from "./file-lock-sync.js";
2
- import { type SidecarLockSnapshot } from "./sidecar-lock-reclaim.js";
3
4
  import { type FileLockSyncRootAuthority, type FileLockSyncRootPath } from "./file-lock-sync-root.js";
4
5
  import { type FileLockSyncRootFileReceipt, type FileLockSyncRootSnapshot } from "./file-lock-sync-root-io.js";
5
6
  type RootSyncHeldLockReleaseState = "active" | "releasing" | "exit-cleaning" | "released";
@@ -7,22 +8,14 @@ type RootSyncHeldLockHandleDisposition = {
7
8
  held: RootSyncHeldLock;
8
9
  released: boolean;
9
10
  };
10
- export type RootSyncHeldLock = {
11
+ export type RootSyncHeldLock = SidecarLockOptionFields<SyncHeldLock & {
11
12
  deferredExitReleases?: Set<RootSyncHeldLockHandleDisposition>;
12
- fd: number | undefined;
13
- lockPath: string;
14
- normalizedTargetPath: string;
15
- parsePayload?: (raw: string) => unknown;
16
- refCount: number;
17
- reentrantOwner?: string;
18
13
  releaseState: RootSyncHeldLockReleaseState;
19
14
  revision: number;
20
15
  rootAuthority: FileLockSyncRootAuthority;
21
16
  rootPath: FileLockSyncRootPath;
22
17
  rootReceipt: FileLockSyncRootFileReceipt;
23
- snapshot: SidecarLockSnapshot;
24
- timer?: NodeJS.Timeout;
25
- };
18
+ }>;
26
19
  export declare function readRootSidecarSnapshotSync(rootPath: FileLockSyncRootPath, parsePayload?: (raw: string) => unknown, onOpenFailure?: (error: unknown) => void, expectedReceipt?: FileLockSyncRootFileReceipt): FileLockSyncRootSnapshot | null;
27
20
  export declare function getRootSyncHeldLocks(): Map<string, RootSyncHeldLock>;
28
21
  export declare function ensureRootSyncExitCleanupRegistered(): void;
@@ -53,9 +53,6 @@ export function getRootSyncHeldLocks() {
53
53
  }
54
54
  return globalWithState[ROOT_SYNC_HELD_LOCKS_KEY];
55
55
  }
56
- function existingRootSyncHeldLocks() {
57
- return globalThis[ROOT_SYNC_HELD_LOCKS_KEY];
58
- }
59
56
  function reactivateExactHeldLock(heldLocks, held, restoreConsumedReference) {
60
57
  if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released") {
61
58
  return false;
@@ -123,7 +120,7 @@ function releaseAllRootSyncHeldLocks() {
123
120
  try {
124
121
  // A newListener callback can invoke the candidate handler before
125
122
  // registration succeeds. Do not create acquisition state in that window.
126
- const heldLocks = existingRootSyncHeldLocks();
123
+ const heldLocks = globalThis[ROOT_SYNC_HELD_LOCKS_KEY];
127
124
  if (!heldLocks)
128
125
  return;
129
126
  getSyncLockAdmissions().clear();
@@ -1,4 +1,4 @@
1
- import { type BigIntStats, type Stats } from "node:fs";
1
+ import { type BigIntStats } from "node:fs";
2
2
  import type { RootDefaults } from "./root-options.js";
3
3
  import { type FileLockSyncRootPath } from "./file-lock-sync-root.js";
4
4
  export type ExactIdentity = Readonly<{
@@ -14,15 +14,12 @@ export type FileLockSyncRootFileReceipt = Readonly<{
14
14
  identity: ExactIdentity;
15
15
  parent: DirectoryReceipt;
16
16
  }>;
17
- export type FileLockSyncRootDirectoryReceipt = Readonly<{
18
- identity: ExactIdentity;
19
- parent: DirectoryReceipt;
20
- }>;
17
+ export type FileLockSyncRootDirectoryReceipt = FileLockSyncRootFileReceipt;
21
18
  export type FileLockSyncRootDiskSnapshot = {
22
19
  ownershipToken?: never;
23
20
  payload: unknown;
24
21
  raw: string;
25
- stat: Stats;
22
+ stat: BigIntStats;
26
23
  };
27
24
  export type FileLockSyncRootSnapshot = Readonly<{
28
25
  receipt: FileLockSyncRootFileReceipt;
@@ -143,7 +143,7 @@ export function readFileLockSyncRootSnapshot(pathAuthority, options = {}) {
143
143
  throw error;
144
144
  }
145
145
  assertRegularFile(after, pathAuthority.authority.hardlinks);
146
- const stat = fs.fstatSync(fd);
146
+ const stat = fs.fstatSync(fd, { bigint: true });
147
147
  if (expectedReceipt) {
148
148
  assertRetainedParentCurrent(pathAuthority, parent);
149
149
  }
@@ -1,19 +1,11 @@
1
1
  import type { FileLockSyncAcquireOptions } from "./file-lock-sync.js";
2
- import type { SidecarLockCompromisedInfo, SidecarLockRetryOptions, SidecarLockStaleRecovery } from "./sidecar-lock-types.js";
3
- type CapturedRootSyncAcquireOptions<TPayload extends Record<string, unknown>> = Readonly<{
4
- compromiseCheckIntervalMs?: number;
5
- lockPath?: string;
6
- onCompromised?: (info: SidecarLockCompromisedInfo) => void;
2
+ import type { SidecarLockRetryOptions } from "./sidecar-lock-types.js";
3
+ type CapturedRootSyncAcquireOptions<TPayload extends Record<string, unknown>> = Readonly<Pick<FileLockSyncAcquireOptions<TPayload>, "compromiseCheckIntervalMs" | "lockPath" | "onCompromised" | "parsePayload" | "payload" | "reentrantOwner" | "staleRecovery" | "timeoutMs"> & {
7
4
  optionsReceiver: FileLockSyncAcquireOptions<TPayload>;
8
- parsePayload?: (raw: string) => unknown;
9
- payload: () => TPayload;
10
- reentrantOwner?: string;
11
5
  retry: Readonly<SidecarLockRetryOptions>;
12
6
  shouldReclaim?: FileLockSyncAcquireOptions<TPayload>["shouldReclaim"];
13
7
  shouldRemoveStaleLock?: FileLockSyncAcquireOptions<TPayload>["shouldRemoveStaleLock"];
14
8
  staleMs: number;
15
- staleRecovery?: SidecarLockStaleRecovery;
16
- timeoutMs?: number;
17
9
  }>;
18
10
  export declare function captureRootSyncAcquireOptions<TPayload extends Record<string, unknown>>(options: FileLockSyncAcquireOptions<TPayload>): CapturedRootSyncAcquireOptions<TPayload>;
19
11
  export {};
@@ -1,3 +1,4 @@
1
+ import { type DenyMutationPolicy } from "./deny-mutations.js";
1
2
  import { type RootContext } from "./root-context.js";
2
3
  import type { Root } from "./root-impl.js";
3
4
  import type { RootDefaults } from "./root-options.js";
@@ -7,10 +8,7 @@ export type FileLockSyncRootAuthority = Readonly<{
7
8
  adapter: object;
8
9
  context: RootContext;
9
10
  assertBeforeMutation?: () => void;
10
- denyMutations?: Readonly<{
11
- paths?: readonly string[];
12
- prefixes?: readonly string[];
13
- }>;
11
+ denyMutations?: Readonly<DenyMutationPolicy>;
14
12
  hardlinks?: RootDefaults["hardlinks"];
15
13
  mutationPolicy: RootPathResolutionPolicy;
16
14
  readPolicy: RootPathResolutionPolicy;