@openclaw/fs-safe 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +134 -0
- package/LICENSE +1 -0
- package/README.md +52 -4
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +5 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +5 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- package/dist/archive-durability.d.ts +6 -6
- package/dist/archive-durability.d.ts.map +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -5
- package/dist/archive-gzip-tail.d.ts +3 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +23 -3
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +4 -2
- package/dist/archive-merge.d.ts +5 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +16 -13
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -6
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +83 -74
- package/dist/archive-staging.d.ts +6 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +42 -22
- package/dist/archive-tar-stream.d.ts +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +23 -10
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +18 -14
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +48 -12
- package/dist/archive-zip-loader.d.ts +6 -0
- package/dist/archive-zip-loader.d.ts.map +1 -0
- package/dist/archive-zip-loader.js +38 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +13 -8
- package/dist/archive-zip-preflight.d.ts +2 -3
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +2 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +12 -10
- package/dist/bounded-read.d.ts +12 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +82 -45
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +22 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +69 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +10 -1
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +27 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +222 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +125 -0
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +5 -4
- package/dist/directory-guard.d.ts +11 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +53 -11
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -1
- package/dist/file-handle-transfer.d.ts +14 -0
- package/dist/file-handle-transfer.d.ts.map +1 -0
- package/dist/file-handle-transfer.js +64 -0
- package/dist/file-hash.d.ts +9 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +135 -39
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +7 -5
- package/dist/file-store-path.d.ts +3 -0
- package/dist/file-store-path.d.ts.map +1 -0
- package/dist/file-store-path.js +27 -0
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +6 -4
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +56 -44
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +2 -18
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +4 -13
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +4 -2
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +8 -4
- package/dist/guest-dispatch-python.d.ts +2 -0
- package/dist/guest-dispatch-python.d.ts.map +1 -0
- package/dist/guest-dispatch-python.js +117 -0
- package/dist/guest-native-python.d.ts +4 -0
- package/dist/guest-native-python.d.ts.map +1 -0
- package/dist/guest-native-python.js +135 -0
- package/dist/guest.d.ts +9 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +413 -0
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +5 -8
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +19 -18
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +24 -25
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +56 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +30 -34
- package/dist/mutation-authority.d.ts +9 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +29 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +19 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +3 -1
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +5 -4
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -0
- package/dist/overwrite-file-handle.d.ts +8 -0
- package/dist/overwrite-file-handle.d.ts.map +1 -0
- package/dist/overwrite-file-handle.js +42 -0
- package/dist/path-case.d.ts +7 -0
- package/dist/path-case.d.ts.map +1 -0
- package/dist/path-case.js +136 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +5 -2
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +85 -53
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +7 -3
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +51 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +6 -5
- package/dist/realpath.d.ts +4 -0
- package/dist/realpath.d.ts.map +1 -0
- package/dist/realpath.js +43 -0
- package/dist/recursive-mkdir-path.d.ts +3 -0
- package/dist/recursive-mkdir-path.d.ts.map +1 -0
- package/dist/recursive-mkdir-path.js +8 -0
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +2 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +23 -31
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +7 -12
- package/dist/replace-file-mode.d.ts +3 -0
- package/dist/replace-file-mode.d.ts.map +1 -0
- package/dist/replace-file-mode.js +10 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +14 -8
- package/dist/root-context.d.ts +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +53 -28
- package/dist/root-create-input.d.ts +10 -0
- package/dist/root-create-input.d.ts.map +1 -0
- package/dist/root-create-input.js +80 -0
- package/dist/root-directory-list.d.ts +26 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +219 -0
- package/dist/root-entries.d.ts +11 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +61 -0
- package/dist/root-errors.d.ts +6 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +16 -12
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +17 -51
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +358 -234
- package/dist/root-options.d.ts +77 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +59 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +13 -9
- package/dist/root-remove.d.ts +5 -0
- package/dist/root-remove.d.ts.map +1 -0
- package/dist/root-remove.js +286 -0
- package/dist/root-symlink-policy.d.ts +14 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +3 -5
- package/dist/root.d.ts +4 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -8
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +2 -1
- package/dist/sibling-staged-file.d.ts +1 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +42 -8
- package/dist/sibling-temp.d.ts +2 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +6 -4
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +17 -5
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- package/dist/temp-target.d.ts +14 -12
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +12 -6
- package/dist/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +9 -7
- package/dist/unicode-path.d.ts +3 -0
- package/dist/unicode-path.d.ts.map +1 -0
- package/dist/unicode-path.js +13 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +9 -28
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +26 -0
- package/docs/advanced.md +22 -4
- package/docs/archive.md +44 -9
- package/docs/atomic.md +24 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +36 -1
- package/docs/copy.md +155 -0
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +53 -3
- package/docs/entries.md +109 -0
- package/docs/errors.md +4 -4
- package/docs/file-store.md +15 -0
- package/docs/guest.md +141 -0
- package/docs/in-place-write.md +81 -0
- package/docs/index.md +2 -0
- package/docs/install.md +31 -0
- package/docs/local-roots.md +8 -1
- package/docs/native-helper.md +10 -3
- package/docs/native.md +18 -1
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/permissions.md +29 -10
- package/docs/positional-read.md +63 -0
- package/docs/public-api.md +31 -2
- package/docs/root.md +196 -6
- package/docs/secure-file.md +2 -0
- package/docs/security-model.md +14 -0
- package/docs/sidecar-lock.md +12 -3
- package/docs/temp.md +35 -6
- package/docs/timing.md +2 -0
- package/docs/types.md +19 -12
- package/docs/walk.md +54 -5
- package/docs/writing.md +173 -5
- package/package.json +19 -8
package/docs/native-helper.md
CHANGED
|
@@ -30,10 +30,17 @@ The equivalent environment variables are `FS_SAFE_NATIVE_MODE` and `OPENCLAW_FS_
|
|
|
30
30
|
| `require` | Throw `FsSafeError("helper-unavailable")` instead of falling back when an operation needs the native binding and it cannot load. |
|
|
31
31
|
|
|
32
32
|
TAR/gzip in the guarded JavaScript path uses a bundled, import-free WASM build
|
|
33
|
-
of the same Rust parser used by native. `off` still disables native
|
|
34
|
-
|
|
33
|
+
of the same Rust parser used by native. `off` still disables the optional native
|
|
34
|
+
filesystem helper; it does not disable this portable parser. ZIP fallback still requires
|
|
35
35
|
optional `jszip`, and zstd/bzip2 remain native-only.
|
|
36
36
|
|
|
37
|
+
On Bun macOS/Linux, the [runtime path adapter](install.md#bun-runtime) uses the
|
|
38
|
+
same Rust addon for system canonicalization in `auto` and `require`. No JIT is
|
|
39
|
+
needed. With `off` or a missing addon in `auto`, Bun's own resolver retains its
|
|
40
|
+
path and permission limitations. Canonicalization in `require` fails with
|
|
41
|
+
`helper-unavailable` if the addon or its canonicalizer is missing, including
|
|
42
|
+
when admitting a temp workspace. Containment and identity checks stay intact.
|
|
43
|
+
|
|
37
44
|
Configure the mode once during startup. Loading is lazy and cached; changing from `auto` to `require` after a failed load changes failure policy but does not repeatedly probe the binary.
|
|
38
45
|
|
|
39
46
|
[`tempWorkspace()` and its scoped/sync variants](temp.md#private-temp-workspaces)
|
|
@@ -57,7 +64,7 @@ change the mode policy of existing fallback-capable APIs.
|
|
|
57
64
|
|
|
58
65
|
The native layer exposes policy-free filesystem mechanisms: beneath-root
|
|
59
66
|
open/mkdir/link, replace and no-replace rename, identity reads, archive decode/execution,
|
|
60
|
-
clone/copy/hash workers, and Windows security descriptor calls. The TypeScript
|
|
67
|
+
clone/copy/hash workers, POSIX canonicalization, and Windows security descriptor calls. The TypeScript
|
|
61
68
|
layer owns policy, retries, filters, budgets, modes, cleanup, error
|
|
62
69
|
normalization, and the decision to fall back.
|
|
63
70
|
|
package/docs/native.md
CHANGED
|
@@ -59,6 +59,19 @@ whether a path, archive entry, mode, owner, or cleanup policy is acceptable.
|
|
|
59
59
|
|
|
60
60
|
## Archives
|
|
61
61
|
|
|
62
|
+
Native ZIP and TAR entry reads retain a private, unpooled input Buffer in
|
|
63
|
+
the async reader. ZIP reuses its parsed directory; TAR retains admitted member
|
|
64
|
+
offsets. TypeScript validates and selects the requested member before reading. The internal binding borrows that allocation: it must never be
|
|
65
|
+
mutated or detached while the reader or a read task exists. The public API only
|
|
66
|
+
accepts a pathname and owns this buffer exclusively. An N-API reference keeps
|
|
67
|
+
the bytes alive; a mutex serializes access to the retained ZIP cursor. Plain
|
|
68
|
+
TAR copies only the selected range after complete admission, while gzip, zstd,
|
|
69
|
+
and bzip2 replay bounded decompression and validate the full physical stream.
|
|
70
|
+
Concurrent TAR reads share immutable input and own separate decoder state.
|
|
71
|
+
Each output owns a new vector, which N-API transfers to Node without a second
|
|
72
|
+
payload copy on runtimes supporting external buffers. No entry-read path
|
|
73
|
+
requires temporary-file staging. Extraction retains its private staged input.
|
|
74
|
+
|
|
62
75
|
Rust streams ZIP and TAR payloads, including gzip, zstd, and bzip2. It first
|
|
63
76
|
returns a bounded manifest. TypeScript applies the shared path, filter, strip,
|
|
64
77
|
mode, and byte policies and returns an index-bound extraction plan. Rust then
|
|
@@ -120,6 +133,10 @@ workers rather than the JavaScript event loop.
|
|
|
120
133
|
| `require` | Try once, cache the result | Throw `FsSafeError("helper-unavailable")` |
|
|
121
134
|
| `off` | Never attempt a binding load | Always use guarded JavaScript |
|
|
122
135
|
|
|
136
|
+
`sha256FileSync()` is a synchronous Node implementation in all three modes and
|
|
137
|
+
does not load the binding. Use asynchronous `sha256File()` for native hashing
|
|
138
|
+
and cancellation that can respond while JavaScript callbacks run.
|
|
139
|
+
|
|
123
140
|
Features without a safe JavaScript implementation, including zstd/bzip2 TAR,
|
|
124
141
|
Windows private-directory creation, and [retained-directory staging](staged-file.md),
|
|
125
142
|
fail with `helper-unavailable` when native support is absent or off. Staging
|
|
@@ -165,7 +182,7 @@ remain TypeScript-owned. What changes is the syscall strength or availability:
|
|
|
165
182
|
| Zstd/bzip2 TAR | Supported. | Unsupported; typed `helper-unavailable`. |
|
|
166
183
|
| Publication copy | Clone, Linux `copy_file_range`, async native SHA-256. | Exclusive `wx` byte loop and Node SHA-256 with the same content/identity fences. |
|
|
167
184
|
| `rename-noreplace` | Atomic platform no-replace rename. | Unsupported; no emulation by check-then-rename. |
|
|
168
|
-
| Windows DACL read | Direct `GetSecurityInfo`; the public facts API exposes ordered basic allow/deny ACE SIDs, masks, and decoded flags without trust policy. |
|
|
185
|
+
| Windows DACL read | Direct `GetSecurityInfo`; the public facts API exposes ordered basic allow/deny ACE SIDs, masks, and decoded flags without trust policy. | Structured .NET owner/DACL inspection for coarse permission checks; the public raw ACE facts API remains native-only. |
|
|
169
186
|
| Windows private directory | Creation-time protected DACL. | Unsupported; no weaker pathname-only substitute. |
|
|
170
187
|
|
|
171
188
|
Use `off` in CI to keep the fallback contract exercised. Use `require` when a
|
package/docs/output.md
CHANGED
|
@@ -35,6 +35,7 @@ type ExternalFileWriteOptions<T = void> = {
|
|
|
35
35
|
maxBytes?: number;
|
|
36
36
|
mode?: number;
|
|
37
37
|
staging?: "workspace" | "sibling"; // default: "workspace"
|
|
38
|
+
producerIsolation?: "private-directory"; // opt-in for sibling staging
|
|
38
39
|
fallbackFileName?: string; // safe staged-name fallback
|
|
39
40
|
};
|
|
40
41
|
|
|
@@ -60,7 +61,7 @@ hazards but does not trim Windows-normalized trailing dots or spaces; reject or
|
|
|
60
61
|
rewrite those when cross-platform filename uniqueness matters.
|
|
61
62
|
`staging: "workspace"` passes the sanitized basename to the producer.
|
|
62
63
|
`staging: "sibling"` embeds that basename in its randomized temporary name.
|
|
63
|
-
When the complete temporary component would exceed 255 bytes under NFC or NFD,
|
|
64
|
+
When the complete temporary component would exceed 255 bytes as written or under NFC or NFD,
|
|
64
65
|
only the embedded tail is shortened, preserving its extension when possible;
|
|
65
66
|
short callback paths remain unchanged. The final target and returned `path` use
|
|
66
67
|
the destination basename, sanitized
|
|
@@ -76,8 +77,8 @@ the temp and destination filesystems may differ, or when an externally produced
|
|
|
76
77
|
partial file must never appear in the destination directory. The final target
|
|
77
78
|
still appears only after guarded finalization.
|
|
78
79
|
|
|
79
|
-
`staging: "sibling"` gives the producer a randomized temp path in
|
|
80
|
-
directory. Choose it only when that directory itself is the approved writable
|
|
80
|
+
By default, `staging: "sibling"` gives the producer a randomized temp path in
|
|
81
|
+
the target directory. Choose it only when that directory itself is the approved writable
|
|
81
82
|
boundary and same-filesystem atomic replacement is required. After the callback
|
|
82
83
|
returns, fs-safe pins and validates the staged regular file, rejects hardlinks
|
|
83
84
|
and size-limit violations, applies `mode`, fsyncs it, and atomically renames it
|
|
@@ -91,11 +92,36 @@ cleanup retry.
|
|
|
91
92
|
Sibling staging shares the [callback sibling owner](temp.md#sibling-temp-writes):
|
|
92
93
|
it checks exact pre-open, descriptor, and current-path identities, retains the
|
|
93
94
|
descriptor through publication, and never chmods or reads a replacement by path.
|
|
94
|
-
|
|
95
|
-
throws before admission. Native-off and Windows
|
|
96
|
-
the platform limits and non-atomic
|
|
95
|
+
Without producer isolation, cleanup preserves unverified paths, including partial
|
|
96
|
+
output when the callback throws before admission. Native-off and Windows
|
|
97
|
+
operation remain supported with the platform limits and non-atomic
|
|
98
|
+
rename/unlink identity checks described there.
|
|
97
99
|
When `mode` is omitted, output-sibling staging preserves the producer's mode.
|
|
98
100
|
|
|
101
|
+
Add `producerIsolation: "private-directory"` to sibling staging when the
|
|
102
|
+
producer can leave partial output before throwing. It receives an initially
|
|
103
|
+
absent file path inside a private child workspace under the target parent, on
|
|
104
|
+
the target filesystem. Directory cleanup ownership is captured before the
|
|
105
|
+
callback. A callback exception triggers owned workspace cleanup, including
|
|
106
|
+
partial output, subject to directory identity checks and I/O failures.
|
|
107
|
+
After success, `Root.move` checks source aliases and moves the output to the
|
|
108
|
+
ordinary sibling path; an escaping symlink can fail with `path-alias` here.
|
|
109
|
+
Rejected output still inside the workspace follows its cleanup contract. Once
|
|
110
|
+
output moves to the sibling path, the existing unadmitted-file retention and
|
|
111
|
+
single-link regular-file admission, mode, file sync, and final rename rules apply.
|
|
112
|
+
|
|
113
|
+
Exact bigint parent and workspace identities are rechecked before moving
|
|
114
|
+
output to the sibling path to reject observed replacements. Cleanup uses the
|
|
115
|
+
existing [`withTempFile` ownership contract](temp.md#withtempfile). A moved or replaced parent or workspace can
|
|
116
|
+
leave original or replacement paths behind; the option does not promise
|
|
117
|
+
cleanup through a retained directory after a rename. The existing Windows,
|
|
118
|
+
native-off, and JavaScript guard limitations remain, with no additional
|
|
119
|
+
permissions or durability guarantee. See the [producer-isolation contract](temp.md#sibling-temp-writes)
|
|
120
|
+
for cleanup and pathname-race details. The option affects only `staging: "sibling"`;
|
|
121
|
+
with `staging: "workspace"`, it is redundant and harmless because the producer
|
|
122
|
+
already uses a private workspace. Omitting it leaves both staging defaults
|
|
123
|
+
unchanged.
|
|
124
|
+
|
|
99
125
|
## Why not pass the final path to the library?
|
|
100
126
|
|
|
101
127
|
If a target parent can be swapped after validation, handing an external library
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Path case probing
|
|
2
|
+
|
|
3
|
+
`probePathCaseInsensitiveSync()` observes whether a path's lookup location
|
|
4
|
+
folds ASCII case. It returns `true`, `false`, or `undefined` when the observation
|
|
5
|
+
cannot establish an answer. It does not infer a filesystem property from the
|
|
6
|
+
operating system or cache its result.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { probePathCaseInsensitiveSync } from "@openclaw/fs-safe/advanced";
|
|
10
|
+
|
|
11
|
+
const insensitive = probePathCaseInsensitiveSync("/srv/data/future.json", {
|
|
12
|
+
allowTemporaryProbe: false,
|
|
13
|
+
});
|
|
14
|
+
if (insensitive === undefined) {
|
|
15
|
+
// The application decides how to handle an unavailable observation.
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Lookup location
|
|
20
|
+
|
|
21
|
+
The input is resolved with Node's `path.resolve()`. Existing targets, including
|
|
22
|
+
directories and final symlinks, are first compared by basename in their parent.
|
|
23
|
+
The probe does not follow a final symlink to decide the target's case behavior.
|
|
24
|
+
Parent aliases are followed. For a missing path, it walks to the nearest existing
|
|
25
|
+
directory whose metadata can be read, without creating the missing directories.
|
|
26
|
+
An unreadable directory listing returns `undefined`.
|
|
27
|
+
|
|
28
|
+
The probe checks existing directory entries before considering a temporary
|
|
29
|
+
file. Separately listed case variants count as distinct entries even when they
|
|
30
|
+
are hardlinks to the same inode. Identity comparisons retain bigint precision;
|
|
31
|
+
unknown Windows identities do not count as matches. The original entry is
|
|
32
|
+
rechecked after looking up its case variant, so its disappearance or replacement
|
|
33
|
+
invalidates the observation. A detected directory replacement also returns
|
|
34
|
+
`undefined`.
|
|
35
|
+
|
|
36
|
+
## Temporary probes and cleanup
|
|
37
|
+
|
|
38
|
+
`allowTemporaryProbe` defaults to `true`. When existing entries give no answer,
|
|
39
|
+
the helper exclusively creates one empty `.fs-safe-case-probe-*` file in the
|
|
40
|
+
selected directory at mode `0o600`. It uses the existing temporary-file owner
|
|
41
|
+
to retain the descriptor and exact cleanup identity. Successful ordinary
|
|
42
|
+
completion removes the probe and closes the descriptor.
|
|
43
|
+
|
|
44
|
+
Set `allowTemporaryProbe: false` for strictly read-only observation. In that
|
|
45
|
+
mode an empty directory, or one with no useful ASCII-case names, returns
|
|
46
|
+
`undefined` without creating a temporary file. Temporary probing can change
|
|
47
|
+
directory timestamps and trigger filesystem watchers even when cleanup succeeds.
|
|
48
|
+
|
|
49
|
+
Operational failures, unverified identities, changed entries, and cleanup
|
|
50
|
+
failures return `undefined`. A substituted or hardlinked temporary entry is
|
|
51
|
+
preserved. When cleanup fails operationally, the existing owner retains its
|
|
52
|
+
identity-bound process-exit retry. A creation whose identity cannot be obtained
|
|
53
|
+
may leave an empty file; the helper never guesses cleanup ownership. Therefore
|
|
54
|
+
`undefined` does not promise that no temporary artifact remains.
|
|
55
|
+
|
|
56
|
+
## Limits
|
|
57
|
+
|
|
58
|
+
This is a local ASCII-case observation, not a Unicode-normalization test,
|
|
59
|
+
filesystem-wide guarantee, lock, or authorization receipt. Directory enumeration
|
|
60
|
+
and metadata lookups are separate operations. Concurrent changes can invalidate
|
|
61
|
+
or immediately stale a result, and the final identity check and unlink are not
|
|
62
|
+
an atomic conditional deletion. Use temporary probing only where creating a
|
|
63
|
+
temporary file is permitted. The caller retains any admission, serialization,
|
|
64
|
+
fallback, or later mutation policy.
|
package/docs/path-scope.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pathScope()
|
|
2
2
|
|
|
3
|
-
`pathScope()`
|
|
3
|
+
`pathScope()` prepares absolute paths and returns plain `{ ok, path }` results. `resolve()` and `resolveAll()` check lexical containment without touching the filesystem; `existing()`, `files()`, and `writable()` add the filesystem checks described below. Use it to prepare paths before handing them to another library, whose file-opening and mutation behavior still applies.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import { pathScope } from "@openclaw/fs-safe/advanced";
|
package/docs/permissions.md
CHANGED
|
@@ -68,11 +68,27 @@ createIcaclsResetCommand(targetPath, { isDir, env });
|
|
|
68
68
|
resolveWindowsUserPrincipal(env);
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The fallback Windows inspector
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
The fallback Windows inspector reads the owner and DACL together through one
|
|
72
|
+
built-in Windows PowerShell/.NET query. It returns canonical SIDs and numeric
|
|
73
|
+
access masks, so Unicode paths and account names do not pass through lossy
|
|
74
|
+
console display text. `inspectWindowsAcl()` uses native descriptor facts for
|
|
75
|
+
complete local ACLs with nonzero inherited ACEs (or empty/null DACLs) when the
|
|
76
|
+
optional Windows binding is available. It applies
|
|
77
|
+
the same classifier to native facts and the fallback query, returning canonical
|
|
78
|
+
SIDs in its `principal` fields with normalized rights tokens. Explicit `env` or
|
|
79
|
+
`exec` options retain the query path. Disabled or unavailable native helpers,
|
|
80
|
+
remote or incomplete descriptors, leaf symbolic links, and native query errors
|
|
81
|
+
use the fallback. Explicit ACEs and zero-mask entries also retain the query so
|
|
82
|
+
.NET continues to own its ACE ordering and normalization.
|
|
83
|
+
Structured ACLs containing only canonical SIDs are classified directly from
|
|
84
|
+
the current-user SID without requiring a separate account-name lookup.
|
|
85
|
+
The advanced options retain `currentUserSid` as an explicit classification
|
|
86
|
+
override and `principalTranslationFailed: true` as an immediate unverified
|
|
87
|
+
result. The optional `principalSids` translation cache is still accepted but
|
|
88
|
+
is no longer needed because the query returns SIDs directly.
|
|
89
|
+
The existing classifier assigns principals to trusted, world, or group;
|
|
90
|
+
trusted defaults include the current user, SYSTEM, and Administrators.
|
|
91
|
+
The built-in query has a fixed 30-second process deadline. A command failure or timeout returns an
|
|
76
92
|
unverified result (`source: "unknown"`) so callers fail closed. Advanced callers
|
|
77
93
|
that inject a custom `exec` implementation own that executor's deadline.
|
|
78
94
|
Failed owner and ACL inspections retain `error` text and an optional
|
|
@@ -86,15 +102,18 @@ characters, including a trailing `…` when truncated. Diagnostics do not copy
|
|
|
86
102
|
stdout or read target file contents. The separate `errorCause` retains the
|
|
87
103
|
original exception for restricted local diagnosis; do not serialize or expose
|
|
88
104
|
it as display text.
|
|
89
|
-
The parser
|
|
90
|
-
`icacls` output
|
|
105
|
+
The parser and remediation command builders remain on the advanced surface for
|
|
106
|
+
CLIs processing captured `icacls` output or presenting an explicit repair.
|
|
107
|
+
Runtime inspection does not parse that display text. A null DACL reports
|
|
108
|
+
unrestricted access; an empty DACL grants nothing. Inherit-only ACEs do not
|
|
109
|
+
apply to the inspected object, and deny ACEs never subtract coarse grants or
|
|
110
|
+
claim effective-access evaluation. Unsupported ACE layouts remain unverified.
|
|
91
111
|
|
|
92
112
|
When the native binding is available, `inspectPathPermissions()`
|
|
93
113
|
reads the owner and DACL directly with Windows security APIs. It classifies the
|
|
94
114
|
current user, LocalSystem, and built-in Administrators as trusted and reports
|
|
95
115
|
the world/group read/write facts consumed by secure reads. Descriptor forms it
|
|
96
|
-
cannot classify equivalently fall back to the
|
|
97
|
-
`icacls` path; `mode: "off"` exercises that fallback deterministically.
|
|
116
|
+
cannot classify equivalently fall back to the structured .NET query; `mode: "off"` exercises that fallback deterministically.
|
|
98
117
|
|
|
99
118
|
## Policy-free owner and DACL facts
|
|
100
119
|
|
|
@@ -164,7 +183,7 @@ This API is Windows-only and native-only; it fails closed with
|
|
|
164
183
|
or when the binding is unavailable. POSIX callers should create private
|
|
165
184
|
directories through their existing trusted-root creation policy rather than a
|
|
166
185
|
pathname-only compatibility shim. Existing Windows permission inspection still
|
|
167
|
-
retains its .NET
|
|
186
|
+
retains its structured .NET compatibility fallback.
|
|
168
187
|
|
|
169
188
|
Use `createIcaclsResetCommand()` when you need a structured command and argv pair. Use `formatIcaclsResetCommand()` when you only need a remediation string for a user-facing message.
|
|
170
189
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Positional reads
|
|
2
|
+
|
|
3
|
+
Use `readFileWindowFully()` and `readFileWindowFullySync()` to read a bounded
|
|
4
|
+
window from an already-open file. They fill a caller-owned `Buffer`, completing
|
|
5
|
+
short reads until the buffer is full or the file reaches EOF, and return the
|
|
6
|
+
number of bytes read. They never allocate a payload buffer, close the descriptor,
|
|
7
|
+
or change its current offset.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { root } from "@openclaw/fs-safe";
|
|
11
|
+
import { readFileWindowFully } from "@openclaw/fs-safe/advanced";
|
|
12
|
+
|
|
13
|
+
const workspace = await root("/srv/workspace");
|
|
14
|
+
await using opened = await workspace.open("large.log");
|
|
15
|
+
const buffer = Buffer.allocUnsafe(4096);
|
|
16
|
+
const bytesRead = await readFileWindowFully(opened.handle, buffer, 8192);
|
|
17
|
+
const window = buffer.subarray(0, bytesRead);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Signatures
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
type ReadFileWindowOptions = { signal?: AbortSignal };
|
|
24
|
+
|
|
25
|
+
function readFileWindowFully(
|
|
26
|
+
handle: import("node:fs/promises").FileHandle,
|
|
27
|
+
buffer: Buffer,
|
|
28
|
+
position: number,
|
|
29
|
+
options?: ReadFileWindowOptions,
|
|
30
|
+
): Promise<number>;
|
|
31
|
+
|
|
32
|
+
function readFileWindowFullySync(
|
|
33
|
+
fd: number,
|
|
34
|
+
buffer: Buffer,
|
|
35
|
+
position: number,
|
|
36
|
+
): number;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`position` and the exclusive window end (`position + buffer.length`) must be
|
|
40
|
+
non-negative safe integers. Invalid ranges throw `RangeError` before reading.
|
|
41
|
+
A zero-length buffer returns zero without I/O. Reading at or beyond EOF also
|
|
42
|
+
returns zero. If EOF occurs within the window, only the returned prefix is
|
|
43
|
+
written; the remaining buffer bytes stay unchanged. Always slice by the returned
|
|
44
|
+
count before using an unsafe-allocated buffer.
|
|
45
|
+
|
|
46
|
+
These helpers use the caller's open descriptor directly. They do not establish
|
|
47
|
+
path containment, file identity, file-type admission, or a snapshot of concurrently
|
|
48
|
+
modified contents. Use [`Root.open()`](root.md#reads) to admit untrusted paths,
|
|
49
|
+
and keep the handle open and the buffer available until the operation settles.
|
|
50
|
+
Underlying I/O errors propagate unchanged.
|
|
51
|
+
|
|
52
|
+
## Cancellation
|
|
53
|
+
|
|
54
|
+
The async variant accepts `signal`. A pre-aborted signal rejects before I/O.
|
|
55
|
+
In-flight cancellation is checked after the pending read settles and before
|
|
56
|
+
another read starts, preserving the signal's reason. Bytes already read remain
|
|
57
|
+
in the buffer; cancellation does not roll them back. Once the promise settles,
|
|
58
|
+
the caller can reuse the buffer or close its handle without a hidden read still
|
|
59
|
+
running.
|
|
60
|
+
|
|
61
|
+
For a whole-file read that rejects files exceeding a byte limit, use the
|
|
62
|
+
[bounded descriptor readers](advanced.md#files-and-identity) instead. Positional
|
|
63
|
+
reads stop successfully at the requested window and do not probe for extra data.
|
package/docs/public-api.md
CHANGED
|
@@ -38,6 +38,33 @@ The advanced root-file primitive exports `OpenRootFileParams`,
|
|
|
38
38
|
`RootFileOpenFailureReason`. These are composition types for callers building
|
|
39
39
|
their own pinned-open flow, not substitutes for the higher-level `Root` verbs.
|
|
40
40
|
|
|
41
|
+
`copyFileHandle` and `CopyFileHandleOptions` transfer bytes between already-open
|
|
42
|
+
regular files without taking over their cursors, lifetime, or publication.
|
|
43
|
+
See [borrowed-handle transfers](copy.md#borrowed-filehandle-transfers).
|
|
44
|
+
|
|
45
|
+
`readDirectoryIdentity`, `assertDirectoryIdentitySync`, and `DirectoryIdentity`
|
|
46
|
+
provide exact directory observations without owning a descriptor or a mutation.
|
|
47
|
+
The assertion accepts an observed path and optional expected canonical path;
|
|
48
|
+
see [directory identity](directory-identity.md).
|
|
49
|
+
|
|
50
|
+
`overwriteFileHandle` and `OverwriteFileHandleOptions` provide in-place byte
|
|
51
|
+
replacement through a borrowed regular-file handle. Its once-only `beforeWrite`
|
|
52
|
+
callback admits the complete write and any required best-effort rollback after
|
|
53
|
+
prefix preparation. See [in-place writes](in-place-write.md).
|
|
54
|
+
|
|
55
|
+
`probePathCaseInsensitiveSync` and `ProbePathCaseOptions` are advanced exports
|
|
56
|
+
for local ASCII-case observations. An unavailable answer remains `undefined`;
|
|
57
|
+
the caller selects any fallback. See [path case probing](path-case.md).
|
|
58
|
+
|
|
59
|
+
## Guest source
|
|
60
|
+
|
|
61
|
+
`@openclaw/fs-safe/guest` exports `GUEST_FILESYSTEM_PYTHON`,
|
|
62
|
+
`GUEST_FILESYSTEM_CREATE_EXISTS_EXIT_CODE`,
|
|
63
|
+
`GUEST_FILESYSTEM_READ_NOT_FOUND_EXIT_CODE`, and
|
|
64
|
+
`GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON`. These are source and protocol
|
|
65
|
+
constants; the caller launches the Python guest and owns authorization and
|
|
66
|
+
transport lifetime. See the [guest protocol](guest.md).
|
|
67
|
+
|
|
41
68
|
## `json` and `store`
|
|
42
69
|
|
|
43
70
|
Standalone structured reads use `ReadJsonOptions`, `ReadRootJsonSyncOptions`,
|
|
@@ -89,8 +116,10 @@ The durability surface also exports the synchronous strict
|
|
|
89
116
|
`EnsureDurableDirectoryOptions`, `PublishFileExclusiveResult`,
|
|
90
117
|
`PublishFileExclusiveStrategy`, `PublishFileExclusiveCleanup`,
|
|
91
118
|
`PublishFileExclusiveFailurePhase`,
|
|
92
|
-
`PublishFileExclusiveDirectorySyncFailure`, `Sha256FileInput`,
|
|
93
|
-
`Sha256FileResult`.
|
|
119
|
+
`PublishFileExclusiveDirectorySyncFailure`, `Sha256FileInput`,
|
|
120
|
+
`Sha256FileSyncInput`, `Sha256FileOptions`, and `Sha256FileResult`.
|
|
121
|
+
`sha256FileSync()` provides synchronous pathname or borrowed-descriptor hashing
|
|
122
|
+
with the same byte-budget and digest-result contracts as `sha256File()`.
|
|
94
123
|
|
|
95
124
|
## Archives
|
|
96
125
|
|