@openclaw/fs-safe 0.5.6 → 0.7.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 (289) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +23 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts +2 -0
  7. package/dist/archive-crc32.d.ts.map +1 -0
  8. package/dist/archive-crc32.js +14 -0
  9. package/dist/archive-deadline.d.ts +3 -0
  10. package/dist/archive-deadline.d.ts.map +1 -1
  11. package/dist/archive-deadline.js +44 -8
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +1 -0
  14. package/dist/archive-errors.d.ts +1 -0
  15. package/dist/archive-errors.d.ts.map +1 -1
  16. package/dist/archive-errors.js +3 -0
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +26 -16
  19. package/dist/archive-kind.js +2 -2
  20. package/dist/archive-limits.d.ts +11 -3
  21. package/dist/archive-limits.d.ts.map +1 -1
  22. package/dist/archive-limits.js +24 -0
  23. package/dist/archive-native.d.ts +3 -2
  24. package/dist/archive-native.d.ts.map +1 -1
  25. package/dist/archive-native.js +31 -9
  26. package/dist/archive-policy.d.ts +2 -0
  27. package/dist/archive-policy.d.ts.map +1 -1
  28. package/dist/archive-policy.js +9 -1
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +88 -41
  31. package/dist/archive-staging.d.ts +3 -0
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +91 -43
  34. package/dist/archive-tar-admission.d.ts +7 -0
  35. package/dist/archive-tar-admission.d.ts.map +1 -0
  36. package/dist/archive-tar-admission.js +43 -0
  37. package/dist/archive-tar-gnu.d.ts +2 -0
  38. package/dist/archive-tar-gnu.d.ts.map +1 -0
  39. package/dist/archive-tar-gnu.js +20 -0
  40. package/dist/archive-tar-header.d.ts +8 -0
  41. package/dist/archive-tar-header.d.ts.map +1 -0
  42. package/dist/archive-tar-header.js +47 -0
  43. package/dist/archive-tar-meta.d.ts +29 -1
  44. package/dist/archive-tar-meta.d.ts.map +1 -1
  45. package/dist/archive-tar-meta.js +135 -19
  46. package/dist/archive-tar-pax.d.ts +8 -0
  47. package/dist/archive-tar-pax.d.ts.map +1 -0
  48. package/dist/archive-tar-pax.js +100 -0
  49. package/dist/archive-tar-runtime.d.ts +4 -0
  50. package/dist/archive-tar-runtime.d.ts.map +1 -1
  51. package/dist/archive-tar-runtime.js +3 -0
  52. package/dist/archive-tar.d.ts.map +1 -1
  53. package/dist/archive-tar.js +8 -2
  54. package/dist/archive-zip-admission.d.ts +7 -0
  55. package/dist/archive-zip-admission.d.ts.map +1 -0
  56. package/dist/archive-zip-admission.js +60 -0
  57. package/dist/archive-zip-count.d.ts +2 -0
  58. package/dist/archive-zip-count.d.ts.map +1 -0
  59. package/dist/archive-zip-count.js +140 -0
  60. package/dist/archive-zip-directory.d.ts +8 -0
  61. package/dist/archive-zip-directory.d.ts.map +1 -0
  62. package/dist/archive-zip-directory.js +224 -0
  63. package/dist/archive-zip-integrity.d.ts.map +1 -1
  64. package/dist/archive-zip-integrity.js +1 -14
  65. package/dist/archive-zip-names.d.ts +12 -0
  66. package/dist/archive-zip-names.d.ts.map +1 -0
  67. package/dist/archive-zip-names.js +99 -0
  68. package/dist/archive-zip-preflight.d.ts +1 -1
  69. package/dist/archive-zip-preflight.d.ts.map +1 -1
  70. package/dist/archive-zip-preflight.js +8 -145
  71. package/dist/archive.d.ts.map +1 -1
  72. package/dist/archive.js +63 -42
  73. package/dist/atomic.d.ts +1 -1
  74. package/dist/atomic.d.ts.map +1 -1
  75. package/dist/bounded-read-stream.d.ts.map +1 -1
  76. package/dist/bounded-read-stream.js +2 -4
  77. package/dist/bounded-read.d.ts.map +1 -1
  78. package/dist/bounded-read.js +3 -10
  79. package/dist/byte-budget.d.ts +5 -0
  80. package/dist/byte-budget.d.ts.map +1 -0
  81. package/dist/byte-budget.js +9 -0
  82. package/dist/file-hash.d.ts.map +1 -1
  83. package/dist/file-hash.js +25 -19
  84. package/dist/file-lock-sync.d.ts.map +1 -1
  85. package/dist/file-lock-sync.js +26 -9
  86. package/dist/file-store-boundary.d.ts.map +1 -1
  87. package/dist/file-store-boundary.js +9 -5
  88. package/dist/file-store-limit.d.ts +2 -0
  89. package/dist/file-store-limit.d.ts.map +1 -0
  90. package/dist/file-store-limit.js +8 -0
  91. package/dist/file-store-sync-write.d.ts.map +1 -1
  92. package/dist/file-store-sync-write.js +38 -8
  93. package/dist/file-store.d.ts.map +1 -1
  94. package/dist/file-store.js +26 -26
  95. package/dist/json-durable-queue-directory.d.ts +2 -0
  96. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  97. package/dist/json-durable-queue-directory.js +20 -0
  98. package/dist/json-durable-queue-ownership.d.ts +14 -0
  99. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  100. package/dist/json-durable-queue-ownership.js +168 -0
  101. package/dist/json-durable-queue-retirement.d.ts +9 -0
  102. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  103. package/dist/json-durable-queue-retirement.js +126 -0
  104. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  105. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  106. package/dist/json-durable-queue-transfer-lock.js +19 -0
  107. package/dist/json-durable-queue.d.ts +1 -0
  108. package/dist/json-durable-queue.d.ts.map +1 -1
  109. package/dist/json-durable-queue.js +90 -57
  110. package/dist/json.d.ts.map +1 -1
  111. package/dist/json.js +27 -8
  112. package/dist/local-roots.d.ts.map +1 -1
  113. package/dist/local-roots.js +4 -2
  114. package/dist/native-binding.d.ts +15 -3
  115. package/dist/native-binding.d.ts.map +1 -1
  116. package/dist/native-operations.d.ts +4 -1
  117. package/dist/native-operations.d.ts.map +1 -1
  118. package/dist/native-operations.js +22 -6
  119. package/dist/native-pinned-write-windows.d.ts +8 -0
  120. package/dist/native-pinned-write-windows.d.ts.map +1 -0
  121. package/dist/native-pinned-write-windows.js +92 -0
  122. package/dist/native-pinned-write.d.ts.map +1 -1
  123. package/dist/native-pinned-write.js +136 -127
  124. package/dist/native-staged-file.d.ts +24 -0
  125. package/dist/native-staged-file.d.ts.map +1 -0
  126. package/dist/native-staged-file.js +337 -0
  127. package/dist/native.d.ts.map +1 -1
  128. package/dist/native.js +4 -4
  129. package/dist/opened-realpath.d.ts +2 -0
  130. package/dist/opened-realpath.d.ts.map +1 -1
  131. package/dist/opened-realpath.js +12 -7
  132. package/dist/output-sibling.d.ts.map +1 -1
  133. package/dist/output-sibling.js +11 -110
  134. package/dist/output.d.ts.map +1 -1
  135. package/dist/output.js +4 -2
  136. package/dist/owner-dacl.d.ts.map +1 -1
  137. package/dist/owner-dacl.js +2 -1
  138. package/dist/permission-exec.d.ts +19 -0
  139. package/dist/permission-exec.d.ts.map +1 -1
  140. package/dist/permission-exec.js +57 -11
  141. package/dist/permissions-public.d.ts +1 -1
  142. package/dist/permissions-public.d.ts.map +1 -1
  143. package/dist/permissions-windows.d.ts +3 -0
  144. package/dist/permissions-windows.d.ts.map +1 -1
  145. package/dist/permissions-windows.js +16 -5
  146. package/dist/permissions.d.ts +5 -0
  147. package/dist/permissions.d.ts.map +1 -1
  148. package/dist/pinned-open.d.ts.map +1 -1
  149. package/dist/pinned-open.js +27 -48
  150. package/dist/pinned-write.d.ts +6 -0
  151. package/dist/pinned-write.d.ts.map +1 -1
  152. package/dist/pinned-write.js +26 -37
  153. package/dist/private-directory.d.ts.map +1 -1
  154. package/dist/private-directory.js +3 -2
  155. package/dist/private-temp-workspace.d.ts +3 -1
  156. package/dist/private-temp-workspace.d.ts.map +1 -1
  157. package/dist/private-temp-workspace.js +81 -56
  158. package/dist/publish-file.d.ts.map +1 -1
  159. package/dist/publish-file.js +2 -4
  160. package/dist/read-opened-file.d.ts.map +1 -1
  161. package/dist/read-opened-file.js +6 -4
  162. package/dist/regular-file.d.ts.map +1 -1
  163. package/dist/regular-file.js +136 -82
  164. package/dist/replace-file-copy-fallback.d.ts +3 -1
  165. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  166. package/dist/replace-file-copy-fallback.js +21 -36
  167. package/dist/replace-file-copy-source.d.ts +21 -0
  168. package/dist/replace-file-copy-source.d.ts.map +1 -0
  169. package/dist/replace-file-copy-source.js +112 -0
  170. package/dist/replace-file-descriptor.d.ts +13 -3
  171. package/dist/replace-file-descriptor.d.ts.map +1 -1
  172. package/dist/replace-file-descriptor.js +32 -7
  173. package/dist/replace-file-rename-policy.d.ts +7 -0
  174. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  175. package/dist/replace-file-rename-policy.js +30 -0
  176. package/dist/replace-file-temp-owner.d.ts +46 -0
  177. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  178. package/dist/replace-file-temp-owner.js +346 -0
  179. package/dist/replace-file.d.ts +6 -1
  180. package/dist/replace-file.d.ts.map +1 -1
  181. package/dist/replace-file.js +72 -58
  182. package/dist/root-impl.d.ts.map +1 -1
  183. package/dist/root-impl.js +118 -98
  184. package/dist/root-paths.d.ts +11 -14
  185. package/dist/root-paths.d.ts.map +1 -1
  186. package/dist/root-paths.js +36 -27
  187. package/dist/root-write-verification.d.ts +11 -0
  188. package/dist/root-write-verification.d.ts.map +1 -0
  189. package/dist/root-write-verification.js +91 -0
  190. package/dist/secret-file.d.ts +1 -6
  191. package/dist/secret-file.d.ts.map +1 -1
  192. package/dist/secret-file.js +49 -120
  193. package/dist/secret-read-async.d.ts +1 -1
  194. package/dist/secret-read-async.d.ts.map +1 -1
  195. package/dist/secret-read-async.js +51 -72
  196. package/dist/secret-read-policy.d.ts +13 -0
  197. package/dist/secret-read-policy.d.ts.map +1 -0
  198. package/dist/secret-read-policy.js +28 -0
  199. package/dist/secret.d.ts +2 -1
  200. package/dist/secret.d.ts.map +1 -1
  201. package/dist/secret.js +2 -1
  202. package/dist/secure-file.d.ts.map +1 -1
  203. package/dist/secure-file.js +42 -29
  204. package/dist/sibling-staged-file.d.ts +15 -0
  205. package/dist/sibling-staged-file.d.ts.map +1 -0
  206. package/dist/sibling-staged-file.js +148 -0
  207. package/dist/sibling-temp.d.ts +3 -0
  208. package/dist/sibling-temp.d.ts.map +1 -1
  209. package/dist/sibling-temp.js +30 -74
  210. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  211. package/dist/sidecar-lock-acquire.js +48 -27
  212. package/dist/sidecar-lock-handle.d.ts +6 -2
  213. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  214. package/dist/sidecar-lock-handle.js +17 -3
  215. package/dist/sidecar-lock-policy.d.ts +2 -0
  216. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  217. package/dist/sidecar-lock-policy.js +29 -0
  218. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  219. package/dist/sidecar-lock-reclaim.js +29 -7
  220. package/dist/sidecar-lock.d.ts.map +1 -1
  221. package/dist/sidecar-lock.js +28 -16
  222. package/dist/staged-directory.d.ts +16 -0
  223. package/dist/staged-directory.d.ts.map +1 -0
  224. package/dist/staged-directory.js +60 -0
  225. package/dist/staged-file-types.d.ts +56 -0
  226. package/dist/staged-file-types.d.ts.map +1 -0
  227. package/dist/staged-file-types.js +1 -0
  228. package/dist/staged-file.d.ts +10 -0
  229. package/dist/staged-file.d.ts.map +1 -0
  230. package/dist/staged-file.js +15 -0
  231. package/dist/strict-file-identity.d.ts +6 -0
  232. package/dist/strict-file-identity.d.ts.map +1 -0
  233. package/dist/strict-file-identity.js +48 -0
  234. package/dist/suppressed-error.d.ts +6 -0
  235. package/dist/suppressed-error.d.ts.map +1 -0
  236. package/dist/suppressed-error.js +15 -0
  237. package/dist/temp-cleanup.d.ts +2 -0
  238. package/dist/temp-cleanup.d.ts.map +1 -1
  239. package/dist/temp-cleanup.js +25 -10
  240. package/dist/temp-workspace-owner.d.ts +23 -0
  241. package/dist/temp-workspace-owner.d.ts.map +1 -0
  242. package/dist/temp-workspace-owner.js +320 -0
  243. package/dist/temp.d.ts +1 -1
  244. package/dist/temp.d.ts.map +1 -1
  245. package/dist/test-hooks.d.ts +5 -0
  246. package/dist/test-hooks.d.ts.map +1 -1
  247. package/dist/windows-owner.d.ts +3 -0
  248. package/dist/windows-owner.d.ts.map +1 -1
  249. package/dist/windows-owner.js +10 -2
  250. package/docs/advanced.md +19 -2
  251. package/docs/archive.md +250 -35
  252. package/docs/atomic.md +11 -2
  253. package/docs/config.md +7 -0
  254. package/docs/contributing.md +45 -7
  255. package/docs/durability.md +18 -5
  256. package/docs/errors.md +16 -1
  257. package/docs/file-store.md +2 -0
  258. package/docs/index.md +3 -1
  259. package/docs/install.md +20 -8
  260. package/docs/json.md +8 -4
  261. package/docs/migrating-to-0.5.md +7 -7
  262. package/docs/migrating-to-0.6.md +43 -0
  263. package/docs/native-helper.md +30 -8
  264. package/docs/native.md +73 -17
  265. package/docs/output.md +10 -0
  266. package/docs/path-scope.md +28 -2
  267. package/docs/permissions.md +13 -2
  268. package/docs/public-api.md +6 -3
  269. package/docs/quickstart.md +1 -1
  270. package/docs/reading.md +1 -1
  271. package/docs/regular-file.md +9 -2
  272. package/docs/root.md +3 -1
  273. package/docs/secret-file.md +12 -0
  274. package/docs/secure-file.md +21 -3
  275. package/docs/security-model.md +33 -1
  276. package/docs/sidecar-lock.md +15 -1
  277. package/docs/staged-file.md +178 -0
  278. package/docs/store.md +13 -0
  279. package/docs/temp.md +128 -14
  280. package/docs/testing.md +1 -1
  281. package/docs/writing.md +19 -0
  282. package/package.json +16 -9
  283. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  284. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  285. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  286. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  287. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  288. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  289. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
@@ -1,15 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import { isUnsafeDeviceReadPath } from "./device-path.js";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { sameFileIdentity as hasSameFileIdentity } from "./file-identity.js";
4
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
5
5
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
6
  function isExpectedPathError(error) {
7
7
  const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
8
8
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
9
9
  }
10
- function sameFileIdentity(left, right) {
11
- return hasSameFileIdentity(left, right);
12
- }
13
10
  export function openPinnedFileSync(params) {
14
11
  const ioFs = params.ioFs ?? fs;
15
12
  const allowedType = params.allowedType ?? "file";
@@ -29,53 +26,30 @@ export function openPinnedFileSync(params) {
29
26
  if (isUnsafeDeviceReadPath(realPath)) {
30
27
  return { ok: false, reason: "validation" };
31
28
  }
32
- const preOpenStat = ioFs.lstatSync(realPath);
33
- if (!isAllowedType(preOpenStat, allowedType)) {
34
- return {
35
- ok: false,
36
- reason: "validation",
37
- error: new FsSafeError("not-file", "path does not have the required file type"),
38
- };
39
- }
40
- if (params.rejectHardlinks && preOpenStat.isFile() && preOpenStat.nlink > 1) {
41
- return {
42
- ok: false,
43
- reason: "validation",
44
- error: new FsSafeError("hardlink", "path must not be hardlinked"),
45
- };
46
- }
47
- if (params.maxBytes !== undefined &&
48
- preOpenStat.isFile() &&
49
- preOpenStat.size > params.maxBytes) {
50
- return { ok: false, reason: "validation" };
51
- }
29
+ const preOpenStat = inspectFileIdentitySync(() => {
30
+ const stat = ioFs.lstatSync(realPath, { bigint: true });
31
+ assertAllowedStat(stat, allowedType, params);
32
+ return stat;
33
+ });
52
34
  fd = ioFs.openSync(realPath, openReadFlags);
53
35
  const openedStat = ioFs.fstatSync(fd);
54
- if (!isAllowedType(openedStat, allowedType)) {
55
- return {
56
- ok: false,
57
- reason: "validation",
58
- error: new FsSafeError("not-file", "path does not have the required file type"),
59
- };
60
- }
61
- if (params.rejectHardlinks && openedStat.isFile() && openedStat.nlink > 1) {
62
- return {
63
- ok: false,
64
- reason: "validation",
65
- error: new FsSafeError("hardlink", "path must not be hardlinked"),
66
- };
67
- }
68
- if (params.maxBytes !== undefined && openedStat.isFile() && openedStat.size > params.maxBytes) {
69
- return { ok: false, reason: "validation" };
70
- }
71
- if (!sameFileIdentity(preOpenStat, openedStat)) {
72
- return { ok: false, reason: "validation" };
73
- }
36
+ const identity = inspectFileIdentitySync(() => {
37
+ const stat = ioFs.fstatSync(fd, { bigint: true });
38
+ assertAllowedStat(stat, allowedType, params);
39
+ return stat;
40
+ }, preOpenStat);
41
+ inspectFileIdentitySync(() => {
42
+ const stat = ioFs.lstatSync(realPath, { bigint: true });
43
+ assertAllowedStat(stat, allowedType, params);
44
+ return stat;
45
+ }, identity);
74
46
  const opened = { ok: true, path: realPath, fd, stat: openedStat };
75
47
  fd = null;
76
48
  return opened;
77
49
  }
78
50
  catch (error) {
51
+ if (error instanceof FsSafeError)
52
+ return { ok: false, reason: "validation", error };
79
53
  if (isExpectedPathError(error)) {
80
54
  return { ok: false, reason: "path", error };
81
55
  }
@@ -87,9 +61,14 @@ export function openPinnedFileSync(params) {
87
61
  }
88
62
  }
89
63
  }
90
- function isAllowedType(stat, allowedType) {
91
- if (allowedType === "directory") {
92
- return stat.isDirectory();
64
+ function assertAllowedStat(stat, allowedType, params) {
65
+ if (stat.isSymbolicLink() || !(allowedType === "directory" ? stat.isDirectory() : stat.isFile())) {
66
+ throw new FsSafeError("not-file", "path does not have the required file type");
67
+ }
68
+ if (params.rejectHardlinks && stat.isFile() && stat.nlink > 1n) {
69
+ throw new FsSafeError("hardlink", "path must not be hardlinked");
70
+ }
71
+ if (params.maxBytes !== undefined && stat.isFile() && stat.size > params.maxBytes) {
72
+ throw new FsSafeError("too-large", "file exceeds byte limit");
93
73
  }
94
- return stat.isFile();
95
74
  }
@@ -1,4 +1,5 @@
1
1
  import type { Readable } from "node:stream";
2
+ import { type AsyncDirectoryGuard } from "./directory-guard.js";
2
3
  import type { FileIdentityStat } from "./file-identity.js";
3
4
  export type PinnedWriteInput = {
4
5
  kind: "buffer";
@@ -10,6 +11,10 @@ export type PinnedWriteInput = {
10
11
  };
11
12
  export type RenameIdentityMismatchPolicy = "throw" | "verify-content";
12
13
  export type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
14
+ export type PublishedWriteIdentity = Readonly<{
15
+ dev: bigint;
16
+ ino: bigint;
17
+ }>;
13
18
  export type PinnedWriteParams = {
14
19
  rootPath: string;
15
20
  relativeParentPath: string;
@@ -21,6 +26,7 @@ export type PinnedWriteParams = {
21
26
  input: PinnedWriteInput;
22
27
  rootIdentity?: FileIdentityStat;
23
28
  onRenameIdentityMismatch?: RenameIdentityMismatchPolicy;
29
+ verifyPublished?: (fd: number, identity: PublishedWriteIdentity, parentGuard: AsyncDirectoryGuard) => Promise<void>;
24
30
  };
25
31
  export declare function runPinnedWriteHelper(params: PinnedWriteParams): Promise<FileIdentityStat>;
26
32
  export declare function runPinnedWriteWithRenamePolicy(params: PinnedWriteParams & {
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAU3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAgEzC,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;CACzD,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
1
+ {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAkE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhI,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAgEzC,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;IAExD,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,mBAAmB,KAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import fsSync from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { normalizeMaxBytes } from "./byte-budget.js";
5
6
  import { createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
6
7
  import { FsSafeError } from "./errors.js";
7
8
  import { syncDirectoryBestEffort } from "./fsync.js";
@@ -11,6 +12,7 @@ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
11
12
  import { runPinnedWriteNative } from "./native-pinned-write.js";
12
13
  import { getNativeBinding } from "./native.js";
13
14
  import { validatePinnedOperationPayload } from "./pinned-operation.js";
15
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
14
16
  import { withSidecarLock } from "./sidecar-lock.js";
15
17
  import { getFsSafeTestHooks } from "./test-hooks.js";
16
18
  function byteLength(input, encoding) {
@@ -59,20 +61,21 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
59
61
  }
60
62
  }
61
63
  export async function runPinnedWriteHelper(params) {
64
+ const normalizedParams = { ...params, maxBytes: normalizeMaxBytes(params.maxBytes) };
62
65
  assertSafeBasename(params.basename);
63
66
  validatePinnedOperationPayload({
64
67
  relativeParentPath: params.relativeParentPath,
65
68
  });
66
69
  // The explicit compatibility policy uses the guarded Node fallback, where
67
70
  // content verification can replace the strict post-rename inode check.
68
- if (params.onRenameIdentityMismatch === "verify-content") {
69
- return await runPinnedWriteFallback(params);
71
+ if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
72
+ return await runPinnedWriteFallback(normalizedParams);
70
73
  }
71
74
  const native = getNativeBinding();
72
75
  if (native) {
73
- return await runPinnedWriteNative(native, params);
76
+ return await runPinnedWriteNative(native, normalizedParams);
74
77
  }
75
- return await runPinnedWriteFallback(params);
78
+ return await runPinnedWriteFallback(normalizedParams);
76
79
  }
77
80
  export async function runPinnedWriteWithRenamePolicy(params) {
78
81
  const { targetPath, renameIdentity, ...writeParams } = params;
@@ -115,7 +118,7 @@ async function runPinnedWriteFallback(params) {
115
118
  : await createNearestExistingDirectoryGuard(params.rootPath, parentPath);
116
119
  const targetPath = path.join(parentPath, params.basename);
117
120
  if (params.overwrite === false) {
118
- let handle = await withAsyncDirectoryGuards([parentGuard], async () => await fs.open(targetPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL, params.mode), {
121
+ const handle = await withAsyncDirectoryGuards([parentGuard], async () => await fs.open(targetPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL, params.mode), {
119
122
  onPostGuardFailure: async (openedHandle) => {
120
123
  // The parent failed verification, so targetPath may now resolve
121
124
  // somewhere else. Close the fd, but do not clean up by path.
@@ -124,6 +127,7 @@ async function runPinnedWriteFallback(params) {
124
127
  });
125
128
  let created = true;
126
129
  try {
130
+ const verificationIdentity = await handle.stat({ bigint: true });
127
131
  await handle.chmod(params.mode);
128
132
  if (params.input.kind === "buffer") {
129
133
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
@@ -139,9 +143,10 @@ async function runPinnedWriteFallback(params) {
139
143
  }
140
144
  await syncFileBestEffort(handle);
141
145
  const stat = await handle.stat();
142
- await handle.close().catch(() => undefined);
143
146
  await syncDirectoryBestEffort(parentPath);
147
+ // Publication is complete. A failed outer check must not remove its target.
144
148
  created = false;
149
+ await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
145
150
  return { dev: stat.dev, ino: stat.ino };
146
151
  }
147
152
  finally {
@@ -160,10 +165,11 @@ async function runPinnedWriteFallback(params) {
160
165
  : 0);
161
166
  let handle;
162
167
  let tempStat;
163
- let targetStat;
168
+ let readHandle;
164
169
  let renamed = false;
165
170
  try {
166
171
  handle = await fs.open(tempPath, tempFlags, params.mode);
172
+ let verificationIdentity = await handle.stat({ bigint: true });
167
173
  await handle.chmod(params.mode);
168
174
  if (params.input.kind === "buffer") {
169
175
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
@@ -184,14 +190,13 @@ async function runPinnedWriteFallback(params) {
184
190
  }
185
191
  const expectedTempStat = tempStat;
186
192
  await syncFileBestEffort(handle);
187
- await handle.close().catch(() => undefined);
188
- handle = undefined;
193
+ let verifiedIdentity = expectedTempStat;
189
194
  await withAsyncDirectoryGuards([parentGuard], async () => {
190
195
  await fs.rename(tempPath, targetPath);
191
196
  renamed = true;
192
197
  await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
193
198
  await syncDirectoryBestEffort(parentPath);
194
- targetStat = await fs.lstat(targetPath);
199
+ const targetStat = await fs.lstat(targetPath);
195
200
  if (targetStat.isSymbolicLink()) {
196
201
  throw new FsSafeError("path-mismatch", "fallback target changed during write");
197
202
  }
@@ -207,42 +212,26 @@ async function runPinnedWriteFallback(params) {
207
212
  throw new FsSafeError("path-mismatch", "fallback target changed during write");
208
213
  }
209
214
  const expectedHash = sha256Hex(params.input.data, params.input.encoding);
210
- const readFlags = fsSync.constants.O_RDONLY |
211
- (process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants
212
- ? fsSync.constants.O_NOFOLLOW
213
- : 0);
214
- const readHandle = await fs.open(targetPath, readFlags);
215
- let actualHash;
216
- let readHandleStat;
217
- try {
218
- // Capture fd-based identity before reading — this is stable across all
219
- // subsequent lookups (on FUSE and locally), unlike the lstat-based
220
- // targetStat that triggered this fallback.
221
- readHandleStat = await readHandle.stat();
222
- actualHash = sha256Hex(await readHandle.readFile());
223
- }
224
- finally {
225
- await readHandle.close().catch(() => undefined);
226
- }
215
+ readHandle = await fs.open(targetPath, resolveReadOpenFlags());
216
+ const readHandleStat = await readHandle.stat({ bigint: true });
217
+ const actualHash = sha256Hex(await readHandle.readFile());
227
218
  if (actualHash !== expectedHash) {
228
219
  throw new FsSafeError("path-mismatch", "fallback target changed during write");
229
220
  }
230
- // Replace the unreliable lstat-based targetStat with the fd-based stat so
231
- // the returned identity is consistent with what subsequent verifications
232
- // (e.g. verifyAtomicWriteResult) will obtain by opening the same file.
233
- targetStat = readHandleStat;
221
+ // The content-verified destination, not the old temp inode, is now pinned.
222
+ verificationIdentity = readHandleStat;
223
+ // Preserve the helper's legacy numeric return facts, not the private proof.
224
+ verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
234
225
  }
235
226
  });
227
+ await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
228
+ return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
236
229
  }
237
- catch (error) {
230
+ finally {
231
+ await readHandle?.close().catch(() => undefined);
238
232
  await handle?.close().catch(() => undefined);
239
233
  if (!renamed) {
240
234
  await fs.rm(tempPath, { force: true }).catch(() => undefined);
241
235
  }
242
- throw error;
243
- }
244
- if (!targetStat) {
245
- throw new FsSafeError("path-mismatch", "fallback target was not verified");
246
236
  }
247
- return { dev: targetStat.dev, ino: targetStat.ino };
248
237
  }
@@ -1 +1 @@
1
- {"version":3,"file":"private-directory.d.ts","sourceRoot":"","sources":["../src/private-directory.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
1
+ {"version":3,"file":"private-directory.d.ts","sourceRoot":"","sources":["../src/private-directory.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -3,11 +3,12 @@ import { getNativeBinding } from "./native.js";
3
3
  export async function createPrivateDirectory(targetPath, options) {
4
4
  const platform = options?.platform ?? process.platform;
5
5
  if (platform !== "win32") {
6
- throw new FsSafeError("helper-unavailable", "private-directory creation is available only on Windows through the bundled native binding");
6
+ throw new FsSafeError("helper-unavailable", "private-directory creation is supported only on Windows");
7
7
  }
8
8
  const native = getNativeBinding();
9
9
  if (!native) {
10
- throw new FsSafeError("helper-unavailable", "private Windows directory creation requires the bundled native binding");
10
+ throw new FsSafeError("helper-unavailable", "private Windows directory creation requires the matching optional native platform package; " +
11
+ "install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
11
12
  }
12
13
  native.createPrivateDirectory(targetPath);
13
14
  }
@@ -1,11 +1,13 @@
1
1
  import { type FileStore, type FileStoreSync } from "./file-store.js";
2
2
  import { type TempPathIdentityReceipt } from "./temp-cleanup.js";
3
- export type TempWorkspaceCleanupResult = "removed" | "missing" | "identity-mismatch";
3
+ import { type TempWorkspaceCleanupResult, type TempWorkspaceCleanupSafety } from "./temp-workspace-owner.js";
4
+ export type { TempWorkspaceCleanupResult, TempWorkspaceCleanupSafety, } from "./temp-workspace-owner.js";
4
5
  export type TempWorkspaceOptions = {
5
6
  rootDir: string;
6
7
  prefix: string;
7
8
  dirMode?: number;
8
9
  mode?: number;
10
+ cleanupSafety?: TempWorkspaceCleanupSafety;
9
11
  };
10
12
  export type TempWorkspace = {
11
13
  dir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAUzB,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,CAAC;AAErF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC/C,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,0BAA0B,CAAC;IACtC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA+KF,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAExB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CA8EnB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,CAAC,GACvC,CAAC,CAUH"}
1
+ {"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,0BAA0B,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC/C,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,0BAA0B,CAAC;IACtC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAuKF,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAExB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAgGnB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,CAAC,GACvC,CAAC,CAUH"}
@@ -4,11 +4,18 @@ import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { fileStore, fileStoreSync, } from "./file-store.js";
6
6
  import { FsSafeError } from "./errors.js";
7
- import { sameFileIdentityForCleanup } from "./file-identity.js";
8
7
  import { isNotFoundPathError } from "./path.js";
9
8
  import { throwFsSafeReadError } from "./read-error.js";
10
9
  import { matchRootFileOpenFailure, openRootFileSync, } from "./root-file.js";
11
10
  import { registerTempPathForExit, } from "./temp-cleanup.js";
11
+ import { TempWorkspaceCleanupCapability, TempWorkspaceCleanupOwner, } from "./temp-workspace-owner.js";
12
+ function resolveTempWorkspaceCleanupSafety(value) {
13
+ if (value === undefined || value === "compatible")
14
+ return "compatible";
15
+ if (value === "require-bounded")
16
+ return value;
17
+ throw new TypeError("cleanupSafety must be compatible or require-bounded");
18
+ }
12
19
  function sanitizeTempPrefix(prefix) {
13
20
  const sanitized = prefix.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
14
21
  if (!sanitized || sanitized === "." || sanitized === "..") {
@@ -79,52 +86,47 @@ function ensurePrivateDirectorySync(dir, mode) {
79
86
  // Best-effort on platforms that do not enforce POSIX modes.
80
87
  }
81
88
  }
82
- async function cleanupWorkspace(dir, identity) {
83
- let current;
84
- try {
85
- current = await fs.lstat(dir, { bigint: true });
86
- }
87
- catch (error) {
88
- return error.code === "ENOENT" ? "missing" : "identity-mismatch";
89
- }
90
- if (!sameFileIdentityForCleanup(current, identity)) {
91
- return "identity-mismatch";
92
- }
93
- await fs.rm(dir, { recursive: true, force: true });
94
- return "removed";
95
- }
96
- function cleanupWorkspaceSync(dir, identity) {
97
- let current;
98
- try {
99
- current = fsSync.lstatSync(dir, { bigint: true });
100
- }
101
- catch (error) {
102
- return error.code === "ENOENT" ? "missing" : "identity-mismatch";
103
- }
104
- if (!sameFileIdentityForCleanup(current, identity)) {
105
- return "identity-mismatch";
106
- }
107
- fsSync.rmSync(dir, { recursive: true, force: true });
108
- return "removed";
109
- }
110
89
  async function createTempWorkspace(options) {
111
90
  const dirMode = options.dirMode ?? 0o700;
112
91
  const mode = options.mode ?? 0o600;
92
+ const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
113
93
  const requestedRoot = path.resolve(options.rootDir);
114
94
  const root = await fs.realpath(requestedRoot).catch(() => requestedRoot);
115
95
  await ensurePrivateDirectory(root, dirMode);
116
- const dir = await fs.mkdtemp(path.join(root, sanitizeTempPrefix(options.prefix)));
117
- await fs.chmod(dir, dirMode).catch(() => undefined);
118
- const stat = await fs.lstat(dir, { bigint: true });
119
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
120
- throw new Error(`Temp workspace must be a directory: ${dir}`);
96
+ const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety);
97
+ let dir;
98
+ let stat;
99
+ let cleanupOwner;
100
+ let unregisterTempDir;
101
+ try {
102
+ dir = await fs.mkdtemp(path.join(root, sanitizeTempPrefix(options.prefix)));
103
+ await fs.chmod(dir, dirMode).catch(() => undefined);
104
+ stat = await fs.lstat(dir, { bigint: true });
105
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
106
+ throw new Error(`Temp workspace must be a directory: ${dir}`);
107
+ }
108
+ if (capability.parent)
109
+ capability.assertCurrent();
110
+ cleanupOwner = new TempWorkspaceCleanupOwner(dir, stat, capability);
111
+ unregisterTempDir = registerTempPathForExit(dir, {
112
+ cleanupSync: () => cleanupOwner.cleanupSync(),
113
+ });
121
114
  }
115
+ catch (error) {
116
+ try {
117
+ if (cleanupOwner)
118
+ cleanupOwner.cleanupSync();
119
+ else
120
+ capability.close();
121
+ }
122
+ catch (cleanupError) {
123
+ throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
124
+ }
125
+ throw error;
126
+ }
127
+ const owner = cleanupOwner;
122
128
  const identity = { dev: Number(stat.dev), ino: Number(stat.ino) };
123
- const cleanupIdentity = { dev: stat.dev, ino: stat.ino };
124
- const unregisterTempDir = registerTempPathForExit(dir, {
125
- recursive: true,
126
- identity: cleanupIdentity,
127
- });
129
+ // Once registered, even a store-construction failure remains exit-cleanable.
128
130
  const store = fileStore({ rootDir: dir, private: true, dirMode, mode });
129
131
  return {
130
132
  dir,
@@ -148,7 +150,7 @@ async function createTempWorkspace(options) {
148
150
  },
149
151
  cleanup: async () => {
150
152
  try {
151
- return await cleanupWorkspace(dir, cleanupIdentity);
153
+ return await owner.cleanup();
152
154
  }
153
155
  finally {
154
156
  unregisterTempDir();
@@ -156,7 +158,7 @@ async function createTempWorkspace(options) {
156
158
  },
157
159
  [Symbol.asyncDispose]: async () => {
158
160
  try {
159
- await cleanupWorkspace(dir, cleanupIdentity);
161
+ await owner.cleanup();
160
162
  }
161
163
  finally {
162
164
  unregisterTempDir();
@@ -182,6 +184,7 @@ export async function withTempWorkspace(options, run) {
182
184
  export function tempWorkspaceSync(options) {
183
185
  const dirMode = options.dirMode ?? 0o700;
184
186
  const mode = options.mode ?? 0o600;
187
+ const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
185
188
  const requestedRoot = path.resolve(options.rootDir);
186
189
  let root = requestedRoot;
187
190
  try {
@@ -191,23 +194,45 @@ export function tempWorkspaceSync(options) {
191
194
  root = requestedRoot;
192
195
  }
193
196
  ensurePrivateDirectorySync(root, dirMode);
194
- const dir = fsSync.mkdtempSync(path.join(root, sanitizeTempPrefix(options.prefix)));
197
+ const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety);
198
+ let dir;
199
+ let stat;
200
+ let cleanupOwner;
201
+ let unregisterTempDir;
195
202
  try {
196
- fsSync.chmodSync(dir, dirMode);
203
+ dir = fsSync.mkdtempSync(path.join(root, sanitizeTempPrefix(options.prefix)));
204
+ try {
205
+ fsSync.chmodSync(dir, dirMode);
206
+ }
207
+ catch {
208
+ // Best-effort on platforms that do not enforce POSIX modes.
209
+ }
210
+ stat = fsSync.lstatSync(dir, { bigint: true });
211
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
212
+ throw new Error(`Temp workspace must be a directory: ${dir}`);
213
+ }
214
+ if (capability.parent)
215
+ capability.assertCurrent();
216
+ cleanupOwner = new TempWorkspaceCleanupOwner(dir, stat, capability);
217
+ unregisterTempDir = registerTempPathForExit(dir, {
218
+ cleanupSync: () => cleanupOwner.cleanupSync(),
219
+ });
197
220
  }
198
- catch {
199
- // Best-effort on platforms that do not enforce POSIX modes.
200
- }
201
- const stat = fsSync.lstatSync(dir, { bigint: true });
202
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
203
- throw new Error(`Temp workspace must be a directory: ${dir}`);
221
+ catch (error) {
222
+ try {
223
+ if (cleanupOwner)
224
+ cleanupOwner.cleanupSync();
225
+ else
226
+ capability.close();
227
+ }
228
+ catch (cleanupError) {
229
+ throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
230
+ }
231
+ throw error;
204
232
  }
233
+ const owner = cleanupOwner;
205
234
  const identity = { dev: Number(stat.dev), ino: Number(stat.ino) };
206
- const cleanupIdentity = { dev: stat.dev, ino: stat.ino };
207
- const unregisterTempDir = registerTempPathForExit(dir, {
208
- recursive: true,
209
- identity: cleanupIdentity,
210
- });
235
+ // Once registered, even a store-construction failure remains exit-cleanable.
211
236
  const store = fileStoreSync({ rootDir: dir, private: true, dirMode, mode });
212
237
  return {
213
238
  dir,
@@ -244,7 +269,7 @@ export function tempWorkspaceSync(options) {
244
269
  },
245
270
  cleanup: () => {
246
271
  try {
247
- return cleanupWorkspaceSync(dir, cleanupIdentity);
272
+ return owner.cleanupSync();
248
273
  }
249
274
  finally {
250
275
  unregisterTempDir();
@@ -252,7 +277,7 @@ export function tempWorkspaceSync(options) {
252
277
  },
253
278
  [Symbol.dispose]: () => {
254
279
  try {
255
- cleanupWorkspaceSync(dir, cleanupIdentity);
280
+ owner.cleanupSync();
256
281
  }
257
282
  finally {
258
283
  unregisterTempDir();
@@ -1 +1 @@
1
- {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA2ND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4MtC"}
1
+ {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AAsND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4MtC"}
@@ -8,6 +8,7 @@ import { hashFileHandle } from "./file-hash.js";
8
8
  import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.js";
9
9
  import { syncNativeFileBestEffort } from "./native-operations.js";
10
10
  import { getNativeBinding, requireNativeBinding } from "./native.js";
11
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
11
12
  import { directorySyncFailure, publicationFailure, rememberCreatedTarget, } from "./publish-file-failure.js";
12
13
  import { getFsSafeTestHooks } from "./test-hooks.js";
13
14
  const HARDLINK_FALLBACK_CODES = new Set([
@@ -29,10 +30,7 @@ export function isHardlinkFallbackError(error) {
29
30
  return HARDLINK_FALLBACK_CODES.has(error?.code ?? "");
30
31
  }
31
32
  function sourceOpenFlags() {
32
- return (fsSync.constants.O_RDONLY |
33
- (process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
34
- ? fsSync.constants.O_NOFOLLOW
35
- : 0));
33
+ return resolveReadOpenFlags();
36
34
  }
37
35
  function directoryOpenFlags() {
38
36
  return (fsSync.constants.O_RDONLY |
@@ -1 +1 @@
1
- {"version":3,"file":"read-opened-file.d.ts","sourceRoot":"","sources":["../src/read-opened-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBtB"}
1
+ {"version":3,"file":"read-opened-file.d.ts","sourceRoot":"","sources":["../src/read-opened-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAkBtB"}
@@ -1,12 +1,14 @@
1
+ import { normalizeMaxBytes } from "./byte-budget.js";
1
2
  import { readFileHandleBounded } from "./bounded-read.js";
2
3
  import { FsSafeError } from "./errors.js";
3
4
  export async function readOpenedFileSafely(params) {
4
- if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
5
- throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
5
+ const maxBytes = normalizeMaxBytes(params.maxBytes);
6
+ if (maxBytes !== undefined && params.opened.stat.size > maxBytes) {
7
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got ${params.opened.stat.size})`);
6
8
  }
7
- const buffer = params.maxBytes === undefined
9
+ const buffer = maxBytes === undefined
8
10
  ? await params.opened.handle.readFile()
9
- : await readFileHandleBounded(params.opened.handle, params.maxBytes);
11
+ : await readFileHandleBounded(params.opened.handle, maxBytes);
10
12
  return {
11
13
  buffer,
12
14
  containment: params.opened.containment,
@@ -1 +1 @@
1
- {"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAY7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAyD3C;AA6DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CA6BA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDxF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CA2D7E"}
1
+ {"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,MAAM,MAAM,SAAS,CAAC;AAc7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CA6D3C;AA2DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CAoCA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwExF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAyE7E"}