@openclaw/fs-safe 0.15.0 → 0.17.0

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 (311) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +36 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +2 -8
  5. package/dist/advanced.d.ts +3 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -0
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +22 -23
  10. package/dist/archive-kind.d.ts +0 -1
  11. package/dist/archive-kind.d.ts.map +1 -1
  12. package/dist/archive-kind.js +5 -17
  13. package/dist/archive-merge.d.ts +1 -0
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +4 -4
  16. package/dist/archive-native.d.ts +1 -0
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +1 -0
  19. package/dist/archive-options.d.ts +2 -0
  20. package/dist/archive-options.d.ts.map +1 -1
  21. package/dist/archive-parser.wasm +0 -0
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +6 -7
  24. package/dist/archive-tar-stream.d.ts +3 -0
  25. package/dist/archive-tar-stream.d.ts.map +1 -1
  26. package/dist/archive-tar-stream.js +56 -37
  27. package/dist/archive-tar-wasm.d.ts +16 -4
  28. package/dist/archive-tar-wasm.d.ts.map +1 -1
  29. package/dist/archive-tar-wasm.js +134 -34
  30. package/dist/archive-zip-count.d.ts.map +1 -1
  31. package/dist/archive-zip-count.js +21 -1
  32. package/dist/archive-zip-directory.d.ts.map +1 -1
  33. package/dist/archive-zip-directory.js +23 -1
  34. package/dist/archive-zip-loader.d.ts +2 -0
  35. package/dist/archive-zip-loader.d.ts.map +1 -1
  36. package/dist/archive-zip-loader.js +7 -0
  37. package/dist/archive-zip-names.d.ts.map +1 -1
  38. package/dist/archive-zip-names.js +7 -2
  39. package/dist/archive.d.ts.map +1 -1
  40. package/dist/archive.js +14 -9
  41. package/dist/byte-view.d.ts +3 -0
  42. package/dist/byte-view.d.ts.map +1 -0
  43. package/dist/byte-view.js +13 -0
  44. package/dist/clone-metadata.d.ts +1 -0
  45. package/dist/clone-metadata.d.ts.map +1 -1
  46. package/dist/clone-metadata.js +6 -2
  47. package/dist/create-directory.d.ts +20 -0
  48. package/dist/create-directory.d.ts.map +1 -0
  49. package/dist/create-directory.js +130 -0
  50. package/dist/create-file-async.d.ts +7 -0
  51. package/dist/create-file-async.d.ts.map +1 -0
  52. package/dist/create-file-async.js +121 -0
  53. package/dist/create-file.d.ts +8 -0
  54. package/dist/create-file.d.ts.map +1 -0
  55. package/dist/create-file.js +190 -0
  56. package/dist/create-owned-file.d.ts +8 -0
  57. package/dist/create-owned-file.d.ts.map +1 -0
  58. package/dist/create-owned-file.js +16 -0
  59. package/dist/create.d.ts +4 -0
  60. package/dist/create.d.ts.map +1 -0
  61. package/dist/create.js +2 -0
  62. package/dist/creation-darwin.d.ts +6 -0
  63. package/dist/creation-darwin.d.ts.map +1 -0
  64. package/dist/creation-darwin.js +70 -0
  65. package/dist/creation-file-state.d.ts +19 -0
  66. package/dist/creation-file-state.d.ts.map +1 -0
  67. package/dist/creation-file-state.js +118 -0
  68. package/dist/creation-path.d.ts +21 -0
  69. package/dist/creation-path.d.ts.map +1 -0
  70. package/dist/creation-path.js +71 -0
  71. package/dist/creation-permissions.d.ts +19 -0
  72. package/dist/creation-permissions.d.ts.map +1 -0
  73. package/dist/creation-permissions.js +125 -0
  74. package/dist/directory-durability.d.ts +7 -7
  75. package/dist/directory-durability.d.ts.map +1 -1
  76. package/dist/directory-durability.js +22 -80
  77. package/dist/directory-guard.d.ts +3 -0
  78. package/dist/directory-guard.d.ts.map +1 -1
  79. package/dist/directory-mode-node.d.ts +2 -0
  80. package/dist/directory-mode-node.d.ts.map +1 -1
  81. package/dist/directory-mode-node.js +8 -0
  82. package/dist/directory-receipt.d.ts +24 -0
  83. package/dist/directory-receipt.d.ts.map +1 -0
  84. package/dist/directory-receipt.js +123 -0
  85. package/dist/file-cleanup.d.ts +20 -0
  86. package/dist/file-cleanup.d.ts.map +1 -0
  87. package/dist/file-cleanup.js +81 -0
  88. package/dist/file-contents.d.ts +6 -0
  89. package/dist/file-contents.d.ts.map +1 -0
  90. package/dist/file-contents.js +40 -0
  91. package/dist/file-hash.d.ts.map +1 -1
  92. package/dist/file-hash.js +16 -4
  93. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
  94. package/dist/file-lock-sync-root-acquire.js +3 -0
  95. package/dist/file-lock-sync-root-held.d.ts +1 -2
  96. package/dist/file-lock-sync-root-held.d.ts.map +1 -1
  97. package/dist/file-lock-sync-root-held.js +7 -5
  98. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
  99. package/dist/file-lock-sync-stale-admission.js +3 -0
  100. package/dist/file-lock-sync.d.ts.map +1 -1
  101. package/dist/file-lock-sync.js +8 -11
  102. package/dist/file-observation.d.ts +1 -1
  103. package/dist/file-observation.d.ts.map +1 -1
  104. package/dist/file-store-boundary.d.ts +2 -6
  105. package/dist/file-store-boundary.d.ts.map +1 -1
  106. package/dist/file-store-boundary.js +3 -9
  107. package/dist/file-store-sync-write.d.ts.map +1 -1
  108. package/dist/file-store-sync-write.js +2 -5
  109. package/dist/file-store.js +3 -3
  110. package/dist/guarded-mkdir.d.ts +1 -0
  111. package/dist/guarded-mkdir.d.ts.map +1 -1
  112. package/dist/guarded-mkdir.js +27 -19
  113. package/dist/install-path.d.ts.map +1 -1
  114. package/dist/install-path.js +2 -5
  115. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  116. package/dist/json-durable-queue-ownership.js +2 -6
  117. package/dist/json-durable-queue-paths.d.ts.map +1 -1
  118. package/dist/json-durable-queue-paths.js +2 -24
  119. package/dist/json-durable-queue.d.ts.map +1 -1
  120. package/dist/json-durable-queue.js +10 -9
  121. package/dist/json.d.ts.map +1 -1
  122. package/dist/json.js +32 -75
  123. package/dist/local-roots.d.ts.map +1 -1
  124. package/dist/local-roots.js +19 -21
  125. package/dist/move-path-cleanup.d.ts +5 -19
  126. package/dist/move-path-cleanup.d.ts.map +1 -1
  127. package/dist/move-path-cleanup.js +57 -21
  128. package/dist/move-path.d.ts.map +1 -1
  129. package/dist/move-path.js +63 -40
  130. package/dist/native-binding.d.ts +11 -1
  131. package/dist/native-binding.d.ts.map +1 -1
  132. package/dist/native-fallback-warning.d.ts +4 -0
  133. package/dist/native-fallback-warning.d.ts.map +1 -0
  134. package/dist/native-fallback-warning.js +11 -0
  135. package/dist/native-operations.d.ts +0 -2
  136. package/dist/native-operations.d.ts.map +1 -1
  137. package/dist/native-operations.js +0 -24
  138. package/dist/native-parent-admission.d.ts +2 -0
  139. package/dist/native-parent-admission.d.ts.map +1 -1
  140. package/dist/native-parent-admission.js +3 -2
  141. package/dist/native-pinned-write-windows.d.ts +1 -1
  142. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  143. package/dist/native-pinned-write-windows.js +173 -28
  144. package/dist/native-pinned-write.d.ts.map +1 -1
  145. package/dist/native-pinned-write.js +19 -3
  146. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  147. package/dist/native-policy-parent-windows.js +15 -6
  148. package/dist/native-staged-file.d.ts +5 -3
  149. package/dist/native-staged-file.d.ts.map +1 -1
  150. package/dist/native-staged-file.js +90 -40
  151. package/dist/native.js +2 -2
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +11 -2
  154. package/dist/owner-dacl.d.ts.map +1 -1
  155. package/dist/owner-dacl.js +10 -4
  156. package/dist/path.d.ts.map +1 -1
  157. package/dist/path.js +2 -1
  158. package/dist/permissions.d.ts.map +1 -1
  159. package/dist/permissions.js +3 -17
  160. package/dist/pinned-write-input.d.ts +4 -0
  161. package/dist/pinned-write-input.d.ts.map +1 -0
  162. package/dist/pinned-write-input.js +35 -0
  163. package/dist/pinned-write-mode.d.ts +5 -0
  164. package/dist/pinned-write-mode.d.ts.map +1 -0
  165. package/dist/pinned-write-mode.js +31 -0
  166. package/dist/pinned-write-staged.d.ts +6 -0
  167. package/dist/pinned-write-staged.d.ts.map +1 -0
  168. package/dist/pinned-write-staged.js +186 -0
  169. package/dist/pinned-write-types.d.ts +3 -0
  170. package/dist/pinned-write-types.d.ts.map +1 -1
  171. package/dist/pinned-write.d.ts.map +1 -1
  172. package/dist/pinned-write.js +41 -147
  173. package/dist/private-directory.d.ts.map +1 -1
  174. package/dist/private-directory.js +18 -4
  175. package/dist/private-producer-handoff-sync.d.ts +14 -0
  176. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  177. package/dist/private-producer-handoff-sync.js +114 -0
  178. package/dist/private-producer-handoff.d.ts +22 -4
  179. package/dist/private-producer-handoff.d.ts.map +1 -1
  180. package/dist/private-producer-handoff.js +140 -77
  181. package/dist/publish-copy-stage.d.ts +2 -1
  182. package/dist/publish-copy-stage.d.ts.map +1 -1
  183. package/dist/publish-copy-stage.js +16 -7
  184. package/dist/publish-file.d.ts +2 -2
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +58 -98
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +35 -44
  189. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  190. package/dist/replace-file-copy-fallback.js +28 -26
  191. package/dist/replace-file-copy-source.d.ts.map +1 -1
  192. package/dist/replace-file-copy-source.js +13 -22
  193. package/dist/replace-file-descriptor.d.ts.map +1 -1
  194. package/dist/replace-file-descriptor.js +10 -16
  195. package/dist/replace-file-temp-owner.d.ts +0 -7
  196. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  197. package/dist/replace-file-temp-owner.js +9 -60
  198. package/dist/replace-file.d.ts.map +1 -1
  199. package/dist/replace-file.js +9 -13
  200. package/dist/root-create-input.d.ts +2 -1
  201. package/dist/root-create-input.d.ts.map +1 -1
  202. package/dist/root-create-input.js +13 -4
  203. package/dist/root-directory-creation.d.ts +3 -3
  204. package/dist/root-directory-creation.d.ts.map +1 -1
  205. package/dist/root-directory-creation.js +15 -3
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +20 -3
  208. package/dist/root-file-final-admission.d.ts +1 -1
  209. package/dist/root-file-final-admission.d.ts.map +1 -1
  210. package/dist/root-file-final-admission.js +5 -2
  211. package/dist/root-file.d.ts.map +1 -1
  212. package/dist/root-file.js +3 -2
  213. package/dist/root-impl.d.ts.map +1 -1
  214. package/dist/root-impl.js +78 -27
  215. package/dist/root-move-noreplace.d.ts +2 -0
  216. package/dist/root-move-noreplace.d.ts.map +1 -1
  217. package/dist/root-move-noreplace.js +22 -13
  218. package/dist/root-options.d.ts +12 -4
  219. package/dist/root-options.d.ts.map +1 -1
  220. package/dist/root-path-stat.d.ts.map +1 -1
  221. package/dist/root-path-stat.js +59 -7
  222. package/dist/root-read-admission.d.ts.map +1 -1
  223. package/dist/root-read-admission.js +7 -2
  224. package/dist/root-remove.d.ts.map +1 -1
  225. package/dist/root-remove.js +15 -1
  226. package/dist/root-write-publication.js +1 -1
  227. package/dist/secret-file.d.ts.map +1 -1
  228. package/dist/secret-file.js +1 -0
  229. package/dist/secure-file-windows.d.ts +6 -0
  230. package/dist/secure-file-windows.d.ts.map +1 -1
  231. package/dist/secure-file-windows.js +34 -117
  232. package/dist/secure-file.js +2 -2
  233. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  234. package/dist/sidecar-lock-acquire.js +4 -6
  235. package/dist/sidecar-lock-handle.d.ts +3 -0
  236. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  237. package/dist/sidecar-lock-handle.js +6 -0
  238. package/dist/sidecar-lock-reclaim.d.ts +1 -1
  239. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  240. package/dist/sidecar-lock-reclaim.js +11 -8
  241. package/dist/sidecar-lock-root.d.ts.map +1 -1
  242. package/dist/sidecar-lock-root.js +2 -1
  243. package/dist/sidecar-lock.d.ts.map +1 -1
  244. package/dist/sidecar-lock.js +3 -5
  245. package/dist/staged-directory.d.ts +2 -2
  246. package/dist/staged-directory.d.ts.map +1 -1
  247. package/dist/staged-directory.js +6 -6
  248. package/dist/staged-file-settlement.d.ts +17 -0
  249. package/dist/staged-file-settlement.d.ts.map +1 -0
  250. package/dist/staged-file-settlement.js +57 -0
  251. package/dist/strict-file-identity.d.ts +1 -1
  252. package/dist/strict-file-identity.d.ts.map +1 -1
  253. package/dist/strict-file-identity.js +9 -9
  254. package/dist/symlink-parents.d.ts.map +1 -1
  255. package/dist/symlink-parents.js +2 -27
  256. package/dist/temp-workspace-owner.js +4 -4
  257. package/dist/unicode-path.d.ts.map +1 -1
  258. package/dist/unicode-path.js +3 -0
  259. package/dist/walk.d.ts.map +1 -1
  260. package/dist/walk.js +4 -2
  261. package/dist/windows-owner.d.ts.map +1 -1
  262. package/dist/windows-owner.js +2 -1
  263. package/dist/windows-security-bridge.cs +336 -0
  264. package/dist/windows-security-bridge.ps1 +15 -0
  265. package/dist/windows-security-command.d.ts +26 -0
  266. package/dist/windows-security-command.d.ts.map +1 -0
  267. package/dist/windows-security-command.js +363 -0
  268. package/dist/windows-security-facts.d.ts +6 -0
  269. package/dist/windows-security-facts.d.ts.map +1 -0
  270. package/dist/windows-security-facts.js +108 -0
  271. package/dist/write-file-handle.d.ts +7 -0
  272. package/dist/write-file-handle.d.ts.map +1 -1
  273. package/dist/write-file-handle.js +23 -0
  274. package/dist/write-open-flags.d.ts.map +1 -1
  275. package/dist/write-open-flags.js +1 -8
  276. package/dist/write-queue.d.ts.map +1 -1
  277. package/dist/write-queue.js +1 -4
  278. package/docs/advanced.md +71 -2
  279. package/docs/archive.md +102 -39
  280. package/docs/atomic.md +29 -5
  281. package/docs/config.md +6 -2
  282. package/docs/contributing.md +48 -4
  283. package/docs/copy.md +2 -0
  284. package/docs/creation.md +132 -0
  285. package/docs/durability.md +59 -0
  286. package/docs/file-contents.md +68 -0
  287. package/docs/install.md +31 -7
  288. package/docs/json.md +5 -4
  289. package/docs/local-roots.md +2 -0
  290. package/docs/migrating-to-0.5.md +15 -6
  291. package/docs/migrating-to-0.6.md +9 -4
  292. package/docs/mutation-policy-proof.md +5 -3
  293. package/docs/native-helper.md +22 -9
  294. package/docs/native.md +47 -15
  295. package/docs/path.md +4 -4
  296. package/docs/permissions.md +37 -14
  297. package/docs/public-api.md +5 -0
  298. package/docs/quickstart.md +1 -1
  299. package/docs/reading.md +2 -2
  300. package/docs/regular-file.md +3 -0
  301. package/docs/root.md +43 -0
  302. package/docs/secret-file.md +11 -2
  303. package/docs/secure-file.md +9 -4
  304. package/docs/sidecar-lock.md +14 -5
  305. package/docs/staged-file.md +9 -3
  306. package/docs/store.md +3 -1
  307. package/docs/temp.md +4 -1
  308. package/docs/types.md +18 -2
  309. package/docs/walk.md +7 -0
  310. package/docs/writing.md +80 -7
  311. package/package.json +18 -15
@@ -3,7 +3,8 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { ensureAbsoluteDirectory } from "./absolute-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
- import { sameFileIdentity } from "./file-identity.js";
6
+ import { assertDirectoryReceiptCurrentSync, copyRetainedDirectoryReceipt, createDirectoryReceiptSync, directoryReceiptAuthority, directoryReceiptIdentity, ownDirectoryReceipt, } from "./directory-receipt.js";
7
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
7
8
  import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
8
9
  import { realpathSync } from "./realpath.js";
9
10
  function directoryOpenFlags() {
@@ -38,81 +39,23 @@ function unsupportedOutcome(error) {
38
39
  const code = error.code;
39
40
  return code ? { status: "unsupported", code } : { status: "unsupported" };
40
41
  }
41
- function assertDirectory(identity, pathname, label) {
42
- if (identity.isSymbolicLink() || !identity.isDirectory()) {
43
- throw new FsSafeError("not-file", `${label} must be a real directory: ${pathname}`);
44
- }
45
- }
46
- function ownDirectoryReceipt(receipt) {
47
- const pathname = receipt.path;
48
- const realPath = receipt.realPath;
49
- const identity = receipt.identity;
50
- return { path: pathname, realPath, identity };
51
- }
52
- function assertDirectoryReceiptPaths(receipt, label) {
53
- assertNoWindowsPathAlias(receipt.path, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
54
- assertNoWindowsPathAlias(receipt.realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
55
- }
56
42
  async function createDirectoryReceipt(directoryPath, label) {
57
- assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
58
- const resolvedPath = resolvePathPreservingWindowsRoot(directoryPath);
59
- assertNoWindowsPathAlias(resolvedPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
60
- const operationPath = pathForWindowsFilesystem(resolvedPath);
61
- const identity = fsSync.lstatSync(operationPath);
62
- assertDirectory(identity, resolvedPath, label);
63
- const realPath = realpathSync.native(operationPath);
64
- assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
65
- return {
66
- path: resolvedPath,
67
- realPath,
68
- identity,
69
- };
70
- }
71
- function createDirectoryReceiptSync(directoryPath, label) {
72
- assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
73
- const resolvedPath = resolvePathPreservingWindowsRoot(directoryPath);
74
- assertNoWindowsPathAlias(resolvedPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
75
- const operationPath = pathForWindowsFilesystem(resolvedPath);
76
- const identity = fsSync.lstatSync(operationPath);
77
- assertDirectory(identity, resolvedPath, label);
78
- const realPath = realpathSync(operationPath);
79
- assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
80
- return {
81
- path: resolvedPath,
82
- realPath,
83
- identity,
84
- };
43
+ return createDirectoryReceiptSync(directoryPath, label, realpathSync.native);
85
44
  }
86
45
  async function assertDirectoryReceiptCurrent(receipt, label) {
87
- assertDirectoryReceiptPaths(receipt, label);
88
- const operationPath = pathForWindowsFilesystem(receipt.path);
89
- const currentIdentity = fsSync.lstatSync(operationPath);
90
- assertDirectory(currentIdentity, receipt.path, label);
91
- const realPath = realpathSync.native(operationPath);
92
- assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
93
- if (!sameFileIdentity(receipt.identity, currentIdentity) ||
94
- realPath !== receipt.realPath) {
95
- throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
96
- }
46
+ assertDirectoryReceiptCurrentSync(receipt, label, realpathSync.native);
97
47
  }
98
- function assertDirectoryReceiptCurrentSync(receipt, label) {
99
- assertDirectoryReceiptPaths(receipt, label);
100
- const operationPath = pathForWindowsFilesystem(receipt.path);
101
- const currentIdentity = fsSync.lstatSync(operationPath);
102
- assertDirectory(currentIdentity, receipt.path, label);
103
- const realPath = realpathSync(operationPath);
104
- assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
105
- if (!sameFileIdentity(receipt.identity, currentIdentity) ||
106
- realPath !== receipt.realPath) {
107
- throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
108
- }
48
+ function assertOpenDirectoryIdentity(descriptor, receipt, label) {
49
+ inspectFileIdentitySync(() => {
50
+ const opened = fsSync.fstatSync(descriptor, { bigint: true });
51
+ if (!opened.isDirectory()) {
52
+ throw new FsSafeError("not-file", `${label} must be a real directory: ${receipt.path}`);
53
+ }
54
+ return opened;
55
+ }, directoryReceiptAuthority(receipt).identity);
109
56
  }
110
57
  async function assertOpenDirectoryCurrent(handle, receipt, label) {
111
- const openedIdentity = fsSync.fstatSync(handle.fd);
112
- assertDirectory(openedIdentity, receipt.path, label);
113
- if (!sameFileIdentity(receipt.identity, openedIdentity)) {
114
- throw new FsSafeError("path-mismatch", `${label} handle changed during directory sync: ${receipt.path}`);
115
- }
58
+ assertOpenDirectoryIdentity(handle.fd, receipt, label);
116
59
  await assertDirectoryReceiptCurrent(receipt, label);
117
60
  }
118
61
  class PinnedDirectoryImpl {
@@ -124,7 +67,7 @@ class PinnedDirectoryImpl {
124
67
  constructor(handle, receipt, label) {
125
68
  this.#handle = handle;
126
69
  this.#authority = receipt;
127
- this.receipt = ownDirectoryReceipt(receipt);
70
+ this.receipt = copyRetainedDirectoryReceipt(receipt);
128
71
  this.#label = label;
129
72
  }
130
73
  async assertCurrent() {
@@ -213,11 +156,7 @@ export function syncDirectorySync(directory, options = {}) {
213
156
  return unsupportedOutcome(error);
214
157
  }
215
158
  try {
216
- const openedIdentity = fsSync.fstatSync(descriptor);
217
- assertDirectory(openedIdentity, receipt.path, label);
218
- if (!sameFileIdentity(receipt.identity, openedIdentity)) {
219
- throw new FsSafeError("path-mismatch", `${label} handle changed during directory sync: ${receipt.path}`);
220
- }
159
+ assertOpenDirectoryIdentity(descriptor, receipt, label);
221
160
  assertDirectoryReceiptCurrentSync(receipt, label);
222
161
  try {
223
162
  fsSync.fsyncSync(descriptor);
@@ -273,13 +212,16 @@ export async function ensureDurableDirectory(options) {
273
212
  assertNoWindowsPathAlias(requestedDirectoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
274
213
  const directoryPath = resolvePathPreservingWindowsRoot(requestedDirectoryPath);
275
214
  assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
215
+ const expectedInput = options.expectedExistingIdentity;
216
+ const expectedIdentity = expectedInput && directoryReceiptIdentity(expectedInput);
276
217
  const ancestorReceipt = await findExistingAncestorReceipt(directoryPath, label);
277
218
  const targetExists = ancestorReceipt.path === directoryPath;
278
- const expectedExistingIdentity = options.expectedExistingIdentity;
279
- if (expectedExistingIdentity &&
280
- (!targetExists || !sameFileIdentity(expectedExistingIdentity, ancestorReceipt.identity))) {
219
+ if (expectedIdentity && !targetExists) {
281
220
  throw new FsSafeError("path-mismatch", `${label} changed before durable directory pinning: ${directoryPath}`);
282
221
  }
222
+ if (expectedIdentity) {
223
+ inspectFileIdentitySync(() => directoryReceiptAuthority(ancestorReceipt).identity, expectedIdentity);
224
+ }
283
225
  const ancestor = await pinDirectory(ancestorReceipt, { label });
284
226
  const pinnedDirectories = [ancestor];
285
227
  try {
@@ -336,7 +278,7 @@ export async function ensureDurableDirectory(options) {
336
278
  }
337
279
  await ancestor.assertCurrent();
338
280
  await assertDirectoryReceiptCurrent(finalReceipt, label);
339
- return { ...finalReceipt, parentSync };
281
+ return Object.assign(copyRetainedDirectoryReceipt(finalReceipt), { parentSync });
340
282
  }
341
283
  finally {
342
284
  await Promise.all(pinnedDirectories.toReversed().map(async (directory) => directory.close()));
@@ -34,6 +34,9 @@ export declare function assertAsyncDirectoryGuard(guard: AnyAsyncDirectoryGuard)
34
34
  export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuard;
35
35
  export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard | AnyAsyncDirectoryGuard): void;
36
36
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
37
+ export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
38
+ bigint: true;
39
+ }): Promise<AsyncDirectoryGuard<BigIntStats>>;
37
40
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
38
41
  bigint: boolean;
39
42
  }): Promise<AnyAsyncDirectoryGuard>;
@@ -1 +1 @@
1
- {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { type BigIntStats } from "node:fs";
2
2
  import { type DirectoryModeOwner } from "./directory-mode-owner.js";
3
+ /** Darwin descriptor inspection avoids requesting directory-content reads. */
4
+ export declare function nodeDarwinDirectoryMetadataFlags(): number;
3
5
  export declare function nodeDirectorySearchOnlyFlags(): {
4
6
  flags: number;
5
7
  proc: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,wBAAgB,4BAA4B,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAO3F;AAED,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE;IAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAsFlE"}
1
+ {"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,8EAA8E;AAC9E,wBAAgB,gCAAgC,IAAI,MAAM,CAMzD;AAED,wBAAgB,4BAA4B,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAO3F;AAED,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE;IAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAsFlE"}
@@ -4,6 +4,14 @@ 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
6
  import { assertOwnedDirectory, ownDirectoryMode } from "./directory-mode-owner.js";
7
+ /** Darwin descriptor inspection avoids requesting directory-content reads. */
8
+ export function nodeDarwinDirectoryMetadataFlags() {
9
+ if (process.platform !== "darwin" || (process.arch !== "x64" && process.arch !== "arm64")) {
10
+ throw new FsSafeError("helper-unavailable", "Darwin directory metadata descriptors are unavailable");
11
+ }
12
+ // Darwin SDK O_EVTONLY; Node omits this flag from its exported constants.
13
+ return 0x8000 | constants.O_DIRECTORY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
14
+ }
7
15
  export function nodeDirectorySearchOnlyFlags() {
8
16
  if (process.arch !== "x64" && process.arch !== "arm64")
9
17
  return undefined;
@@ -0,0 +1,24 @@
1
+ import { type BigIntStats, type Stats } from "node:fs";
2
+ import type { DirectoryReceipt } from "./directory-durability.js";
3
+ import type { FileIdentityStat } from "./file-identity.js";
4
+ type ExactIdentity = Readonly<Pick<BigIntStats, "dev" | "ino">>;
5
+ declare const numericFields: readonly ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"];
6
+ declare const timeFields: readonly ["atime", "mtime", "ctime", "birthtime"];
7
+ type DirectoryMetadata = Pick<Stats, typeof numericFields[number] | `${typeof timeFields[number]}Ms`>;
8
+ type DirectoryProvenance = Readonly<{
9
+ identity: ExactIdentity;
10
+ metadata: Readonly<DirectoryMetadata>;
11
+ }>;
12
+ type DirectoryAuthority = DirectoryProvenance & Readonly<{
13
+ path: string;
14
+ realPath: string;
15
+ }>;
16
+ export declare function directoryReceiptIdentity(identity: FileIdentityStat): ExactIdentity;
17
+ export declare function directoryReceiptAuthority(receipt: DirectoryReceipt<Stats | BigIntStats>): DirectoryAuthority;
18
+ export declare function ownDirectoryReceipt(receipt: DirectoryReceipt<Stats | BigIntStats>): DirectoryReceipt;
19
+ export declare function copyRetainedDirectoryReceipt(receipt: DirectoryReceipt): DirectoryReceipt;
20
+ export declare function createDirectoryReceiptFromIdentity(pathname: string, realPath: string, exactStat: BigIntStats): DirectoryReceipt;
21
+ export declare function createDirectoryReceiptSync(directoryPath: string, label: string, canonicalize?: (pathname: string) => string): DirectoryReceipt;
22
+ export declare function assertDirectoryReceiptCurrentSync(receipt: DirectoryReceipt, label: string, canonicalize?: (pathname: string) => string): void;
23
+ export {};
24
+ //# sourceMappingURL=directory-receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-receipt.d.ts","sourceRoot":"","sources":["../src/directory-receipt.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAGlE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAQ3D,KAAK,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;AAChE,QAAA,MAAM,aAAa,YAAI,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAClH,QAAA,MAAM,UAAU,YAAI,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAU,CAAC;AACrE,KAAK,iBAAiB,GAAG,IAAI,CAAC,KAAK,EAAE,OAAO,aAAa,CAAC,MAAM,CAAC,GAAG,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACtG,KAAK,mBAAmB,GAAG,QAAQ,CAAC;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;CACvC,CAAC,CAAC;AACH,KAAK,kBAAkB,GAAG,mBAAmB,GAAG,QAAQ,CAAC;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAOH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,aAAa,CAuBlF;AAsCD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,kBAAkB,CAE5G;AAQD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,gBAAgB,CAEpG;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAGxF;AAWD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,WAAW,GACrB,gBAAgB,CASlB;AAED,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,YAAY,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAqB,GACxD,gBAAgB,CASlB;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,MAAM,EACb,YAAY,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAqB,GACxD,IAAI,CAUN"}
@@ -0,0 +1,123 @@
1
+ import fs, {} from "node:fs";
2
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { realpathSync } from "./realpath.js";
5
+ import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
6
+ const numericFields = ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"];
7
+ const timeFields = ["atime", "mtime", "ctime", "birthtime"];
8
+ // Live pins retain private authority. New admissions snapshot the supplied
9
+ // fields and accept exact provenance only while its public identity is intact.
10
+ const authorities = new WeakMap();
11
+ const identities = new WeakMap();
12
+ export function directoryReceiptIdentity(identity) {
13
+ if (!identity) {
14
+ throw new FsSafeError("path-mismatch", "directory receipt identity is missing");
15
+ }
16
+ const known = identities.get(identity);
17
+ const dev = identity.dev;
18
+ const ino = identity.ino;
19
+ if (known) {
20
+ const matches = (value, expected) => typeof value === "bigint" ? value === expected : value === Number(expected);
21
+ if (!matches(dev, known.identity.dev) || !matches(ino, known.identity.ino)) {
22
+ throw new FsSafeError("path-mismatch", "directory receipt identity changed");
23
+ }
24
+ return known.identity;
25
+ }
26
+ const exact = (value) => {
27
+ if ((typeof value !== "bigint" && !Number.isSafeInteger(value)) ||
28
+ (process.platform === "win32" && (value === 0 || value === 0n))) {
29
+ throw new FsSafeError("path-mismatch", "directory receipt identity could not be verified");
30
+ }
31
+ return BigInt(value);
32
+ };
33
+ return Object.freeze({ dev: exact(dev), ino: exact(ino) });
34
+ }
35
+ function snapshotDirectoryReceipt(receipt) {
36
+ const pathname = receipt?.path;
37
+ const realPath = receipt?.realPath;
38
+ const stat = receipt?.identity;
39
+ if (typeof pathname !== "string" || typeof realPath !== "string" || stat == null) {
40
+ throw new FsSafeError("path-mismatch", "directory receipt is incomplete");
41
+ }
42
+ assertNoWindowsPathAlias(pathname, "filesystem");
43
+ assertNoWindowsPathAlias(realPath, "filesystem");
44
+ const identity = directoryReceiptIdentity(stat);
45
+ const known = authorities.get(receipt);
46
+ if (known && (pathname !== known.path || realPath !== known.realPath ||
47
+ identity.dev !== known.identity.dev || identity.ino !== known.identity.ino)) {
48
+ throw new FsSafeError("path-mismatch", "directory receipt changed before admission");
49
+ }
50
+ const metadata = known?.metadata ?? identities.get(stat)?.metadata ?? snapshotDirectoryMetadata(stat, identity);
51
+ return Object.freeze({ path: pathname, realPath, identity, metadata });
52
+ }
53
+ function snapshotDirectoryMetadata(stat, identity) {
54
+ const metadata = {};
55
+ for (const field of numericFields) {
56
+ metadata[field] = Number(field === "dev" || field === "ino" ? identity[field] : stat[field]);
57
+ }
58
+ for (const field of timeFields) {
59
+ if ("atimeNs" in stat) {
60
+ const nanoseconds = stat[`${field}Ns`];
61
+ const remainder = ((nanoseconds % 1000000000n) + 1000000000n) % 1000000000n;
62
+ metadata[`${field}Ms`] = Number((nanoseconds - remainder) / 1000000000n) * 1_000 + Number(remainder) / 1_000_000;
63
+ }
64
+ else {
65
+ metadata[`${field}Ms`] = stat[`${field}Ms`];
66
+ }
67
+ }
68
+ return Object.freeze(metadata);
69
+ }
70
+ export function directoryReceiptAuthority(receipt) {
71
+ return authorities.get(receipt) ?? snapshotDirectoryReceipt(receipt);
72
+ }
73
+ function rememberReceipt(receipt, authority) {
74
+ authorities.set(receipt, authority);
75
+ identities.set(receipt.identity, authority);
76
+ return receipt;
77
+ }
78
+ export function ownDirectoryReceipt(receipt) {
79
+ return receiptFromAuthority(snapshotDirectoryReceipt(receipt));
80
+ }
81
+ export function copyRetainedDirectoryReceipt(receipt) {
82
+ const authority = directoryReceiptAuthority(receipt);
83
+ return receiptFromAuthority(authority);
84
+ }
85
+ function receiptFromAuthority(authority) {
86
+ return rememberReceipt({
87
+ path: authority.path,
88
+ realPath: authority.realPath,
89
+ identity: Object.assign(Object.create(fs.Stats.prototype), authority.metadata),
90
+ }, authority);
91
+ }
92
+ // The caller already admitted the exact observation and its paths.
93
+ export function createDirectoryReceiptFromIdentity(pathname, realPath, exactStat) {
94
+ const identity = Object.freeze({ dev: exactStat.dev, ino: exactStat.ino });
95
+ return receiptFromAuthority(Object.freeze({
96
+ path: pathname,
97
+ realPath,
98
+ identity,
99
+ // Project metadata from the admitted observation, never a fresh pathname stat.
100
+ metadata: snapshotDirectoryMetadata(exactStat, identity),
101
+ }));
102
+ }
103
+ export function createDirectoryReceiptSync(directoryPath, label, canonicalize = realpathSync) {
104
+ assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
105
+ const pathname = resolvePathPreservingWindowsRoot(directoryPath);
106
+ assertNoWindowsPathAlias(pathname, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
107
+ const exact = inspectDirectoryIdentitySync(pathname);
108
+ const operationPath = pathForWindowsFilesystem(pathname);
109
+ const realPath = canonicalize(operationPath);
110
+ assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
111
+ return createDirectoryReceiptFromIdentity(pathname, realPath, exact);
112
+ }
113
+ export function assertDirectoryReceiptCurrentSync(receipt, label, canonicalize = realpathSync) {
114
+ const authority = directoryReceiptAuthority(receipt);
115
+ assertNoWindowsPathAlias(authority.path, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
116
+ assertNoWindowsPathAlias(authority.realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
117
+ inspectDirectoryIdentitySync(authority.path, authority.identity);
118
+ const realPath = canonicalize(pathForWindowsFilesystem(authority.path));
119
+ assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
120
+ if (realPath !== authority.realPath) {
121
+ throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${authority.path}`);
122
+ }
123
+ }
@@ -0,0 +1,20 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import fs, { type FileHandle } from "node:fs/promises";
3
+ import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
4
+ export declare function readErrorCode(error: unknown): unknown;
5
+ export declare function hasErrorCode(error: unknown, expected: string): boolean;
6
+ type OwnedPathCleanupStatus = "removed" | "name-absent" | "preserved";
7
+ export declare function removeOwnedPath(params: {
8
+ fsModule: Pick<typeof fs, "lstat" | "unlink">;
9
+ pathname: string;
10
+ identity?: BigIntStats;
11
+ }): Promise<OwnedPathCleanupStatus>;
12
+ export declare function cleanupPinnedFilePath(params: {
13
+ pathname: string;
14
+ handle: Pick<FileHandle, "fd">;
15
+ identity?: BigIntStats;
16
+ parentGuard: AnyAsyncDirectoryGuard;
17
+ throwOnCleanupError?: boolean;
18
+ }): Promise<OwnedPathCleanupStatus>;
19
+ export {};
20
+ //# sourceMappingURL=file-cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-cleanup.d.ts","sourceRoot":"","sources":["../src/file-cleanup.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAA6B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAI9F,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASrD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,KAAK,sBAAsB,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEtE,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAoBlC;AAGD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,WAAW,EAAE,sBAAsB,CAAC;IACpC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAgClC"}
@@ -0,0 +1,81 @@
1
+ import syncFs, {} from "node:fs";
2
+ import fs, {} from "node:fs/promises";
3
+ import { assertAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
6
+ export function readErrorCode(error) {
7
+ if ((typeof error !== "object" || error === null) && typeof error !== "function") {
8
+ return undefined;
9
+ }
10
+ try {
11
+ return Reflect.get(error, "code");
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ }
17
+ export function hasErrorCode(error, expected) {
18
+ return readErrorCode(error) === expected;
19
+ }
20
+ export async function removeOwnedPath(params) {
21
+ if (!params.identity)
22
+ return "preserved";
23
+ try {
24
+ const current = params.fsModule === fs
25
+ ? syncFs.lstatSync(params.pathname, { bigint: true })
26
+ : await params.fsModule.lstat(params.pathname, { bigint: true });
27
+ if (current.isSymbolicLink() ||
28
+ !current.isFile() ||
29
+ current.nlink !== 1n ||
30
+ !sameFileIdentityForCleanup(current, params.identity)) {
31
+ return "preserved";
32
+ }
33
+ await params.fsModule.unlink(params.pathname);
34
+ return "removed";
35
+ }
36
+ catch (error) {
37
+ if (hasErrorCode(error, "ENOENT"))
38
+ return "name-absent";
39
+ throw error;
40
+ }
41
+ }
42
+ // Borrowed handle: the caller retains it until guarded cleanup finishes.
43
+ export async function cleanupPinnedFilePath(params) {
44
+ if (!params.identity)
45
+ return "preserved";
46
+ try {
47
+ const guard = params.parentGuard;
48
+ if ([guard.stat.dev, guard.stat.ino].some((value) => typeof value === "number" && !Number.isSafeInteger(value)))
49
+ return "preserved";
50
+ await assertAsyncDirectoryGuard(guard);
51
+ const parent = syncFs.lstatSync(guard.dir, { bigint: true });
52
+ if (parent.isSymbolicLink() || !parent.isDirectory() ||
53
+ !sameFileIdentityForCleanup(parent, guard.stat))
54
+ return "preserved";
55
+ const opened = syncFs.fstatSync(params.handle.fd, { bigint: true });
56
+ if (!opened.isFile() || opened.nlink !== 1n ||
57
+ !sameFileIdentityForCleanup(opened, params.identity))
58
+ return "preserved";
59
+ }
60
+ catch (error) {
61
+ // Unverifiable authority must preserve the path and the original write failure.
62
+ if (params.throwOnCleanupError &&
63
+ !(error instanceof FsSafeError && error.category === "policy") &&
64
+ !hasErrorCode(error, "ENOENT") && !hasErrorCode(error, "ENOTDIR") &&
65
+ !hasErrorCode(error, "ELOOP"))
66
+ throw error;
67
+ return "preserved";
68
+ }
69
+ try {
70
+ return await removeOwnedPath({
71
+ fsModule: fs,
72
+ pathname: params.pathname,
73
+ identity: params.identity,
74
+ });
75
+ }
76
+ catch (error) {
77
+ if (params.throwOnCleanupError)
78
+ throw error;
79
+ return "preserved";
80
+ }
81
+ }
@@ -0,0 +1,6 @@
1
+ export type SameFileContentsOptions = {
2
+ maxBytes?: number;
3
+ };
4
+ /** Compares borrowed regular files from offset zero without moving or closing either descriptor. */
5
+ export declare function sameFileContentsSync(leftFd: number, rightFd: number, options?: SameFileContentsOptions): boolean;
6
+ //# sourceMappingURL=file-contents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-contents.d.ts","sourceRoot":"","sources":["../src/file-contents.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,oGAAoG;AACpG,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAgCT"}
@@ -0,0 +1,40 @@
1
+ import fs from "node:fs";
2
+ import { normalizeMaxBytes } from "./byte-budget.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { readFileWindowFullySync } from "./positional-read.js";
5
+ /** Compares borrowed regular files from offset zero without moving or closing either descriptor. */
6
+ export function sameFileContentsSync(leftFd, rightFd, options = {}) {
7
+ const maxBytes = normalizeMaxBytes(options.maxBytes) ?? Infinity;
8
+ const limit = Math.min(maxBytes, Number.MAX_SAFE_INTEGER);
9
+ const tooLarge = () => new FsSafeError("too-large", `file comparison exceeds ${limit} bytes`);
10
+ for (const fd of [leftFd, rightFd]) {
11
+ const stat = fs.fstatSync(fd, { bigint: true });
12
+ if (!stat.isFile()) {
13
+ throw new FsSafeError("not-file", "file comparison requires regular-file descriptors");
14
+ }
15
+ if (stat.size > BigInt(limit))
16
+ throw tooLarge();
17
+ }
18
+ const left = Buffer.allocUnsafe(Math.min(1024 * 1024, Math.max(1, limit)));
19
+ const right = Buffer.allocUnsafe(left.length);
20
+ let position = 0;
21
+ while (true) {
22
+ const length = Math.min(left.length, limit - position);
23
+ if (length === 0) {
24
+ // Probe EOF at the limit without forming an unsafe exclusive window end.
25
+ const leftExtra = fs.readSync(leftFd, left, 0, 1, position);
26
+ const rightExtra = fs.readSync(rightFd, right, 0, 1, position);
27
+ if (leftExtra !== 0 || rightExtra !== 0)
28
+ throw tooLarge();
29
+ return true;
30
+ }
31
+ const leftRead = readFileWindowFullySync(leftFd, left.subarray(0, length), position);
32
+ const rightRead = readFileWindowFullySync(rightFd, right.subarray(0, length), position);
33
+ if (leftRead !== rightRead || !left.subarray(0, leftRead).equals(right.subarray(0, rightRead))) {
34
+ return false;
35
+ }
36
+ if (leftRead < length)
37
+ return true;
38
+ position += leftRead;
39
+ }
40
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAKnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAwD3B;AA0DD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAwDD,8FAA8F;AAC9F,wBAAgB,cAAc,CAC5B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAMlB"}
1
+ {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAKnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAwD3B;AA8DD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AA2DD,8FAA8F;AAC9F,wBAAgB,cAAc,CAC5B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAMlB"}
package/dist/file-hash.js CHANGED
@@ -99,14 +99,20 @@ async function hashPath(filePath, options) {
99
99
  catch (error) {
100
100
  rethrowHashOpenError(error);
101
101
  }
102
+ let completed = false;
102
103
  try {
103
104
  options.signal?.throwIfAborted();
104
105
  const opened = await inspectFileIdentity(() => hashDescriptorIdentity(handle.fd), before);
105
106
  await inspectFileIdentity(() => hashPathIdentity(filePath, true), opened);
106
- return await hashFileHandle(handle, getNativeBinding(), options);
107
+ const result = await hashFileHandle(handle, getNativeBinding(), options);
108
+ completed = true;
109
+ return result;
107
110
  }
108
111
  finally {
109
- await handle.close().catch(() => undefined);
112
+ if (completed)
113
+ await handle.close();
114
+ else
115
+ await handle.close().catch(() => undefined);
110
116
  }
111
117
  }
112
118
  export async function sha256File(input, options = {}) {
@@ -157,17 +163,23 @@ function hashPathSync(filePath, options) {
157
163
  catch (error) {
158
164
  rethrowHashOpenError(error);
159
165
  }
166
+ let completed = false;
160
167
  try {
161
168
  options.signal?.throwIfAborted();
162
169
  const opened = inspectFileIdentitySync(() => hashDescriptorIdentity(fd), before);
163
170
  inspectFileIdentitySync(() => hashPathIdentity(filePath, true), opened);
164
- return hashDescriptorSync(fd, options);
171
+ const result = hashDescriptorSync(fd, options);
172
+ completed = true;
173
+ return result;
165
174
  }
166
175
  finally {
167
176
  try {
168
177
  fsSync.closeSync(fd);
169
178
  }
170
- catch { /* Preserve the hashing outcome. */ }
179
+ catch (error) {
180
+ if (completed)
181
+ throw error;
182
+ }
171
183
  }
172
184
  }
173
185
  /** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-sync-root-acquire.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-acquire.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAgD3C,wBAAgB,2BAA2B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClF,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAClD,QAAQ,EAAE,IAAI,GACb,kBAAkB,CA8VpB"}
1
+ {"version":3,"file":"file-lock-sync-root-acquire.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-acquire.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAiD3C,wBAAgB,2BAA2B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClF,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAClD,QAAQ,EAAE,IAAI,GACb,kBAAkB,CAgWpB"}
@@ -7,6 +7,7 @@ import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockD
7
7
  import { serializeSidecarLockPayload, } from "./sidecar-lock-reclaim.js";
8
8
  import { createSuppressedError } from "./suppressed-error.js";
9
9
  import { sleepSync } from "./timing.js";
10
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
10
11
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
11
12
  import { admitFileLockSyncRootPath, assertFileLockSyncRootPathsCurrent, captureFileLockSyncRootAuthority, normalizeFileLockSyncTargetWithRoot, } from "./file-lock-sync-root.js";
12
13
  import { cleanupCreatedRootSyncLock, FileLockSyncRootArbitrationCollision, tryReuseCurrentRootSyncHeldLock, } from "./file-lock-sync-root-arbitration.js";
@@ -299,6 +300,7 @@ export function acquireFileLockSyncWithRoot(targetPath, inputOptions, lockRoot)
299
300
  nowMs,
300
301
  heldByThisProcess: false,
301
302
  }]);
303
+ assertSynchronousCallbackResult(reclaim, "shouldReclaim");
302
304
  assertAdmission();
303
305
  if (!fileLockSyncRootSnapshotStillCurrent(lockRootPath, current)) {
304
306
  throw new FsSafeError("path-mismatch", "sidecar changed during reclaim policy callback");
@@ -324,6 +326,7 @@ export function acquireFileLockSyncWithRoot(targetPath, inputOptions, lockRoot)
324
326
  raw: snapshot.raw,
325
327
  payload: snapshot.payload,
326
328
  }]);
329
+ assertSynchronousCallbackResult(approved, "shouldRemoveStaleLock");
327
330
  assertAdmission();
328
331
  assertOwnedReclaimGuardCurrent();
329
332
  if (approved) {
@@ -28,7 +28,6 @@ export declare function getRootSyncHeldLocks(): Map<string, RootSyncHeldLock>;
28
28
  export declare function ensureRootSyncExitCleanupRegistered(): void;
29
29
  export declare function verifyRootSyncHeldLock(held: RootSyncHeldLock): boolean;
30
30
  export declare function createRootSyncHeldLockHandle(held: RootSyncHeldLock): FileLockSyncHandle;
31
- export declare function isRootSyncHeldLockHandle(handle: FileLockSyncHandle): boolean;
32
- export declare function withRootSyncHeldLockHandle<T>(lock: FileLockSyncHandle, fn: () => T): T;
31
+ export declare function withSyncHeldLockHandle<T>(lock: FileLockSyncHandle, fn: () => T): T;
33
32
  export {};
34
33
  //# sourceMappingURL=file-lock-sync-root-held.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-sync-root-held.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-held.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAIL,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AAQrC,KAAK,4BAA4B,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,UAAU,CAAC;AAE1F,KAAK,iCAAiC,GAAG;IACvC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAOF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,oBAAoB,CAAC,EAAE,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC9D,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,4BAA4B,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,yBAAyB,CAAC;IACzC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,EAAE,2BAA2B,CAAC;IACzC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CACxB,CAAC;AAQF,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,oBAAoB,EAC9B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,EACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACxC,eAAe,CAAC,EAAE,2BAA2B,GAC5C,wBAAwB,GAAG,IAAI,CAmCjC;AAED,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAQpE;AA+KD,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAWtE;AAsHD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CAqBvF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAE5E;AAED,wBAAgB,0BAA0B,CAAC,CAAC,EAC1C,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAoBH"}
1
+ {"version":3,"file":"file-lock-sync-root-held.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-held.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAIL,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AAQrC,KAAK,4BAA4B,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,UAAU,CAAC;AAE1F,KAAK,iCAAiC,GAAG;IACvC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAOF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,oBAAoB,CAAC,EAAE,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC9D,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,4BAA4B,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,yBAAyB,CAAC;IACzC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,EAAE,2BAA2B,CAAC;IACzC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CACxB,CAAC;AAQF,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,oBAAoB,EAC9B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,EACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACxC,eAAe,CAAC,EAAE,2BAA2B,GAC5C,wBAAwB,GAAG,IAAI,CAmCjC;AAED,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAQpE;AA+KD,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAWtE;AAsHD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CAqBvF;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAoBH"}