@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,13 +1,8 @@
1
1
  import { type BigIntStats, type Stats } from "node:fs";
2
- import { type DenyMutationPolicy } from "./deny-mutations.js";
2
+ import type { RootMoveOptions } from "./root-options.js";
3
3
  import { type RootContext } from "./root-context.js";
4
- import { type MutationSymlinkPolicy } from "./root-symlink-policy.js";
5
4
  export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T, overwrite?: boolean): T;
6
- export declare function movePathNoReplaceNative(root: RootContext, params: {
7
- assertBeforeMutation?: () => void;
8
- denyMutations?: DenyMutationPolicy;
9
- mutationSymlinks?: MutationSymlinkPolicy;
10
- }, paths: {
5
+ export declare function movePathNoReplaceNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks">, paths: {
11
6
  sourcePath: string;
12
7
  sourceParentPath: string;
13
8
  targetPath: string;
@@ -4,7 +4,7 @@ import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard } from "./directory
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { assertMutationNotDenied } from "./deny-mutations.js";
6
6
  import { openNativeParentAdmission, openNativeRootAdmission, } from "./native-parent-admission.js";
7
- import { getNativeBinding } from "./native.js";
7
+ import { requireNativeBinding } from "./native.js";
8
8
  import { isNotFoundPathError } from "./path.js";
9
9
  import { assertRootIdentityCurrent, assertRootIdentityCurrentSync } from "./root-context.js";
10
10
  import { resolveRootPathSync } from "./root-path.js";
@@ -78,8 +78,8 @@ export async function movePathNoReplaceNative(root, params, paths) {
78
78
  throw error;
79
79
  // Advisory fast rejection only. renameNoReplace owns the collision decision.
80
80
  }
81
- const binding = getNativeBinding();
82
- if (!binding || typeof binding.renameNoReplace !== "function") {
81
+ const binding = requireNativeBinding();
82
+ if (typeof binding.renameNoReplace !== "function") {
83
83
  throw new FsSafeError("helper-unavailable", "native no-replace move is unavailable");
84
84
  }
85
85
  const rootAdmission = await openNativeRootAdmission(binding, {
@@ -1,7 +1,6 @@
1
1
  import { type RootPathObservationKind, type RootPathObservationReceipt } from "./root-path.js";
2
2
  import { type RootContext } from "./root-context.js";
3
3
  export type PinnedObservedPath = {
4
- rootReal: string;
5
4
  resolved: string;
6
5
  receipt?: RootPathObservationReceipt;
7
6
  };
@@ -63,7 +63,6 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
63
63
  // A receipt is emitted only for the straight traversal whose lexical and
64
64
  // canonical cursors stayed identical and inside the checked boundary.
65
65
  return {
66
- rootReal: resolved.rootCanonicalPath,
67
66
  resolved: resolved.canonicalPath,
68
67
  receipt: observed.receipt,
69
68
  };
@@ -71,7 +70,6 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
71
70
  const relativeResolved = path.relative(resolved.rootCanonicalPath, resolved.canonicalPath);
72
71
  if (relativeResolved === "" || relativeResolved === ".") {
73
72
  return {
74
- rootReal: resolved.rootCanonicalPath,
75
73
  resolved: resolved.canonicalPath,
76
74
  };
77
75
  }
@@ -87,7 +85,6 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
87
85
  if (!admittedCanonicalPath)
88
86
  throw outsideWorkspaceError();
89
87
  return {
90
- rootReal: resolved.rootCanonicalPath,
91
88
  resolved: admittedCanonicalPath.path,
92
89
  };
93
90
  }
@@ -7,14 +7,11 @@ export type RootPathObservationKind = "stat" | "directory";
7
7
  export type RootPathDirectoryObservationGuard = DirectoryObservationGuard | NativeDirectoryObservationGuard;
8
8
  export type RootPathTargetObservation = StatObservationReceipt | NativeDirectoryObservationGuard;
9
9
  /** An exact receipt owned by one stat/list operation. Never cache it. */
10
- export type RootPathObservationReceipt = {
11
- kind: RootPathObservationKind;
12
- rootGuard: DirectoryObservationGuard;
10
+ export type RootPathObservationReceipt = Pick<RootPathObservationRequest & {
13
11
  directoryGuard: RootPathDirectoryObservationGuard;
14
- directoryObserver?: NativeDirectoryObservationBackend;
15
12
  targetPath: string;
16
13
  target: RootPathTargetObservation;
17
- };
14
+ }, "kind" | "rootGuard" | "directoryGuard" | "directoryObserver" | "targetPath" | "target">;
18
15
  /** A failed initial target lookup must not discard its already admitted parent. */
19
16
  export type RootPathParentObservationReceipt = Omit<RootPathObservationReceipt, "kind" | "target"> & {
20
17
  kind: "stat-parent";
@@ -24,16 +21,12 @@ export type RootPathObservationRequest = {
24
21
  rootGuard: DirectoryObservationGuard;
25
22
  directoryObserver?: NativeDirectoryObservationBackend;
26
23
  };
27
- export type RootPathTraversalObservation = {
24
+ export type RootPathTraversalObservation = Omit<Partial<RootPathObservationReceipt> & {
28
25
  enabled: boolean;
29
26
  request: RootPathObservationRequest;
30
27
  targetIndex: number;
31
28
  directoryIndex: number;
32
- directoryGuard?: RootPathDirectoryObservationGuard;
33
- directoryObserver?: NativeDirectoryObservationBackend;
34
- targetPath?: string;
35
- target?: RootPathTargetObservation;
36
- };
29
+ }, "kind" | "rootGuard">;
37
30
  export type RootPathObservedTraversalEntry = StatObservationReceipt | {
38
31
  stat: fs.Stats | BigIntStats;
39
32
  identity?: undefined;
@@ -214,8 +214,8 @@ function finalizeLexicalResolution(context, kind) {
214
214
  rootPath: context.rootPath,
215
215
  rootCanonicalPath: context.rootCanonicalPath,
216
216
  relativePath: relativeInsideRoot(context.rootCanonicalPath, context.state.canonicalCursor),
217
- exists: kind.exists,
218
- kind: kind.kind,
217
+ exists: kind !== "missing",
218
+ kind,
219
219
  };
220
220
  }
221
221
  function applyResolvedSymlinkHop(context, linkCanonical) {
@@ -374,12 +374,9 @@ function traverseRootPath(params, { mode = "native", observation: observationOut
374
374
  const completeObservation = observation?.enabled === true && observation.directoryGuard !== undefined &&
375
375
  observation.targetPath === state.canonicalCursor && observation.target !== undefined;
376
376
  const kind = completeObservation
377
- ? {
378
- exists: true,
379
- kind: isNativeDirectoryObservationGuard(observation.target)
380
- ? "directory"
381
- : toResolvedKind(observation.target.stat),
382
- }
377
+ ? isNativeDirectoryObservationGuard(observation.target)
378
+ ? "directory"
379
+ : toResolvedKind(observation.target.stat)
383
380
  : getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
384
381
  if (completeObservation && observationOutput) {
385
382
  observationOutput.receipt = {
@@ -399,11 +396,11 @@ function getPathKindSync(absolutePath, preserveFinalSymlink) {
399
396
  const stat = preserveFinalSymlink
400
397
  ? fs.lstatSync(operationPath)
401
398
  : fs.statSync(operationPath);
402
- return { exists: true, kind: toResolvedKind(stat) };
399
+ return toResolvedKind(stat);
403
400
  }
404
401
  catch (error) {
405
402
  if (isNotFoundPathError(error)) {
406
- return { exists: false, kind: "missing" };
403
+ return "missing";
407
404
  }
408
405
  throw error;
409
406
  }
@@ -1,3 +1,4 @@
1
+ import { type ResolvePathWithinRootParams } from "./path-scope-lexical.js";
1
2
  import { type DirectoryResult } from "./root-directory.js";
2
3
  export { resolvePathWithinRoot } from "./root-paths-lexical.js";
3
4
  export { ensureDirectoryWithinRoot } from "./root-directory.js";
@@ -44,12 +45,7 @@ export type PathScope = {
44
45
  mode?: number;
45
46
  }): Promise<DirectoryResult>;
46
47
  };
47
- export declare function resolveWritablePathWithinRoot(params: {
48
- rootDir: string;
49
- requestedPath: string;
50
- scopeLabel: string;
51
- defaultFileName?: string;
52
- }): Promise<{
48
+ export declare function resolveWritablePathWithinRoot(params: ResolvePathWithinRootParams): Promise<{
53
49
  ok: true;
54
50
  path: string;
55
51
  } | {
@@ -1,9 +1,7 @@
1
1
  import type { BigIntStats } from "node:fs";
2
2
  type ExactDirectoryIdentity = Readonly<Pick<BigIntStats, "dev" | "ino">>;
3
- export type RemovalDirectoryAssertion = Readonly<{
3
+ export type RemovalDirectoryAssertion = ExactDirectoryIdentity & Readonly<{
4
4
  path: string;
5
- dev: bigint;
6
- ino: bigint;
7
5
  numericDev?: number;
8
6
  numericIno?: number;
9
7
  platform: NodeJS.Platform;
@@ -1,17 +1,21 @@
1
+ import type { DirectoryObservationGuard } from "./directory-guard.js";
2
+ import { type RootPathObservationReceipt } from "./root-path.js";
1
3
  import type { RootDirectoryListing, RootDirectoryListingOptions } from "./root-directory-list.js";
2
4
  import type { PathStat } from "./types.js";
3
- export type RootWalkSymlinkPolicy = "skip" | "follow-within-root";
5
+ export type RootWalkSymlinkPolicy = "skip" | "follow-within-root" | "include";
6
+ type LegacyRootWalkSymlinkPolicy = Exclude<RootWalkSymlinkPolicy, "include">;
4
7
  export type RootWalkLimitBehavior = "truncate" | "throw";
5
8
  export type RootWalkDirectoryErrorBehavior = "throw" | "skip-and-report";
6
9
  export type RootWalkEntryFilterResult = "include" | "skip" | "skip-subtree";
7
- export type RootWalkDataEntryKind = "file" | "directory" | "other";
8
- export type RootWalkEntryKind = RootWalkDataEntryKind | "directory-error" | "truncated";
9
- export type RootWalkDataEntry = {
10
+ export type RootWalkDataEntryKind<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = "file" | "directory" | "other" | ("include" extends Policy ? "symlink" : never);
11
+ export type RootWalkEntryKind<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = RootWalkDataEntryKind<Policy> | "directory-error" | "truncated";
12
+ type WalkEntryOfKind<Kind extends RootWalkDataEntryKind<RootWalkSymlinkPolicy>> = {
10
13
  relativePath: string;
11
- kind: RootWalkDataEntryKind;
14
+ kind: Kind;
12
15
  size: number;
13
16
  };
14
- export type RootWalkEntry = RootWalkDataEntry | {
17
+ export type RootWalkDataEntry<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = WalkEntryOfKind<RootWalkDataEntryKind> | ("include" extends Policy ? WalkEntryOfKind<"symlink"> : never);
18
+ export type RootWalkEntry<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = RootWalkDataEntry<Policy> | {
15
19
  relativePath: string;
16
20
  kind: "truncated";
17
21
  size: 0;
@@ -21,21 +25,24 @@ export type RootWalkEntry = RootWalkDataEntry | {
21
25
  size: 0;
22
26
  error: unknown;
23
27
  };
24
- export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
25
- export type RootWalkOptions = {
28
+ export type RootWalkEntryFilter<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = (entry: RootWalkDataEntry<Policy>) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
29
+ export type RootWalkOptions<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = {
26
30
  maxDepth?: number;
27
31
  maxEntries?: number;
28
32
  order?: "sorted" | "filesystem";
29
- symlinkPolicy: RootWalkSymlinkPolicy;
33
+ symlinkPolicy: Policy;
30
34
  signal?: AbortSignal;
31
35
  limitBehavior?: RootWalkLimitBehavior;
32
- entryFilter?: RootWalkEntryFilter;
36
+ entryFilter?: RootWalkEntryFilter<Policy>;
33
37
  onDirectoryError?: RootWalkDirectoryErrorBehavior;
34
38
  };
39
+ type AnyRootWalkOptions = RootWalkOptions | RootWalkOptions<"include"> | RootWalkOptions<RootWalkSymlinkPolicy>;
40
+ type AnyRootWalkEntry = RootWalkEntry<RootWalkSymlinkPolicy>;
35
41
  type RootWalkCapability = {
36
42
  rootReal: string;
43
+ observeRoot(): Promise<DirectoryObservationGuard | undefined>;
37
44
  stat(relativePath: string): Promise<PathStat>;
38
- list(relativePath: string, options: RootDirectoryListingOptions): Promise<RootDirectoryListing>;
45
+ list(relativePath: string, options: RootDirectoryListingOptions, receipt?: RootPathObservationReceipt): Promise<RootDirectoryListing>;
39
46
  };
40
- export declare function walkRoot(root: RootWalkCapability, relativePath: string, options: RootWalkOptions): AsyncGenerator<RootWalkEntry>;
47
+ export declare function walkRoot(root: RootWalkCapability, relativePath: string, options: AnyRootWalkOptions): AsyncGenerator<AnyRootWalkEntry>;
41
48
  export {};
@@ -1,7 +1,14 @@
1
1
  import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
- import { resolveRootPath, ROOT_PATH_ALIAS_POLICIES } from "./root-path.js";
3
+ import { expandRelativePathWithHome } from "./root-context.js";
4
+ import { resolveRootPath, resolveRootPathWithObservation, ROOT_PATH_ALIAS_POLICIES, RootPathObservationError, } from "./root-path.js";
4
5
  import { createSuppressedError } from "./suppressed-error.js";
6
+ function filterEntry(options, entry) {
7
+ if (entry.kind === "symlink") {
8
+ return options.symlinkPolicy === "include" ? options.entryFilter?.(entry) ?? "include" : "skip";
9
+ }
10
+ return options.entryFilter?.(entry) ?? "include";
11
+ }
5
12
  function validateBudget(name, value) {
6
13
  if (value === undefined)
7
14
  return Number.POSITIVE_INFINITY;
@@ -23,7 +30,7 @@ function limitEntry(relativePath) {
23
30
  return { relativePath, kind: "truncated", size: 0 };
24
31
  }
25
32
  export async function* walkRoot(root, relativePath, options) {
26
- if (!["skip", "follow-within-root"].includes(options.symlinkPolicy)) {
33
+ if (!["skip", "follow-within-root", "include"].includes(options.symlinkPolicy)) {
27
34
  throw new TypeError(`invalid root walk symlink policy: ${String(options.symlinkPolicy)}`);
28
35
  }
29
36
  if (options.order !== undefined && !["sorted", "filesystem"].includes(options.order)) {
@@ -61,23 +68,50 @@ export async function* walkRoot(root, relativePath, options) {
61
68
  throw error;
62
69
  return { relativePath: directory, kind: "directory-error", size: 0, error };
63
70
  };
64
- async function* visit(directory, depth) {
71
+ async function* visit(directory, depth, admittedChildPath) {
65
72
  options.signal?.throwIfAborted();
66
73
  let listing;
74
+ let canonicalDirectory;
67
75
  try {
68
- const skipChildSymlinks = depth > 0 && options.symlinkPolicy === "skip";
69
- const resolvedDirectory = await resolveRootPath({
70
- absolutePath: path.resolve(root.rootReal, directory),
76
+ const expandedDirectory = depth === 0 ? await expandRelativePathWithHome(directory) : directory;
77
+ const noFollowChildSymlinks = depth > 0 && options.symlinkPolicy !== "follow-within-root";
78
+ const includeChild = depth > 0 && options.symlinkPolicy === "include";
79
+ const resolution = {
80
+ absolutePath: admittedChildPath ?? path.resolve(root.rootReal, expandedDirectory),
71
81
  rootPath: root.rootReal,
72
82
  rootCanonicalPath: root.rootReal,
73
83
  boundaryLabel: "root walk",
74
- policy: skipChildSymlinks ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
75
- });
76
- if (skipChildSymlinks && resolvedDirectory.kind === "symlink")
77
- return;
84
+ policy: noFollowChildSymlinks ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
85
+ };
86
+ let receipt;
87
+ let resolvedDirectory;
88
+ if (includeChild) {
89
+ const rootGuard = await root.observeRoot();
90
+ if (!rootGuard) {
91
+ throw new FsSafeError("helper-unavailable", "root walk requires an exact directory observation");
92
+ }
93
+ const observed = await resolveRootPathWithObservation({
94
+ ...resolution,
95
+ rootIdentity: rootGuard.identity,
96
+ }, { kind: "directory", rootGuard });
97
+ resolvedDirectory = observed.resolved;
98
+ receipt = observed.receipt;
99
+ }
100
+ else {
101
+ resolvedDirectory = await resolveRootPath(resolution);
102
+ }
103
+ if (noFollowChildSymlinks && resolvedDirectory.kind === "symlink") {
104
+ if (options.symlinkPolicy === "skip")
105
+ return;
106
+ throw new FsSafeError("path-mismatch", `root walk directory became a symlink: ${directory}`);
107
+ }
78
108
  if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
79
109
  throw new FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
80
110
  }
111
+ if (includeChild && (!receipt || receipt.directoryGuard.realPath !== resolvedDirectory.canonicalPath)) {
112
+ throw new FsSafeError("path-mismatch", "root walk directory observation was not retained");
113
+ }
114
+ canonicalDirectory = resolvedDirectory.canonicalPath;
81
115
  if (visitedDirectories.has(resolvedDirectory.canonicalPath)) {
82
116
  return;
83
117
  }
@@ -87,15 +121,18 @@ export async function* walkRoot(root, relativePath, options) {
87
121
  .relative(root.rootReal, resolvedDirectory.canonicalPath)
88
122
  .split(path.sep)
89
123
  .join(path.posix.sep);
90
- listing = await root.list(listingDirectory, {
124
+ if (depth === 0 && expandedDirectory !== directory)
125
+ directory = listingDirectory;
126
+ // Resolved filesystem names are literal, not caller home-directory shorthand.
127
+ listing = await root.list(`./${listingDirectory}`, {
91
128
  order: options.order ?? "sorted",
92
129
  signal: options.signal,
93
130
  snapshot: maxEntries === Number.POSITIVE_INFINITY,
94
131
  admitEntry,
95
- });
132
+ }, receipt);
96
133
  }
97
134
  catch (error) {
98
- yield onDirectoryError(directory, error);
135
+ yield onDirectoryError(directory, error instanceof RootPathObservationError ? error.error : error);
99
136
  return;
100
137
  }
101
138
  // A thrown undefined still needs to be retained if closing also fails.
@@ -126,10 +163,9 @@ export async function* walkRoot(root, relativePath, options) {
126
163
  const entry = next.entry;
127
164
  let kind = entryKind(entry);
128
165
  let size = entry.size;
129
- if (kind === "symlink") {
130
- if (options.symlinkPolicy === "skip") {
131
- continue;
132
- }
166
+ if (kind === "symlink" && options.symlinkPolicy === "skip")
167
+ continue;
168
+ if (kind === "symlink" && options.symlinkPolicy === "follow-within-root") {
133
169
  const resolved = await resolveRootPath({
134
170
  absolutePath: path.resolve(root.rootReal, child),
135
171
  rootPath: root.rootReal,
@@ -139,12 +175,12 @@ export async function* walkRoot(root, relativePath, options) {
139
175
  if (!resolved.exists) {
140
176
  continue;
141
177
  }
142
- const target = await root.stat(path.relative(root.rootReal, resolved.canonicalPath));
178
+ const target = await root.stat(`./${path.relative(root.rootReal, resolved.canonicalPath)}`);
143
179
  kind = target.isDirectory ? "directory" : target.isFile ? "file" : "other";
144
180
  size = target.size;
145
181
  }
146
182
  const walkEntry = { relativePath: child, kind, size };
147
- let filterResult = options.entryFilter?.(walkEntry) ?? "include";
183
+ let filterResult = filterEntry(options, walkEntry);
148
184
  if (typeof filterResult !== "string") {
149
185
  filterResult = (await filterResult) ?? "include";
150
186
  options.signal?.throwIfAborted();
@@ -172,7 +208,7 @@ export async function* walkRoot(root, relativePath, options) {
172
208
  yield onLimit(child);
173
209
  return;
174
210
  }
175
- yield* visit(child, depth + 1);
211
+ yield* visit(child, depth + 1, options.symlinkPolicy === "include" ? path.join(canonicalDirectory, name) : undefined);
176
212
  if (truncated)
177
213
  return;
178
214
  }
@@ -14,7 +14,7 @@ import { mutationSymlinkResolution, } from "./root-symlink-policy.js";
14
14
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
15
15
  import { realpathSync } from "./realpath.js";
16
16
  import { getFsSafeTestHooks } from "./test-hooks.js";
17
- import { assertPreparedRootWriteParentCurrent, } from "./root-write-complete-parent.js";
17
+ import { assertPreparedRootWriteParentCurrent, writeSelectionChanged, } from "./root-write-complete-parent.js";
18
18
  export function createRootWriteSelectionForFd(selection, fd) {
19
19
  const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
20
20
  return Object.freeze({
@@ -22,9 +22,6 @@ export function createRootWriteSelectionForFd(selection, fd) {
22
22
  identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
23
23
  });
24
24
  }
25
- function writeSelectionChanged(cause) {
26
- return new FsSafeError("path-mismatch", "write target changed during operation", errorCauseOptions(cause));
27
- }
28
25
  function inspectRegularSelectionPath(pathname, expected, follow) {
29
26
  let observed;
30
27
  try {
@@ -204,7 +201,6 @@ export async function resolveGuardedWriteTargetInRoot(root, params) {
204
201
  const relativeParent = path.relative(resolvedPath.rootReal, path.dirname(resolvedPath.resolved));
205
202
  const prepared = await preparePinnedWriteMutationAdmission({
206
203
  rootReal: resolvedPath.rootReal,
207
- rootWithSep: resolvedPath.rootWithSep,
208
204
  rootIdentity: root.rootIdentity,
209
205
  resolvedTargetPath: resolvedPath.resolved,
210
206
  originalPath: params.relativePath,
@@ -291,7 +287,6 @@ export async function resolvePinnedWriteTargetInRoot(root, relativePath, request
291
287
  if (policy) {
292
288
  ({ relativeParentPath, mutationAdmission } = await preparePinnedWriteMutationAdmission({
293
289
  rootReal,
294
- rootWithSep,
295
290
  rootIdentity: root.rootIdentity,
296
291
  resolvedTargetPath: resolved,
297
292
  originalPath: relativePath,
@@ -1,5 +1,6 @@
1
1
  import type { BigIntStats } from "node:fs";
2
2
  import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
+ import { FsSafeError } from "./errors.js";
3
4
  import type { RootContext } from "./root-context.js";
4
5
  import type { GuardedRootWriteTarget } from "./root-write-admission.js";
5
6
  type PreparedWriteTargetObservation = Readonly<{
@@ -36,6 +37,7 @@ export type SharedRootWriteTarget = Readonly<{
36
37
  mutationAdmission: GuardedRootWriteTarget["mutationAdmission"];
37
38
  preparedParent?: PreparedRootWriteParent;
38
39
  }>;
40
+ export declare function writeSelectionChanged(cause?: unknown): FsSafeError;
39
41
  export declare function assertPreparedRootWriteParentCurrent(prepared: PreparedRootWriteParent, verifyTarget?: boolean): void;
40
42
  export declare function prepareSharedRootWriteTarget(root: RootContext, params: SharedRootWriteTargetParams): Promise<SharedRootWriteTarget>;
41
43
  export {};
@@ -12,7 +12,7 @@ import { errorCauseOptions } from "./root-errors.js";
12
12
  import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
13
13
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
14
14
  import { getFsSafeTestHooks } from "./test-hooks.js";
15
- function writeSelectionChanged(cause) {
15
+ export function writeSelectionChanged(cause) {
16
16
  return new FsSafeError("path-mismatch", "write target changed during operation", errorCauseOptions(cause));
17
17
  }
18
18
  function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
@@ -7,5 +7,6 @@ export declare function assertNoDriveRelativePathSegments(value: string, label:
7
7
  export declare function trimHyphenEdges(value: string): string;
8
8
  export declare function isSafePathSegment(segment: string, options?: SafePathSegmentOptions): boolean;
9
9
  export declare function assertSafePathSegment(segment: string, options?: SafePathSegmentOptions): string;
10
+ export declare function normalizeSafePathSegment(value: string): string;
10
11
  export declare function sanitizeSafePathSegment(value: string): string | undefined;
11
12
  export declare function assertSafePathPrefix(prefix: string, options?: SafePathSegmentOptions): string;
@@ -1,3 +1,4 @@
1
+ import { isWindowsReservedDeviceName } from "./device-path.js";
1
2
  import { FsSafeError } from "./errors.js";
2
3
  const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
3
4
  const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
@@ -34,6 +35,8 @@ export function isSafePathSegment(segment, options = {}) {
34
35
  !segment.includes("/") &&
35
36
  !segment.includes("\\") &&
36
37
  !segment.includes("\0") &&
38
+ // Segments are portable identifiers: CON.json and CON.<pid>.tmp name devices on Windows.
39
+ !isWindowsReservedDeviceName(segment) &&
37
40
  (options.allowDotPrefix === true || !segment.startsWith(".")) &&
38
41
  (options.allowDotPrefix === true
39
42
  ? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
@@ -47,13 +50,16 @@ export function assertSafePathSegment(segment, options = {}) {
47
50
  }
48
51
  return segment;
49
52
  }
50
- export function sanitizeSafePathSegment(value) {
53
+ export function normalizeSafePathSegment(value) {
51
54
  const sanitized = value
52
55
  .trim()
53
56
  .replace(/[\\/]+/g, "-")
54
57
  .replace(/\0/g, "")
55
58
  .replace(/[^A-Za-z0-9._-]+/g, "-");
56
- const trimmed = trimHyphenEdges(sanitized);
59
+ return trimHyphenEdges(sanitized);
60
+ }
61
+ export function sanitizeSafePathSegment(value) {
62
+ const trimmed = normalizeSafePathSegment(value);
57
63
  return isSafePathSegment(trimmed, { allowDotPrefix: true }) ? trimmed : undefined;
58
64
  }
59
65
  export function assertSafePathPrefix(prefix, options = {}) {
@@ -13,7 +13,11 @@ type SecretFileWriteParams = {
13
13
  dirMode?: number;
14
14
  durable?: boolean;
15
15
  };
16
- export declare function prepareSecretFileWrite(params: Omit<SecretFileWriteParams, "content">): Promise<{
16
+ type SecretFileCreateParams = Omit<SecretFileWriteParams, "durable"> & {
17
+ /** "file" requires file synchronization; directory synchronization remains best effort. */
18
+ durable?: boolean | "file";
19
+ };
20
+ export declare function prepareSecretFileWrite(params: Pick<SecretFileWriteParams, "rootDir" | "filePath" | "mode" | "dirMode">): Promise<{
17
21
  mode: number;
18
22
  rootGuard: AsyncDirectoryGuard<BigIntStats>;
19
23
  parentGuard: AsyncDirectoryGuard<BigIntStats>;
@@ -21,5 +25,5 @@ export declare function prepareSecretFileWrite(params: Omit<SecretFileWriteParam
21
25
  finalFilePath: string;
22
26
  }>;
23
27
  export declare function writeSecretFileAtomic(params: SecretFileWriteParams): Promise<void>;
24
- export declare function createSecretFileAtomic(params: SecretFileWriteParams): Promise<void>;
28
+ export declare function createSecretFileAtomic(params: SecretFileCreateParams): Promise<void>;
25
29
  export {};
@@ -5,8 +5,7 @@ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
5
5
  import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
6
6
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
7
7
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
8
- import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
9
- import { assertOwnedDirectory } from "./directory-mode-owner.js";
8
+ import { pinNodeDirectoryForMode, assertOwnedDirectory } from "./directory-mode-node.js";
10
9
  import { FsSafeError } from "./errors.js";
11
10
  import { openPinnedFileSync } from "./pinned-open.js";
12
11
  import { runPinnedWriteHelper } from "./pinned-write.js";
@@ -199,7 +198,11 @@ async function ensurePrivateDirectory(rootDir, targetDir, mode) {
199
198
  }
200
199
  return { rootGuard, parentGuard: targetGuard };
201
200
  }
202
- function snapshotSecretFileWriteParams(params, rootDir, filePath) {
201
+ function snapshotSecretFileWriteParams(params) {
202
+ const rootDir = params.rootDir;
203
+ const filePath = params.filePath;
204
+ assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
205
+ assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
203
206
  return {
204
207
  rootDir,
205
208
  filePath,
@@ -282,6 +285,7 @@ async function materializeSecretFileAtomic(params, createOnly) {
282
285
  mode,
283
286
  verifyPosixMode: true,
284
287
  sync: params.durable !== false,
288
+ strictFileSync: createOnly && params.durable === "file",
285
289
  overwrite: !createOnly,
286
290
  input: { kind: "buffer", data: typeof params.content === "string" ? params.content : Buffer.from(params.content) },
287
291
  rootIdentity: { dev: parentGuard.stat.dev, ino: parentGuard.stat.ino },
@@ -305,31 +309,23 @@ async function materializeSecretFileAtomic(params, createOnly) {
305
309
  });
306
310
  }
307
311
  export async function writeSecretFileAtomic(params) {
308
- const rootDir = params.rootDir;
309
- const filePath = params.filePath;
310
- assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
311
- assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
312
- const ownedParams = snapshotSecretFileWriteParams(params, rootDir, filePath);
313
- const canonicalPath = await secretFileWriteQueueKey(filePath);
312
+ const ownedParams = snapshotSecretFileWriteParams(params);
313
+ const canonicalPath = await secretFileWriteQueueKey(ownedParams.filePath);
314
314
  await serializePathWrite(canonicalPath, async () => {
315
315
  await materializeSecretFileAtomic(ownedParams, false);
316
316
  });
317
317
  }
318
318
  export async function createSecretFileAtomic(params) {
319
319
  try {
320
- const rootDir = params.rootDir;
321
- const filePath = params.filePath;
322
- assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
323
- assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
324
- const ownedParams = snapshotSecretFileWriteParams(params, rootDir, filePath);
325
- const canonicalPath = await secretFileWriteQueueKey(filePath);
320
+ const ownedParams = snapshotSecretFileWriteParams(params);
321
+ const canonicalPath = await secretFileWriteQueueKey(ownedParams.filePath);
326
322
  await serializePathWrite(canonicalPath, async () => {
327
323
  await materializeSecretFileAtomic(ownedParams, true);
328
324
  });
329
325
  }
330
326
  catch (error) {
331
327
  if ((error instanceof FsSafeError && error.code === "already-exists") ||
332
- error.code === "EEXIST") {
328
+ error?.code === "EEXIST") {
333
329
  throw new FsSafeError("secret-exists", "Private secret file already exists.", { cause: error });
334
330
  }
335
331
  throw error;
@@ -7,7 +7,7 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
7
7
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
8
8
  export const DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024;
9
9
  export function secretPathErrorCode(error) {
10
- const code = error.code;
10
+ const code = error?.code;
11
11
  return code === "ENOENT" || code === "ENOTDIR" ? "not-found" : "invalid-path";
12
12
  }
13
13
  export function secretReadError(code, action, label, resolvedPath, error) {
@@ -1,29 +1,20 @@
1
- import { FsSafeError } from "./errors.js";
2
- import { recordFileObservationFailure } from "./file-observation.js";
1
+ import { fileIdentityMismatchError } from "./strict-file-identity.js";
3
2
  import { getNativeBinding } from "./native.js";
4
3
  import { getFsSafeNativeConfig } from "./native-config.js";
5
4
  import { warnNativeFallback } from "./native-fallback-warning.js";
6
5
  import { inspectWindowsDescriptorCommand } from "./windows-security-command.js";
7
- import { validateSecureWindowsSecurityFacts } from "./windows-security-facts.js";
6
+ import { unverified as permissionUnverified, validateSecureWindowsSecurityFacts } from "./windows-security-facts.js";
8
7
  const IDENTITY_RE = /^([0-9a-f]{8}):([0-9a-f]{16})$/;
9
8
  const TRUSTED_OWNER_CLASSES = new Set(["current-user", "system", "administrators"]);
10
- function permissionUnverified(message, cause) {
11
- throw new FsSafeError("permission-unverified", message, cause === undefined ? {} : { cause });
12
- }
13
- function identityMismatch() {
14
- const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
15
- recordFileObservationFailure(error, "identity");
16
- throw error;
17
- }
18
9
  function isRecord(value) {
19
10
  return typeof value === "object" && value !== null;
20
11
  }
21
12
  function parseIdentity(value) {
22
13
  if (typeof value !== "string")
23
- identityMismatch();
14
+ throw fileIdentityMismatchError();
24
15
  const match = IDENTITY_RE.exec(value);
25
16
  if (!match)
26
- identityMismatch();
17
+ throw fileIdentityMismatchError();
27
18
  return {
28
19
  dev: BigInt(`0x${match[1]}`),
29
20
  ino: BigInt(`0x${match[2]}`),
@@ -34,7 +25,7 @@ function inspectDescriptorResult(params, result, mechanism) {
34
25
  permissionUnverified("Windows descriptor ACL facts were malformed");
35
26
  const observed = parseIdentity(result.identity);
36
27
  if (observed.dev !== params.identity.dev || observed.ino !== params.identity.ino) {
37
- identityMismatch();
28
+ throw fileIdentityMismatchError();
38
29
  }
39
30
  const facts = validateSecureWindowsSecurityFacts(result.security);
40
31
  return {