@openclaw/openshell-sandbox 2026.10.2 → 2026.10.5-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/dist/index.js +69 -111
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +35 -0
- package/node_modules/@openclaw/fs-safe/README.md +1 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +5 -4
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +22 -38
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +117 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +8 -5
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +27 -50
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-acquire.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-acquire.js +57 -11
- package/node_modules/@openclaw/fs-safe/dist/guarded-mutation.js +5 -5
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +52 -5
- package/node_modules/@openclaw/fs-safe/dist/guest.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +22 -26
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +34 -7
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +6 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-input.d.ts +15 -1
- package/node_modules/@openclaw/fs-safe/dist/root-create-input.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +6 -12
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +15 -15
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.d.ts +10 -1
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.js +20 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +19 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +25 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +187 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +15 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-directory-diagnostics.d.ts +10 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-directory-diagnostics.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +4 -4
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +17 -16
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +20 -28
- package/node_modules/@openclaw/fs-safe/dist/walk.js +2 -13
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/watch.js +2 -3
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/archive.md +3 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +11 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/copy.md +48 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +21 -4
- package/node_modules/@openclaw/fs-safe/docs/path-scope.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +46 -8
- package/node_modules/@openclaw/fs-safe/docs/temp.md +12 -0
- package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
- package/node_modules/@openclaw/fs-safe/package.json +15 -15
- 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-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-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/file-lock-sync-root-arbitration.d.ts +0 -17
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-arbitration.js +0 -66
- package/node_modules/@openclaw/fs-safe/dist/publish-file-native.d.ts +0 -4
- package/node_modules/@openclaw/fs-safe/dist/publish-file-native.js +0 -35
- package/node_modules/@openclaw/fs-safe/dist/root-move-preflight.d.ts +0 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-preflight.js +0 -16
|
@@ -55,15 +55,9 @@ export function mergeWatchRescan(scopes, before, slice, requested) {
|
|
|
55
55
|
for (const name of slice.childPaths?.get(directory)?.values() ?? [])
|
|
56
56
|
selected.add(name);
|
|
57
57
|
}
|
|
58
|
-
for (const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const current = slice.entries.get(name);
|
|
62
|
-
if ((previous.startsWith("directory:") || current?.startsWith("directory:")) && identity(previous) !== identity(current))
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
for (const [name, current] of slice.entries) {
|
|
66
|
-
if (selected.has(name) && current.startsWith("directory:") && identity(current) !== identity(before.entries.get(name)))
|
|
58
|
+
for (const name of selected) {
|
|
59
|
+
const previous = before.entries.get(name), current = slice.entries.get(name);
|
|
60
|
+
if ((previous?.startsWith("directory:") || current?.startsWith("directory:")) && identity(previous) !== identity(current))
|
|
67
61
|
return;
|
|
68
62
|
}
|
|
69
63
|
// Exclusion changes can retire or admit a subtree, including callback-driven changes.
|
|
@@ -5,7 +5,6 @@ import { rootHandleContext } from "./root-handle-context.js";
|
|
|
5
5
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
6
6
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
7
7
|
import { mergeWatchRescan, watchRescanScopes } from "./watch-rescan.js";
|
|
8
|
-
import { watchStreamPaths } from "./watch-stream.js";
|
|
9
8
|
import { admittedNativeChanges, changedEntries, excludedWatchPath, guardedHintChanges, scopedChanges } from "./watch-hints.js";
|
|
10
9
|
import path from "node:path";
|
|
11
10
|
import { watchBinding, NativeWatchBackend } from "./watch-native.js";
|
|
@@ -338,7 +337,7 @@ export function watch(root, input) {
|
|
|
338
337
|
}, maxPendingPaths, persistent);
|
|
339
338
|
backend = candidate;
|
|
340
339
|
if (snapshot)
|
|
341
|
-
candidate.configure(
|
|
340
|
+
candidate.configure(snapshot, g.scopes);
|
|
342
341
|
const hookResult = getFsSafeTestHooks()?.afterWatchBackendCreated?.(context.rootReal, batch => {
|
|
343
342
|
if (backend === candidate)
|
|
344
343
|
onHint(g, batch);
|
|
@@ -401,7 +400,7 @@ export function watch(root, input) {
|
|
|
401
400
|
}
|
|
402
401
|
try {
|
|
403
402
|
const entriesChanged = backend?.entries(next);
|
|
404
|
-
const streamsChanged = backend?.configure(
|
|
403
|
+
const streamsChanged = backend?.configure(next, g.scopes);
|
|
405
404
|
if (entriesChanged || streamsChanged) {
|
|
406
405
|
// Complete a guarded pass with the new transport before publishing readiness.
|
|
407
406
|
// Bound handovers under churn; later passes still reconcile ongoing changes.
|
|
@@ -82,7 +82,7 @@ concurrent namespace changes.
|
|
|
82
82
|
| `appendRegularFile`, `appendRegularFileSync`, `readRegularFile`, `readRegularFileSync`, `statRegularFile`, `statRegularFileSync`, `resolveRegularFileAppendFlags`, `AppendRegularFileOptions`, `RegularFileStatResult` | [regular-file.md](regular-file.md) | Type-checked regular-file I/O. |
|
|
83
83
|
| `retainEntryForPublication`, `RetainedEntryPublication`, `RetainEntryForPublicationOptions`, `EntryPublicationReceipt`, `EntryPublicationResult`, `EntryPublicationIssue`, `PublicationIdentity`, `PublicationParent` | [One-way entry publication](entry-publication.md) | Native no-replace directory/regular-file/symlink export, including Windows NTFS junctions, under caller-exclusive namespace; explicit transition receipts, close-only disposal, no source CAS. |
|
|
84
84
|
| `retainFileInDirectory`, `RetainedFile`, related receipt/result types | [Retained Windows files](retained-file.md) | Existing-file native handle custody and explicit removal; local NTFS, producer authority required, no persistence guarantee. |
|
|
85
|
-
| `copyRootFileSync`, `CopyRootFileSyncOptions`, `CopiedRootFileSync` | [copy.md](copy.md#synchronous-guarded-file-copies) | Synchronous guarded exclusive file copy, optionally cloned and bound to an expected source identity, returning an owned destination descriptor and both identities. |
|
|
85
|
+
| `copyRootFileSync`, `createRootFileCopyBatchSync`, `RootFileCopyBatchSync`, `CopyRootFileSyncOptions`, `CopiedRootFileSync` | [copy.md](copy.md#synchronous-guarded-file-copies) | Synchronous guarded exclusive file copy, optionally cloned and bound to an expected source identity, returning an owned destination descriptor and both identities. |
|
|
86
86
|
| `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality. |
|
|
87
87
|
| `readDirectoryIdentity`, `assertDirectoryIdentitySync`, `DirectoryIdentity` | [directory-identity.md](directory-identity.md) | Observe exact bigint directory identity and synchronously check a caller-selected path, optionally retaining its canonical path. |
|
|
88
88
|
| `pathExists`, `pathExistsSync` | – | Boolean existence check that does not throw on `ENOENT`. |
|
|
@@ -702,6 +702,9 @@ await extractArchive({
|
|
|
702
702
|
|
|
703
703
|
## `readArchiveEntry`
|
|
704
704
|
|
|
705
|
+
The byte limit is selected and validated once before reading the archive; changing
|
|
706
|
+
the options object during a read does not change the active limit.
|
|
707
|
+
|
|
705
708
|
`readArchiveEntry(archivePath, entryPath, { maxBytes, kind? })` reads one
|
|
706
709
|
regular-file entry into a bounded `Buffer` without extracting a tree. It reads
|
|
707
710
|
the input through an identity-checked descriptor, rejects a requested link or
|
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
|
|
17
17
|
Write `content` to a sibling temp file in the destination directory, apply the parent-directory and final file modes through verified descriptors, optionally `fsync` the file descriptor, optionally `fsync` the parent directory after rename, then atomically rename over the destination. No permission change follows a caller-supplied pathname.
|
|
18
18
|
|
|
19
|
+
On POSIX, non-durable asynchronous writes using Node inspect the staged file's
|
|
20
|
+
complete permission bits with their final identity check and skip a redundant
|
|
21
|
+
mode change when they already match. A restrictive umask or differing special
|
|
22
|
+
bits still require descriptor mode correction before publication.
|
|
23
|
+
|
|
19
24
|
On POSIX, the parent is opened with no-follow and directory-only flags, checked against its exact pre-open device/inode identity, and mode-adjusted through that descriptor. A replacement symlink is rejected rather than followed. If the directory cannot be opened for descriptor access, the operation fails closed instead of retrying by pathname. Windows does not enforce POSIX directory modes and Node cannot consistently open directory descriptors there, so `dirMode` is passed only to `mkdir`; no pathname `chmod` fallback is attempted.
|
|
20
25
|
|
|
21
26
|
Async replacements to the same destination are serialized inside the current process, so two overlapping `replaceFileAtomic()` calls do not interleave their temp-write/rename phases. Use a sidecar lock when multiple processes may write the same target.
|
|
@@ -258,7 +263,9 @@ opened, removed, or replaced. A successor at that name is left untouched.
|
|
|
258
263
|
`details.cleanup: "restored"` means the retained original file's bytes and mode
|
|
259
264
|
were restored and synchronized, not that the pathname still names it. The existing
|
|
260
265
|
`writing` receipt identifies that file; no `published` receipt is emitted for a
|
|
261
|
-
failed replacement. Restoration I/O failures report `"restore-failed"
|
|
266
|
+
failed replacement. Restoration I/O failures report `"restore-failed"`, even if
|
|
267
|
+
the thrown error itself carries a `cleanup: "restored"` receipt from another
|
|
268
|
+
operation; the cause retains both the replacement and restoration failures.
|
|
262
269
|
|
|
263
270
|
Callback refusals, detected identity/type/link changes, and other metadata errors
|
|
264
271
|
remain terminal. Failed descriptor revalidation also stops restoration. These
|
|
@@ -539,8 +546,9 @@ remain a best-effort sequence, not atomic.
|
|
|
539
546
|
`onDestinationPublished` runs exactly once after a successful rename resolves,
|
|
540
547
|
before awaited post-rename directory checks or source cleanup. It receives a
|
|
541
548
|
frozen receipt with the resolved absolute destination path and the exact bigint
|
|
542
|
-
device/inode identity observed on the rename source
|
|
543
|
-
|
|
549
|
+
device/inode identity observed on the rename source after the synchronous
|
|
550
|
+
authority callbacks return and before dispatch: the source selected for a direct
|
|
551
|
+
rename, or the staged copy for fallback. Failed
|
|
544
552
|
rename attempts never emit receipts. The return contract remains `Promise<void>`.
|
|
545
553
|
|
|
546
554
|
The receipt records an observed identity, not authorization, durable storage,
|
|
@@ -175,7 +175,7 @@ temporary storage; session scratch trees may suppress macOS filesystem events.
|
|
|
175
175
|
### Optional Linux Testbox
|
|
176
176
|
|
|
177
177
|
The manual `testbox-validation.yml` workflow prepares a 16-vCPU Ubuntu 24.04
|
|
178
|
-
Blacksmith Testbox with Node 24.21.0, pnpm 12.
|
|
178
|
+
Blacksmith Testbox with Node 24.21.0, pnpm 12.10.1, dependencies, the Rust WASM
|
|
179
179
|
target, and the pinned portable archive compiler. It leaves library builds and
|
|
180
180
|
validation commands to the caller and does not replace required CI checks.
|
|
181
181
|
|
|
@@ -257,3 +257,51 @@ replacement entries. This helper creates directly at the destination name, so
|
|
|
257
257
|
other processes may observe partial bytes until it returns. It does not promise
|
|
258
258
|
crash durability or a coherent snapshot of concurrent in-place source edits.
|
|
259
259
|
Use a private capture directory and keep sources unchanged while capturing.
|
|
260
|
+
|
|
261
|
+
### Batches of synchronous guarded copies
|
|
262
|
+
|
|
263
|
+
`createRootFileCopyBatchSync()` on `advanced` returns a disposable
|
|
264
|
+
`RootFileCopyBatchSync`. Its `copyFile(options)` takes the same
|
|
265
|
+
`CopyRootFileSyncOptions` and returns the same owned `CopiedRootFileSync` as
|
|
266
|
+
`copyRootFileSync`:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { createRootFileCopyBatchSync } from "@openclaw/fs-safe/advanced";
|
|
270
|
+
|
|
271
|
+
using batch = createRootFileCopyBatchSync();
|
|
272
|
+
for (const name of ["plugin.js", "package.json"]) {
|
|
273
|
+
using copied = batch.copyFile({
|
|
274
|
+
source: { rootPath: "/srv/plugins", absolutePath: `/srv/plugins/${name}` },
|
|
275
|
+
destination: { rootPath: "/srv/capture", absolutePath: `/srv/capture/${name}` },
|
|
276
|
+
clone: "auto",
|
|
277
|
+
maxBytes: 16 * 1024 * 1024,
|
|
278
|
+
});
|
|
279
|
+
console.log(copied.bytes, copied.sourceIdentity);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
On canonical POSIX paths, the batch retains root and ancestor directory
|
|
284
|
+
identities across files. Every use still checks those identities without
|
|
285
|
+
following directory symlinks; root and parent canonical paths are rechecked.
|
|
286
|
+
Source opens, source and destination file identities, hardlink policy, exclusive
|
|
287
|
+
creation, bounds, permissions, and cleanup are checked separately for every file.
|
|
288
|
+
No source descriptor, file contents, or successful file-admission result is cached.
|
|
289
|
+
An ancestor replacement rejects even when the leaf parent has not changed.
|
|
290
|
+
Windows and noncanonical path spellings keep the full per-file resolver and its
|
|
291
|
+
alias handling. There is no option to bypass validation.
|
|
292
|
+
|
|
293
|
+
Keep the batch scoped to one capture or materialization, with unchanged source
|
|
294
|
+
and destination directory topology. `close()` and `Symbol.dispose` idempotently
|
|
295
|
+
release its retained metadata; later calls reject with `invalid-path`. Returned
|
|
296
|
+
file descriptors remain independently caller-owned. Successful earlier files
|
|
297
|
+
are not rolled back if a later copy fails. A failed copy keeps the ordinary
|
|
298
|
+
identity-checked cleanup contract; the batch is not an atomic tree transaction
|
|
299
|
+
or a coherent snapshot of concurrent edits. The single-file helper also reuses
|
|
300
|
+
directory admissions for the duration of its one copy.
|
|
301
|
+
|
|
302
|
+
Both APIs reuse the freshly checked canonical root during initial POSIX path
|
|
303
|
+
admission. This avoids repeatedly resolving every absolute root component while
|
|
304
|
+
retaining lexical traversal and the before/after root and parent identity checks.
|
|
305
|
+
Windows and root aliases retain the ordinary resolver, including its handling
|
|
306
|
+
of dot segments in symlink targets. No caller changes are needed for the
|
|
307
|
+
single-file improvement.
|
|
@@ -43,9 +43,26 @@ The fragment has no argument preflight of its own.
|
|
|
43
43
|
|
|
44
44
|
On Linux it uses `renameat2(RENAME_NOREPLACE)`; on macOS it uses
|
|
45
45
|
`renameatx_np(RENAME_EXCL)`. If Linux lacks that function or reports it as
|
|
46
|
-
unsupported, it falls back to `link(..., follow_symlinks=False)`,
|
|
47
|
-
source
|
|
48
|
-
|
|
46
|
+
unsupported, it falls back to `link(..., follow_symlinks=False)`, verifying the
|
|
47
|
+
source and target against the admitted inode with exactly two links
|
|
48
|
+
before unlinking the source. Admission and final target verification require
|
|
49
|
+
one link. A successful file publication consumes the source in both paths.
|
|
50
|
+
|
|
51
|
+
For directories, a definitive Linux rejection (`EINVAL`, `ENOSYS`, `ENOTSUP`,
|
|
52
|
+
or `EOPNOTSUPP`) or missing `renameat2` rechecks the source identity and target
|
|
53
|
+
absence, then uses plain rename through the same directory descriptors. A
|
|
54
|
+
trailing slash enforces directory source type at the syscall. Directory rename
|
|
55
|
+
can replace only an **empty directory created concurrently**; non-empty
|
|
56
|
+
directories and non-directory targets are refused as `EEXIST`. An existing
|
|
57
|
+
empty directory observed by the absence check is also refused. No empty claim
|
|
58
|
+
directory is exposed before publishing the completed source.
|
|
59
|
+
|
|
60
|
+
Identity checks and mutation remain separate, not source-name compare-and-swap.
|
|
61
|
+
The fallback verifies the published identity afterward. A file fallback failure
|
|
62
|
+
can leave both names, and post-publication failures preserve the destination
|
|
63
|
+
for caller reconciliation. Exclusive-create cleanup preserves changed payloads
|
|
64
|
+
and incomplete link pairs. The guest has no strict/require mode; host native
|
|
65
|
+
policy does not apply. macOS and other errors retain their fail-closed behavior.
|
|
49
66
|
|
|
50
67
|
## Invocation protocol
|
|
51
68
|
|
|
@@ -107,7 +124,7 @@ entries, including symlinks, without following them. A force removal tolerates
|
|
|
107
124
|
a missing leaf but does not suppress failure to open its parent.
|
|
108
125
|
|
|
109
126
|
Write preserves an existing regular file's mode and otherwise creates private
|
|
110
|
-
files. Copy
|
|
127
|
+
files. Copy and cross-device file moves preserve the admitted source descriptor's mode. Exclusive create publishes a mode-0600
|
|
111
128
|
file from a private staging directory. Staging names retain the `.openclaw-*`
|
|
112
129
|
prefixes and use short random suffixes independent of the destination basename,
|
|
113
130
|
so legal names near the filesystem's component limit also work for writes and
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# pathScope()
|
|
2
2
|
|
|
3
|
+
Paths supplied to a scope are literal filesystem names. In particular, `~/file`
|
|
4
|
+
selects a file inside a directory named `~` beneath the scope; it does not expand
|
|
5
|
+
the user's home directory.
|
|
6
|
+
|
|
3
7
|
`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
8
|
|
|
5
9
|
```ts
|
|
@@ -35,7 +35,7 @@ try {
|
|
|
35
35
|
|
|
36
36
|
The lock file sits next to the protected resource. If a process crashes mid-lock, the next acquirer notices the held entry, inspects its payload (PID, host, acquired-at timestamp), and decides — via `shouldReclaim` (defaulting to "is the lock older than `staleMs`?") — whether it should keep waiting or fail.
|
|
37
37
|
|
|
38
|
-
On natural event-loop shutdown, a globally deduplicated `process.on("beforeExit")` handler attempts asynchronous cleanup of held Root-backed locks through their
|
|
38
|
+
On natural event-loop shutdown, a globally deduplicated `process.on("beforeExit")` handler attempts asynchronous cleanup of held Root-backed locks through their creation receipt (or the Root capability when native retention is unavailable). The synchronous `process.on("exit")` handler also cleans retained native sidecars, raw locks, and raw-path reclaim guards. Changed sidecars and failed Root cleanup remain in place; cleanup does not keep retrying during shutdown unless another acquisition re-arms it. Locks acquired with `retainOnExit: true` are exempt from both handlers: their sidecar stays in place after exit and is governed only by the caller's own stale policy. Because exit handlers are globally deduplicated across package copies, `retainOnExit` fails closed with `helper-unavailable` if an older copy that cannot honor it registered the handlers first.
|
|
39
39
|
|
|
40
40
|
Asynchronous Root-backed stale recovery uses a regular file at the
|
|
41
41
|
`.reclaim` name, created, verified, and removed through that Root. Its ownership
|
|
@@ -57,17 +57,17 @@ even if guard ownership has also changed.
|
|
|
57
57
|
Root guard; let its original attempt settle before retrying that guarded path.
|
|
58
58
|
It stops compromise monitoring for forgotten holders, including callbacks from checks already in flight.
|
|
59
59
|
|
|
60
|
-
Always release locks in a `finally` block. Application-managed graceful shutdown can await `release()` or `manager.drain()` before terminating. Explicit `process.exit()
|
|
60
|
+
Always release locks in a `finally` block. Application-managed graceful shutdown can await `release()` or `manager.drain()` before terminating. Explicit `process.exit()` runs synchronous cleanup for retained native sidecars, but cannot run asynchronous fallback Root cleanup. Uncaught failures, crashes, default signal handling, and fatal termination (including `SIGKILL`) may leave sidecars. Recover only after an application-owned liveness policy proves the holder cannot still be writing.
|
|
61
61
|
|
|
62
62
|
Exit cleanup tolerates shared managers created by older package copies that lack reclaim-guard state, and continues through later manager domains. Handler ownership remains first-registration-wins: loading an updated copy does not replace an older copy's registered handler. First registration is committed only after Node accepts the listener; synchronous `newListener` reentry fails closed and a thrown registration rolls back so a later acquisition can retry. Restart with the updated copy registering first to obtain the fix. After building, `node scripts/legacy-lock-exit-proof.mjs` checks clean exit, removal of legacy and modern raw locks, and preservation of a retained lock using synthetic temporary files.
|
|
63
63
|
|
|
64
|
-
Each new sidecar also carries an internal random ownership token encoded as JSON trailing whitespace. `JSON.parse()` and every payload callback still see exactly the caller-provided object. Only the process that successfully created the sidecar keeps that token as release authority; merely reading token-shaped bytes from disk does not enable this mode.
|
|
64
|
+
Each new sidecar also carries an internal random ownership token encoded as JSON trailing whitespace. `JSON.parse()` and every payload callback still see exactly the caller-provided object. Only the process that successfully created the sidecar keeps that token as release authority; merely reading token-shaped bytes from disk does not enable this mode. Raw and JavaScript fallback release compare the in-memory token and exact serialized bytes, and require the pathname to remain a regular file, instead of requiring an opened descriptor and pathname lookup to report the same inode identity. Retained native Root locks additionally require the exact created inode and a single link, as described below. This preserves ownership checks on filesystems such as Docker Desktop VirtioFS where those two views can legitimately differ. Sidecars created by older releases have no token and retain the legacy identity-plus-content check. Raw acquisition, snapshot, and exit cleanup capture bigint device/inode values; unsafe numeric identities from older in-process receipts cannot authorize cleanup. If Windows reports a zero device or inode for either identity observation, that check is inconclusive and removal is skipped.
|
|
65
65
|
|
|
66
66
|
Last-chance raw-lock exit cleanup requires known Windows device and inode values from both pathname observations and the opened descriptor. A zero value leaves the sidecar in place, including for token-owned locks. Known descriptor/path identity differences remain supported; a pathname identity change during the read still prevents cleanup.
|
|
67
67
|
|
|
68
68
|
The raw sidecar bytes are not a canonical JSON representation: tools that trim or rewrite the trailing whitespace invalidate the ownership token, so release leaves the changed sidecar in place and fails closed. The token distinguishes cooperating acquisitions; it is not a secret and does not make pathname compare-and-remove atomic against a hostile process that can replace files outside the lock protocol.
|
|
69
69
|
|
|
70
|
-
`release()` propagates an I/O failure that prevents deletion of an unchanged, owned sidecar; it never reports successful cleanup while leaving that lock behind.
|
|
70
|
+
`release()` propagates an I/O failure that prevents deletion of an unchanged, owned sidecar; it never reports successful cleanup while leaving that lock behind. Raw and JavaScript fallback handles and managers retain the cleanup receipt after an I/O failure, so the same handle can retry `release()` and `manager.drain()` can retry cleanup. Retained native Root locks settle once and close their retained handles on every outcome, including failure; a failed deletion requires application-owned recovery. A changed sidecar is left untouched. Retained native Root release reports `FsSafeError` code `path-mismatch`, distinguishing a changed identity from a changed owner record; this includes a replacement inode containing an exact copy of the original bytes. If both a `withFileLock()` or `withFileLockSync()` callback and release fail, the release error is the primary `SuppressedError.error` and the callback failure remains available as `SuppressedError.suppressed`. Failed asynchronous acquisition cleanup uses the same shape, with the cleanup error primary and the acquisition failure suppressed. On Node runtimes without the global `SuppressedError` constructor, fs-safe returns the equivalent `Error` shape with the same name and properties.
|
|
71
71
|
|
|
72
72
|
## API
|
|
73
73
|
|
|
@@ -265,10 +265,48 @@ its separate canonical-path mutation queue. The synchronous APIs implement the
|
|
|
265
265
|
same owner/refcount rules; a mismatched synchronous acquisition blocks the
|
|
266
266
|
calling thread according to its retry and timeout options.
|
|
267
267
|
|
|
268
|
-
Pass `lockRoot` to
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
Pass `lockRoot` to admit sidecar creation, reads, and verification through an
|
|
269
|
+
existing `Root` capability. `lockPath` must resolve inside that root.
|
|
270
|
+
|
|
271
|
+
With the maintained native helper, asynchronous acquisition retains cleanup
|
|
272
|
+
authority over only the sidecar it created: an identity-checked parent descriptor
|
|
273
|
+
and file descriptor on POSIX, or a shared-delete parent directory handle on Windows
|
|
274
|
+
(local NTFS). `release()` and process-exit cleanup verify the original named
|
|
275
|
+
entry's exact identity, regular-file type, single link, and complete owner record
|
|
276
|
+
through those retained handles. They then use guarded descriptor-relative removal
|
|
277
|
+
or Windows disposition-delete, without resolving the Root again. Cleanup therefore
|
|
278
|
+
works after the Root moves, is replaced, or its parent becomes a symlink. A foreign
|
|
279
|
+
entry at either the original Root path or the retained sidecar name is preserved.
|
|
280
|
+
The retained handles close on success, mismatch, failed acquisition, and exit
|
|
281
|
+
(including `retainOnExit`, which closes handles without deleting).
|
|
282
|
+
If retention itself fails after creation, rollback checks the original identity
|
|
283
|
+
and owner bytes through the creator's still-open descriptors before removing the
|
|
284
|
+
claim. A changed entry is preserved and both the retention and cleanup errors are
|
|
285
|
+
reported; cleanup never falls back to token-only deletion of a replacement.
|
|
286
|
+
Retention duplicates the already-admitted parent authority instead of walking
|
|
287
|
+
its pathname again or requiring extra access to its ancestors.
|
|
288
|
+
Windows opens the named file through the retained parent only for settlement,
|
|
289
|
+
with delete access and sharing; keeping a file handle open would prevent ordinary
|
|
290
|
+
directory relocation. Retained cleanup compares the owner bytes directly and does
|
|
291
|
+
not invoke `parsePayload`; verification and stale-policy reads still do.
|
|
292
|
+
Windows permits renaming that retained parent itself, but refuses renaming an
|
|
293
|
+
ancestor containing the open directory handle, even with delete sharing and no
|
|
294
|
+
data access. Such a refused move leaves the Root and lock in place; release or
|
|
295
|
+
exit cleanup closes the pin so the ancestor can then move. POSIX also supports
|
|
296
|
+
cleanup after a successful ancestor move or symlink swap.
|
|
297
|
+
|
|
298
|
+
`verifyStillHeld()` retains its Root-based semantics and can still reject a moved
|
|
299
|
+
Root even though `release()` can remove its own sidecar. This creation-specific
|
|
300
|
+
cleanup receipt does not authorize other Root operations or stale reclamation;
|
|
301
|
+
those continue to enforce current confinement and mutation policy. As with the
|
|
302
|
+
sidecar protocol generally, the POSIX final identity check and unlink are not an
|
|
303
|
+
atomic compare-and-delete against an uncooperative process racing the same name.
|
|
304
|
+
|
|
305
|
+
When native retention is unavailable or explicitly disabled, release keeps the
|
|
306
|
+
existing Root-based fail-closed behavior; a moved Root can leave a sidecar requiring
|
|
307
|
+
application-owned recovery. Use the matching maintained native package to obtain
|
|
308
|
+
relocation-safe cleanup. Synchronous Root locks keep their existing Root authority.
|
|
309
|
+
Identity-conditioned removal remains the only release and reclaim deletion path.
|
|
272
310
|
Root mutation refusals during stale removal propagate unchanged, including
|
|
273
311
|
`null`, `undefined`, and errors carrying `ENOENT`; they do not become missing-sidecar retries.
|
|
274
312
|
|
|
@@ -35,6 +35,18 @@ mode, correction uses a verified directory descriptor.
|
|
|
35
35
|
An initial mode-descriptor admission error is preserved if closing that rejected
|
|
36
36
|
descriptor also fails; close failures after successful admission remain reportable.
|
|
37
37
|
|
|
38
|
+
These rules apply equally to effective UID 0: root-owned `0700` or `0755` temp
|
|
39
|
+
roots beneath root-owned sticky `1777` `/tmp` are accepted. Passing `/tmp` itself
|
|
40
|
+
as `rootDir` is rejected because the workspace root must be private. Permission
|
|
41
|
+
and ownership errors name the rejected directory (including an unsafe ancestor),
|
|
42
|
+
the observed mode or owner UID, the expected property, and a recovery path.
|
|
43
|
+
Prefer `resolveSecureTempRoot()` or a dedicated directory owned by the effective
|
|
44
|
+
user with mode `0700`. For a non-sticky writable ancestor, have its administrator
|
|
45
|
+
restore appropriate permissions (`1777` for shared system temp) or move the root
|
|
46
|
+
under trusted ancestry. Do not make shared `/tmp` private or take ownership of
|
|
47
|
+
another user's directory. A caller's preferred root takes precedence over
|
|
48
|
+
`TMPDIR`; changing `TMPDIR` only changes the resolver's fallback location.
|
|
49
|
+
|
|
38
50
|
On Linux, non-identity `/proc/self/uid_map` and `/proc/self/gid_map` evidence
|
|
39
51
|
permits ancestors whose UID and GID equal unmapped kernel overflow IDs,
|
|
40
52
|
provided the same ancestor mode checks pass.
|
|
@@ -152,8 +152,9 @@ FSEvents drops, including during recursive deletion. A shallow tree anchored abo
|
|
|
152
152
|
a busy unselected subtree still receives recursive traffic; pending hints fold
|
|
153
153
|
under pressure, while genuine FSEvents drops can still overflow.
|
|
154
154
|
Absolute hints are reduced lexically against the admitted canonical Root;
|
|
155
|
-
outside paths never become detail. Dropped/wrapped streams, RootChanged
|
|
156
|
-
|
|
155
|
+
outside paths never become detail. Dropped/wrapped streams, RootChanged, Unmount
|
|
156
|
+
and notifications naming the Root itself trigger guarded reconciliation without detail.
|
|
157
|
+
Pathname hints can reflect activity
|
|
157
158
|
after a swap, but the Root is never replaced and names require guarded admission.
|
|
158
159
|
Removal synchronously stops, invalidates and releases the stream on its queue.
|
|
159
160
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -173,32 +173,32 @@
|
|
|
173
173
|
"archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
|
|
174
174
|
},
|
|
175
175
|
"optionalDependencies": {
|
|
176
|
-
"@openclaw/fs-safe-darwin-arm64": "0.
|
|
177
|
-
"@openclaw/fs-safe-darwin-x64": "0.
|
|
178
|
-
"@openclaw/fs-safe-linux-arm64-gnu": "0.
|
|
179
|
-
"@openclaw/fs-safe-linux-arm64-musl": "0.
|
|
180
|
-
"@openclaw/fs-safe-linux-x64-gnu": "0.
|
|
181
|
-
"@openclaw/fs-safe-linux-x64-musl": "0.
|
|
182
|
-
"@openclaw/fs-safe-win32-x64-msvc": "0.
|
|
176
|
+
"@openclaw/fs-safe-darwin-arm64": "0.25.0",
|
|
177
|
+
"@openclaw/fs-safe-darwin-x64": "0.25.0",
|
|
178
|
+
"@openclaw/fs-safe-linux-arm64-gnu": "0.25.0",
|
|
179
|
+
"@openclaw/fs-safe-linux-arm64-musl": "0.25.0",
|
|
180
|
+
"@openclaw/fs-safe-linux-x64-gnu": "0.25.0",
|
|
181
|
+
"@openclaw/fs-safe-linux-x64-musl": "0.25.0",
|
|
182
|
+
"@openclaw/fs-safe-win32-x64-msvc": "0.25.0",
|
|
183
183
|
"jszip": "^3.10.2"
|
|
184
184
|
},
|
|
185
185
|
"devDependencies": {
|
|
186
|
-
"@emnapi/runtime": "2.0.0-alpha.
|
|
187
|
-
"@napi-rs/cli": "3.10.
|
|
188
|
-
"@types/node": "^26.6.
|
|
186
|
+
"@emnapi/runtime": "2.0.0-alpha.6",
|
|
187
|
+
"@napi-rs/cli": "3.10.8",
|
|
188
|
+
"@types/node": "^26.6.4",
|
|
189
189
|
"@vitest/coverage-v8": "5.0.3",
|
|
190
|
-
"fast-check": "^4.10.
|
|
190
|
+
"fast-check": "^4.10.2",
|
|
191
191
|
"istanbul-lib-coverage": "3.2.2",
|
|
192
192
|
"istanbul-lib-report": "3.0.1",
|
|
193
193
|
"istanbul-reports": "3.2.0",
|
|
194
194
|
"sigstore": "5.0.0",
|
|
195
195
|
"tar": "7.5.22",
|
|
196
196
|
"typescript": "^7.0.2",
|
|
197
|
-
"vite": "8.3.
|
|
198
|
-
"vitest": "^5.0.
|
|
197
|
+
"vite": "8.3.3",
|
|
198
|
+
"vitest": "^5.0.3"
|
|
199
199
|
},
|
|
200
200
|
"engines": {
|
|
201
201
|
"node": ">=22"
|
|
202
202
|
},
|
|
203
|
-
"packageManager": "pnpm@12.
|
|
203
|
+
"packageManager": "pnpm@12.10.1"
|
|
204
204
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/openshell-sandbox",
|
|
3
|
-
"version": "2026.10.2",
|
|
3
|
+
"version": "2026.10.5-beta.2",
|
|
4
4
|
"description": "OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@openclaw/fs-safe": "0.
|
|
11
|
+
"@openclaw/fs-safe": "0.25.0",
|
|
12
12
|
"p-limit": "7.3.3",
|
|
13
13
|
"zod": "4.6.5"
|
|
14
14
|
},
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"minHostVersion": ">=2026.5.12-beta.1"
|
|
24
24
|
},
|
|
25
25
|
"compat": {
|
|
26
|
-
"pluginApi": ">=2026.10.2"
|
|
26
|
+
"pluginApi": ">=2026.10.5-beta.2"
|
|
27
27
|
},
|
|
28
28
|
"build": {
|
|
29
|
-
"openclawVersion": "2026.10.2",
|
|
29
|
+
"openclawVersion": "2026.10.5-beta.2",
|
|
30
30
|
"bundledDist": false
|
|
31
31
|
},
|
|
32
32
|
"release": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"assets/activity.svg"
|
|
46
46
|
],
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"openclaw": ">=2026.10.2"
|
|
48
|
+
"openclaw": ">=2026.10.5-beta.2"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"openclaw": {
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { FsSafeError } from "./errors.js";
|
|
2
|
-
import type { FileLockSyncHandle } from "./file-lock-sync.js";
|
|
3
|
-
import { type FileLockSyncRootAuthority, type FileLockSyncRootPath } from "./file-lock-sync-root.js";
|
|
4
|
-
import type { FileLockSyncRootFileReceipt } from "./file-lock-sync-root-io.js";
|
|
5
|
-
import { type RootSyncHeldLock } from "./file-lock-sync-root-held.js";
|
|
6
|
-
export declare class FileLockSyncRootArbitrationCollision extends FsSafeError {
|
|
7
|
-
constructor();
|
|
8
|
-
}
|
|
9
|
-
export type FileLockSyncRootArbitration = Readonly<{
|
|
10
|
-
authority: FileLockSyncRootAuthority;
|
|
11
|
-
heldLocks: Map<string, RootSyncHeldLock>;
|
|
12
|
-
lockRootPath: FileLockSyncRootPath;
|
|
13
|
-
normalizedTargetPath: string;
|
|
14
|
-
reentrantOwner?: string;
|
|
15
|
-
}>;
|
|
16
|
-
export declare function tryReuseCurrentRootSyncHeldLock(arbitration: FileLockSyncRootArbitration): FileLockSyncHandle | undefined;
|
|
17
|
-
export declare function cleanupCreatedRootSyncLock(lockRootPath: FileLockSyncRootPath, fd: number, receipt: FileLockSyncRootFileReceipt, timer?: NodeJS.Timeout): void;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { foreignSyncHeldLock } from "./file-lock-sync-admission.js";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { FsSafeError } from "./errors.js";
|
|
5
|
-
import {} from "./file-lock-sync-root.js";
|
|
6
|
-
import { removeFileLockSyncRootFile } from "./file-lock-sync-root-mutation.js";
|
|
7
|
-
import { createSuppressedError } from "./suppressed-error.js";
|
|
8
|
-
import { createRootSyncHeldLockHandle, verifyRootSyncHeldLock, } from "./file-lock-sync-root-held.js";
|
|
9
|
-
export class FileLockSyncRootArbitrationCollision extends FsSafeError {
|
|
10
|
-
constructor() {
|
|
11
|
-
super("path-mismatch", "file lock arbitration changed during local creation");
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function sameRootLockPath(left, right) {
|
|
15
|
-
return left.relativePath === right.relativePath && path.relative(left.path, right.path) === "";
|
|
16
|
-
}
|
|
17
|
-
export function tryReuseCurrentRootSyncHeldLock(arbitration) {
|
|
18
|
-
const held = arbitration.heldLocks.get(arbitration.normalizedTargetPath);
|
|
19
|
-
const reusable = held && !foreignSyncHeldLock("root", arbitration.normalizedTargetPath) &&
|
|
20
|
-
arbitration.reentrantOwner !== undefined &&
|
|
21
|
-
held.reentrantOwner !== undefined &&
|
|
22
|
-
arbitration.reentrantOwner === held.reentrantOwner &&
|
|
23
|
-
arbitration.authority.adapter === held.rootAuthority.adapter &&
|
|
24
|
-
sameRootLockPath(arbitration.lockRootPath, held.rootPath) &&
|
|
25
|
-
held.releaseState !== "released";
|
|
26
|
-
if (!reusable)
|
|
27
|
-
return undefined;
|
|
28
|
-
if (!verifyRootSyncHeldLock(held)) {
|
|
29
|
-
throw new FsSafeError("path-mismatch", "held sidecar lock changed before reentrant reuse");
|
|
30
|
-
}
|
|
31
|
-
// Verification can invoke a custom parser. Re-fetch the exact entry before
|
|
32
|
-
// granting a reference, while retaining supported releasing/exit states.
|
|
33
|
-
if (arbitration.heldLocks.get(arbitration.normalizedTargetPath) !== held ||
|
|
34
|
-
held.releaseState === "released" || foreignSyncHeldLock("root", arbitration.normalizedTargetPath)) {
|
|
35
|
-
throw new FsSafeError("path-mismatch", "held sidecar lock changed during reentrant reuse");
|
|
36
|
-
}
|
|
37
|
-
held.refCount += 1;
|
|
38
|
-
held.revision += 1;
|
|
39
|
-
return createRootSyncHeldLockHandle(held);
|
|
40
|
-
}
|
|
41
|
-
export function cleanupCreatedRootSyncLock(lockRootPath, fd, receipt, timer) {
|
|
42
|
-
let timerCleanupFailed = false;
|
|
43
|
-
let timerCleanupError;
|
|
44
|
-
try {
|
|
45
|
-
if (timer)
|
|
46
|
-
clearInterval(timer);
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
timerCleanupFailed = true;
|
|
50
|
-
timerCleanupError = error;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
fs.closeSync(fd);
|
|
54
|
-
if (!removeFileLockSyncRootFile(lockRootPath, receipt)) {
|
|
55
|
-
throw new FsSafeError("path-mismatch", "created sidecar lock changed before cleanup");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (fileCleanupError) {
|
|
59
|
-
if (timerCleanupFailed) {
|
|
60
|
-
throw createSuppressedError(timerCleanupError, fileCleanupError, "unpublished lock timer and file cleanup both failed");
|
|
61
|
-
}
|
|
62
|
-
throw fileCleanupError;
|
|
63
|
-
}
|
|
64
|
-
if (timerCleanupFailed)
|
|
65
|
-
throw timerCleanupError;
|
|
66
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { BigIntStats } from "node:fs";
|
|
2
|
-
import type { NativeBinding } from "./native-binding.js";
|
|
3
|
-
import { type PublishFailureState } from "./publish-file-failure.js";
|
|
4
|
-
export declare function publishFileNoReplaceNative(binding: NativeBinding, paths: Parameters<NativeBinding["renameNoReplace"]>, expected: BigIntStats, failure: PublishFailureState): "link-unlink" | undefined;
|