@openclaw/fs-safe 0.15.0 → 0.16.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 (184) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +28 -6
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-parser.wasm +0 -0
  10. package/dist/archive-read.d.ts.map +1 -1
  11. package/dist/archive-read.js +6 -7
  12. package/dist/archive-tar-stream.d.ts +3 -0
  13. package/dist/archive-tar-stream.d.ts.map +1 -1
  14. package/dist/archive-tar-stream.js +56 -37
  15. package/dist/archive-tar-wasm.d.ts +16 -4
  16. package/dist/archive-tar-wasm.d.ts.map +1 -1
  17. package/dist/archive-tar-wasm.js +134 -34
  18. package/dist/archive.d.ts.map +1 -1
  19. package/dist/archive.js +5 -6
  20. package/dist/clone-metadata.d.ts +1 -0
  21. package/dist/clone-metadata.d.ts.map +1 -1
  22. package/dist/clone-metadata.js +6 -2
  23. package/dist/create-directory.d.ts +20 -0
  24. package/dist/create-directory.d.ts.map +1 -0
  25. package/dist/create-directory.js +130 -0
  26. package/dist/create-file-async.d.ts +7 -0
  27. package/dist/create-file-async.d.ts.map +1 -0
  28. package/dist/create-file-async.js +121 -0
  29. package/dist/create-file.d.ts +8 -0
  30. package/dist/create-file.d.ts.map +1 -0
  31. package/dist/create-file.js +190 -0
  32. package/dist/create-owned-file.d.ts +8 -0
  33. package/dist/create-owned-file.d.ts.map +1 -0
  34. package/dist/create-owned-file.js +16 -0
  35. package/dist/create.d.ts +4 -0
  36. package/dist/create.d.ts.map +1 -0
  37. package/dist/create.js +2 -0
  38. package/dist/creation-darwin.d.ts +7 -0
  39. package/dist/creation-darwin.d.ts.map +1 -0
  40. package/dist/creation-darwin.js +79 -0
  41. package/dist/creation-file-state.d.ts +19 -0
  42. package/dist/creation-file-state.d.ts.map +1 -0
  43. package/dist/creation-file-state.js +118 -0
  44. package/dist/creation-path.d.ts +21 -0
  45. package/dist/creation-path.d.ts.map +1 -0
  46. package/dist/creation-path.js +71 -0
  47. package/dist/creation-permissions.d.ts +19 -0
  48. package/dist/creation-permissions.d.ts.map +1 -0
  49. package/dist/creation-permissions.js +125 -0
  50. package/dist/directory-durability.d.ts +1 -1
  51. package/dist/directory-durability.d.ts.map +1 -1
  52. package/dist/directory-durability.js +22 -80
  53. package/dist/directory-guard.d.ts +3 -0
  54. package/dist/directory-guard.d.ts.map +1 -1
  55. package/dist/directory-mode-node.d.ts +2 -0
  56. package/dist/directory-mode-node.d.ts.map +1 -1
  57. package/dist/directory-mode-node.js +8 -0
  58. package/dist/directory-receipt.d.ts +24 -0
  59. package/dist/directory-receipt.d.ts.map +1 -0
  60. package/dist/directory-receipt.js +127 -0
  61. package/dist/file-cleanup.d.ts +19 -0
  62. package/dist/file-cleanup.d.ts.map +1 -0
  63. package/dist/file-cleanup.js +78 -0
  64. package/dist/file-observation.d.ts +1 -1
  65. package/dist/file-observation.d.ts.map +1 -1
  66. package/dist/file-store-boundary.d.ts +2 -6
  67. package/dist/file-store-boundary.d.ts.map +1 -1
  68. package/dist/file-store-boundary.js +3 -9
  69. package/dist/file-store-sync-write.d.ts.map +1 -1
  70. package/dist/file-store-sync-write.js +2 -5
  71. package/dist/guarded-mkdir.d.ts +1 -0
  72. package/dist/guarded-mkdir.d.ts.map +1 -1
  73. package/dist/guarded-mkdir.js +36 -7
  74. package/dist/move-path.js +1 -1
  75. package/dist/native-binding.d.ts +11 -1
  76. package/dist/native-binding.d.ts.map +1 -1
  77. package/dist/native-fallback-warning.d.ts +4 -0
  78. package/dist/native-fallback-warning.d.ts.map +1 -0
  79. package/dist/native-fallback-warning.js +11 -0
  80. package/dist/native-operations.d.ts +0 -2
  81. package/dist/native-operations.d.ts.map +1 -1
  82. package/dist/native-operations.js +0 -24
  83. package/dist/native-parent-admission.d.ts +2 -0
  84. package/dist/native-parent-admission.d.ts.map +1 -1
  85. package/dist/native-parent-admission.js +3 -2
  86. package/dist/native-pinned-write-windows.d.ts +1 -1
  87. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  88. package/dist/native-pinned-write-windows.js +173 -28
  89. package/dist/native-pinned-write.d.ts.map +1 -1
  90. package/dist/native-pinned-write.js +19 -3
  91. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  92. package/dist/native-policy-parent-windows.js +15 -6
  93. package/dist/native-staged-file.d.ts +3 -2
  94. package/dist/native-staged-file.d.ts.map +1 -1
  95. package/dist/native-staged-file.js +86 -39
  96. package/dist/owner-dacl.d.ts.map +1 -1
  97. package/dist/owner-dacl.js +10 -4
  98. package/dist/pinned-write-input.d.ts +4 -0
  99. package/dist/pinned-write-input.d.ts.map +1 -0
  100. package/dist/pinned-write-input.js +25 -0
  101. package/dist/pinned-write-mode.d.ts +5 -0
  102. package/dist/pinned-write-mode.d.ts.map +1 -0
  103. package/dist/pinned-write-mode.js +24 -0
  104. package/dist/pinned-write-staged.d.ts +6 -0
  105. package/dist/pinned-write-staged.d.ts.map +1 -0
  106. package/dist/pinned-write-staged.js +187 -0
  107. package/dist/pinned-write-types.d.ts +3 -0
  108. package/dist/pinned-write-types.d.ts.map +1 -1
  109. package/dist/pinned-write.d.ts.map +1 -1
  110. package/dist/pinned-write.js +35 -145
  111. package/dist/private-directory.d.ts.map +1 -1
  112. package/dist/private-directory.js +18 -4
  113. package/dist/private-producer-handoff-sync.d.ts +14 -0
  114. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  115. package/dist/private-producer-handoff-sync.js +114 -0
  116. package/dist/private-producer-handoff.d.ts +22 -4
  117. package/dist/private-producer-handoff.d.ts.map +1 -1
  118. package/dist/private-producer-handoff.js +140 -77
  119. package/dist/publish-copy-stage.d.ts +2 -1
  120. package/dist/publish-copy-stage.d.ts.map +1 -1
  121. package/dist/publish-copy-stage.js +16 -7
  122. package/dist/publish-file.d.ts.map +1 -1
  123. package/dist/publish-file.js +2 -2
  124. package/dist/replace-file-temp-owner.d.ts +0 -7
  125. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  126. package/dist/replace-file-temp-owner.js +3 -54
  127. package/dist/root-create-input.d.ts +2 -1
  128. package/dist/root-create-input.d.ts.map +1 -1
  129. package/dist/root-create-input.js +13 -4
  130. package/dist/root-directory-creation.d.ts +3 -3
  131. package/dist/root-directory-creation.d.ts.map +1 -1
  132. package/dist/root-directory-creation.js +15 -3
  133. package/dist/root-impl.d.ts.map +1 -1
  134. package/dist/root-impl.js +28 -7
  135. package/dist/root-move-noreplace.d.ts.map +1 -1
  136. package/dist/root-move-noreplace.js +22 -13
  137. package/dist/root-options.d.ts +12 -4
  138. package/dist/root-options.d.ts.map +1 -1
  139. package/dist/root-path-stat.d.ts.map +1 -1
  140. package/dist/root-path-stat.js +59 -7
  141. package/dist/root-write-publication.js +1 -1
  142. package/dist/secret-file.d.ts.map +1 -1
  143. package/dist/secret-file.js +1 -0
  144. package/dist/secure-file-windows.d.ts +6 -0
  145. package/dist/secure-file-windows.d.ts.map +1 -1
  146. package/dist/secure-file-windows.js +34 -117
  147. package/dist/secure-file.js +2 -2
  148. package/dist/sidecar-lock-root.d.ts.map +1 -1
  149. package/dist/sidecar-lock-root.js +2 -1
  150. package/dist/staged-directory.d.ts.map +1 -1
  151. package/dist/staged-directory.js +6 -6
  152. package/dist/staged-file-settlement.d.ts +17 -0
  153. package/dist/staged-file-settlement.d.ts.map +1 -0
  154. package/dist/staged-file-settlement.js +57 -0
  155. package/dist/windows-owner.d.ts.map +1 -1
  156. package/dist/windows-owner.js +2 -1
  157. package/dist/windows-security-bridge.cs +336 -0
  158. package/dist/windows-security-bridge.ps1 +15 -0
  159. package/dist/windows-security-command.d.ts +26 -0
  160. package/dist/windows-security-command.d.ts.map +1 -0
  161. package/dist/windows-security-command.js +363 -0
  162. package/dist/windows-security-facts.d.ts +6 -0
  163. package/dist/windows-security-facts.d.ts.map +1 -0
  164. package/dist/windows-security-facts.js +108 -0
  165. package/docs/advanced.md +3 -1
  166. package/docs/archive.md +61 -37
  167. package/docs/config.md +6 -2
  168. package/docs/contributing.md +44 -4
  169. package/docs/copy.md +2 -0
  170. package/docs/creation.md +128 -0
  171. package/docs/durability.md +24 -0
  172. package/docs/install.md +31 -7
  173. package/docs/migrating-to-0.5.md +15 -6
  174. package/docs/migrating-to-0.6.md +9 -4
  175. package/docs/native-helper.md +22 -9
  176. package/docs/native.md +38 -7
  177. package/docs/permissions.md +37 -14
  178. package/docs/root.md +34 -0
  179. package/docs/secret-file.md +11 -2
  180. package/docs/secure-file.md +9 -4
  181. package/docs/sidecar-lock.md +5 -4
  182. package/docs/staged-file.md +5 -0
  183. package/docs/writing.md +71 -5
  184. package/package.json +18 -15
@@ -2,11 +2,20 @@ import { syncFileBestEffortSync } from "./file-sync.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import fsSync, {} from "node:fs";
4
4
  import path from "node:path";
5
+ import { createFileHandle } from "./create.js";
6
+ import { hasPreservedCreationArtifacts } from "./creation-file-state.js";
7
+ import { assertSyncDirectoryGuard } from "./directory-guard.js";
5
8
  import { FsSafeError } from "./errors.js";
6
- import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, writeNativeInput, } from "./native-operations.js";
9
+ import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, } from "./native-operations.js";
10
+ import { writePinnedInput } from "./pinned-write-input.js";
7
11
  import { captureNativeFdClose } from "./native-binding.js";
8
12
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
9
13
  import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
14
+ import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
15
+ import { cleanupPinnedFilePath } from "./file-cleanup.js";
16
+ import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
17
+ import { exactIdentityMatches } from "./staged-directory.js";
18
+ import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
10
19
  function closeWriteFd(closeFd, fd) {
11
20
  if (fd === undefined)
12
21
  return;
@@ -17,39 +26,129 @@ function closeWriteFd(closeFd, fd) {
17
26
  return { error };
18
27
  }
19
28
  }
29
+ function readParentIdentity(parentFd, guard) {
30
+ const stat = inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true }));
31
+ if (!stat.isDirectory() || !exactIdentityMatches(guard.stat, stat)) {
32
+ throw new FsSafeError("path-mismatch", "native write parent identity changed");
33
+ }
34
+ return stat;
35
+ }
36
+ function assertNamedParentCurrent(binding, guard, identity) {
37
+ if (typeof binding.observeDirectory === "function") {
38
+ try {
39
+ const observed = inspectNativeDirectoryObservation(binding, guard.dir, identity);
40
+ if (observed.realPath !== guard.realPath) {
41
+ throw new FsSafeError("path-mismatch", "native write parent pathname changed");
42
+ }
43
+ return;
44
+ }
45
+ catch (error) {
46
+ if (error?.code !== "OBSERVATION_UNAVAILABLE")
47
+ throw error;
48
+ }
49
+ }
50
+ assertSyncDirectoryGuard({ ...guard, stat: identity });
51
+ }
20
52
  export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
21
53
  const closeFd = captureNativeFdClose(binding);
22
54
  const parentPath = parentGuard.realPath;
23
55
  let tempFd;
56
+ let privateHandle;
24
57
  let targetFd;
58
+ let publicationProbeFd;
25
59
  let tempIdentity;
26
60
  let tempName = "";
27
61
  let renamed = false;
28
62
  let completed = false;
63
+ const completeCreate = params.overwrite === false && params.input.kind !== "file" && params.input.stageBeforePublish === true;
64
+ let receipt;
65
+ let publication = Object.freeze({ status: "not-published" });
66
+ let phase = "prepare";
67
+ let failure;
68
+ const closeTemp = async () => {
69
+ const handle = privateHandle;
70
+ const fd = tempFd;
71
+ privateHandle = undefined;
72
+ tempFd = undefined;
73
+ if (handle)
74
+ await handle.close();
75
+ else if (fd !== undefined)
76
+ closeFd(fd);
77
+ };
29
78
  try {
30
79
  tempName = `.fs-safe-${randomUUID()}.tmp`;
31
80
  params.assertBeforeMutation?.();
32
- tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
81
+ if (params.private) {
82
+ const parentIdentity = fsSync.fstatSync(parentFd, { bigint: true });
83
+ privateHandle = await createFileHandle(path.join(parentPath, tempName), {
84
+ private: true,
85
+ mode: 0o600,
86
+ assertBeforeMutation: params.assertBeforeMutation,
87
+ }, {
88
+ expectedParentIdentity: {
89
+ dev: parentIdentity.dev, ino: parentIdentity.ino, realPath: parentPath,
90
+ },
91
+ });
92
+ tempFd = privateHandle.fd;
93
+ }
94
+ else {
95
+ tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
96
+ }
33
97
  const verificationIdentity = inspectFileIdentitySync(() => fsSync.fstatSync(tempFd, { bigint: true }));
34
98
  tempIdentity = verificationIdentity;
35
99
  // Creation is requested at 0600 in the binding, but a restrictive umask
36
100
  // can remove owner access. Keep the unpublished inode private and
37
101
  // reopenable until the published name has been identity-fenced.
38
102
  fsSync.fchmodSync(tempFd, 0o600);
39
- await writeNativeInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
40
- if (params.sync !== false)
41
- syncFileBestEffortSync(tempFd);
103
+ await writePinnedInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
104
+ if (params.sync !== false) {
105
+ if (params.strictFileSync)
106
+ fsSync.fsyncSync(tempFd);
107
+ else
108
+ syncFileBestEffortSync(tempFd);
109
+ }
42
110
  if (params.input.kind === "file")
43
111
  await params.input.verifySource();
112
+ if (completeCreate) {
113
+ const parentIdentity = readParentIdentity(parentFd, parentGuard);
114
+ receipt = createStagedFileReceipt(Object.freeze({
115
+ path: parentGuard.dir,
116
+ realPath: parentGuard.realPath,
117
+ identity: Object.freeze({ dev: parentIdentity.dev, ino: parentIdentity.ino }),
118
+ }), tempName, fsSync.fstatSync(tempFd, { bigint: true }));
119
+ }
120
+ phase = "publish";
44
121
  assertFinalSymlinkRejected(path.join(parentPath, params.basename), params.rejectFinalSymlink);
45
122
  params.assertBeforeMutation?.();
46
- if (params.overwrite === false) {
47
- binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
123
+ if (params.assertBeforeMutation) {
124
+ // Callbacks can replace names synchronously. Fence the named parent and
125
+ // stage against retained descriptors before the rename can expose bytes.
126
+ assertNamedParentCurrent(binding, parentGuard, readParentIdentity(parentFd, parentGuard));
127
+ publicationProbeFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
128
+ const named = inspectFileIdentitySync(() => fsSync.fstatSync(publicationProbeFd, { bigint: true }), verificationIdentity);
129
+ const retained = inspectFileIdentitySync(() => fsSync.fstatSync(tempFd, { bigint: true }), verificationIdentity);
130
+ if (!named.isFile() || !retained.isFile() || named.nlink !== 1n || retained.nlink !== 1n) {
131
+ throw new FsSafeError("path-mismatch", "native write stage changed before publication");
132
+ }
133
+ assertFinalSymlinkRejected(path.join(parentPath, params.basename), params.rejectFinalSymlink);
48
134
  }
49
- else {
50
- binding.renameReplace(parentFd, tempName, parentFd, params.basename);
135
+ try {
136
+ if (params.overwrite === false) {
137
+ binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
138
+ }
139
+ else {
140
+ binding.renameReplace(parentFd, tempName, parentFd, params.basename);
141
+ }
142
+ }
143
+ catch (error) {
144
+ if (completeCreate && classifyNativeRenameFailure(error) === "indeterminate") {
145
+ publication = Object.freeze({ status: "indeterminate", basename: params.basename, overwrite: false });
146
+ }
147
+ throw error;
51
148
  }
52
149
  renamed = true;
150
+ if (receipt)
151
+ publication = Object.freeze({ status: "published", staged: receipt, basename: params.basename, overwrite: false });
53
152
  params.onPublished?.(verificationIdentity);
54
153
  targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
55
154
  const targetStat = inspectFileIdentitySync(() => fsSync.fstatSync(targetFd, { bigint: true }), verificationIdentity);
@@ -62,15 +161,21 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
62
161
  // verifiable and so broader modes are never exposed before that fence.
63
162
  try {
64
163
  fsSync.fchmodSync(targetFd, params.mode);
65
- if (params.sync !== false)
66
- syncFileBestEffortSync(targetFd);
164
+ // Windows FlushFileBuffers needs write access; the verification reopen is read-only.
165
+ if (params.sync !== false) {
166
+ if (params.strictFileSync)
167
+ fsSync.fsyncSync(tempFd);
168
+ else
169
+ syncFileBestEffortSync(tempFd);
170
+ }
67
171
  }
68
172
  catch (error) {
69
- closeWriteFd(closeFd, targetFd);
70
- targetFd = undefined;
173
+ if (!completeCreate) {
174
+ closeWriteFd(closeFd, targetFd);
175
+ targetFd = undefined;
176
+ }
71
177
  // Staged copies and streamed creates preserve publication for caller recovery.
72
- if (params.input.kind === "buffer" ||
73
- (params.input.kind === "stream" && !params.input.stageBeforePublish)) {
178
+ if (params.input.kind !== "file" && !params.input.stageBeforePublish) {
74
179
  removeNativeCreatedFileIfStillPinned({
75
180
  parentPath,
76
181
  parentFd,
@@ -87,21 +192,61 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
87
192
  completed = true;
88
193
  return { dev: targetIdentity.dev, ino: targetIdentity.ino };
89
194
  }
195
+ catch (error) {
196
+ failure = { error };
197
+ throw error;
198
+ }
90
199
  finally {
91
- const targetCloseFailure = closeWriteFd(closeFd, targetFd);
92
- const tempCloseFailure = closeWriteFd(closeFd, tempFd);
93
- if (!renamed) {
94
- removeNativeCreatedFileIfStillPinned({
95
- parentPath,
96
- parentFd,
97
- basename: tempName,
98
- created: tempIdentity,
200
+ const preservedPreparation = params.private && tempFd === undefined && hasPreservedCreationArtifacts(failure?.error);
201
+ if (completeCreate || preservedPreparation) {
202
+ await settleStagedFile({
203
+ temporaryBasename: tempName, publication, phase, failure,
204
+ cleanup: async () => {
205
+ if (preservedPreparation)
206
+ return "preserved";
207
+ if (publication.status === "indeterminate")
208
+ return "preserved";
209
+ if (renamed || tempFd === undefined)
210
+ return "not-needed";
211
+ return await cleanupPinnedFilePath({
212
+ pathname: path.join(parentPath, tempName), handle: { fd: tempFd },
213
+ identity: tempIdentity, parentGuard, throwOnCleanupError: true,
214
+ });
215
+ },
216
+ close: [
217
+ () => { if (targetFd !== undefined)
218
+ closeFd(targetFd); },
219
+ () => { if (publicationProbeFd !== undefined)
220
+ closeFd(publicationProbeFd); },
221
+ closeTemp,
222
+ () => closeFd(parentFd),
223
+ () => root.close(),
224
+ ],
99
225
  });
100
226
  }
101
- const parentCloseFailure = closeWriteFd(closeFd, parentFd);
102
- await root.close().catch(() => undefined);
103
- const closeFailure = targetCloseFailure ?? tempCloseFailure ?? parentCloseFailure;
104
- if (completed && closeFailure)
105
- throw closeFailure.error;
227
+ else {
228
+ const targetCloseFailure = closeWriteFd(closeFd, targetFd);
229
+ const probeCloseFailure = closeWriteFd(closeFd, publicationProbeFd);
230
+ let tempCloseFailure;
231
+ try {
232
+ await closeTemp();
233
+ }
234
+ catch (error) {
235
+ tempCloseFailure = { error };
236
+ }
237
+ if (!renamed) {
238
+ removeNativeCreatedFileIfStillPinned({
239
+ parentPath,
240
+ parentFd,
241
+ basename: tempName,
242
+ created: tempIdentity,
243
+ });
244
+ }
245
+ const parentCloseFailure = closeWriteFd(closeFd, parentFd);
246
+ await root.close().catch(() => undefined);
247
+ const closeFailure = targetCloseFailure ?? probeCloseFailure ?? tempCloseFailure ?? parentCloseFailure;
248
+ if (completed && closeFailure)
249
+ throw closeFailure.error;
250
+ }
106
251
  }
107
252
  }
@@ -1 +1 @@
1
- {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AA8W3B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6FvH"}
1
+ {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AA8W3B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8GvH"}
@@ -368,15 +368,18 @@ export async function runPinnedWriteNative(binding, params) {
368
368
  assertNativeStaging(binding);
369
369
  }
370
370
  const directoryFlags = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0);
371
+ const completeCreate = params.overwrite === false && params.input.kind !== "file" && params.input.stageBeforePublish === true;
371
372
  const rootAdmission = await openNativeRootAdmission(binding, {
372
373
  rootPath: params.rootPath,
373
374
  rootIdentity: params.rootIdentity,
374
375
  operation: "native write",
376
+ reportCloseErrors: completeCreate,
375
377
  });
376
378
  const root = rootAdmission.root;
377
379
  const posixRoot = __addDisposableResource(env_2, windows ? undefined : root, true);
378
380
  let parentFd;
379
381
  let windowsOwnsDirectories = false;
382
+ let admissionFailure;
380
383
  // Until stage construction takes ownership, even admission failures must close
381
384
  // the raw POSIX parent. Disposal preserves both admission and close failures.
382
385
  const parentGuard = __addDisposableResource(env_2, {
@@ -435,8 +438,13 @@ export async function runPinnedWriteNative(binding, params) {
435
438
  parentFd = undefined;
436
439
  return await writeNativeStage(binding, ownedParent, closeFd, directory, params, verificationGuard);
437
440
  }
441
+ catch (error) {
442
+ admissionFailure = { error };
443
+ throw error;
444
+ }
438
445
  finally {
439
446
  if (windows && !windowsOwnsDirectories) {
447
+ const closeErrors = [];
440
448
  if (parentFd !== undefined) {
441
449
  // Match the Windows leaf cleanup contract: an admission or identity
442
450
  // failure remains primary, while every owned directory is still given
@@ -445,11 +453,19 @@ export async function runPinnedWriteNative(binding, params) {
445
453
  try {
446
454
  closeFd(parentFd);
447
455
  }
448
- catch {
449
- // Best effort while propagating the operation failure.
456
+ catch (error) {
457
+ closeErrors.push(error);
450
458
  }
451
459
  }
452
- await root.close().catch(() => undefined);
460
+ try {
461
+ await root.close();
462
+ }
463
+ catch (error) {
464
+ closeErrors.push(error);
465
+ }
466
+ if (completeCreate && closeErrors.length > 0) {
467
+ throw new AggregateError([...(admissionFailure ? [admissionFailure.error] : []), ...closeErrors], "native create admission and close failed");
468
+ }
453
469
  }
454
470
  }
455
471
  }
@@ -1 +1 @@
1
- {"version":3,"file":"native-policy-parent-windows.d.ts","sourceRoot":"","sources":["../src/native-policy-parent-windows.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,OAAO,KAAK,EAAE,iBAAiB,EAAkG,MAAM,mBAAmB,CAAC;AAC3J,OAAO,EAA4B,KAAK,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAI/G,KAAK,YAAY,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACxC,WAAW,EAAE,4BAA4B,CAAC;CAC3C,CAAC;AAqBF,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,mBAAmB,GACjC,OAAO,CAAC,YAAY,CAAC,CA2KvB"}
1
+ {"version":3,"file":"native-policy-parent-windows.d.ts","sourceRoot":"","sources":["../src/native-policy-parent-windows.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,OAAO,KAAK,EAAE,iBAAiB,EAAkG,MAAM,mBAAmB,CAAC;AAC3J,OAAO,EAA4B,KAAK,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAK/G,KAAK,YAAY,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACxC,WAAW,EAAE,4BAA4B,CAAC;CAC3C,CAAC;AA4BF,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,mBAAmB,GACjC,OAAO,CAAC,YAAY,CAAC,CA+KvB"}
@@ -10,6 +10,7 @@ import { isSymlinkOpenError } from "./path.js";
10
10
  import { checkedMutationDirectory } from "./pinned-mutation-observation.js";
11
11
  import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
12
12
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
13
+ import { createSuppressedError } from "./suppressed-error.js";
13
14
  function assertParentCurrent(parent) {
14
15
  const current = inspectFileIdentitySync(() => fsSync.fstatSync(parent.fd, { bigint: true }), parent.guard.stat);
15
16
  assertDirectoryIdentitySync(parent.guard.dir, {
@@ -19,14 +20,17 @@ function assertParentCurrent(parent) {
19
20
  });
20
21
  return current;
21
22
  }
22
- function closeAfterFailure(closeFd, fd) {
23
+ function closeAfterFailure(closeFd, fd, failure, report = false) {
23
24
  if (fd === undefined)
24
25
  return;
25
26
  try {
26
27
  closeFd(fd);
27
28
  }
28
- catch {
29
- // Windows retains the operation failure while attempting every owned close.
29
+ catch (error) {
30
+ if (report)
31
+ throw failure
32
+ ? createSuppressedError(error, failure.error, "native parent admission and close failed")
33
+ : error;
30
34
  }
31
35
  }
32
36
  export async function capturePolicyAwareWindowsParent(binding, params, rootAdmission) {
@@ -87,7 +91,7 @@ export async function capturePolicyAwareWindowsParent(binding, params, rootAdmis
87
91
  return complete;
88
92
  }
89
93
  catch (error) {
90
- closeAfterFailure(closeFd, complete.fd);
94
+ closeAfterFailure(closeFd, complete.fd, { error }, rootAdmission.reportCloseErrors);
91
95
  throw error;
92
96
  }
93
97
  }
@@ -102,6 +106,7 @@ export async function capturePolicyAwareWindowsParent(binding, params, rootAdmis
102
106
  }),
103
107
  };
104
108
  let ownedFd;
109
+ let failure;
105
110
  const initialTarget = joinPathSegmentRoute(params.rootPath, route, 0, params.basename);
106
111
  if (canReuseParentWithMutationAssertion(params.assertBeforeMutation, params.rootPath, initialTarget)) {
107
112
  session = params.mutationAdmission?.beginNativeParentWalk?.() ??
@@ -178,7 +183,7 @@ export async function capturePolicyAwareWindowsParent(binding, params, rootAdmis
178
183
  }
179
184
  }
180
185
  catch (error) {
181
- closeAfterFailure(closeFd, child.fd);
186
+ closeAfterFailure(closeFd, child.fd, { error }, rootAdmission.reportCloseErrors);
182
187
  throw error;
183
188
  }
184
189
  const previousFd = ownedFd;
@@ -193,8 +198,12 @@ export async function capturePolicyAwareWindowsParent(binding, params, rootAdmis
193
198
  ownedFd = undefined;
194
199
  return current;
195
200
  }
201
+ catch (error) {
202
+ failure = { error };
203
+ throw error;
204
+ }
196
205
  finally {
197
206
  session?.dispose();
198
- closeAfterFailure(closeFd, ownedFd);
207
+ closeAfterFailure(closeFd, ownedFd, failure, rootAdmission.reportCloseErrors);
199
208
  }
200
209
  }
@@ -6,10 +6,11 @@ import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write.js";
6
6
  import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
7
7
  export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
8
8
  export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
9
+ type StagedPermissionPolicy = "private-creation" | "mode-only";
9
10
  declare class NativeStagedFile implements StagedFile {
10
11
  #private;
11
- constructor(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void, name?: string);
12
- static create(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void, exclusiveBasename?: string): Promise<NativeStagedFile>;
12
+ constructor(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void, name?: string, strictFileSync?: boolean, permissionPolicy?: StagedPermissionPolicy);
13
+ static create(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void, exclusiveBasename?: string, strictFileSync?: boolean, permissionPolicy?: StagedPermissionPolicy): Promise<NativeStagedFile>;
13
14
  static write(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
14
15
  get receipt(): StagedFileReceipt;
15
16
  assertCurrent(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"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"}