@openclaw/matrix 2026.10.6 → 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
@@ -2,6 +2,54 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.26.0 - 2026-10-10
6
+
7
+ ### Highlights
8
+
9
+ - **New platforms:** native packages for FreeBSD x64/arm64 and Windows ARM64. ([#905](https://github.com/openclaw/fs-safe/pull/905), [#893](https://github.com/openclaw/fs-safe/pull/893))
10
+ - **Native capabilities:** anonymous pipes, Linux write leases, Darwin ACL inspection, ReFS block cloning, and metadata- and link-preserving `Root.copyIn`. ([#892](https://github.com/openclaw/fs-safe/pull/892), [#896](https://github.com/openclaw/fs-safe/pull/896), [#920](https://github.com/openclaw/fs-safe/pull/920), [#921](https://github.com/openclaw/fs-safe/pull/921))
11
+ - **Android/Termux publication:** staged no-replace copies and writes work when the OS refuses hard links. ([#888](https://github.com/openclaw/fs-safe/pull/888))
12
+ - **Performance:** faster deep archive extraction, borrowed-handle copies and overwrites, sidecar locks, and Linux descriptor verification, with identity checks retained. ([#912](https://github.com/openclaw/fs-safe/pull/912), [#906](https://github.com/openclaw/fs-safe/pull/906), [#902](https://github.com/openclaw/fs-safe/pull/902), [#901](https://github.com/openclaw/fs-safe/pull/901), [#899](https://github.com/openclaw/fs-safe/pull/899))
13
+
14
+ ### Features
15
+
16
+ - **ReFS file copies:** support guarded `Root.copyIn` block cloning on Windows ReFS and Dev Drive, with exclusive publication, cancellation cleanup, and automatic byte-copy fallback for unavailable clone capabilities. ([#920](https://github.com/openclaw/fs-safe/pull/920))
17
+ - **Guarded file copying:** add `Root.copyIn` metadata preservation and exclusive literal-link copying, including dangling Windows file and directory links; keep permission selection separate from timestamps and attributes. ([#921](https://github.com/openclaw/fs-safe/pull/921))
18
+
19
+ - **Native filesystem capabilities:** add Linux write leases on caller-owned descriptors, no-follow Darwin ACL inspection with separate file and directory inheritance facts, and Windows sharing-lock, attribute, and physical-extent test fixtures. ([#896](https://github.com/openclaw/fs-safe/pull/896))
20
+ - **Anonymous pipes:** add `@openclaw/fs-safe/pipe` with native-owned, reopenable close-on-exec pipe ends for one-shot child input on POSIX, including safe stream closure in Workers. ([#892](https://github.com/openclaw/fs-safe/pull/892))
21
+
22
+ ### Fixes
23
+
24
+ - **Windows creation diagnostics:** retain native Win32 error numbers through private directory and file creation failures, including the command fallback, without changing semantic error codes. ([#919](https://github.com/openclaw/fs-safe/pull/919))
25
+ - **FreeBSD native packages:** add x64 and arm64 packages for FreeBSD 14.4+, with native anonymous pipes and canonicalization, and explicit capability absence for unported operations. ([#905](https://github.com/openclaw/fs-safe/pull/905))
26
+ - **Partial native bindings:** select guarded fallbacks when an operation's native capabilities are absent, and report typed capability errors in required mode before mutation. ([#905](https://github.com/openclaw/fs-safe/pull/905))
27
+ - **Android/Termux publication:** publish staged no-replace copies and writes when hardlinks are refused, using a verified empty placeholder before atomic replacement; include `EACCES` in the public hardlink-to-copy fallback classifier. Reported in [openclaw/openclaw#167791](https://github.com/openclaw/openclaw/issues/167791). ([#888](https://github.com/openclaw/fs-safe/pull/888))
28
+ - **Native ZIP names:** preserve CP437 decoding for unflagged legacy filenames after the ZIP 9 upgrade, including collision rejection and bounded entry selection; continue rejecting invalid Unicode Path checksums and encodings before decoder normalization. ([#885](https://github.com/openclaw/fs-safe/pull/885))
29
+ - **Native ZIP Unicode fields:** retain rejection of invalid Unicode Comment CRCs and UTF-8 before callbacks or bounded reads, and validate sequential Unicode fields in decoder order without repeated hashing of the original name. ([#887](https://github.com/openclaw/fs-safe/pull/887))
30
+ - **Portable ZIP extraction:** accept safe relative root-directory markers such as `./` from libarchive and other producers without turning them into absolute paths; root markers remain skipped before callbacks and publication. ([#886](https://github.com/openclaw/fs-safe/pull/886))
31
+ - **Durable JSON queues on Android/Termux:** claim, failed-entry quarantine, and retirement recovery report typed `helper-unavailable` errors when hardlinks are refused, preserving pending, claimed, and retirement evidence for retry. ([#889](https://github.com/openclaw/fs-safe/pull/889))
32
+ - **Windows exclusive publication:** preserve dangling destination symlinks when a refused hard link falls back to placeholder publication. ([#894](https://github.com/openclaw/fs-safe/pull/894))
33
+ - **Native mkdir collisions:** `Root.mkdir` consistently reports `not-file` for an existing regular-file destination in native require mode. ([#895](https://github.com/openclaw/fs-safe/pull/895))
34
+ - **Guest directory publication:** report a changed source identity instead of a false destination collision when a Linux fallback rename refuses a substituted source. ([#898](https://github.com/openclaw/fs-safe/pull/898))
35
+ - **Native recursive removal:** honor a zero entry budget before ignoring a missing target, including with `force`. ([#904](https://github.com/openclaw/fs-safe/pull/904))
36
+
37
+ ### Performance
38
+
39
+ - **Linux opened-file verification:** read ordinary procfs descriptor paths directly instead of walking realpath, keeping identity checks and deleted-path canonicalization; `readLocalFileSafely` is 25–28% faster on deep paths. ([#899](https://github.com/openclaw/fs-safe/pull/899))
40
+ - **Borrowed handles and locks:** keep descriptor metadata inspection off the asynchronous I/O queue for small overwrites (26–28% faster) and copies (31–34% faster), and skip a redundant parent `mkdir` when acquiring sidecar locks (15–18% faster). ([#901](https://github.com/openclaw/fs-safe/pull/901), [#902](https://github.com/openclaw/fs-safe/pull/902), [#906](https://github.com/openclaw/fs-safe/pull/906))
41
+ - **Deep archive extraction:** native extraction verifies every retained ancestor through its open descriptor instead of repeated realpath walks: about 16% faster at depth 8 and 5–6% at depth 4, with every ancestor substitution check retained. ([#912](https://github.com/openclaw/fs-safe/pull/912))
42
+
43
+ ### Docs and Tooling
44
+
45
+ - **Docs accuracy:** correct native-mode migration guidance for removed Python settings and bundled TAR fallbacks, clarify Root streaming ownership, create-only visibility, and Linux no-clobber move fallback limits, and fix the JSON-store and stale-lock examples so denied process probes keep live locks; README and docs examples are now type-checked in CI. ([#897](https://github.com/openclaw/fs-safe/pull/897), [#900](https://github.com/openclaw/fs-safe/pull/900), [#903](https://github.com/openclaw/fs-safe/pull/903))
46
+
47
+ ### Compatibility
48
+
49
+ - **Windows ARM64:** add a native ARM64 package with real Windows 11 ARM64 and x64-emulation validation, including private ACL creation and Worker descriptor ownership. ([#893](https://github.com/openclaw/fs-safe/pull/893))
50
+
51
+ - **Native ZIP metadata:** invalid UTF-8-flagged names now reject instead of decoding lossily or falling back to CP437. Public ZIP extraction, preflight, and reads already reject these names during physical admission. ([#885](https://github.com/openclaw/fs-safe/pull/885))
52
+
5
53
  ## 0.25.0 - 2026-10-08
6
54
 
7
55
  ### Highlights
@@ -10,6 +10,9 @@
10
10
 
11
11
  Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.
12
12
 
13
+ Platform-specific additions include [Linux write leases, Darwin ACL inspection,
14
+ and Windows test fixtures](docs/platform-capabilities.md).
15
+
13
16
  Think Go's `os.Root` / `OpenInRoot` or Rust's [`cap-std`](https://github.com/bytecodealliance/cap-std), but for Node. Hand `root()` a trusted directory and you get back a handle whose every method resolves relative paths against it and defends against `..`, symlink swaps, hardlink aliases, and TOCTOU rename races. The exact containment strength is reported per mechanism: Linux `openat2` opens are kernel-atomic; guarded Linux fallback, macOS, Windows, and JavaScript paths are best-effort.
14
17
 
15
18
  ```ts
@@ -113,9 +116,14 @@ operation; per-call options handle exceptions. See the [Root reference](docs/roo
113
116
 
114
117
  `write()` replaces contents by default. Use `create()` or `overwrite: false`
115
118
  when an existing destination should be an error. `move()` defaults to no clobber
116
- and requires native support for the atomic collision decision; it fails with
117
- `helper-unavailable` when unavailable. Pass `overwrite: true` when replacement
118
- is intended.
119
+ and requires native support. On Linux, `auto` can replace a concurrently created
120
+ empty directory if no-replace rename is unsupported; use native `require` for
121
+ strict refusal. See the [move fallback contract](docs/writing.md#fs-move-from-to-options).
122
+ Pass `overwrite: true` when replacement is intended.
123
+
124
+ Staged JavaScript copies and creates also work when hardlinks are refused
125
+ (including Android/Termux), using a verified empty placeholder before atomic
126
+ replacement. See the [visibility and concurrency tradeoffs](docs/writing.md#publication-without-hardlinks).
119
127
 
120
128
  See [Writing](docs/writing.md) for copy sources, mutation authority, symlink
121
129
  policy, writable handles, and bounded removal, and [Creation](docs/creation.md)
@@ -159,6 +167,8 @@ and error exports. Prefer focused subpaths when a consumer needs a narrower
159
167
  contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are grouped under
160
168
  `@openclaw/fs-safe/advanced` instead of being separate public leaf contracts.
161
169
 
170
+ Use [`@openclaw/fs-safe/pipe`](docs/pipe.md) for native-owned, reopenable anonymous pipes and one-shot child input.
171
+
162
172
  See the [complete subpath catalogue](docs/install.md#subpath-exports) for every entry point and its contents.
163
173
 
164
174
  ## Failure semantics in the name
@@ -236,7 +246,7 @@ updates:
236
246
  import { fileStore } from "@openclaw/fs-safe/store";
237
247
 
238
248
  const files = fileStore({ rootDir: "/safe/workspace/state", private: true });
239
- const store = files.json("settings.json", { lock: true });
249
+ const store = files.json<{ enabled: boolean }>("settings.json", { lock: true });
240
250
 
241
251
  await store.updateOr({ enabled: false }, (current) => ({ ...current, enabled: true }));
242
252
  ```
@@ -17,6 +17,7 @@ import { syncFileBestEffortSync } from "./file-sync.js";
17
17
  import { finalizeArchivePublication } from "./archive-durability.js";
18
18
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
19
19
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
20
+ import { ArchiveSecurityError } from "./archive-errors.js";
20
21
  export async function withStagedArchivePublication(params, extract) {
21
22
  await withStagedArchiveDestination({
22
23
  destinationRealDir: params.destinationGuard.realPath,
@@ -101,6 +102,23 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
101
102
  assertDirectoryIdentityGuard(destinationGuard);
102
103
  check();
103
104
  for (const ancestor of ancestors) {
105
+ // Verify every retained ancestor, including its current name and physical path.
106
+ // Shallow chains cost less to verify directly than crossing the native bridge.
107
+ if (ancestors.length >= 4 && ancestor.owner.verifyCanonical) {
108
+ let verified;
109
+ try {
110
+ verified = await ancestor.owner.verifyCanonical();
111
+ }
112
+ catch (error) {
113
+ if (error instanceof FsSafeError || isNotFoundPathError(error)) {
114
+ throw createArchiveSymlinkTraversalError(path.relative(destinationDir, ancestor.guard.dir));
115
+ }
116
+ throw error;
117
+ }
118
+ check();
119
+ if (verified)
120
+ continue;
121
+ }
104
122
  assertDirectoryIdentityGuard(ancestor.guard);
105
123
  check();
106
124
  await ancestor.owner.verify(check);
@@ -156,15 +174,22 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
156
174
  await ownExtractionDestinationMutation(params.deadline, async () => {
157
175
  await assertGuards();
158
176
  assertSourceFrontier();
159
- const owner = await pinNodeDirectoryForMode(destinationPath).catch((error) => {
177
+ const guard = await createDirectoryIdentityGuard(destinationPath).catch((error) => {
178
+ if (error instanceof ArchiveSecurityError && error.code === "destination-symlink") {
179
+ throw createArchiveSymlinkTraversalError(originalPath);
180
+ }
181
+ throw error;
182
+ });
183
+ check();
184
+ const owner = await pinNodeDirectoryForMode(destinationPath, {
185
+ expectedIdentity: guard.stat, canonicalPath: guard.realPath,
186
+ }).catch((error) => {
160
187
  if (error instanceof FsSafeError && (error.code === "not-file" || error.code === "path-mismatch")) {
161
188
  throw createArchiveSymlinkTraversalError(originalPath);
162
189
  }
163
190
  throw error;
164
191
  });
165
192
  try {
166
- check();
167
- const guard = await createDirectoryIdentityGuard(destinationPath);
168
193
  check();
169
194
  await owner.verify(check);
170
195
  ancestors.push({ guard, owner });
@@ -17,7 +17,7 @@ import { FsSafeError } from "./errors.js";
17
17
  import { inspectFileIdentity } from "./strict-file-identity.js";
18
18
  import { resolveReadOpenFlags } from "./read-open-flags.js";
19
19
  import { realpathSync } from "./realpath.js";
20
- import { getNativeBinding } from "./native.js";
20
+ import { assertNativeCapabilities, getNativeBinding } from "./native.js";
21
21
  import { admitZipBuffer } from "./archive-zip-admission.js";
22
22
  import { validateNativeZipManifest } from "./archive-zip-manifest.js";
23
23
  import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
@@ -199,9 +199,15 @@ async function readNativeBufferEntry(native, buffer, kind, requested, displayPat
199
199
  try {
200
200
  const signal = new AbortController().signal;
201
201
  const limits = resolveTarMeterLimits();
202
- const reader = kind === "zip"
203
- ? await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]))
204
- : await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
202
+ let reader;
203
+ if (kind === "zip") {
204
+ assertNativeCapabilities(native, "openZipBufferNative");
205
+ reader = await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]));
206
+ }
207
+ else {
208
+ assertNativeCapabilities(native, "openTarBufferNative");
209
+ reader = await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
210
+ }
205
211
  const manifest = reader.entries;
206
212
  if (kind === "zip")
207
213
  validateNativeZipManifest(manifest, zipEntries);
@@ -227,7 +233,7 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
227
233
  const zipEntries = [];
228
234
  if (kind === "zip")
229
235
  admitZipBuffer(buffer, resolveExtractLimits(), entry => { zipEntries.push(entry); });
230
- const native = getNativeBinding();
236
+ const native = getNativeBinding(kind === "zip" ? "openZipBufferNative" : "openTarBufferNative");
231
237
  if (native)
232
238
  return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, maxBytes, zipEntries);
233
239
  return kind === "zip" ? await readZipEntry(buffer, requestedEntry, maxBytes, zipEntries)
@@ -118,11 +118,13 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
118
118
  const entries = new Map();
119
119
  for (const [name, entry, physical] of normalized) {
120
120
  const appendSlash = physical.kind === "directory" && !name.endsWith("/");
121
- const normalizedName = appendSlash ? `${name}/` : name;
121
+ // JSZip reduces a relative root marker such as "./" to an empty name.
122
+ // Keep its directory spelling relative when restoring the terminal slash.
123
+ const normalizedName = appendSlash ? `${name || "."}/` : name;
122
124
  if (Object.hasOwn(files, normalizedName))
123
125
  collision();
124
126
  if (appendSlash) {
125
- entry.name = `${entry.name}/`;
127
+ entry.name = `${entry.name || "."}/`;
126
128
  }
127
129
  files[normalizedName] = entry;
128
130
  entries.set(physical.portableKey, createAdmittedZipEntry(entry, normalizedName, physical));
@@ -154,7 +154,7 @@ export async function extractArchive(params) {
154
154
  const label = kind === "zip" ? "extract zip" : "extract tar";
155
155
  const limits = resolveExtractLimits(params.limits);
156
156
  const tarLimits = resolveTarMeterLimits(limits);
157
- const native = getNativeBinding();
157
+ const native = getNativeBinding("inspectArchiveNative", "extractArchiveNative");
158
158
  assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
159
159
  assertNoWindowsPathAlias(destDir, "filesystem", "archive destination uses a Windows filesystem namespace alias");
160
160
  // Read the declared public fields before private adapters copy options: class
@@ -282,7 +282,7 @@ export async function inspectTarArchive(params) {
282
282
  const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
283
283
  const limits = resolveExtractLimits(params.limits);
284
284
  const tarLimits = resolveTarMeterLimits(limits);
285
- const native = getNativeBinding();
285
+ const native = getNativeBinding("inspectArchiveNative");
286
286
  assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
287
287
  return await withExtractionDeadline(params.timeoutMs, "inspect tar", async (deadline) => {
288
288
  const staged = await stageArchiveFileForExtraction({ archivePath, limits, deadline });
@@ -6,7 +6,7 @@ import { getNativeBinding, requireNativeBinding } from "./native.js";
6
6
  */
7
7
  export async function readCloneFileMetadata(files) {
8
8
  const paths = files.map(assertAbsolutePathInput);
9
- const native = process.platform === "darwin" ? requireNativeBinding() : getNativeBinding();
9
+ const native = process.platform === "darwin" ? requireNativeBinding("readCloneFileMetadata") : getNativeBinding("readCloneFileMetadata");
10
10
  if (!native)
11
11
  return paths.map(() => undefined);
12
12
  const results = await native.readCloneFileMetadata(paths);
@@ -3,12 +3,14 @@ import type { FileHandle } from "node:fs/promises";
3
3
  import { type CopyCloneMode } from "./copy-policy.js";
4
4
  import { type NativeBinding } from "./native.js";
5
5
  import { type NativeFileCopyResult } from "./native-binding.js";
6
+ import type { CopyMetadata } from "./copy-metadata.js";
6
7
  export type CopyFileInput = {
7
8
  kind: "file";
8
9
  handle: FileHandle;
9
10
  size: number;
10
11
  clone: CopyCloneMode;
11
12
  signal?: AbortSignal;
13
+ metadata?: CopyMetadata;
12
14
  verifySource(): Promise<void>;
13
15
  };
14
16
  export declare function resolveFileCopyCloneMode(mode?: CopyCloneMode): CopyCloneMode;
@@ -1,7 +1,7 @@
1
1
  import fs, {} from "node:fs";
2
2
  import { resolveCopyCloneMode } from "./copy-policy.js";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { getNativeBinding } from "./native.js";
4
+ import { getNativeBinding, selectNativeBinding } from "./native.js";
5
5
  import { captureNativeFdClose } from "./native-binding.js";
6
6
  import { inspectFileIdentity } from "./strict-file-identity.js";
7
7
  import { transferFileHandle } from "./file-handle-transfer.js";
@@ -29,7 +29,8 @@ export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutatio
29
29
  }
30
30
  export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes) {
31
31
  input.signal?.throwIfAborted();
32
- if (!native.copyFileExclusive) {
32
+ const copier = selectNativeBinding(native, "copyFileExclusive");
33
+ if (!copier) {
33
34
  if (input.clone === "always") {
34
35
  throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
35
36
  }
@@ -39,7 +40,7 @@ export async function createNativeCopyFile(native, input, parentFd, basename, ma
39
40
  const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
40
41
  try {
41
42
  // The caller adopts this descriptor before observing a later cancellation.
42
- return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
43
+ return await copier.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
43
44
  }
44
45
  catch (error) {
45
46
  const code = error?.code;
@@ -0,0 +1,26 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
+ import type { NativeBinding } from "./native-binding.js";
4
+ import type { PinnedWriteParams } from "./pinned-write-types.js";
5
+ export type CopyLinkInput = {
6
+ kind: "link";
7
+ stageBeforePublish: true;
8
+ sourcePath: string;
9
+ identity: BigIntStats;
10
+ target?: Buffer;
11
+ preserveMetadata: boolean;
12
+ signal?: AbortSignal;
13
+ verifySource(): Promise<void>;
14
+ };
15
+ /** Shared by tree byte copying and per-file copying; never resolves the target. */
16
+ export declare function readCopyLinkTarget(source: string): Promise<Buffer>;
17
+ export declare function assertCopyLinkAvailable(): void;
18
+ export declare function admitCopyLink(sourcePath: string, preserveMetadata: boolean, signal?: AbortSignal): Promise<CopyLinkInput | undefined>;
19
+ /** The caller has admitted the destination parent under Root's mutation policy. */
20
+ export declare function copyLinkAtParent(params: PinnedWriteParams, input: CopyLinkInput, parent: AnyAsyncDirectoryGuard, native?: {
21
+ binding: NativeBinding;
22
+ fd: number;
23
+ }): Promise<{
24
+ dev: bigint;
25
+ ino: bigint;
26
+ }>;
@@ -0,0 +1,169 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs, {} from "node:fs";
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
6
+ import { FsSafeError } from "./errors.js";
7
+ import { getNativeBinding } from "./native.js";
8
+ import { isFsSafeNativeRequired } from "./native-config.js";
9
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
10
+ import { timestampSeconds } from "./copy-metadata.js";
11
+ import { syncDirectoryBestEffort } from "./directory-durability.js";
12
+ import { noReplaceUnavailable } from "./native-noreplace.js";
13
+ import { createSuppressedError } from "./suppressed-error.js";
14
+ /** Shared by tree byte copying and per-file copying; never resolves the target. */
15
+ export async function readCopyLinkTarget(source) {
16
+ return await fsp.readlink(source, { encoding: "buffer" });
17
+ }
18
+ export function assertCopyLinkAvailable() {
19
+ const native = getNativeBinding();
20
+ if (process.platform === "darwin" && !native?.createCopySymlink) {
21
+ throw new FsSafeError("helper-unavailable", "exclusive macOS link copying requires the native helper; link(2) follows its source");
22
+ }
23
+ if (process.platform === "win32" && (!native?.copyLinkExclusive || !native.publishCopyLink || !native.removeCopyLink)) {
24
+ throw new FsSafeError("helper-unavailable", "copying Windows links without following them requires the native helper");
25
+ }
26
+ if (native && process.platform !== "win32" && !native.createCopySymlink) {
27
+ throw new FsSafeError("helper-unavailable", "native link copying is unavailable");
28
+ }
29
+ }
30
+ export async function admitCopyLink(sourcePath, preserveMetadata, signal) {
31
+ const parent = await createAsyncDirectoryGuard(path.dirname(sourcePath), { bigint: true });
32
+ sourcePath = path.join(parent.realPath, path.basename(sourcePath));
33
+ const identity = fs.lstatSync(sourcePath, { bigint: true });
34
+ if (!identity.isSymbolicLink())
35
+ return undefined;
36
+ const target = process.platform === "win32" ? undefined : await readCopyLinkTarget(sourcePath);
37
+ const verifySource = async () => {
38
+ signal?.throwIfAborted();
39
+ await assertAsyncDirectoryGuard(parent);
40
+ const current = inspectFileIdentitySync(() => fs.lstatSync(sourcePath, { bigint: true }), identity);
41
+ if (!current.isSymbolicLink() || current.mtimeNs !== identity.mtimeNs || current.ctimeNs !== identity.ctimeNs) {
42
+ throw new FsSafeError("path-mismatch", "copy source link changed");
43
+ }
44
+ };
45
+ await verifySource();
46
+ return { kind: "link", stageBeforePublish: true, sourcePath, identity, target, preserveMetadata, signal, verifySource };
47
+ }
48
+ /** The caller has admitted the destination parent under Root's mutation policy. */
49
+ export async function copyLinkAtParent(params, input, parent, native) {
50
+ const temporaryName = `.fs-safe-${randomUUID()}.tmp`;
51
+ const temporaryPath = path.join(parent.realPath, temporaryName);
52
+ const destination = path.join(parent.realPath, params.basename);
53
+ let identity;
54
+ let sourceParent;
55
+ let failure;
56
+ const assertStage = () => {
57
+ assertSyncDirectoryGuard(parent);
58
+ const stat = inspectFileIdentitySync(() => fs.lstatSync(temporaryPath, { bigint: true }), identity);
59
+ if (!stat.isSymbolicLink() || stat.nlink !== 1n)
60
+ throw new FsSafeError("path-mismatch", "copy link stage changed");
61
+ };
62
+ try {
63
+ await input.verifySource();
64
+ await assertAsyncDirectoryGuard(parent);
65
+ if (process.platform === "win32") {
66
+ if (!native?.binding.copyLinkExclusive)
67
+ throw new FsSafeError("helper-unavailable", "native Windows link copying is unavailable");
68
+ const sourceGuard = await createAsyncDirectoryGuard(path.dirname(input.sourcePath), { bigint: true });
69
+ sourceParent = await fsp.open(sourceGuard.realPath, fs.constants.O_RDONLY);
70
+ inspectFileIdentitySync(() => fs.fstatSync(sourceParent.fd, { bigint: true }), sourceGuard.stat);
71
+ await input.verifySource();
72
+ params.assertBeforeMutation?.();
73
+ assertSyncDirectoryGuard(parent);
74
+ identity = native.binding.copyLinkExclusive(sourceParent.fd, path.basename(input.sourcePath), native.fd, temporaryName, input.identity.dev, input.identity.ino, input.preserveMetadata, params.mode);
75
+ }
76
+ else {
77
+ if (native && !native.binding.createCopySymlink)
78
+ throw new FsSafeError("helper-unavailable", "native link copying is unavailable");
79
+ params.assertBeforeMutation?.();
80
+ assertSyncDirectoryGuard(parent);
81
+ if (native?.binding.createCopySymlink) {
82
+ const created = native.binding.createCopySymlink(native.fd, temporaryName, input.target, params.mode, input.preserveMetadata ? input.identity.atimeNs : undefined, input.preserveMetadata ? input.identity.mtimeNs : undefined);
83
+ identity = created;
84
+ if (created.errorCode)
85
+ throw Object.assign(new Error(created.errorMessage), { code: created.errorCode });
86
+ }
87
+ else {
88
+ fs.symlinkSync(input.target, temporaryPath);
89
+ identity = fs.lstatSync(temporaryPath, { bigint: true });
90
+ }
91
+ if (!native && input.preserveMetadata) {
92
+ assertStage();
93
+ fs.lutimesSync(temporaryPath, timestampSeconds(input.identity.atimeNs), timestampSeconds(input.identity.mtimeNs));
94
+ }
95
+ }
96
+ assertStage();
97
+ await input.verifySource();
98
+ params.assertBeforeMutation?.();
99
+ input.signal?.throwIfAborted();
100
+ assertStage();
101
+ if (process.platform === "win32") {
102
+ native.binding.publishCopyLink(native.fd, temporaryName, params.basename, identity.dev, identity.ino);
103
+ }
104
+ else if (native) {
105
+ const renameNoReplace = native.binding.renameNoReplace;
106
+ if (!renameNoReplace)
107
+ throw new FsSafeError("helper-unavailable", "native link publication is unavailable");
108
+ try {
109
+ renameNoReplace(native.fd, temporaryName, native.fd, params.basename);
110
+ }
111
+ catch (error) {
112
+ const unavailable = noReplaceUnavailable(error, "link copy publication", true);
113
+ if (!unavailable || isFsSafeNativeRequired() || process.platform !== "linux")
114
+ throw error;
115
+ // Linux link(2) links the symlink inode itself, never its target.
116
+ assertStage();
117
+ fs.linkSync(temporaryPath, destination);
118
+ }
119
+ }
120
+ else {
121
+ fs.linkSync(temporaryPath, destination);
122
+ }
123
+ params.onPublished?.(identity);
124
+ assertSyncDirectoryGuard(parent);
125
+ const final = inspectFileIdentitySync(() => fs.lstatSync(destination, { bigint: true }), identity);
126
+ if (!final.isSymbolicLink())
127
+ throw new FsSafeError("path-mismatch", "published copy link changed");
128
+ if (params.sync !== false)
129
+ await syncDirectoryBestEffort(parent.realPath);
130
+ await input.verifySource();
131
+ return identity;
132
+ }
133
+ catch (error) {
134
+ failure = { error };
135
+ throw error;
136
+ }
137
+ finally {
138
+ let cleanupFailure;
139
+ try {
140
+ if (identity) {
141
+ assertSyncDirectoryGuard(parent);
142
+ const remaining = fs.lstatSync(temporaryPath, { bigint: true, throwIfNoEntry: false });
143
+ if (remaining) {
144
+ inspectFileIdentitySync(() => remaining, identity);
145
+ if (!remaining.isSymbolicLink())
146
+ throw new FsSafeError("path-mismatch", "copy link cleanup identity changed");
147
+ if (process.platform === "win32")
148
+ native.binding.removeCopyLink(native.fd, temporaryName, identity.dev, identity.ino);
149
+ else
150
+ fs.unlinkSync(temporaryPath);
151
+ }
152
+ }
153
+ }
154
+ catch (error) {
155
+ cleanupFailure = { error };
156
+ }
157
+ try {
158
+ await sourceParent?.close();
159
+ }
160
+ catch (error) {
161
+ cleanupFailure = { error: cleanupFailure
162
+ ? createSuppressedError(error, cleanupFailure.error, "copy link cleanup and close failed") : error };
163
+ }
164
+ if (cleanupFailure)
165
+ throw failure
166
+ ? createSuppressedError(cleanupFailure.error, failure.error, "copy link operation and cleanup failed")
167
+ : cleanupFailure.error;
168
+ }
169
+ }
@@ -0,0 +1,9 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export type CopyMetadata = Readonly<{
3
+ atimeNs: bigint;
4
+ mtimeNs: bigint;
5
+ restoreNative?: (fd: number) => void;
6
+ }>;
7
+ export declare function timestampSeconds(nanoseconds: bigint): string;
8
+ export declare function captureCopyMetadata(fd: number, stat: BigIntStats): CopyMetadata;
9
+ export declare function restoreCopyMetadata(fd: number, metadata: CopyMetadata | undefined): void;
@@ -0,0 +1,39 @@
1
+ import fs, {} from "node:fs";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { getNativeBinding } from "./native.js";
4
+ import { isFsSafeNativeRequired } from "./native-config.js";
5
+ export function timestampSeconds(nanoseconds) {
6
+ // Numeric strings retain fractional and pre-epoch times in Node's utimes API.
7
+ return String(Number(nanoseconds / 1000000000n) + Number(nanoseconds % 1000000000n) / 1e9);
8
+ }
9
+ export function captureCopyMetadata(fd, stat) {
10
+ let restoreNative;
11
+ const native = getNativeBinding();
12
+ if (process.platform === "win32") {
13
+ if (native?.readCopyMetadata && native.restoreCopyMetadata) {
14
+ const snapshot = native.readCopyMetadata(fd);
15
+ const restore = native.restoreCopyMetadata.bind(native);
16
+ restoreNative = target => restore(target, snapshot);
17
+ }
18
+ else if (isFsSafeNativeRequired()) {
19
+ throw new FsSafeError("helper-unavailable", "native Windows copy metadata preservation is unavailable");
20
+ }
21
+ }
22
+ else if (native?.restoreCopyFileTimes) {
23
+ const restore = native.restoreCopyFileTimes.bind(native);
24
+ const { atimeNs, mtimeNs } = stat;
25
+ restoreNative = target => restore(target, atimeNs, mtimeNs);
26
+ }
27
+ else if (isFsSafeNativeRequired()) {
28
+ throw new FsSafeError("helper-unavailable", "native copy timestamp preservation is unavailable");
29
+ }
30
+ return Object.freeze({ atimeNs: stat.atimeNs, mtimeNs: stat.mtimeNs, restoreNative });
31
+ }
32
+ export function restoreCopyMetadata(fd, metadata) {
33
+ if (!metadata)
34
+ return;
35
+ if (metadata.restoreNative)
36
+ metadata.restoreNative(fd);
37
+ else
38
+ fs.futimesSync(fd, timestampSeconds(metadata.atimeNs), timestampSeconds(metadata.mtimeNs));
39
+ }
@@ -68,7 +68,7 @@ function copyRootFileWithAdmissionSync(options, cache) {
68
68
  let parentOpen = false;
69
69
  let target;
70
70
  let identity;
71
- const native = getNativeBinding();
71
+ const native = process.platform !== "win32" ? getNativeBinding("copyFileExclusiveSync", "removeStagedFile") : getNativeBinding();
72
72
  const nativeCopy = process.platform !== "win32" && native?.copyFileExclusiveSync && native.removeStagedFile
73
73
  ? native.copyFileExclusiveSync.bind(native) : undefined;
74
74
  try {
@@ -4,12 +4,9 @@ import fsp, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
7
+ import { timestampSeconds } from "./copy-metadata.js";
8
+ import { readCopyLinkTarget } from "./copy-link.js";
7
9
  import { assertStagedDirectoryCurrent, exactIdentityMatches, openStagedDirectory, } from "./staged-directory.js";
8
- function timestampSeconds(nanoseconds) {
9
- // Dates discard sub-millisecond precision; negative numbers mean "now" in
10
- // Node's utimes API. Numeric strings retain fractional, pre-epoch timestamps.
11
- return String(Number(nanoseconds / 1000000000n) + Number(nanoseconds % 1000000000n) / 1e9);
12
- }
13
10
  /** Byte-copy adapter for the shared immutable-source, caller-owned namespace contract. */
14
11
  export async function copyOwnedTree(source, destination, options) {
15
12
  const callerSignal = options.signal ? AbortSignal.any([options.signal]) : undefined;
@@ -145,7 +142,7 @@ export async function copyOwnedTree(source, destination, options) {
145
142
  else if (child.isSymbolicLink()) {
146
143
  const link = process.platform === "win32"
147
144
  ? await fsp.readlink(childSource)
148
- : await fsp.readlink(childSource, { encoding: "buffer" });
145
+ : await readCopyLinkTarget(childSource);
149
146
  let type;
150
147
  if (process.platform === "win32") {
151
148
  // Older Node releases infer link type from the destination, where a
@@ -21,7 +21,7 @@ function closeDescriptor(fd, failure) {
21
21
  }
22
22
  /** Inspect an existing real directory without creating probe files. */
23
23
  export function probeTreeClone(parentPath) {
24
- const native = getNativeBinding();
24
+ const native = getNativeBinding("probeTreeClone");
25
25
  if (!native)
26
26
  return undefined;
27
27
  const parent = openStagedDirectory(assertAbsolutePathInput(parentPath));
@@ -64,9 +64,9 @@ async function materializeTree(source, destination, options, policy) {
64
64
  throw new FsSafeError("invalid-path", "clone concurrency must be an integer between 1 and 32");
65
65
  }
66
66
  const native = policy === "always"
67
- ? requireNativeBinding()
67
+ ? requireNativeBinding("probeTreeClone", "cloneTree")
68
68
  : policy === "auto"
69
- ? getNativeBinding()
69
+ ? getNativeBinding("probeTreeClone", "cloneTree")
70
70
  : undefined;
71
71
  const target = assertAbsolutePathInput(destination);
72
72
  const name = path.basename(target);
@@ -0,0 +1,12 @@
1
+ export type DarwinAclInspection = {
2
+ kind: "none";
3
+ } | {
4
+ kind: "present";
5
+ inheritsToFiles: boolean;
6
+ inheritsToDirectories: boolean;
7
+ } | {
8
+ kind: "unknown";
9
+ reason: string;
10
+ };
11
+ /** Inspect one no-follow descriptor. Unknown is never evidence of no ACL. */
12
+ export declare function inspectDarwinAcl(path: string): DarwinAclInspection;