@openclaw/openshell-sandbox 2026.10.5-beta.4 → 2026.10.10-beta.2
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.
- package/README.md +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
- package/node_modules/@openclaw/fs-safe/README.md +20 -4
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
- package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
- package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
- package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
- package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
- package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
- package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
- package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
- package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
- package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
- package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
- package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
- package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
- package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
- package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
- package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
- package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
- package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
- package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
- package/node_modules/@openclaw/fs-safe/package.json +21 -12
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
|
@@ -87,6 +87,12 @@ be exactly representable. Ambiguous identity fails closed.
|
|
|
87
87
|
Returned receipts are frozen descriptive snapshots, not mutable authority.
|
|
88
88
|
Changing a supplied receipt after admission cannot retarget the lifecycle.
|
|
89
89
|
|
|
90
|
+
Use `fs.lstat(path, { bigint: true })` for caller-created directory receipts:
|
|
91
|
+
bigint stats retain exact Windows file IDs. Copied or reconstructed receipts
|
|
92
|
+
with unsafe Number identities reject with `FsSafeError("path-mismatch")`.
|
|
93
|
+
Original library receipts retain their private exact identity even when their
|
|
94
|
+
public numeric metadata cannot represent every bit.
|
|
95
|
+
|
|
90
96
|
`StagedFileReceipt` contains `directory` (`path`, `realPath`, and exact bigint
|
|
91
97
|
`identity.dev`/`identity.ino`), `temporaryBasename`, and the prepared file's
|
|
92
98
|
`identity` (`dev`, `ino`, `size`, `nlink`, `mtimeNs`, `ctimeNs` as bigint;
|
|
@@ -197,3 +203,15 @@ they do not gain this cleanup authority.
|
|
|
197
203
|
- [Advanced composition](advanced.md)
|
|
198
204
|
- [Native architecture](native.md)
|
|
199
205
|
- [Security model](security-model.md)
|
|
206
|
+
|
|
207
|
+
## Round-tripping identities
|
|
208
|
+
|
|
209
|
+
`StagedFileReceipt.identity` and `directory.identity` already contain exact
|
|
210
|
+
bigint `dev`/`ino` values. Retain those values directly; do not convert them to
|
|
211
|
+
Number. `PublishedFileReceipt.staged.identity` is the same staged file's identity,
|
|
212
|
+
not a new pathname observation. File metadata such as size and timestamps is a
|
|
213
|
+
preparation-time snapshot and can change during writing. Publication callbacks
|
|
214
|
+
using `PublishedWriteIdentity` likewise already return exact bigint identities.
|
|
215
|
+
|
|
216
|
+
For the standalone `publishFileExclusive()` API, retain its `exactIdentity`
|
|
217
|
+
companion instead of its legacy `identity: Stats`; see [directory durability](durability.md#exclusive-file-publication).
|
|
@@ -84,6 +84,21 @@ Batch loading skips invalid entry names, malformed, oversized, or unreadable ent
|
|
|
84
84
|
|
|
85
85
|
Failed destinations are create-only. Quarantine publishes the claimed file by hardlink, so the queue and failed directories must share a filesystem with hardlink support. If `failed/<id>.json` already exists, quarantine rejects while preserving both that earlier evidence and the current claimed entry instead of overwriting either file. The `read` callback continues to receive the logical `.json` path even though bytes are read and migrations are written through the claimed path.
|
|
86
86
|
|
|
87
|
+
Claim, quarantine, and retirement recovery require hardlinks. If a link syscall
|
|
88
|
+
is refused with `EACCES`, `EPERM`, `EXDEV`, `ENOTSUP`, `EOPNOTSUPP`, or `ENOSYS`,
|
|
89
|
+
the operation rejects with `FsSafeError("helper-unavailable")` and retains the
|
|
90
|
+
original error in `cause`. This includes Android/Termux environments that deny
|
|
91
|
+
hardlinks. The refusal leaves the pending entry, processing claim, or retirement
|
|
92
|
+
record intact for retry after the capability or permission problem is resolved;
|
|
93
|
+
it does not acknowledge delivery. Batch loading propagates this capability
|
|
94
|
+
failure too. The queue does not fall back to copying or overwriting rename:
|
|
95
|
+
those operations cannot preserve its shared-inode recovery and create-only
|
|
96
|
+
collision evidence. Enqueue alone does not prove that an entry can be claimed.
|
|
97
|
+
|
|
98
|
+
The [link-free queue design](queue-link-free-design.md) describes an unimplemented
|
|
99
|
+
v2 candidate and its mixed-version barriers. It does not change these helpers or
|
|
100
|
+
provide Android/Termux queue support.
|
|
101
|
+
|
|
87
102
|
Migrations stay bound to the exact processing file opened for that load. The
|
|
88
103
|
read descriptor remains pinned while the callback runs outside the transfer
|
|
89
104
|
lock; after the callback returns, migration reacquires the lock and rechecks the
|
|
@@ -47,6 +47,26 @@ under trusted ancestry. Do not make shared `/tmp` private or take ownership of
|
|
|
47
47
|
another user's directory. A caller's preferred root takes precedence over
|
|
48
48
|
`TMPDIR`; changing `TMPDIR` only changes the resolver's fallback location.
|
|
49
49
|
|
|
50
|
+
On Android only (`process.platform === "android"`), ancestors may also be owned
|
|
51
|
+
by Android's OS system identity, AID_SYSTEM (UID 1000). The system is trusted like
|
|
52
|
+
root; this does not trust arbitrary foreign UIDs or stop the walk at `HOME` or
|
|
53
|
+
`PREFIX`. Mode/sticky checks still apply, with one Android group-write exception:
|
|
54
|
+
an ancestor's group may write when both owner and group are AID_SYSTEM and the
|
|
55
|
+
caller belongs to neither that effective nor supplementary group, or when both
|
|
56
|
+
are the calling app's private Android UID/GID (app ID 10000–19999, including
|
|
57
|
+
multi-user UID offsets). Android reserves those groups for the system or the
|
|
58
|
+
same app identity. Unknown group membership fails closed for system-owned
|
|
59
|
+
group-writable ancestors. World-write still needs sticky protection.
|
|
60
|
+
|
|
61
|
+
This admits Termux's system-owned `/data` and `/data/data` at `0771` and its
|
|
62
|
+
app-owned `files` ancestor at `0771`, without allowing another app or an
|
|
63
|
+
untrusted writable group to replace the next child. Canonical ancestors are
|
|
64
|
+
rechecked through `/`; symlink/type and identity checks remain in force. The
|
|
65
|
+
supplied temp root and created workspace must still be owned by the effective
|
|
66
|
+
user and reject **all** group/world write, including these Android groups and
|
|
67
|
+
sticky directories. No Android native-addon or Linux user-namespace assumption
|
|
68
|
+
is made by this policy.
|
|
69
|
+
|
|
50
70
|
On Linux, non-identity `/proc/self/uid_map` and `/proc/self/gid_map` evidence
|
|
51
71
|
permits ancestors whose UID and GID equal unmapped kernel overflow IDs,
|
|
52
72
|
provided the same ancestor mode checks pass.
|
|
@@ -209,6 +229,17 @@ identity before opening it, rejects mount crossings, and traverses descendants
|
|
|
209
229
|
only through opened directory handles; symlinks/reparse entries are removed as
|
|
210
230
|
leaves and never traversed. Windows marks the exact opened objects for deletion by handle.
|
|
211
231
|
|
|
232
|
+
Empty quarantines can be removed without directory enumeration: the native
|
|
233
|
+
helper can attempt a descriptor-relative empty-directory removal, and the
|
|
234
|
+
compatible POSIX JavaScript path can attempt `rmdir`. Unchanged creation timestamps
|
|
235
|
+
select this attempt as a performance hint, never as emptiness or ownership proof;
|
|
236
|
+
all fresh identity checks still run. Changed timestamps and nonempty directories
|
|
237
|
+
continue through the same guarded recursive removal. The native empty attempt is bounded
|
|
238
|
+
and synchronous, including for async workspaces; recursive native cleanup stays
|
|
239
|
+
asynchronous. Older bindings and Windows compatible cleanup retain their
|
|
240
|
+
existing recursive route; Windows can defer directory timestamp changes, so
|
|
241
|
+
the empty-attempt hint does not select a faster compatible path there.
|
|
242
|
+
|
|
212
243
|
POSIX has no unlink-by-fd or expected-inode unlink for directory entries. After
|
|
213
244
|
the final identity check, each `unlinkat` can still be raced; the possible side
|
|
214
245
|
effect is bounded to one substituted non-directory leaf or one empty directory
|
|
@@ -754,3 +785,11 @@ it("processes a fixture", async () => {
|
|
|
754
785
|
- [Atomic writes](atomic.md) — `replaceDirectoryAtomic` for whole-directory swaps.
|
|
755
786
|
- [`root()`](root.md) — `fs.copyIn(rel, sourceAbs)` for moving files from a temp into a `Root`.
|
|
756
787
|
- [File lock](sidecar-lock.md) — when many processes share a temp tree.
|
|
788
|
+
|
|
789
|
+
## Exact workspace receipts
|
|
790
|
+
|
|
791
|
+
`tempWorkspace()` and `tempWorkspaceSync()` return `exactIdentity` alongside
|
|
792
|
+
their unchanged legacy `identity`. Preserve the bigint companion for identity
|
|
793
|
+
comparisons. `tempFile().identity` already uses bigint `dev` and `ino` and needs
|
|
794
|
+
no conversion. Cleanup retains its own exact authority; editing or discarding a
|
|
795
|
+
public receipt does not rebind cleanup to another file or directory.
|
|
@@ -135,6 +135,19 @@ Lifecycle memory is sampled across ten intervals, with
|
|
|
135
135
|
the first two excluded as warm-up; idle's Linux wakeup bound scales with the
|
|
136
136
|
requested duration (three per minute). Adversarial cases remain unchanged.
|
|
137
137
|
|
|
138
|
+
## Native syscall budgets
|
|
139
|
+
|
|
140
|
+
After building the host addon and package, run the Linux syscall proof with
|
|
141
|
+
`node scripts/native-call-budget-proof.mjs --operation remove` (or `mkdir`,
|
|
142
|
+
`ensureRoot`, `move`, `append`, `openWritable`, `create`, `write`, `tempWorkspace`).
|
|
143
|
+
It traces all threads with `strace -f -yy` and enforces exact per-mode syscall
|
|
144
|
+
and async-resource counts. Canonicalization ancestors and worker-thread I/O
|
|
145
|
+
remain counted. The only runtime filesystem exclusion is Node/V8 Abseil's
|
|
146
|
+
read-only open of `/sys/devices/system/cpu/cpu0/tsc_freq_khz`, a lazy CPU-clock
|
|
147
|
+
probe unrelated to the temporary fixture that can occur on the main thread.
|
|
148
|
+
Other sysfs calls and subsequent I/O remain counted. Failed proofs print the
|
|
149
|
+
retained raw trace path for diagnosis.
|
|
150
|
+
|
|
138
151
|
## Linux openat2 fallback
|
|
139
152
|
|
|
140
153
|
Build the host addon and package first. The test hook is cached with the native
|
|
@@ -38,7 +38,7 @@ type PathStat = {
|
|
|
38
38
|
};
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
The shape returned by `Root.stat()`.
|
|
41
|
+
The shape returned by `Root.stat()`. Its enumerable fields are a serializable legacy view of the identity,
|
|
42
42
|
ownership, mode, size, timestamp, link count, and three file-kind facts the
|
|
43
43
|
boundary uses. Unlike Node's `Stats`, `isFile`, `isDirectory`, and
|
|
44
44
|
`isSymbolicLink` are boolean fields rather than methods.
|
|
@@ -79,103 +79,68 @@ Used by helpers that accept either an encoding (returning a string) or `null` (r
|
|
|
79
79
|
|
|
80
80
|
## `OpenResult` / `ReadResult`
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Import the result types rather than copying their shapes:
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
|
-
type OpenResult
|
|
86
|
-
handle: import("node:fs/promises").FileHandle;
|
|
87
|
-
containment: "kernel-atomic" | "best-effort";
|
|
88
|
-
realPath: string;
|
|
89
|
-
stat: import("node:fs").Stats;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
type ReadResult = {
|
|
93
|
-
buffer: Buffer;
|
|
94
|
-
containment: "kernel-atomic" | "best-effort";
|
|
95
|
-
realPath: string;
|
|
96
|
-
stat: import("node:fs").Stats;
|
|
97
|
-
};
|
|
85
|
+
import type { OpenResult, ReadResult, WritableOpenResult } from "@openclaw/fs-safe/root";
|
|
98
86
|
```
|
|
99
87
|
|
|
100
88
|
`realPath` is the canonical real path the read or open landed on, after symlink resolution; `stat` is the verified `fstat` result. Public root results currently report `containment: "best-effort"`; the union also describes direct native `openBeneath()` results, which report `"kernel-atomic"` on Linux. See the [security model](security-model.md#containment-guarantees-by-platform).
|
|
101
89
|
|
|
90
|
+
`ReadResult` carries the read `buffer`. `OpenResult` and `WritableOpenResult`
|
|
91
|
+
carry an owned Node `FileHandle` as `handle` and implement
|
|
92
|
+
`[Symbol.asyncDispose]()`; use `await using` or explicitly close the handle.
|
|
93
|
+
See [reading](reading.md#fs-open-rel-options) and
|
|
94
|
+
[writable handles](writing.md#openwritable-for-streaming) for ownership and streaming.
|
|
95
|
+
|
|
102
96
|
## `RootDefaults` / `RootOptions`
|
|
103
97
|
|
|
104
98
|
```ts
|
|
105
|
-
type
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
hardlinks?: "reject" | "allow";
|
|
112
|
-
maxBytes?: number;
|
|
113
|
-
mkdir?: boolean; // default true for mutation methods
|
|
114
|
-
mode?: number;
|
|
115
|
-
renameIdentity?: RenameIdentityPolicy;
|
|
116
|
-
symlinks?: "reject" | "follow-within-root" | "follow-parents-within-root";
|
|
117
|
-
mutationSymlinks?: MutationSymlinkPolicy;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
type DenyMutationPolicy = {
|
|
121
|
-
paths?: readonly string[];
|
|
122
|
-
prefixes?: readonly string[];
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
type RootOptions = {
|
|
126
|
-
rootDir: string;
|
|
127
|
-
defaults?: RootDefaults;
|
|
128
|
-
};
|
|
99
|
+
import type {
|
|
100
|
+
DenyMutationPolicy,
|
|
101
|
+
RenameIdentityPolicy,
|
|
102
|
+
RootDefaults,
|
|
103
|
+
RootOptions,
|
|
104
|
+
} from "@openclaw/fs-safe/root";
|
|
129
105
|
```
|
|
130
106
|
|
|
131
|
-
`RootDefaults` is what `root(rootDir, defaults)` accepts.
|
|
107
|
+
`RootDefaults` is what `root(rootDir, defaults)` accepts. `RootOptions` is the
|
|
108
|
+
`{ rootDir, defaults? }` record. See the [Root signature and defaults](root.md#signature)
|
|
109
|
+
for fields and behavior. `denyMutations` and `assertBeforeMutation` compose with
|
|
110
|
+
per-call restrictions: deny entries are merged, and the root authority assertion
|
|
111
|
+
runs before the per-call assertion.
|
|
132
112
|
|
|
133
113
|
## `RootReadOptions` / `RootWriteOptions` / `RootCopyOptions`
|
|
134
114
|
|
|
135
115
|
```ts
|
|
136
|
-
import type {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
};
|
|
151
|
-
type RootOpenWritableOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mkdir" | "mode" | "mutationSymlinks"> & {
|
|
152
|
-
writeMode?: "replace" | "append" | "update";
|
|
153
|
-
};
|
|
154
|
-
type RootWriteJsonOptions = RootWriteOptions & {
|
|
155
|
-
replacer?: Parameters<typeof JSON.stringify>[1];
|
|
156
|
-
space?: Parameters<typeof JSON.stringify>[2];
|
|
157
|
-
trailingNewline?: boolean;
|
|
158
|
-
};
|
|
159
|
-
type RootAppendOptions = RootWriteOptions & {
|
|
160
|
-
prependNewlineIfNeeded?: boolean;
|
|
161
|
-
};
|
|
162
|
-
type RootMoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
|
|
163
|
-
overwrite?: boolean;
|
|
164
|
-
};
|
|
165
|
-
type RootRemoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
|
|
166
|
-
recursive?: boolean;
|
|
167
|
-
force?: boolean;
|
|
168
|
-
order?: "filesystem" | "sorted";
|
|
169
|
-
maxEntries?: number;
|
|
170
|
-
maxDepth?: number;
|
|
171
|
-
signal?: AbortSignal;
|
|
172
|
-
};
|
|
173
|
-
type RootMkdirOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
|
|
174
|
-
private?: boolean;
|
|
175
|
-
};
|
|
116
|
+
import type {
|
|
117
|
+
RootAppendOptions,
|
|
118
|
+
RootCopyOptions,
|
|
119
|
+
RootCreateJsonOptions,
|
|
120
|
+
RootCreateOptions,
|
|
121
|
+
RootCreateStreamOptions,
|
|
122
|
+
RootMkdirOptions,
|
|
123
|
+
RootMoveOptions,
|
|
124
|
+
RootOpenOptions,
|
|
125
|
+
RootOpenWritableOptions,
|
|
126
|
+
RootReadOptions,
|
|
127
|
+
RootRemoveOptions,
|
|
128
|
+
RootWriteJsonOptions,
|
|
129
|
+
RootWriteOptions,
|
|
130
|
+
} from "@openclaw/fs-safe/root";
|
|
176
131
|
```
|
|
177
132
|
|
|
178
|
-
|
|
133
|
+
Each method accepts only its applicable defaults and method-specific options:
|
|
134
|
+
|
|
135
|
+
| Types | Contract |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `RootReadOptions` | Read symlink/hardlink policy and `maxBytes`; see [read options](reading.md#read-options). |
|
|
138
|
+
| `RootOpenOptions` | Read link policies, without `maxBytes`. The returned handle's I/O belongs to the caller. |
|
|
139
|
+
| `RootWriteOptions`, `RootWriteJsonOptions`, `RootAppendOptions` | [Write options](writing.md#write-options), JSON formatting, and append newline handling. |
|
|
140
|
+
| `RootCreateOptions`, `RootCreateJsonOptions`, `RootCreateStreamOptions` | [Exclusive creation](writing.md#atomic-buffered-creation), private permissions, and `durable: "file"`; streamed creation also accepts byte limits and cancellation. |
|
|
141
|
+
| `RootCopyOptions` | [Guarded copying](writing.md#write-verbs), source policy, `CopyCloneMode`, and `RootCopyPublicationReceipt`. |
|
|
142
|
+
| `RootOpenWritableOptions` | [Writable handles](writing.md#openwritable-for-streaming) with `writeMode`, without buffered-write durability or byte limits. |
|
|
143
|
+
| `RootMoveOptions`, `RootRemoveOptions`, `RootMkdirOptions` | [Mutation methods](root.md#writes) with their collision, removal-budget, and directory-privacy options. |
|
|
179
144
|
|
|
180
145
|
## `SymlinkPolicy` / `MutationSymlinkPolicy` / `HardlinkPolicy`
|
|
181
146
|
|
|
@@ -202,3 +167,8 @@ policy; omission preserves each mutation method's existing behavior.
|
|
|
202
167
|
- [`root()`](root.md) — how `RootDefaults` and `Root*Options` are used.
|
|
203
168
|
- [Errors](errors.md) — the closed code union in context.
|
|
204
169
|
- [Reading](reading.md), [Writing](writing.md) — option shapes per verb.
|
|
170
|
+
|
|
171
|
+
`PathStat.exactIdentity` is an immutable bigint `dev`/`ino` companion, present on
|
|
172
|
+
returned values and non-enumerable to preserve the numeric JSON shape. Typed
|
|
173
|
+
`list()` and `entries()` results have the same field. Copy it explicitly when
|
|
174
|
+
spreading metadata that will later be used for identity checks.
|
|
@@ -171,7 +171,10 @@ Without `maxEntries`, sorted walks reuse a full directory metadata snapshot
|
|
|
171
171
|
from the `Root.list()` owner. This preserves the existing fast complete-scan
|
|
172
172
|
behavior and its snapshot semantics: changes made after a directory is listed
|
|
173
173
|
do not alter its already-captured entries. Supply an entry budget or use
|
|
174
|
-
filesystem order when metadata work must remain incremental.
|
|
174
|
+
filesystem order when metadata work must remain incremental. Complete snapshots
|
|
175
|
+
are consumed directly between public iterator yields, without per-entry listing
|
|
176
|
+
promises. Directory admission, cancellation, and awaited-filter identity fences
|
|
177
|
+
remain in place. Sorted entries
|
|
175
178
|
describe the observations captured in their directory snapshot or batch.
|
|
176
179
|
|
|
177
180
|
Use `order: "filesystem"` when a wide directory must not be fully enumerated:
|
|
@@ -127,8 +127,9 @@ pending detail and queued batches are bounded. The last removal stops and joins
|
|
|
127
127
|
the native thread. No Worker threads, eval programs, or JS `fs.watch` are used.
|
|
128
128
|
|
|
129
129
|
macOS entry scopes use nonrecursive `EVFILT_VNODE` watches on the admitted parent
|
|
130
|
-
and, when present, the entry itself.
|
|
131
|
-
|
|
130
|
+
and, when present, the entry itself. Child activity requests a guarded scan only
|
|
131
|
+
while the selected entry is missing. Once it exists, sibling activity does not
|
|
132
|
+
wake JavaScript; the entry descriptor covers replacement, content and attribute
|
|
132
133
|
changes and is replaced when a guarded scan admits a new identity. Missing paths
|
|
133
134
|
use their nearest admitted ancestor. Descriptors are opened without following
|
|
134
135
|
symlinks; a symlink entry uses `O_SYMLINK` to observe the link itself. All
|
|
@@ -223,12 +223,12 @@ await fs.createJson("config/settings.json", { enabled: true }, { atomic: true })
|
|
|
223
223
|
await fs.create("config/flushed.json", initial, { atomic: true, durable: "file" });
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
`atomic: true`
|
|
226
|
+
`atomic: true` finishes all bytes in a sibling stage before publication.
|
|
227
227
|
The native backend uses its no-replace rename; the JavaScript fallback hardlinks
|
|
228
228
|
the completed stage and unlinks its temporary name in the same JavaScript turn.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
When the OS refuses hardlinks, it uses the [placeholder-rename fallback](#publication-without-hardlinks).
|
|
230
|
+
Other processes can briefly observe both linked names, or an empty placeholder
|
|
231
|
+
before the atomic replacement. Entries present at the exclusive claim are preserved, including dangling symlinks;
|
|
232
232
|
ordinary confinement, type, hardlink, and symlink-policy rejections still apply.
|
|
233
233
|
`assertBeforeMutation` retains its live checks through content writes and publication.
|
|
234
234
|
|
|
@@ -254,7 +254,7 @@ complete publication, and an indeterminate link or native rename must preserve n
|
|
|
254
254
|
recovery. A cleanup or close failure also retains the original operation failure.
|
|
255
255
|
The JavaScript fallback checks the destination again immediately before attempting
|
|
256
256
|
publication. A collision observed there leaves publication unattempted and cleans
|
|
257
|
-
the owned stage. A later collision or
|
|
257
|
+
the owned stage. A later collision or unclassified error from the link call remains
|
|
258
258
|
indeterminate, including `EEXIST`; the error code alone does not prove that the
|
|
259
259
|
filesystem left both names unchanged.
|
|
260
260
|
No later verification, mode, or synchronization failure authorizes deleting an
|
|
@@ -293,9 +293,10 @@ unlimited. Zero permits an empty input only. Invalid limits reject before I/O.
|
|
|
293
293
|
Unlike buffered creation's JavaScript fallback, streamed creation stages all
|
|
294
294
|
chunks before publishing the final name. Native mode uses no-replace rename;
|
|
295
295
|
the JavaScript fallback hardlinks the completed stage and removes its temporary
|
|
296
|
-
name in the same JavaScript turn.
|
|
297
|
-
hardlinks; other processes may
|
|
298
|
-
|
|
296
|
+
name in the same JavaScript turn. If hardlinks are refused, it uses the
|
|
297
|
+
[placeholder-rename fallback](#publication-without-hardlinks); other processes may
|
|
298
|
+
briefly observe an empty placeholder. A destination present at the exclusive
|
|
299
|
+
claim is preserved. Existing-target preflight does
|
|
299
300
|
not consume the input. The final mode and durability policy use the same guarded
|
|
300
301
|
writer as other Root operations.
|
|
301
302
|
|
|
@@ -408,8 +409,103 @@ With `overwrite: false`, an existing destination produces `already-exists` and
|
|
|
408
409
|
is never altered. Copying prepares a private sibling file before publishing its
|
|
409
410
|
completed contents. Native mode uses no-replace rename. The guarded JavaScript
|
|
410
411
|
fallback links the completed stage and removes its temporary name in the same
|
|
411
|
-
JavaScript turn
|
|
412
|
-
|
|
412
|
+
JavaScript turn. If the OS refuses hardlinks, it uses the placeholder-rename
|
|
413
|
+
fallback below. Other processes can briefly observe both linked names or an
|
|
414
|
+
empty placeholder. The source is never hardlinked to the destination.
|
|
415
|
+
|
|
416
|
+
### Copy metadata and source links
|
|
417
|
+
|
|
418
|
+
`preserveMetadata: true` restores metadata to the completed staging entry before
|
|
419
|
+
publication. It defaults to `false`; it does not select permissions, ownership,
|
|
420
|
+
ACLs, or extended attributes. Use the existing `preserveSourceMode` option as
|
|
421
|
+
well when source permissions should be selected. An explicit `mode` or Root
|
|
422
|
+
mode default continues to take precedence over `preserveSourceMode`.
|
|
423
|
+
|
|
424
|
+
| State | `preserveMetadata: true` | Permission selection |
|
|
425
|
+
| --- | --- | --- |
|
|
426
|
+
| POSIX access and modification times | Preserved | Not affected by `mode` |
|
|
427
|
+
| POSIX permission and executable bits | Not selected by this option | Existing `mode` / `preserveSourceMode` rules |
|
|
428
|
+
| Windows creation, access and write times | Preserved by native copying | Not affected by `mode` |
|
|
429
|
+
| Windows hidden, system, archive, temporary, offline, not-content-indexed | Preserved by native copying | Not affected by `mode` |
|
|
430
|
+
| Windows readonly | Not selected by this option | Existing `mode` / `preserveSourceMode` rules |
|
|
431
|
+
| Ownership, ACLs, POSIX extended attributes | Not copied | Existing destination policy |
|
|
432
|
+
| Windows sparse/integrity flags | Not set through metadata copying | Owned by the clone/FSCTL mechanism |
|
|
433
|
+
|
|
434
|
+
This applies to clones, native byte copies, and JavaScript byte copies. On
|
|
435
|
+
POSIX, native timestamp restoration retains nanoseconds; JavaScript restoration
|
|
436
|
+
uses Node's timestamp precision, as the portable tree copier does. On
|
|
437
|
+
Windows without the native metadata helper, JavaScript can restore only access
|
|
438
|
+
and write times; creation time and the non-permission attributes are not
|
|
439
|
+
preserved. Native `require` mode rejects a missing helper before publication.
|
|
440
|
+
The Windows attribute mask and POSIX timestamp conversion share the
|
|
441
|
+
[tree-copy implementation](copy.md#api); native tree copying has additional
|
|
442
|
+
metadata guarantees that this narrower option does not request.
|
|
443
|
+
|
|
444
|
+
`sourceSymlinks` defaults to `"reject"`, preserving the existing rejection of
|
|
445
|
+
symlinked absolute string sources with code `symlink` and message
|
|
446
|
+
`symlink not allowed`. There is no follow mode. Set
|
|
447
|
+
`sourceSymlinks: "copy-link"` to copy a source link itself, including a dangling
|
|
448
|
+
file or directory link. Its literal target remains data: copying never resolves
|
|
449
|
+
it. Windows uses the link's own directory attribute and copies the reparse
|
|
450
|
+
payload through the same native mechanism as `copyTree`.
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
await destination.copyIn("template-link", "/srv/templates/template-link", {
|
|
454
|
+
sourceSymlinks: "copy-link",
|
|
455
|
+
preserveMetadata: true,
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Link destinations are always exclusive. Omit `overwrite` or set it to `false`;
|
|
460
|
+
explicit `overwrite: true` with a link source fails with `invalid-path` before
|
|
461
|
+
mutation. Regular files retain the usual overwrite behavior. Destination Root
|
|
462
|
+
confinement, denied paths, parent-link policy, and mutation-authority checks
|
|
463
|
+
apply to copied links too.
|
|
464
|
+
macOS link permissions use the selected mode on the link itself; Linux link
|
|
465
|
+
permissions remain fixed by the kernel. The link target's permissions are never
|
|
466
|
+
changed.
|
|
467
|
+
|
|
468
|
+
`"copy-link"` requires an absolute-path string source. A `{ root, relativePath }`
|
|
469
|
+
source fails with `invalid-path` before either source method is called or a
|
|
470
|
+
destination is mutated: its `open` and `stat` capabilities cannot expose literal
|
|
471
|
+
link text or Windows link type without following the link. With the default
|
|
472
|
+
`"reject"`, capability sources continue to use their own Root read policies.
|
|
473
|
+
|
|
474
|
+
macOS and Windows link copying requires native support and otherwise reports
|
|
475
|
+
`helper-unavailable` before destination mutation. macOS `link(2)` follows its
|
|
476
|
+
source, so it cannot publish dangling symlink inodes exclusively. Linux can
|
|
477
|
+
publish the staged symlink inode with guarded hardlink/unlink when native
|
|
478
|
+
support is off or automatic no-replace rename is unavailable. This has the
|
|
479
|
+
same documented pathname identity windows as the JavaScript write fallback;
|
|
480
|
+
no operation follows the link target. Successful native publication uses
|
|
481
|
+
no-replace rename. No link publication uses the regular-file placeholder
|
|
482
|
+
fallback.
|
|
483
|
+
|
|
484
|
+
### Publication without hardlinks
|
|
485
|
+
|
|
486
|
+
For staged JavaScript no-replace copies and writes, `EACCES`, `EPERM`, `EXDEV`,
|
|
487
|
+
`ENOTSUP`, `EOPNOTSUPP`, and `ENOSYS` from the link operation permit a
|
|
488
|
+
**placeholder-rename** fallback, including Android/Termux hardlink restrictions.
|
|
489
|
+
It exclusively creates a mode-`0600`, no-follow destination, closes it after
|
|
490
|
+
capturing its exact identity, rechecks the parent and stage, and requires the
|
|
491
|
+
same empty, singly linked regular placeholder before renaming the completed
|
|
492
|
+
stage over it. A real creation-permission failure still rejects. `EEXIST` from
|
|
493
|
+
the link operation does not permit fallback or establish non-publication.
|
|
494
|
+
|
|
495
|
+
An empty placeholder is observable before the atomic replacement, and a crash
|
|
496
|
+
can leave that placeholder and the completed stage. Detected placeholder swaps
|
|
497
|
+
reject without removing foreign entries; failed pre-rename operations remove
|
|
498
|
+
only a verified owned empty placeholder. Failed cleanup reports `helper-failed`
|
|
499
|
+
with `details.publication: "not-published"` and `details.cleanup: "failed"`.
|
|
500
|
+
After rename, verification requires the stage name to be absent and the target
|
|
501
|
+
to retain the staged identity; the complete published file is preserved on failure.
|
|
502
|
+
|
|
503
|
+
These pathname checks are best-effort, not compare-and-swap: a same-directory
|
|
504
|
+
writer can replace the placeholder after the final check, and rename can replace
|
|
505
|
+
that intervening file or symlink (without following it). Protect the parent from
|
|
506
|
+
untrusted mutation when strict concurrent no-clobber is required. This fallback
|
|
507
|
+
preserves existing receipt shapes; `RootCopyPublicationReceipt` reports identity
|
|
508
|
+
and path, with no method or fallback field.
|
|
413
509
|
|
|
414
510
|
`clone` chooses the file-data transfer strategy through `CopyCloneMode`, shared
|
|
415
511
|
with [`copyTree`](copy.md#api). File copies default to `"never"`; tree copies
|
|
@@ -421,11 +517,19 @@ default to `"auto"`:
|
|
|
421
517
|
| `auto` | Try native file cloning, then copy offload or ordinary byte copying when cloning is unavailable. |
|
|
422
518
|
| `always` | Require native cloning; fail when the binding or filesystem cannot provide it. |
|
|
423
519
|
|
|
424
|
-
Native file cloning supports APFS
|
|
520
|
+
Native file cloning supports APFS, supported Linux filesystems, and Windows ReFS
|
|
521
|
+
(including Dev Drive). On Linux, a
|
|
425
522
|
`FICLONE` request denied with `EPERM`, as container seccomp profiles commonly
|
|
426
523
|
do, counts as unavailable cloning: `auto` copies bytes and `always` fails with
|
|
427
|
-
`unsupported-platform`. Windows
|
|
428
|
-
`
|
|
524
|
+
`unsupported-platform`. On Windows, `always` requires source and destination on
|
|
525
|
+
the same ReFS volume; other volumes report `unsupported-platform`. `auto` falls
|
|
526
|
+
back to bytes only for unavailable cloning capabilities, and `never` copies bytes.
|
|
527
|
+
ReFS named streams are rejected rather than silently discarded by cloning.
|
|
528
|
+
For successful native ReFS clones through either `copyIn` or `copyTree`, all full
|
|
529
|
+
clusters are block-cloned; a final partial cluster may be copied. Exact tail bytes and logical file size are
|
|
530
|
+
preserved. See the [shared ReFS contract](copy.md) and Microsoft's
|
|
531
|
+
[cluster-alignment and EOF requirements](https://learn.microsoft.com/en-us/windows/win32/fileio/block-cloning#restrictions-and-remarks).
|
|
532
|
+
Clone choice
|
|
429
533
|
does not change modes, durability, root confinement, or source and publication
|
|
430
534
|
identity checks. The shared strategy does not replace Root's guarded regular-file
|
|
431
535
|
contract with `copyTree`'s caller-owned immutable-tree and metadata contract.
|
|
@@ -560,6 +664,8 @@ await fs.remove("scratch/finished-job", {
|
|
|
560
664
|
Budgets must be non-negative safe integers or explicit `Infinity`, and require
|
|
561
665
|
`recursive: true`. Omitted budgets retain their finite defaults. An entry or
|
|
562
666
|
depth limit throws `too-large` before processing the over-budget entry.
|
|
667
|
+
Once the parent is admitted, `maxEntries: 0` rejects before inspecting even a
|
|
668
|
+
missing requested entry; `force: true` does not bypass that budget.
|
|
563
669
|
|
|
564
670
|
The default filesystem order streams each directory once, using memory and open
|
|
565
671
|
handles proportional to depth rather than directory width. Sorted order also
|
|
@@ -643,7 +749,8 @@ for the precise parent-pinning guarantee and remaining same-call limitations.
|
|
|
643
749
|
|
|
644
750
|
### `fs.mkdir(rel)`
|
|
645
751
|
|
|
646
|
-
`mkdir -p`. Creates missing parents.
|
|
752
|
+
`mkdir -p`. Creates missing parents. An existing regular file at the requested
|
|
753
|
+
directory name rejects with `not-file` in every native mode and remains unchanged.
|
|
647
754
|
|
|
648
755
|
`require` mode creates each missing component relative to a retained directory,
|
|
649
756
|
opens the child without following a final symlink, and checks its identity before
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.5",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist/archive-parser.wasm",
|
|
28
|
-
"dist/windows-security-
|
|
29
|
-
"dist/windows-security-bridge.ps1",
|
|
28
|
+
"dist/windows-security-helper.exe",
|
|
30
29
|
"dist/**/*.js",
|
|
31
30
|
"dist/**/*.d.ts",
|
|
32
31
|
"docs/**/*.md",
|
|
@@ -83,6 +82,10 @@
|
|
|
83
82
|
"types": "./dist/secret.d.ts",
|
|
84
83
|
"default": "./dist/secret.js"
|
|
85
84
|
},
|
|
85
|
+
"./pipe": {
|
|
86
|
+
"types": "./dist/pipe.d.ts",
|
|
87
|
+
"default": "./dist/pipe.js"
|
|
88
|
+
},
|
|
86
89
|
"./permissions": {
|
|
87
90
|
"types": "./dist/permissions-public.d.ts",
|
|
88
91
|
"default": "./dist/permissions-public.js"
|
|
@@ -140,7 +143,9 @@
|
|
|
140
143
|
"scripts": {
|
|
141
144
|
"benchmark": "node scripts/benchmark.mjs",
|
|
142
145
|
"benchmark:methods": "node benchmarks/runner.mjs",
|
|
146
|
+
"benchmark:node": "node benchmarks/node-baseline.mjs",
|
|
143
147
|
"benchmark:publish": "pnpm build && node scripts/bench-publish.mjs",
|
|
148
|
+
"build:windows-helper": "node scripts/build-windows-helper.mjs",
|
|
144
149
|
"build": "node scripts/prepack-build.mjs",
|
|
145
150
|
"lint:file-size": "node scripts/check-file-size.mjs",
|
|
146
151
|
"lint:fs-boundary": "node scripts/check-fs-boundary-primitives.mjs",
|
|
@@ -156,6 +161,7 @@
|
|
|
156
161
|
"docs:check": "node scripts/check-doc-examples.mjs",
|
|
157
162
|
"docs:site": "node scripts/build-docs-site.mjs",
|
|
158
163
|
"native:build": "pnpm --filter @openclaw/fs-safe-native-build build",
|
|
164
|
+
"native:build:freebsd": "node scripts/build-freebsd.mjs",
|
|
159
165
|
"native:test": "cargo test --workspace",
|
|
160
166
|
"pack:check": "pnpm build && node scripts/check-pack.mjs",
|
|
161
167
|
"public-api:update": "pnpm build && node scripts/check-pack.mjs --update-public-api",
|
|
@@ -173,19 +179,22 @@
|
|
|
173
179
|
"archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
|
|
174
180
|
},
|
|
175
181
|
"optionalDependencies": {
|
|
176
|
-
"@openclaw/fs-safe-darwin-arm64": "0.
|
|
177
|
-
"@openclaw/fs-safe-darwin-x64": "0.
|
|
178
|
-
"@openclaw/fs-safe-
|
|
179
|
-
"@openclaw/fs-safe-
|
|
180
|
-
"@openclaw/fs-safe-linux-
|
|
181
|
-
"@openclaw/fs-safe-linux-
|
|
182
|
-
"@openclaw/fs-safe-
|
|
182
|
+
"@openclaw/fs-safe-darwin-arm64": "0.26.5",
|
|
183
|
+
"@openclaw/fs-safe-darwin-x64": "0.26.5",
|
|
184
|
+
"@openclaw/fs-safe-freebsd-arm64": "0.26.5",
|
|
185
|
+
"@openclaw/fs-safe-freebsd-x64": "0.26.5",
|
|
186
|
+
"@openclaw/fs-safe-linux-arm64-gnu": "0.26.5",
|
|
187
|
+
"@openclaw/fs-safe-linux-arm64-musl": "0.26.5",
|
|
188
|
+
"@openclaw/fs-safe-linux-x64-gnu": "0.26.5",
|
|
189
|
+
"@openclaw/fs-safe-linux-x64-musl": "0.26.5",
|
|
190
|
+
"@openclaw/fs-safe-win32-arm64-msvc": "0.26.5",
|
|
191
|
+
"@openclaw/fs-safe-win32-x64-msvc": "0.26.5",
|
|
183
192
|
"jszip": "^3.10.2"
|
|
184
193
|
},
|
|
185
194
|
"devDependencies": {
|
|
186
195
|
"@emnapi/runtime": "2.0.0-alpha.6",
|
|
187
196
|
"@napi-rs/cli": "3.10.8",
|
|
188
|
-
"@types/node": "^26.6.
|
|
197
|
+
"@types/node": "^26.6.5",
|
|
189
198
|
"@vitest/coverage-v8": "5.0.3",
|
|
190
199
|
"fast-check": "^4.10.2",
|
|
191
200
|
"istanbul-lib-coverage": "3.2.2",
|
|
@@ -194,7 +203,7 @@
|
|
|
194
203
|
"sigstore": "5.0.0",
|
|
195
204
|
"tar": "7.5.22",
|
|
196
205
|
"typescript": "^7.0.2",
|
|
197
|
-
"vite": "8.3.
|
|
206
|
+
"vite": "8.3.4",
|
|
198
207
|
"vitest": "^5.0.3"
|
|
199
208
|
},
|
|
200
209
|
"engines": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|