@openclaw/matrix 2026.10.5 → 2026.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +48 -0
  2. package/node_modules/@openclaw/fs-safe/README.md +14 -4
  3. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
  4. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  5. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
  6. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
  7. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
  8. package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +1 -1
  9. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +2 -0
  10. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +4 -3
  11. package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
  12. package/node_modules/@openclaw/fs-safe/dist/copy-link.js +169 -0
  13. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
  14. package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
  15. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +1 -1
  16. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
  17. package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
  18. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
  19. package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
  20. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
  21. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
  22. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +1 -1
  23. package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
  24. package/node_modules/@openclaw/fs-safe/dist/file-hash.js +4 -3
  25. package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
  26. package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
  27. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
  28. package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
  29. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  30. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
  31. package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
  32. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
  33. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +57 -18
  34. package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
  36. package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +1 -1
  38. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +1 -1
  39. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +2 -0
  40. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +1 -1
  41. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +19 -1
  42. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
  43. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +18 -0
  44. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
  45. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
  46. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
  47. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +3 -0
  48. package/node_modules/@openclaw/fs-safe/dist/native.d.ts +5 -3
  49. package/node_modules/@openclaw/fs-safe/dist/native.js +25 -6
  50. package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
  51. package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
  52. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
  53. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  54. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +36 -47
  55. package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +2 -0
  56. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +3 -0
  57. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
  58. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +5 -3
  59. package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
  60. package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
  61. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
  62. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
  63. package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +1 -1
  64. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +12 -11
  65. package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
  66. package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
  67. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +12 -4
  68. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +6 -92
  69. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +1 -1
  70. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
  71. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +8 -1
  72. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
  73. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
  74. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
  75. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
  76. package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
  77. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
  78. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +11 -5
  79. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +9 -1
  80. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
  81. package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
  82. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  83. package/node_modules/@openclaw/fs-safe/docs/contributing.md +35 -5
  84. package/node_modules/@openclaw/fs-safe/docs/copy.md +22 -0
  85. package/node_modules/@openclaw/fs-safe/docs/durability.md +4 -2
  86. package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
  87. package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
  88. package/node_modules/@openclaw/fs-safe/docs/install.md +13 -5
  89. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +5 -5
  90. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +15 -5
  91. package/node_modules/@openclaw/fs-safe/docs/native.md +51 -2
  92. package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
  93. package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
  94. package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
  95. package/node_modules/@openclaw/fs-safe/docs/reading.md +7 -1
  96. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
  97. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
  98. package/node_modules/@openclaw/fs-safe/docs/store.md +11 -0
  99. package/node_modules/@openclaw/fs-safe/docs/types.md +45 -80
  100. package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
  101. package/node_modules/@openclaw/fs-safe/package.json +18 -10
  102. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  103. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  104. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  105. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  106. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  107. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
  108. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  109. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
  110. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  111. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  112. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  113. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  114. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  115. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  116. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  117. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  118. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  119. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
  120. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  121. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  122. package/package.json +5 -5
@@ -0,0 +1,29 @@
1
+ import fs from "node:fs";
2
+ import { getNativeBinding } from "./native.js";
3
+ /** Inspect one no-follow descriptor. Unknown is never evidence of no ACL. */
4
+ export function inspectDarwinAcl(path) {
5
+ if (process.platform !== "darwin")
6
+ return { kind: "unknown", reason: "unsupported-platform" };
7
+ let fd;
8
+ try {
9
+ const native = getNativeBinding();
10
+ if (!native?.inspectDarwinAcl)
11
+ return { kind: "unknown", reason: "helper-unavailable" };
12
+ fd = fs.openSync(path, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
13
+ const facts = native.inspectDarwinAcl(fd);
14
+ if (facts.state === "absent" || facts.state === "empty")
15
+ return { kind: "none" };
16
+ if (facts.state === "present" && typeof facts.inheritsToFiles === "boolean" &&
17
+ typeof facts.inheritsToDirectories === "boolean") {
18
+ return { kind: "present", inheritsToFiles: facts.inheritsToFiles, inheritsToDirectories: facts.inheritsToDirectories };
19
+ }
20
+ return { kind: "unknown", reason: "incomplete-acl-facts" };
21
+ }
22
+ catch (error) {
23
+ return { kind: "unknown", reason: error instanceof Error ? error.message : String(error) };
24
+ }
25
+ finally {
26
+ if (fd !== undefined)
27
+ fs.closeSync(fd);
28
+ }
29
+ }
@@ -5,6 +5,7 @@ export type DirectoryModeChecks = {
5
5
  };
6
6
  export type DirectoryModeOwner = {
7
7
  verify(check?: () => void): Promise<void>;
8
+ verifyCanonical?(): Promise<boolean>;
8
9
  apply(mode: number, checks?: DirectoryModeChecks): Promise<void>;
9
10
  close(): Promise<void>;
10
11
  };
@@ -12,6 +13,7 @@ export declare function assertOwnedDirectory(expected: Stats | BigIntStats, actu
12
13
  /** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
13
14
  export declare function ownDirectoryMode(params: {
14
15
  inspect: () => Promise<number>;
16
+ inspectCanonical?: () => number | undefined;
15
17
  chmod: (mode: number) => Promise<void>;
16
18
  prepareChmod?: () => Promise<void>;
17
19
  verifyChmod?: () => Promise<void>;
@@ -28,6 +30,7 @@ export declare function nodeDirectorySearchOnlyFlags(): {
28
30
  export declare function pinNodeDirectoryForMode(dirPath: string, options?: {
29
31
  expectedIdentity?: BigIntStats;
30
32
  ownerUid?: number;
33
+ canonicalPath?: string;
31
34
  }): Promise<DirectoryModeOwner>;
32
35
  /** Synchronous counterpart with the same no-follow, identity, and owner checks. */
33
36
  export declare function pinNodeDirectoryForModeSync(dirPath: string, options?: {
@@ -4,6 +4,8 @@ import { FsSafeError } from "./errors.js";
4
4
  import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
5
5
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
6
  import { sameFileIdentityForCleanup } from "./file-identity.js";
7
+ import { getNativeBinding } from "./native.js";
8
+ import { nativePolicyDirectoryObserver } from "./native-policy-directory-observation.js";
7
9
  export function assertOwnedDirectory(expected, actual) {
8
10
  if (actual.isSymbolicLink() || !actual.isDirectory()) {
9
11
  throw new FsSafeError("not-file", "directory mode target must be a real directory");
@@ -23,7 +25,7 @@ export function ownDirectoryMode(params) {
23
25
  pending = operation.catch(() => undefined);
24
26
  return operation;
25
27
  };
26
- return {
28
+ const owner = {
27
29
  verify: (check) => enqueue(async () => {
28
30
  check?.();
29
31
  await params.inspect();
@@ -79,6 +81,14 @@ export function ownDirectoryMode(params) {
79
81
  return closing;
80
82
  },
81
83
  };
84
+ if (params.inspectCanonical) {
85
+ owner.verifyCanonical = async () => {
86
+ let verified = false;
87
+ await enqueue(async () => { verified = params.inspectCanonical() !== undefined; });
88
+ return verified;
89
+ };
90
+ }
91
+ return owner;
82
92
  }
83
93
  /** Darwin descriptor inspection avoids requesting directory-content reads. */
84
94
  export function nodeDarwinDirectoryMetadataFlags() {
@@ -131,7 +141,11 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
131
141
  proc = route.proc;
132
142
  return await fs.open(dirPath, route.flags | flags);
133
143
  });
144
+ let observer;
134
145
  try {
146
+ observer = options.canonicalPath === dirPath && ownerUid === undefined
147
+ ? nativePolicyDirectoryObserver(getNativeBinding(), handle.fd, dirPath)
148
+ : undefined;
135
149
  const inspect = async () => {
136
150
  const opened = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), expected);
137
151
  assertOwnedDirectory(expected, opened);
@@ -153,8 +167,18 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
153
167
  assertOwner(opened);
154
168
  assertOwner(followed);
155
169
  };
170
+ const observeCanonical = observer;
156
171
  const owner = ownDirectoryMode({
157
172
  inspect,
173
+ inspectCanonical: observeCanonical ? () => {
174
+ const observed = observeCanonical();
175
+ if (!observed)
176
+ return undefined;
177
+ if (observed.identity.dev !== expected.dev || observed.identity.ino !== expected.ino) {
178
+ throw new FsSafeError("path-mismatch", "directory changed before its mode could be applied");
179
+ }
180
+ return Number(observed.identity.mode & 4095n);
181
+ } : undefined,
158
182
  prepareChmod: proc ? assertProcAuthority : undefined,
159
183
  verifyChmod: proc ? assertProcAuthority : undefined,
160
184
  async chmod(mode) {
@@ -165,12 +189,13 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
165
189
  await handle.chmod(mode);
166
190
  }
167
191
  },
168
- close: () => handle.close(),
192
+ close: () => { observer?.dispose(); return handle.close(); },
169
193
  });
170
194
  await owner.verify();
171
195
  return owner;
172
196
  }
173
197
  catch (error) {
198
+ observer?.dispose();
174
199
  try {
175
200
  await handle.close();
176
201
  }
@@ -230,7 +230,7 @@ export function retainEntryForPublication(options) {
230
230
  inspectEntry(sourcePath, source.expected);
231
231
  const sourceFd = kind === "symlink"
232
232
  ? binding.openStagedSymlink(sourceParent.fd, source.basename)
233
- : binding.openBeneath(sourceParent.fd, source.basename, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK |
233
+ : native.openBeneath(sourceParent.fd, source.basename, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK |
234
234
  (kind === "directory" ? fs.constants.O_DIRECTORY : 0))?.fd;
235
235
  if (!Number.isInteger(sourceFd) || sourceFd < 0) {
236
236
  throw new FsSafeError("helper-unavailable", "publication source descriptor is unavailable");
@@ -113,7 +113,8 @@ export async function copyFileHandle(source, target, options = {}) {
113
113
  const assertBeforeMutation = composeMutationAssertions(undefined, options.assertBeforeMutation);
114
114
  const sourceStat = await inspectFileIdentity(() => {
115
115
  signal?.throwIfAborted();
116
- return source.stat({ bigint: true });
116
+ const fd = source.fd;
117
+ return fd < 0 ? source.stat({ bigint: true }) : fs.fstatSync(fd, { bigint: true });
117
118
  });
118
119
  signal?.throwIfAborted();
119
120
  if (!sourceStat.isFile())
@@ -123,7 +124,8 @@ export async function copyFileHandle(source, target, options = {}) {
123
124
  }
124
125
  const targetStat = await inspectFileIdentity(() => {
125
126
  signal?.throwIfAborted();
126
- return target.stat({ bigint: true });
127
+ const fd = target.fd;
128
+ return fd < 0 ? target.stat({ bigint: true }) : fs.fstatSync(fd, { bigint: true });
127
129
  });
128
130
  signal?.throwIfAborted();
129
131
  if (!targetStat.isFile())
@@ -55,7 +55,7 @@ import fsSync from "node:fs";
55
55
  import fs from "node:fs/promises";
56
56
  import { normalizeMaxBytes } from "./byte-budget.js";
57
57
  import { FsSafeError } from "./errors.js";
58
- import { getNativeBinding } from "./native.js";
58
+ import { getNativeBinding, selectNativeBinding } from "./native.js";
59
59
  import { resolveReadOpenFlags } from "./read-open-flags.js";
60
60
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
61
61
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
@@ -100,11 +100,12 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
100
100
  if (stat.size > maxBytes) {
101
101
  throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
102
102
  }
103
- if (native) {
103
+ const hasher = selectNativeBinding(native, "sha256File");
104
+ if (hasher) {
104
105
  // A completed N-API task can mask later aborts on the same signal.
105
106
  const nativeSignal = signal ? AbortSignal.any([signal]) : undefined;
106
107
  try {
107
- const result = await native.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
108
+ const result = await hasher.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
108
109
  signal?.throwIfAborted();
109
110
  return result;
110
111
  }
@@ -1,4 +1,5 @@
1
1
  import { type SidecarLockAcquireOptions, type SidecarLockHandle, type SidecarLockHeldEntry, type SidecarLockRetryOptions, type SidecarLockStaleRecovery } from "./sidecar-lock.js";
2
+ export { tryAcquireWriteLease, type FileWriteLease } from "./file-write-lease.js";
2
3
  export { acquireFileLockSync, withFileLockSync, type FileLockSyncAcquireOptions, type FileLockSyncHandle, } from "./file-lock-sync.js";
3
4
  export type FileLockRetryOptions = SidecarLockRetryOptions;
4
5
  export type FileLockStaleRecovery = SidecarLockStaleRecovery;
@@ -1,5 +1,6 @@
1
1
  import { createSidecarLockManager, } from "./sidecar-lock.js";
2
2
  import { getFsSafeLockConfig } from "./lock-config.js";
3
+ export { tryAcquireWriteLease } from "./file-write-lease.js";
3
4
  export { acquireFileLockSync, withFileLockSync, } from "./file-lock-sync.js";
4
5
  function resolveFileLockManagerKey(targetPath, managerKey) {
5
6
  return managerKey ?? `fs-safe.file-lock:${targetPath}`;
@@ -0,0 +1,9 @@
1
+ export type FileWriteLease = {
2
+ readonly fd: number;
3
+ /** False after release or when the kernel breaks or downgrades the lease. */
4
+ isHeld(): boolean;
5
+ release(): void;
6
+ [Symbol.dispose](): void;
7
+ };
8
+ /** Linux kernel write lease. The caller retains the open fd and SIGIO handling. */
9
+ export declare function tryAcquireWriteLease(fd: number): FileWriteLease | null;
@@ -0,0 +1,42 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { requireNativeBinding } from "./native.js";
3
+ /** Linux kernel write lease. The caller retains the open fd and SIGIO handling. */
4
+ export function tryAcquireWriteLease(fd) {
5
+ if (process.platform !== "linux") {
6
+ throw new FsSafeError("unsupported-platform", "File write leases require Linux");
7
+ }
8
+ if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
9
+ throw new FsSafeError("invalid-path", "Expected an open file descriptor");
10
+ }
11
+ const native = requireNativeBinding();
12
+ const acquire = native.tryAcquireWriteLease?.bind(native);
13
+ const held = native.isFileWriteLeaseHeld?.bind(native);
14
+ const unlock = native.releaseFileWriteLease?.bind(native);
15
+ if (!acquire || !held || !unlock) {
16
+ throw new FsSafeError("helper-unavailable", "Native file write leases are unavailable");
17
+ }
18
+ const call = (operation) => {
19
+ try {
20
+ return operation();
21
+ }
22
+ catch (cause) {
23
+ throw new FsSafeError("helper-failed", "Native file write lease operation failed", { cause });
24
+ }
25
+ };
26
+ if (!call(() => acquire(fd)))
27
+ return null;
28
+ let released = false;
29
+ const release = () => {
30
+ if (released)
31
+ return;
32
+ // Never retry an ambiguous settlement against a potentially recycled fd.
33
+ released = true;
34
+ call(() => unlock(fd));
35
+ };
36
+ return Object.freeze({
37
+ fd,
38
+ isHeld: () => !released && call(() => held(fd)),
39
+ release,
40
+ [Symbol.dispose]: release,
41
+ });
42
+ }
@@ -92,6 +92,9 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
92
92
  " try:",
93
93
  " os.rename(src_basename + '/', dst_basename, src_dir_fd=src_parent_fd, dst_dir_fd=dst_parent_fd)",
94
94
  " except OSError as error:",
95
+ " # ENOTDIR can describe the slash-checked source, not just the target.",
96
+ " if error.errno == errno.ENOTDIR:",
97
+ " verify(src_parent_fd, src_basename, 1)",
95
98
  " if error.errno in (errno.ENOTEMPTY, errno.ENOTDIR, errno.EEXIST):",
96
99
  " raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), dst_basename) from error",
97
100
  " raise",
@@ -0,0 +1 @@
1
+ export declare function isHardlinkFallbackError(error: unknown): boolean;
@@ -0,0 +1,5 @@
1
+ // These refusals leave the destination uncreated. EEXIST is deliberately excluded.
2
+ const HARDLINK_FALLBACK_CODES = new Set(["EACCES", "EPERM", "EXDEV", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
3
+ export function isHardlinkFallbackError(error) {
4
+ return HARDLINK_FALLBACK_CODES.has(error?.code ?? "");
5
+ }
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import { syncDirectory } from "./directory-durability.js";
7
7
  import { FsSafeError } from "./errors.js";
8
8
  import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
9
+ import { isHardlinkFallbackError } from "./hardlink-fallback.js";
9
10
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
10
11
  import { serializePathWrite } from "./write-queue.js";
11
12
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
@@ -61,6 +62,18 @@ async function withQueueEntryLock(paths, run) {
61
62
  assertNoWindowsPathAlias(resolvedJsonPath);
62
63
  return await serializePathWrite(paths.jsonPath, async () => await withQueueTransferLock(resolvedJsonPath, run));
63
64
  }
65
+ async function linkQueueGeneration(sourcePath, targetPath) {
66
+ try {
67
+ await fs.link(sourcePath, targetPath);
68
+ }
69
+ catch (error) {
70
+ // Shared inode identity is recovery evidence; a copy cannot replace it.
71
+ if (isHardlinkFallbackError(error)) {
72
+ throw new FsSafeError("helper-unavailable", "durable queue ownership requires hardlink support", { cause: error });
73
+ }
74
+ throw error;
75
+ }
76
+ }
64
77
  async function claimDurableQueueEntryUnlocked(paths, options = {}) {
65
78
  const processingPath = paths.processingPath;
66
79
  await recoverDurableQueueRetirement({ jsonPath: paths.jsonPath, processingPath });
@@ -92,7 +105,7 @@ async function claimDurableQueueEntryUnlocked(paths, options = {}) {
92
105
  throw new FsSafeError("path-mismatch", "queue entry is not exclusively owned");
93
106
  }
94
107
  try {
95
- await fs.link(paths.jsonPath, processingPath);
108
+ await linkQueueGeneration(paths.jsonPath, processingPath);
96
109
  }
97
110
  catch (error) {
98
111
  if (getErrorCode(error) === "EEXIST") {
@@ -227,7 +240,7 @@ export async function moveDurableQueueEntryToFailed(params) {
227
240
  throw new FsSafeError("already-exists", "failed queue destination already exists");
228
241
  }
229
242
  if (!failed)
230
- await fs.link(sourcePath, failedPath);
243
+ await linkQueueGeneration(sourcePath, failedPath);
231
244
  await syncDirectory(path.dirname(failedPath));
232
245
  await fs.unlink(sourcePath);
233
246
  await syncDirectory(path.dirname(sourcePath));
@@ -305,7 +318,7 @@ async function recoverDurableQueueRetirement(params) {
305
318
  if (!sameFileIdentityForCleanup(entry, processing)) {
306
319
  const pending = regularQueueFileIdentity(params.jsonPath, "retirement");
307
320
  if (!pending) {
308
- await fs.link(entryPath, params.jsonPath);
321
+ await linkQueueGeneration(entryPath, params.jsonPath);
309
322
  await syncDirectory(path.dirname(params.jsonPath));
310
323
  }
311
324
  else if (sameFileIdentityForCleanup(pending, entry)) {
@@ -124,6 +124,8 @@ export interface NativeWindowsDirectoryReceipt {
124
124
  }
125
125
  export interface NativeDarwinAclFacts {
126
126
  state: "absent" | "empty" | "present";
127
+ inheritsToFiles?: boolean;
128
+ inheritsToDirectories?: boolean;
127
129
  }
128
130
  type NativeTwoPathArgs = [
129
131
  sourceRootFd: number,
@@ -132,6 +134,37 @@ type NativeTwoPathArgs = [
132
134
  targetRelPath: string
133
135
  ];
134
136
  export interface NativeBinding {
137
+ readCopyMetadata?(fd: number): Buffer;
138
+ restoreCopyMetadata?(fd: number, snapshot: Buffer): void;
139
+ restoreCopyFileTimes?(fd: number, atimeNs: bigint, mtimeNs: bigint): void;
140
+ copyLinkExclusive?(sourceParent: number, sourceName: string, parent: number, name: string, dev: bigint, ino: bigint, preserveMetadata: boolean, mode: number): {
141
+ dev: bigint;
142
+ ino: bigint;
143
+ };
144
+ publishCopyLink?(parent: number, name: string, destination: string, dev: bigint, ino: bigint): void;
145
+ removeCopyLink?(parent: number, name: string, dev: bigint, ino: bigint): void;
146
+ createCopySymlink?(parent: number, name: string, target: Buffer, mode: number, atimeNs?: bigint, mtimeNs?: bigint): {
147
+ dev: bigint;
148
+ ino: bigint;
149
+ errorCode?: string;
150
+ errorMessage?: string;
151
+ };
152
+ tryAcquireWriteLease?(fd: number): boolean;
153
+ isFileWriteLeaseHeld?(fd: number): boolean;
154
+ releaseFileWriteLease?(fd: number): void;
155
+ holdWindowsSharingLock?(path: string): {
156
+ close(): void;
157
+ };
158
+ setWindowsFileAttributes?(path: string, attrs: {
159
+ readOnly?: boolean;
160
+ hidden?: boolean;
161
+ system?: boolean;
162
+ }): void;
163
+ readWindowsFileExtents?(path: string): {
164
+ vcn: bigint;
165
+ lcn: bigint;
166
+ clusters: bigint;
167
+ }[];
135
168
  rootRemovalStat?(parent: number, name: string): NativeRootRemovalEntry;
136
169
  rootRemovalUnlink?(parent: number, name: string, dev: bigint, ino: bigint, directory: boolean): void;
137
170
  openRootRemovalDirectory?(parent: number, name: string, dev: bigint, ino: bigint): NativeRootRemovalDirectory;
@@ -169,6 +202,11 @@ export interface NativeBinding {
169
202
  };
170
203
  /** Internal: consumes only a descriptor returned by this binding. */
171
204
  closeOwnedFd(fd: number): void;
205
+ createPipe?(): {
206
+ reader: number;
207
+ writer: number;
208
+ atomicCloseOnExec: boolean;
209
+ };
172
210
  /** Internal Darwin-only synchronous inspection; the caller retains its fd. */
173
211
  inspectDarwinAcl?(fd: number, inheritanceTarget?: "file" | "directory"): NativeDarwinAclFacts;
174
212
  /** POSIX system canonicalization; confinement and identity policy stay with callers. */
@@ -183,18 +221,18 @@ export interface NativeBinding {
183
221
  /** Linux/Windows byte transfer; callers retain both admitted descriptors until settlement. */
184
222
  copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
185
223
  /** Internal: same private, immutable input ownership as the ZIP buffer reader. */
186
- openTarBufferNative(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
224
+ openTarBufferNative?(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
187
225
  readonly entries: NativeArchiveEntry[];
188
226
  readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
189
227
  }>;
190
228
  /** Internal: input remains private, unpooled and immutable until the reader is released. */
191
- openZipBufferNative(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
229
+ openZipBufferNative?(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
192
230
  readonly entries: NativeArchiveEntry[];
193
231
  readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
194
232
  }>;
195
- readCloneFileMetadata(paths: string[]): Promise<(Buffer | null)[]>;
196
- probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
197
- cloneTree(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
233
+ readCloneFileMetadata?(paths: string[]): Promise<(Buffer | null)[]>;
234
+ probeTreeClone?(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
235
+ cloneTree?(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
198
236
  openStagedSymlink?(parentFd: number, basename: string): number;
199
237
  stagedSymlinkTarget?(parentFd: number, basename: string, linkFd: number): string;
200
238
  stagedSymlinkMatches?(parentFd: number, basename: string, linkFd: number): boolean;
@@ -205,28 +243,28 @@ export interface NativeBinding {
205
243
  removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
206
244
  copyFileExclusive?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined, signal: AbortSignal | undefined): Promise<NativeFileCopyResult>;
207
245
  copyFileExclusiveSync?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined): NativeFileCopyResult;
208
- cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
209
- copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
210
- createPrivateDirectory(path: string): void;
246
+ cloneFileExclusive?(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
247
+ copyFileRangeExclusive?(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
248
+ createPrivateDirectory?(path: string): void;
211
249
  /** Internal Windows creation receipts use full FILE_ID_INFO, never Node's projection. */
212
250
  inspectWindowsDirectory?(path: string, requirePrivate: boolean): NativeWindowsDirectoryReceipt;
213
251
  createPrivateDirectoryWithParentIdentity?(path: string, expectedParentIdentity: string): NativeWindowsDirectoryReceipt;
214
252
  /** Protects an already-private borrowed Node file; descriptor ownership stays with Node. */
215
253
  protectPrivateWindowsFile?(fd: number, path: string, expectedParentIdentity: string): NativeWindowsDirectoryReceipt;
216
254
  verifyPrivateWindowsFile?(fd: number, path: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks: number): void;
217
- extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
218
- fstatIdentity(fd: number): NativeFileIdentity;
219
- inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
220
- linkBeneath(...args: NativeTwoPathArgs): void;
255
+ extractArchiveNative?(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
256
+ fstatIdentity?(fd: number): NativeFileIdentity;
257
+ inspectArchiveNative?(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
258
+ linkBeneath?(...args: NativeTwoPathArgs): void;
221
259
  /** Direct-child mkdir; true is receipt provenance only, never cleanup ownership. */
222
260
  mkdirChildBeneath?(parentFd: number, basename: string, mode: number): boolean;
223
261
  mkdirOpenChildBeneath?(parentFd: number, basename: string, mode: number, flags: number): {
224
262
  fd: number;
225
263
  created: boolean;
226
264
  };
227
- mkdirBeneath(rootFd: number, relPath: string, mode: number): void;
228
- openBeneath(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
229
- readOwnerAndDacl(path: string): NativeWindowsSecurityFacts;
265
+ mkdirBeneath?(rootFd: number, relPath: string, mode: number): void;
266
+ openBeneath?(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
267
+ readOwnerAndDacl?(path: string): NativeWindowsSecurityFacts;
230
268
  /** Internal Windows-only inspection of the exact borrowed Node descriptor. */
231
269
  inspectWindowsSecureFileHandle?(fd: number): NativeWindowsDescriptorSecurityFacts;
232
270
  ownedTreeRemovalAvailable?(parentFd: number): boolean;
@@ -236,13 +274,14 @@ export interface NativeBinding {
236
274
  retainWindowsEntryPublication?(sourcePath: string, name: string, sourceDev: bigint, sourceIno: bigint, targetPath: string, targetName: string, targetDev: bigint, targetIno: bigint, dev: bigint, ino: bigint, kind: "file" | "directory" | "symlink"): NativeWindowsEntryPublication;
237
275
  entryPublicationFilesystem?(parentFd: number): string;
238
276
  publishRetainedEntryNoReplace?(sourceParentFd: number, sourceBasename: string, sourceFd: number, targetParentFd: number, targetBasename: string): NativePublicationTransition;
239
- renameNoReplace(...args: NativeTwoPathArgs): void;
277
+ renameNoReplace?(...args: NativeTwoPathArgs): void;
240
278
  /** Linux auto-mode fallback through already-admitted direct-child parents. */
241
279
  moveNoReplaceFallback?(...args: [...paths: NativeTwoPathArgs, expectedSourceDev: bigint, expectedSourceIno: bigint]): void;
242
280
  /** Identity-fenced retained-directory rename capability. */
243
281
  renameNoReplaceWithIdentity?(...args: [...paths: NativeTwoPathArgs, expectedSourceDev: bigint, expectedSourceIno: bigint]): void;
244
- renameReplace(...args: NativeTwoPathArgs): void;
245
- sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
282
+ renameReplace?(...args: NativeTwoPathArgs): void;
283
+ sha256File?(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
246
284
  }
285
+ export type CapableNativeBinding<K extends keyof NativeBinding> = NativeBinding & Required<Pick<NativeBinding, K>>;
247
286
  export declare function captureNativeFdClose(binding: NativeBinding): (fd: number) => void;
248
287
  export {};
@@ -5,7 +5,7 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
5
5
  export function getNativeDirectoryObservationBackend(enabled = true) {
6
6
  if (!enabled)
7
7
  return undefined;
8
- const binding = getNativeBinding();
8
+ const binding = getNativeBinding("observeDirectory");
9
9
  return binding && typeof binding.observeDirectory === "function"
10
10
  ? binding
11
11
  : undefined;
@@ -1,3 +1,3 @@
1
1
  import type { BigIntStats } from "node:fs";
2
2
  import type { NativeBinding } from "./native-binding.js";
3
- export declare function nativeMoveNoReplace(binding: NativeBinding, paths: Parameters<NativeBinding["renameNoReplace"]>, expected: BigIntStats | undefined, allowFallback: boolean, operation?: string): "link-unlink" | undefined;
3
+ export declare function nativeMoveNoReplace(binding: NativeBinding, paths: Parameters<NonNullable<NativeBinding["renameNoReplace"]>>, expected: BigIntStats | undefined, allowFallback: boolean, operation?: string): "link-unlink" | undefined;
@@ -1,4 +1,5 @@
1
1
  import { FsSafeError } from "./errors.js";
2
+ import { assertNativeCapabilities } from "./native.js";
2
3
  import { cachedNoReplaceUnavailable, noReplaceUnavailable, rememberNoReplaceUnavailable, } from "./native-noreplace.js";
3
4
  function fallbackError(error, operation) {
4
5
  const code = error?.code;
@@ -24,6 +25,7 @@ function fallbackError(error, operation) {
24
25
  return error;
25
26
  }
26
27
  export function nativeMoveNoReplace(binding, paths, expected, allowFallback, operation = "move") {
28
+ assertNativeCapabilities(binding, "renameNoReplace");
27
29
  const [sourceFd, source, targetFd, target] = paths;
28
30
  let unavailable = cachedNoReplaceUnavailable(binding, sourceFd, operation);
29
31
  if (!unavailable) {
@@ -32,7 +32,7 @@ export function removeNativeCreatedFileIfStillPinned(params) {
32
32
  }
33
33
  }
34
34
  export async function createNativeExclusiveFile(targetPath, mode) {
35
- const binding = getNativeBinding();
35
+ const binding = getNativeBinding("openBeneath");
36
36
  if (!binding) {
37
37
  return undefined;
38
38
  }
@@ -1,7 +1,7 @@
1
1
  import { type BigIntStats, type Stats } from "node:fs";
2
2
  import type { FileHandle } from "node:fs/promises";
3
3
  import type { FileIdentityStat } from "./file-identity.js";
4
- import type { NativeBinding } from "./native.js";
4
+ import { type NativeBinding } from "./native.js";
5
5
  import { describeStagedDirectory } from "./staged-directory.js";
6
6
  type RootIdentity = Pick<FileIdentityStat, "dev" | "ino">;
7
7
  export type NativeRootAdmission = {
@@ -5,6 +5,7 @@ import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./direct
5
5
  import { nodeDirectorySearchOnlyFlags } from "./directory-mode-node.js";
6
6
  import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
7
7
  import { FsSafeError } from "./errors.js";
8
+ import { assertNativeCapabilities } from "./native.js";
8
9
  import { captureNativeFdClose } from "./native-binding.js";
9
10
  import { realpathSync } from "./realpath.js";
10
11
  import { describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
@@ -22,6 +23,7 @@ function assertParentAdmissionAvailable(binding) {
22
23
  throw unavailable("native parent directory admission is unavailable");
23
24
  }
24
25
  captureNativeFdClose(binding);
26
+ assertNativeCapabilities(binding, "openBeneath");
25
27
  }
26
28
  export async function openNativeRootAdmission(binding, params) {
27
29
  assertParentAdmissionAvailable(binding);
@@ -1,6 +1,6 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
2
  import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import type { FileIdentityStat } from "./file-identity.js";
4
- import type { NativeBinding } from "./native.js";
4
+ import { type NativeBinding } from "./native.js";
5
5
  import type { PinnedWriteParams } from "./pinned-write-types.js";
6
6
  export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: FileHandle, parentFd: number, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
@@ -8,6 +8,8 @@ import { assertSyncDirectoryGuard } from "./directory-guard.js";
8
8
  import { FsSafeError } from "./errors.js";
9
9
  import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, } from "./native-operations.js";
10
10
  import { writePinnedInput } from "./pinned-write-input.js";
11
+ import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
12
+ import { assertNativeCapabilities } from "./native.js";
11
13
  import { captureNativeFdClose } from "./native-binding.js";
12
14
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
13
15
  import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
@@ -15,6 +17,7 @@ import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
15
17
  import { cleanupPinnedFilePath } from "./file-cleanup.js";
16
18
  import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
17
19
  import { exactIdentityMatches } from "./staged-directory.js";
20
+ import { restoreCopyMetadata } from "./copy-metadata.js";
18
21
  import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
19
22
  function closeWriteFd(closeFd, fd) {
20
23
  if (fd === undefined)
@@ -50,6 +53,7 @@ function assertNamedParentCurrent(binding, guard, identity) {
50
53
  assertSyncDirectoryGuard({ ...guard, stat: identity });
51
54
  }
52
55
  export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
56
+ assertNativeCapabilities(binding, "openBeneath", "renameNoReplace", "renameReplace", "fstatIdentity");
53
57
  const closeFd = captureNativeFdClose(binding);
54
58
  const parentPath = parentGuard.realPath;
55
59
  let tempFd;
@@ -78,6 +82,9 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
78
82
  try {
79
83
  tempName = `.fs-safe-${randomUUID()}.tmp`;
80
84
  params.assertBeforeMutation?.();
85
+ const copied = params.input.kind === "file" && !params.private
86
+ ? await createNativeCopyFile(binding, params.input, parentFd, tempName, params.maxBytes)
87
+ : undefined;
81
88
  if (params.private) {
82
89
  const parentIdentity = fsSync.fstatSync(parentFd, { bigint: true });
83
90
  privateHandle = await createFileHandle(path.join(parentPath, tempName), {
@@ -91,16 +98,24 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
91
98
  });
92
99
  tempFd = privateHandle.fd;
93
100
  }
101
+ else if (copied) {
102
+ tempFd = copied.fd;
103
+ }
94
104
  else {
95
105
  tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
96
106
  }
97
107
  const verificationIdentity = inspectFileIdentitySync(() => fsSync.fstatSync(tempFd, { bigint: true }));
98
108
  tempIdentity = verificationIdentity;
109
+ if (params.input.kind === "file")
110
+ assertNativeCopyCompleted(params.input, copied);
99
111
  // Creation is requested at 0600 in the binding, but a restrictive umask
100
112
  // can remove owner access. Keep the unpublished inode private and
101
113
  // reopenable until the published name has been identity-fenced.
102
114
  fsSync.fchmodSync(tempFd, 0o600);
103
- await writePinnedInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
115
+ if (!copied)
116
+ await writePinnedInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
117
+ if (params.input.kind === "file")
118
+ restoreCopyMetadata(tempFd, params.input.metadata);
104
119
  if (params.sync !== false) {
105
120
  if (params.strictFileSync)
106
121
  fsSync.fsyncSync(tempFd);
@@ -132,6 +147,9 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
132
147
  }
133
148
  assertFinalSymlinkRejected(path.join(parentPath, params.basename), params.rejectFinalSymlink);
134
149
  }
150
+ // The authority callback itself may have cancelled an otherwise completed copy.
151
+ if (params.input.kind === "file")
152
+ params.input.signal?.throwIfAborted();
135
153
  try {
136
154
  if (params.overwrite === false) {
137
155
  binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
@@ -1,4 +1,7 @@
1
1
  import type { FileIdentityStat } from "./file-identity.js";
2
- import type { NativeBinding } from "./native.js";
2
+ import { type NativeBinding } from "./native.js";
3
3
  import type { PinnedWriteParams } from "./pinned-write-types.js";
4
+ type PinnedWriteCapabilitiesInput = Pick<PinnedWriteParams, "input" | "private" | "verifyPosixMode">;
5
+ export declare function getPinnedWriteNativeBinding(params: PinnedWriteCapabilitiesInput): NativeBinding | undefined;
4
6
  export declare function runPinnedWriteNative(binding: NativeBinding, params: PinnedWriteParams, fallback: (input: PinnedWriteParams["input"]) => Promise<FileIdentityStat>): Promise<FileIdentityStat>;
7
+ export {};