@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
@@ -0,0 +1,363 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { DEFAULT_PERMISSION_EXEC_TIMEOUT_MS, PermissionCommandError } from "./permission-exec.js";
5
+ import { resolveWindowsSystemCommand } from "./windows-command.js";
6
+ import { parseWindowsSecurityCommandFacts } from "./windows-security-facts.js";
7
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
8
+ const TERMINATION_GRACE_MS = 1_000;
9
+ const FULL_IDENTITY = /^[0-9a-f]{16}:[0-9a-f]{32}$/;
10
+ function isFullIdentity(identity) {
11
+ return typeof identity === "string" && identity.length === 49 && FULL_IDENTITY.test(identity);
12
+ }
13
+ function assertIdentity(identity) {
14
+ if (!isFullIdentity(identity)) {
15
+ throw new TypeError("Windows security operation requires a complete file identity");
16
+ }
17
+ }
18
+ function command(operation, params) {
19
+ const targetPath = params.targetPath ?? "";
20
+ // Preserve native EINVAL before child-process environment validation runs.
21
+ if (targetPath.includes("\0")) {
22
+ throw Object.assign(new Error("Windows path contains a NUL byte"), { code: "EINVAL" });
23
+ }
24
+ if (params.fd !== undefined || operation === "descriptor" || operation === "protect-file" || operation === "verify-file") {
25
+ if (typeof params.fd !== "number" || !Number.isInteger(params.fd) || params.fd < 0 || params.fd > 0x7fff_ffff) {
26
+ unverified("Windows security inspection requires a valid descriptor");
27
+ }
28
+ }
29
+ if (params.expectedParentIdentity !== undefined)
30
+ assertIdentity(params.expectedParentIdentity);
31
+ if (params.expectedFileIdentity !== undefined)
32
+ assertIdentity(params.expectedFileIdentity);
33
+ if (params.expectedLinks !== undefined &&
34
+ (!Number.isInteger(params.expectedLinks) || params.expectedLinks < 1 || params.expectedLinks > 0xffff_ffff)) {
35
+ throw new TypeError("Windows security operation requires a positive uint32 link count");
36
+ }
37
+ const script = fileURLToPath(new URL("./windows-security-bridge.ps1", import.meta.url));
38
+ return {
39
+ file: resolveWindowsSystemCommand(String.raw `WindowsPowerShell\v1.0\powershell.exe`),
40
+ args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-File", script, "-Operation", operation],
41
+ // Preserve Windows UTF-16 spelling and leave long paths out of the bounded
42
+ // command line. This overrides any inherited value only in this child.
43
+ env: {
44
+ ...process.env,
45
+ FS_SAFE_WINDOWS_SECURITY_PATH: targetPath,
46
+ FS_SAFE_WINDOWS_SECURITY_REQUIRE_PRIVATE: params.requirePrivate ? "1" : "0",
47
+ FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY: params.expectedParentIdentity ?? "",
48
+ FS_SAFE_WINDOWS_SECURITY_FILE_IDENTITY: params.expectedFileIdentity ?? "",
49
+ FS_SAFE_WINDOWS_SECURITY_EXPECTED_LINKS: String(params.expectedLinks ?? 1),
50
+ },
51
+ };
52
+ }
53
+ function unverified(message, cause) {
54
+ throw new FsSafeError("permission-unverified", message, cause === undefined ? {} : { cause });
55
+ }
56
+ function record(value) {
57
+ return value !== null && typeof value === "object" && !Array.isArray(value);
58
+ }
59
+ function parseReply(stdout, operation) {
60
+ try {
61
+ return parseReplyValue(stdout, operation);
62
+ }
63
+ catch (error) {
64
+ if (operation === "create" && error instanceof Error && error.code !== "EEXIST") {
65
+ Object.assign(error, { creationOutcome: "unconfirmed" });
66
+ }
67
+ throw error;
68
+ }
69
+ }
70
+ function parseReplyValue(stdout, operation) {
71
+ let response;
72
+ try {
73
+ response = JSON.parse(stdout.trim());
74
+ }
75
+ catch (cause) {
76
+ unverified("Windows security command returned invalid data", cause);
77
+ }
78
+ if (!record(response) || typeof response.ok !== "boolean") {
79
+ unverified("Windows security command returned an incomplete response");
80
+ }
81
+ if (!response.ok) {
82
+ const codes = new Set(["EACCES", "EPERM", "EEXIST", "ENOENT", "ENOTSUP", "EIO", "EBADF", "ELOOP", "ENOTDIR", "EINVAL", "ENOSPC", "EBUSY"]);
83
+ if (typeof response.code !== "string" || !codes.has(response.code) || typeof response.message !== "string") {
84
+ unverified("Windows security command returned an invalid failure");
85
+ }
86
+ throw Object.assign(new Error(response.message), { code: response.code });
87
+ }
88
+ if (operation === "create" && (!record(response.result) || response.result.created !== true || !isFullIdentity(response.result.identity))) {
89
+ unverified("Windows security command did not verify private-directory creation");
90
+ }
91
+ return response.result;
92
+ }
93
+ function executeSync(operation, params) {
94
+ const { file, args, env } = command(operation, params);
95
+ const startedAt = performance.now();
96
+ const result = spawnSync(file, args, {
97
+ encoding: "utf8", windowsHide: true, env, stdio: [params.fd ?? "ignore", "pipe", "pipe"],
98
+ timeout: DEFAULT_PERMISSION_EXEC_TIMEOUT_MS, killSignal: "SIGKILL", maxBuffer: MAX_OUTPUT_BYTES,
99
+ });
100
+ if (result.error || result.status !== 0) {
101
+ const error = new PermissionCommandError(file, performance.now() - startedAt, {
102
+ cause: result.error, code: result.status,
103
+ signal: result.error?.code === "ETIMEDOUT" ? "SIGKILL" : result.signal,
104
+ stderr: result.stderr,
105
+ killed: result.error?.code === "ETIMEDOUT",
106
+ ...(operation === "create" ? { creationOutcome: "unconfirmed" } : {}),
107
+ });
108
+ if (operation === "create")
109
+ Object.assign(error, { creationOutcome: "unconfirmed" });
110
+ throw error;
111
+ }
112
+ return parseReply(result.stdout, operation);
113
+ }
114
+ class WindowsSecurityCommandError extends PermissionCommandError {
115
+ timedOut;
116
+ creationOutcome;
117
+ processExitConfirmed;
118
+ constructor(file, durationMs, receipt, timedOut) {
119
+ super(file, durationMs, receipt);
120
+ this.timedOut = timedOut;
121
+ this.creationOutcome = receipt.creationOutcome;
122
+ this.processExitConfirmed = receipt.processExitConfirmed;
123
+ if (timedOut)
124
+ this.message = `Windows permission inspection timed out after ${DEFAULT_PERMISSION_EXEC_TIMEOUT_MS}ms`;
125
+ if (!receipt.processExitConfirmed)
126
+ this.message += "; process exit was not confirmed";
127
+ else if (!receipt.outputClosed)
128
+ this.message += "; command output did not close";
129
+ if (receipt.creationOutcome)
130
+ this.message += "; private-directory creation outcome is unconfirmed";
131
+ }
132
+ }
133
+ /** Cleanup must preserve stages still reachable by an unsettled command. */
134
+ export function hasUnsettledWindowsSecurityCommand(error) {
135
+ const pending = [error];
136
+ const seen = new Set();
137
+ for (let inspected = 0; pending.length && inspected < 32; inspected++) {
138
+ const current = pending.pop();
139
+ if (current === null || typeof current !== "object" || seen.has(current))
140
+ continue;
141
+ seen.add(current);
142
+ try {
143
+ if (current instanceof WindowsSecurityCommandError && !current.processExitConfirmed)
144
+ return true;
145
+ const cause = Object.getOwnPropertyDescriptor(current, "cause");
146
+ if (cause && "value" in cause)
147
+ pending.push(cause.value);
148
+ if (current instanceof AggregateError) {
149
+ const errors = Object.getOwnPropertyDescriptor(current, "errors");
150
+ if (errors && "value" in errors && Array.isArray(errors.value))
151
+ pending.push(...errors.value.slice(0, 32));
152
+ }
153
+ }
154
+ catch {
155
+ // Unknown error wrappers cannot prove that command-owned handles settled.
156
+ return true;
157
+ }
158
+ }
159
+ return pending.length > 0;
160
+ }
161
+ function ignoreLateError() { }
162
+ async function execute(operation, params) {
163
+ const { file, args, env } = command(operation, params);
164
+ const startedAt = performance.now();
165
+ return await new Promise((resolve, reject) => {
166
+ const child = spawn(file, args, { windowsHide: true, env, stdio: [params.fd ?? "ignore", "pipe", "pipe"] });
167
+ const output = [];
168
+ const errors = [];
169
+ let bytes = 0;
170
+ let failure;
171
+ let failed = false;
172
+ let timedOut = false;
173
+ let stopping = false;
174
+ let settled = false;
175
+ let processExitConfirmed = false;
176
+ let exitCode = null;
177
+ let exitSignal = null;
178
+ let terminationSignalSent;
179
+ let terminationError;
180
+ let grace;
181
+ let missingPipes;
182
+ const deadlineError = () => new Error("Windows security command exceeded its inspection deadline");
183
+ const timeout = setTimeout(() => {
184
+ timedOut = true;
185
+ fail(deadlineError());
186
+ }, DEFAULT_PERMISSION_EXEC_TIMEOUT_MS);
187
+ const finish = (outputClosed) => {
188
+ if (settled)
189
+ return;
190
+ settled = true;
191
+ clearTimeout(timeout);
192
+ clearTimeout(grace);
193
+ clearImmediate(missingPipes);
194
+ child.removeListener("exit", onExit);
195
+ child.removeListener("close", onClose);
196
+ child.removeListener("error", onChildError);
197
+ child.on("error", ignoreLateError);
198
+ const cleanupErrors = [];
199
+ for (const [stream, collect] of [[child.stdout, collectOutput], [child.stderr, collectErrors]]) {
200
+ if (!stream)
201
+ continue;
202
+ stream.removeListener("data", collect);
203
+ stream.removeListener("error", fail);
204
+ stream.on("error", ignoreLateError);
205
+ if (!outputClosed) {
206
+ try {
207
+ stream.destroy();
208
+ }
209
+ catch (error) {
210
+ cleanupErrors.push(error);
211
+ }
212
+ }
213
+ }
214
+ if (!outputClosed && !processExitConfirmed) {
215
+ try {
216
+ child.unref();
217
+ }
218
+ catch (error) {
219
+ cleanupErrors.push(error);
220
+ }
221
+ }
222
+ if (failed || exitCode !== 0 || !outputClosed) {
223
+ reject(new WindowsSecurityCommandError(file, performance.now() - startedAt, {
224
+ cause: failure, pid: child.pid ?? null, code: exitCode, signal: exitSignal, stderr: Buffer.concat(errors),
225
+ processExitConfirmed, outputClosed, terminationSignalSent, terminationError, cleanupErrors,
226
+ ...(operation === "create" ? { creationOutcome: "unconfirmed" } : {}),
227
+ }, timedOut));
228
+ }
229
+ else {
230
+ try {
231
+ resolve(parseReply(Buffer.concat(output).toString("utf8"), operation));
232
+ }
233
+ catch (error) {
234
+ reject(error);
235
+ }
236
+ }
237
+ output.length = 0;
238
+ errors.length = 0;
239
+ };
240
+ const fail = (error) => {
241
+ if (settled)
242
+ return;
243
+ if (!failed) {
244
+ failed = true;
245
+ failure = error;
246
+ }
247
+ if (stopping)
248
+ return;
249
+ stopping = true;
250
+ clearTimeout(timeout);
251
+ // kill() can synchronously emit error, throw, or return false. None proves exit.
252
+ grace = setTimeout(() => finish(false), TERMINATION_GRACE_MS);
253
+ if (!processExitConfirmed) {
254
+ try {
255
+ terminationSignalSent = child.kill("SIGKILL");
256
+ }
257
+ catch (error) {
258
+ terminationError ??= error;
259
+ }
260
+ }
261
+ };
262
+ const collect = (chunks) => (chunk) => {
263
+ if (failed || settled)
264
+ return;
265
+ bytes += chunk.length;
266
+ if (bytes <= MAX_OUTPUT_BYTES)
267
+ chunks.push(chunk);
268
+ else
269
+ fail(new Error("Windows security command exceeded its output budget"));
270
+ };
271
+ const collectOutput = collect(output);
272
+ const collectErrors = collect(errors);
273
+ const onChildError = (error) => {
274
+ if (settled)
275
+ return;
276
+ if (stopping)
277
+ terminationError ??= error;
278
+ else
279
+ fail(error);
280
+ };
281
+ const onExit = (code, signal) => {
282
+ processExitConfirmed = true;
283
+ exitCode = code;
284
+ exitSignal = signal;
285
+ };
286
+ const onClose = (code, signal) => {
287
+ onExit(code, signal);
288
+ if (!failed && performance.now() - startedAt >= DEFAULT_PERMISSION_EXEC_TIMEOUT_MS) {
289
+ timedOut = true;
290
+ failed = true;
291
+ failure = deadlineError();
292
+ }
293
+ finish(true);
294
+ };
295
+ child.on("error", onChildError);
296
+ child.once("exit", onExit);
297
+ // Success still joins exit and both pipes. Failure releases only owned endpoints
298
+ // after grace; the child inherited its own HANDLE, independent of the caller fd.
299
+ child.once("close", onClose);
300
+ child.stdout?.on("error", fail);
301
+ child.stderr?.on("error", fail);
302
+ child.stdout?.on("data", collectOutput);
303
+ child.stderr?.on("data", collectErrors);
304
+ if (!child.stdout || !child.stderr) {
305
+ // Node reports spawn errors on nextTick, which can follow the current microtask.
306
+ missingPipes = setImmediate(() => { if (!failed && !settled)
307
+ fail(new Error("Windows security command output pipes are unavailable")); });
308
+ }
309
+ });
310
+ }
311
+ export function readWindowsSecurityFactsCommand(targetPath) {
312
+ return parseWindowsSecurityCommandFacts(executeSync("path", { targetPath }));
313
+ }
314
+ export async function inspectWindowsDescriptorCommand(fd) {
315
+ const response = await execute("descriptor", { fd });
316
+ if (!record(response) || typeof response.identity !== "string") {
317
+ unverified("Windows security command returned incomplete handle facts");
318
+ }
319
+ return { identity: response.identity, security: parseWindowsSecurityCommandFacts(response.security) };
320
+ }
321
+ export async function createPrivateWindowsDirectoryCommand(targetPath, expectedParentIdentity) {
322
+ return fullIdentityReceipt(await execute("create", { targetPath, expectedParentIdentity }));
323
+ }
324
+ export function createPrivateWindowsDirectoryCommandSync(targetPath, expectedParentIdentity) {
325
+ return fullIdentityReceipt(executeSync("create", { targetPath, expectedParentIdentity }));
326
+ }
327
+ function fullIdentityReceipt(response, expected) {
328
+ if (!record(response) || !isFullIdentity(response.identity)) {
329
+ unverified("Windows security command returned an incomplete file identity");
330
+ }
331
+ if (expected !== undefined && response.identity !== expected) {
332
+ throw new FsSafeError("path-mismatch", "Windows security command observed a different file");
333
+ }
334
+ return { identity: response.identity };
335
+ }
336
+ export function inspectWindowsDirectoryCommandSync(targetPath, requirePrivate) {
337
+ return fullIdentityReceipt(executeSync("directory", { targetPath, requirePrivate }));
338
+ }
339
+ export async function inspectWindowsDirectoryCommand(targetPath, requirePrivate) {
340
+ return fullIdentityReceipt(await execute("directory", { targetPath, requirePrivate }));
341
+ }
342
+ export function protectPrivateWindowsFileCommandSync(fd, targetPath, expectedParentIdentity) {
343
+ assertIdentity(expectedParentIdentity);
344
+ return fullIdentityReceipt(executeSync("protect-file", { fd, targetPath, expectedParentIdentity }));
345
+ }
346
+ export async function protectPrivateWindowsFileCommand(fd, targetPath, expectedParentIdentity) {
347
+ assertIdentity(expectedParentIdentity);
348
+ return fullIdentityReceipt(await execute("protect-file", { fd, targetPath, expectedParentIdentity }));
349
+ }
350
+ export function verifyPrivateWindowsFileCommandSync(fd, targetPath, expectedFileIdentity, expectedParentIdentity, expectedLinks = 1) {
351
+ assertIdentity(expectedFileIdentity);
352
+ assertIdentity(expectedParentIdentity);
353
+ fullIdentityReceipt(executeSync("verify-file", {
354
+ fd, targetPath, expectedFileIdentity, expectedParentIdentity, expectedLinks,
355
+ }), expectedFileIdentity);
356
+ }
357
+ export async function verifyPrivateWindowsFileCommand(fd, targetPath, expectedFileIdentity, expectedParentIdentity, expectedLinks = 1) {
358
+ assertIdentity(expectedFileIdentity);
359
+ assertIdentity(expectedParentIdentity);
360
+ fullIdentityReceipt(await execute("verify-file", {
361
+ fd, targetPath, expectedFileIdentity, expectedParentIdentity, expectedLinks,
362
+ }), expectedFileIdentity);
363
+ }
@@ -0,0 +1,6 @@
1
+ import type { NativeWindowsSecurityFacts } from "./native-binding.js";
2
+ /** Raw reporting retains unknown flag bits; secure admission validates them below. */
3
+ export declare function parseWindowsSecurityCommandFacts(value: unknown): NativeWindowsSecurityFacts;
4
+ /** Native and command observations share the same fail-closed admission policy. */
5
+ export declare function validateSecureWindowsSecurityFacts(value: unknown): NativeWindowsSecurityFacts;
6
+ //# sourceMappingURL=windows-security-facts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows-security-facts.d.ts","sourceRoot":"","sources":["../src/windows-security-facts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmC,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAmFvG,sFAAsF;AACtF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAU3F;AAED,mFAAmF;AACnF,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAoB7F"}
@@ -0,0 +1,108 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ const SID_RE = /^s-\d+-\d+(?:-\d+)+$/;
3
+ const SYSTEM_SID = "s-1-5-18";
4
+ const ADMINISTRATORS_SID = "s-1-5-32-544";
5
+ const WORLD_SIDS = new Set([
6
+ "s-1-1-0", "s-1-5-11", "s-1-5-32-545", "s-1-5-7",
7
+ "s-1-5-32-546", "s-1-5-4", "s-1-5-2",
8
+ ]);
9
+ function unverified(message) {
10
+ throw new FsSafeError("permission-unverified", message);
11
+ }
12
+ function record(value) {
13
+ return value !== null && typeof value === "object" && !Array.isArray(value);
14
+ }
15
+ function uint32(value) {
16
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff_ffff;
17
+ }
18
+ function validateAce(value, allowUnknownFlags) {
19
+ if (!record(value) || typeof value.sid !== "string" || !SID_RE.test(value.sid) ||
20
+ !uint32(value.mask) || (value.aceType !== "allow" && value.aceType !== "deny") ||
21
+ !record(value.flags))
22
+ return false;
23
+ const { raw } = value.flags;
24
+ if (!uint32(raw) || (raw & ~(allowUnknownFlags ? 0xff : 0xdf)) !== 0)
25
+ return false;
26
+ const fields = [
27
+ ["objectInherit", 0x01], ["containerInherit", 0x02],
28
+ ["noPropagateInherit", 0x04], ["inheritOnly", 0x08], ["inherited", 0x10],
29
+ ["successfulAccess", 0x40], ["failedAccess", 0x80],
30
+ ];
31
+ const flags = value.flags;
32
+ return fields.every(([field, bit]) => typeof flags[field] === "boolean" && flags[field] === ((raw & bit) !== 0));
33
+ }
34
+ function validateDescriptor(value, allowUnknownFlags) {
35
+ if (!record(value) || typeof value.ownerSid !== "string" || !SID_RE.test(value.ownerSid) ||
36
+ typeof value.currentUserSid !== "string" || !SID_RE.test(value.currentUserSid) ||
37
+ typeof value.daclPresent !== "boolean" || typeof value.isLocal !== "boolean" ||
38
+ typeof value.aceListComplete !== "boolean" || !Array.isArray(value.unsupportedAceTypes) ||
39
+ !value.unsupportedAceTypes.every(uint32) || !Array.isArray(value.aces) ||
40
+ !value.aces.every(ace => validateAce(ace, allowUnknownFlags))) {
41
+ unverified("Windows descriptor ACL facts were malformed");
42
+ }
43
+ if ((!value.daclPresent && value.aces.length !== 0) ||
44
+ (value.aceListComplete && value.unsupportedAceTypes.length !== 0)) {
45
+ unverified("Windows descriptor DACL facts were inconsistent");
46
+ }
47
+ return value;
48
+ }
49
+ function summarize(facts) {
50
+ const ownerClass = facts.ownerSid === facts.currentUserSid ? "current-user"
51
+ : facts.ownerSid === SYSTEM_SID ? "system"
52
+ : facts.ownerSid === ADMINISTRATORS_SID ? "administrators" : "foreign";
53
+ let worldReadable = !facts.daclPresent;
54
+ let worldWritable = !facts.daclPresent;
55
+ let groupReadable = false;
56
+ let groupWritable = false;
57
+ const trusted = new Set([facts.currentUserSid, SYSTEM_SID, ADMINISTRATORS_SID]);
58
+ for (const ace of facts.aces) {
59
+ if (ace.aceType !== "allow" || ace.flags.inheritOnly || trusted.has(ace.sid))
60
+ continue;
61
+ // Generic and file-specific read/write, delete, and security-control rights.
62
+ const canRead = (ace.mask & 0x9000_0089) !== 0;
63
+ const canWrite = (ace.mask & 0x500d_0156) !== 0;
64
+ if (WORLD_SIDS.has(ace.sid)) {
65
+ worldReadable ||= canRead;
66
+ worldWritable ||= canWrite;
67
+ }
68
+ else {
69
+ groupReadable ||= canRead;
70
+ groupWritable ||= canWrite;
71
+ }
72
+ }
73
+ return { ownerClass, worldReadable, worldWritable, groupReadable, groupWritable };
74
+ }
75
+ /** Raw reporting retains unknown flag bits; secure admission validates them below. */
76
+ export function parseWindowsSecurityCommandFacts(value) {
77
+ const facts = validateDescriptor(value, true);
78
+ if (!record(value) || typeof value.daclProtected !== "boolean") {
79
+ unverified("Windows security command returned malformed descriptor facts");
80
+ }
81
+ return {
82
+ ...facts,
83
+ ...summarize(facts),
84
+ fallbackRequired: !facts.isLocal || !facts.aceListComplete,
85
+ };
86
+ }
87
+ /** Native and command observations share the same fail-closed admission policy. */
88
+ export function validateSecureWindowsSecurityFacts(value) {
89
+ const descriptor = validateDescriptor(value, false);
90
+ const facts = value;
91
+ const summary = summarize(descriptor);
92
+ const booleans = ["worldWritable", "groupWritable", "worldReadable", "groupReadable", "fallbackRequired"];
93
+ if (!booleans.every(field => typeof facts[field] === "boolean") ||
94
+ typeof facts.ownerClass !== "string") {
95
+ unverified("Windows descriptor ACL facts were malformed");
96
+ }
97
+ if (facts.ownerClass !== summary.ownerClass) {
98
+ unverified("Windows descriptor owner facts were inconsistent");
99
+ }
100
+ if (facts.fallbackRequired || !facts.isLocal || !facts.aceListComplete || facts.unsupportedAceTypes.length !== 0) {
101
+ unverified("Windows descriptor ACL facts were incomplete or unsupported");
102
+ }
103
+ if (facts.worldReadable !== summary.worldReadable || facts.worldWritable !== summary.worldWritable ||
104
+ facts.groupReadable !== summary.groupReadable || facts.groupWritable !== summary.groupWritable) {
105
+ unverified("Windows descriptor ACL summary was inconsistent");
106
+ }
107
+ return facts;
108
+ }
@@ -1,4 +1,11 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
+ export type WriteFileWindowOptions = {
3
+ signal?: AbortSignal;
4
+ /** Synchronous authority check immediately before every write, including short-write retries. */
5
+ assertBeforeMutation?: () => void;
6
+ };
7
+ /** Write borrowed bytes completely; null advances the cursor, an explicit position preserves it. */
8
+ export declare function writeFileWindowFully(handle: FileHandle, bytes: Uint8Array, position: number | null, options?: WriteFileWindowOptions): Promise<void>;
2
9
  export declare function writeAllToFile(target: FileHandle | number, data: string | Uint8Array, options?: {
3
10
  encoding?: BufferEncoding;
4
11
  position?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"write-file-handle.d.ts","sourceRoot":"","sources":["../src/write-file-handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GAChG,OAAO,CAAC,IAAI,CAAC,CAoBf"}
1
+ {"version":3,"file":"write-file-handle.d.ts","sourceRoot":"","sources":["../src/write-file-handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAOnD,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iGAAiG;IACjG,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAEF,oGAAoG;AACpG,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GAChG,OAAO,CAAC,IAAI,CAAC,CAoBf"}
@@ -1,6 +1,29 @@
1
1
  import fs from "node:fs";
2
+ import { snapshotByteView } from "./byte-view.js";
2
3
  import { FsSafeError } from "./errors.js";
4
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
3
5
  const WRITE_CHUNK_BYTES = 512 * 1024;
6
+ /** Write borrowed bytes completely; null advances the cursor, an explicit position preserves it. */
7
+ export async function writeFileWindowFully(handle, bytes, position, options = {}) {
8
+ const payload = snapshotByteView(bytes);
9
+ if (position !== null && (!Number.isSafeInteger(position) || position < 0 ||
10
+ !Number.isSafeInteger(position + payload.byteLength))) {
11
+ throw new RangeError("write position and window end must be non-negative safe integers");
12
+ }
13
+ const signal = options.signal;
14
+ const assertion = options.assertBeforeMutation;
15
+ signal?.throwIfAborted();
16
+ await writeAllToFile(handle, payload, {
17
+ position: position === null ? undefined : position,
18
+ assertBeforeMutation: () => {
19
+ signal?.throwIfAborted();
20
+ assertSynchronousCallbackResult(assertion === undefined ? undefined : Reflect.apply(assertion, options, []), "assertBeforeMutation");
21
+ signal?.throwIfAborted();
22
+ },
23
+ });
24
+ // A pending write must settle before cancellation releases the caller's buffer and handle.
25
+ signal?.throwIfAborted();
26
+ }
4
27
  export async function writeAllToFile(target, data, options = {}) {
5
28
  const buffer = typeof data === "string" ? Buffer.from(data, options.encoding ?? "utf8") : data;
6
29
  let offset = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"write-open-flags.d.ts","sourceRoot":"","sources":["../src/write-open-flags.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,wBAAgB,2BAA2B,CACzC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAoB,GACjF,MAAM,CAIR;AAUD,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
1
+ {"version":3,"file":"write-open-flags.d.ts","sourceRoot":"","sources":["../src/write-open-flags.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,wBAAgB,2BAA2B,CACzC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAoB,GACjF,MAAM,CAIR;AAUD,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
@@ -13,14 +13,7 @@ function isNonblockingWriteEnxio(error, flags) {
13
13
  // A no-reader FIFO rejects a nonblocking write-only open before fstat is possible.
14
14
  // Reclassify only a confirmed non-regular path; inconclusive races keep the errno.
15
15
  export async function isNonRegularWriteOpenError(error, filePath, flags) {
16
- if (!isNonblockingWriteEnxio(error, flags))
17
- return false;
18
- try {
19
- return !fsSync.lstatSync(filePath).isFile();
20
- }
21
- catch {
22
- return false;
23
- }
16
+ return isNonRegularWriteOpenErrorSync(error, filePath, flags);
24
17
  }
25
18
  export function isNonRegularWriteOpenErrorSync(error, filePath, flags) {
26
19
  if (!isNonblockingWriteEnxio(error, flags))
@@ -1 +1 @@
1
- {"version":3,"file":"write-queue.d.ts","sourceRoot":"","sources":["../src/write-queue.ts"],"names":[],"mappings":"AAEA,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAkB1F"}
1
+ {"version":3,"file":"write-queue.d.ts","sourceRoot":"","sources":["../src/write-queue.ts"],"names":[],"mappings":"AAEA,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAe1F"}
@@ -1,10 +1,7 @@
1
1
  const writeQueues = new Map();
2
2
  export async function serializePathWrite(key, run) {
3
3
  const previous = writeQueues.get(key) ?? Promise.resolve();
4
- const task = (async () => {
5
- await previous.catch(() => undefined);
6
- return await run();
7
- })();
4
+ const task = previous.then(() => run());
8
5
  const done = task.then(() => undefined, () => undefined);
9
6
  writeQueues.set(key, done);
10
7
  try {