@openclaw/fs-safe 0.18.0 → 0.18.1
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 +10 -0
- package/dist/advanced.d.ts +2 -1
- package/dist/archive-durability.d.ts +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-plan.d.ts +64 -3
- package/dist/archive-plan.js +55 -1
- package/dist/archive-tar-wasm.d.ts +1 -1
- package/dist/archive-zip-directory.d.ts +1 -1
- package/dist/archive-zip-entry.d.ts +1 -1
- package/dist/archive-zip-entry.js +1 -1
- package/dist/archive.d.ts +8 -5
- package/dist/archive.js +122 -9
- package/dist/atomic.d.ts +3 -1
- package/dist/copy-publication.d.ts +1 -1
- package/dist/create.d.ts +16 -2
- package/dist/create.js +388 -2
- package/dist/creation-boundary.d.ts +60 -0
- package/dist/creation-boundary.js +371 -0
- package/dist/directory-mode-node.js +12 -2
- package/dist/durability.d.ts +2 -1
- package/dist/file-lock-sync-acquisition.js +2 -2
- package/dist/file-lock-sync-root-acquire.js +2 -6
- package/dist/file-lock-sync-root.d.ts +2 -1
- package/dist/file-lock-sync-stale-admission.js +3 -10
- package/dist/file-lock-sync.d.ts +4 -21
- package/dist/file-store-boundary.d.ts +7 -0
- package/dist/file-store-boundary.js +58 -1
- package/dist/file-store-sync-write.js +2 -3
- package/dist/file-store.js +1 -6
- package/dist/guarded-mkdir.d.ts +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/json-durable-queue-read.js +7 -3
- package/dist/json-durable-queue.js +1 -2
- package/dist/json-store.js +1 -2
- package/dist/json.js +1 -2
- package/dist/move-path.js +1 -2
- package/dist/native-operations.js +0 -1
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.js +1 -1
- package/dist/native-pinned-write.d.ts +1 -1
- package/dist/native-pinned-write.js +3 -6
- package/dist/native-policy-parent-windows.d.ts +1 -1
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.js +1 -2
- package/dist/pinned-mutation-admission.d.ts +1 -1
- package/dist/pinned-open.js +6 -1
- package/dist/pinned-operation.d.ts +1 -1
- package/dist/pinned-operation.js +3 -16
- package/dist/pinned-write-mode.js +1 -1
- package/dist/pinned-write-staged.js +1 -2
- package/dist/pinned-write.d.ts +0 -1
- package/dist/pinned-write.js +3 -5
- package/dist/private-producer-handoff-sync.d.ts +2 -12
- package/dist/private-producer-handoff-sync.js +1 -1
- package/dist/private-producer-handoff.d.ts +3 -3
- package/dist/private-temp-workspace.js +1 -2
- package/dist/publish-copy-stage.d.ts +1 -1
- package/dist/publish-file.d.ts +0 -1
- package/dist/publish-file.js +1 -2
- package/dist/replace-directory.js +1 -2
- package/dist/replace-file-rename-policy.d.ts +1 -2
- package/dist/replace-file.d.ts +3 -3
- package/dist/replace-file.js +1 -1
- package/dist/root-context.js +3 -7
- package/dist/root-create-input.d.ts +1 -1
- package/dist/root-directory-creation.d.ts +1 -1
- package/dist/root-directory-creation.js +1 -1
- package/dist/root-directory-list.js +3 -7
- package/dist/root-entries.js +2 -2
- package/dist/root-errors.d.ts +4 -0
- package/dist/root-errors.js +5 -3
- package/dist/root-impl.d.ts +0 -5
- package/dist/root-impl.js +22 -21
- package/dist/root-move-noreplace.js +4 -10
- package/dist/root-observed-path.js +2 -4
- package/dist/root-options.d.ts +1 -1
- package/dist/root-path-existing.js +2 -3
- package/dist/root-path-stat.js +2 -4
- package/dist/root-public-types.d.ts +11 -0
- package/dist/root-remove-identity.js +2 -4
- package/dist/root-remove.js +2 -4
- package/dist/root-write-admission.d.ts +1 -1
- package/dist/root-write-admission.js +3 -7
- package/dist/root-write-complete-parent.js +2 -3
- package/dist/root-write-verification.d.ts +1 -1
- package/dist/root.d.ts +2 -6
- package/dist/secret-file.js +0 -1
- package/dist/secure-temp-repair.js +5 -1
- package/dist/sidecar-lock-acquire.js +7 -10
- package/dist/sidecar-lock-policy.d.ts +1 -0
- package/dist/sidecar-lock-policy.js +7 -0
- package/dist/sidecar-lock-stale-admission.js +2 -6
- package/dist/sidecar-lock-types.d.ts +22 -16
- package/dist/temp-workspace-admission.js +1 -3
- package/dist/temp-workspace-child-admission.d.ts +16 -2
- package/dist/temp-workspace-child-admission.js +71 -3
- package/dist/temp-workspace-descriptor.d.ts +1 -1
- package/dist/temp-workspace-descriptor.js +1 -2
- package/dist/text-atomic.js +1 -1
- package/dist/windows-owner.js +1 -1
- package/dist/windows-path-alias.d.ts +1 -0
- package/dist/windows-path-alias.js +5 -0
- package/docs/advanced.md +3 -1
- package/docs/secret-file.md +4 -0
- package/docs/sidecar-lock.md +4 -0
- package/docs/store.md +6 -0
- package/docs/temp.md +8 -1
- package/docs/writing.md +5 -0
- package/package.json +8 -8
- package/dist/archive-native.d.ts +0 -10
- package/dist/archive-native.js +0 -73
- package/dist/archive-options.d.ts +0 -30
- package/dist/archive-policy.d.ts +0 -21
- package/dist/archive-policy.js +0 -38
- package/dist/archive-tar-inspect.d.ts +0 -6
- package/dist/archive-tar-inspect.js +0 -62
- package/dist/archive-tar.d.ts +0 -18
- package/dist/archive-tar.js +0 -21
- package/dist/create-directory.d.ts +0 -18
- package/dist/create-directory.js +0 -111
- package/dist/create-file-async.d.ts +0 -6
- package/dist/create-file-async.js +0 -121
- package/dist/create-file.d.ts +0 -7
- package/dist/create-file.js +0 -179
- package/dist/creation-darwin.d.ts +0 -5
- package/dist/creation-darwin.js +0 -70
- package/dist/creation-file-state.d.ts +0 -18
- package/dist/creation-file-state.js +0 -118
- package/dist/creation-path.d.ts +0 -22
- package/dist/creation-path.js +0 -83
- package/dist/creation-permissions.d.ts +0 -18
- package/dist/creation-permissions.js +0 -115
- package/dist/file-store-copy-source.d.ts +0 -4
- package/dist/file-store-copy-source.js +0 -31
- package/dist/file-store-limit.d.ts +0 -1
- package/dist/file-store-limit.js +0 -7
- package/dist/file-store-path.d.ts +0 -2
- package/dist/file-store-path.js +0 -30
- package/dist/standalone-publication-path.d.ts +0 -1
- package/dist/standalone-publication-path.js +0 -6
- package/dist/temp-workspace-identity.d.ts +0 -14
- package/dist/temp-workspace-identity.js +0 -41
- package/dist/temp-workspace-permissions.d.ts +0 -3
- package/dist/temp-workspace-permissions.js +0 -32
- /package/dist/{archive-options.js → root-public-types.js} +0 -0
package/dist/root-impl.js
CHANGED
|
@@ -56,7 +56,7 @@ import fs from "node:fs/promises";
|
|
|
56
56
|
import path from "node:path";
|
|
57
57
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
58
58
|
import { assertCopySourceCurrent, resolveFileCopyCloneMode } from "./copy-file-input.js";
|
|
59
|
-
import { assertPrivateFileCreationAvailable, resolveCreationPermissions } from "./creation-
|
|
59
|
+
import { assertPrivateFileCreationAvailable, resolveCreationPermissions } from "./creation-boundary.js";
|
|
60
60
|
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
|
|
61
61
|
import { FsSafeError } from "./errors.js";
|
|
62
62
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
@@ -68,7 +68,7 @@ import { openedPathResolutionError, recordExclusiveCreateFailure, recordOpenedFi
|
|
|
68
68
|
import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy } from "./pinned-write.js";
|
|
69
69
|
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy } from "./pinned-mutation-admission.js";
|
|
70
70
|
import { getNativeBinding } from "./native.js";
|
|
71
|
-
import {
|
|
71
|
+
import { validatePinnedRelativePath } from "./pinned-operation.js";
|
|
72
72
|
import { PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
73
73
|
import { assertNoNulPathInput, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
|
|
74
74
|
import { readOpenedFileSafely } from "./read-opened-file.js";
|
|
@@ -86,7 +86,7 @@ import { statResolvedPathInRoot } from "./root-path-stat.js";
|
|
|
86
86
|
import { entriesInRoot } from "./root-entries.js";
|
|
87
87
|
import { assertMoveMutationAllowed } from "./root-move-preflight.js";
|
|
88
88
|
import { assertRootIdentityCurrent, assertRootIdentityCurrentSync, assertValidRootDestinationPath, assertValidRootRelativePath, ensureTrailingSep, expandRelativePathWithHome, resolvePathInRoot, resolveRootContext, rootRelativeReadPath, } from "./root-context.js";
|
|
89
|
-
import { fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, normalizePinnedWriteError, outsideWorkspaceError, } from "./root-errors.js";
|
|
89
|
+
import { errorCauseOptions, fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, normalizePinnedWriteError, outsideWorkspaceError, } from "./root-errors.js";
|
|
90
90
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
91
91
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
92
92
|
import { walkRoot } from "./root-walk.js";
|
|
@@ -304,6 +304,9 @@ export class RootHandle {
|
|
|
304
304
|
mode: this.defaults.mode,
|
|
305
305
|
renameIdentity: this.defaults.renameIdentity,
|
|
306
306
|
...this.mutationOptions(options),
|
|
307
|
+
atomic: undefined,
|
|
308
|
+
private: undefined,
|
|
309
|
+
strictFileSync: undefined,
|
|
307
310
|
relativePath,
|
|
308
311
|
data,
|
|
309
312
|
durable: options.durable ?? this.defaults.durable ?? true,
|
|
@@ -364,18 +367,19 @@ export class RootHandle {
|
|
|
364
367
|
}
|
|
365
368
|
async stat(relativePath) {
|
|
366
369
|
assertValidRootRelativePath(relativePath);
|
|
367
|
-
|
|
370
|
+
validatePinnedRelativePath(relativePath);
|
|
368
371
|
return await statPathFallback(this.context, relativePath);
|
|
369
372
|
}
|
|
370
373
|
async list(relativePath, options = {}) {
|
|
371
374
|
assertValidRootRelativePath(relativePath);
|
|
372
|
-
|
|
375
|
+
validatePinnedRelativePath(relativePath);
|
|
373
376
|
return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
|
|
374
377
|
}
|
|
375
378
|
async move(fromRelative, toRelative, options = {}) {
|
|
376
379
|
assertValidRootRelativePath(fromRelative);
|
|
377
380
|
assertValidRootDestinationPath(toRelative);
|
|
378
|
-
|
|
381
|
+
validatePinnedRelativePath(fromRelative);
|
|
382
|
+
validatePinnedRelativePath(toRelative);
|
|
379
383
|
const mutationOptions = this.mutationOptions(options);
|
|
380
384
|
const { assertBeforeMutation } = mutationOptions;
|
|
381
385
|
const { denyMutations, mutationSymlinks } = snapshotPinnedMutationPolicy(mutationOptions.denyMutations, mutationOptions.mutationSymlinks) ?? {};
|
|
@@ -407,7 +411,7 @@ export class RootHandle {
|
|
|
407
411
|
rootReal: this.context.rootReal,
|
|
408
412
|
stat: relative => this.stat(relative),
|
|
409
413
|
list: async (relative, listingOptions) => {
|
|
410
|
-
|
|
414
|
+
validatePinnedRelativePath(relative);
|
|
411
415
|
const resolved = await resolvePinnedPathInRoot(this.context, { relativePath: relative, allowRoot: true });
|
|
412
416
|
return await openRootDirectoryListing(this.context, resolved.resolved, listingOptions);
|
|
413
417
|
},
|
|
@@ -503,7 +507,6 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
503
507
|
relativePath: params.relativePath, guardedTarget, mkdir: params.mkdir,
|
|
504
508
|
assertBeforeMutation: params.assertBeforeMutation,
|
|
505
509
|
}) : undefined;
|
|
506
|
-
const mutationAdmission = prepared?.mutationAdmission;
|
|
507
510
|
const preparedParent = prepared?.preparedParent;
|
|
508
511
|
let ioPath = prepared?.targetPath ?? (params.mkdir === false ? resolved :
|
|
509
512
|
await prepareRootWriteTarget(root, resolved, params.assertBeforeMutation));
|
|
@@ -763,7 +766,7 @@ async function appendFileInRoot(root, params) {
|
|
|
763
766
|
}
|
|
764
767
|
}
|
|
765
768
|
async function removePathInRoot(root, params) {
|
|
766
|
-
|
|
769
|
+
validatePinnedRelativePath(params.relativePath);
|
|
767
770
|
const removalReceipts = params.recursive ? undefined : new RemovalPathReceipts();
|
|
768
771
|
const resolved = await resolvePinnedPathInRoot(root, {
|
|
769
772
|
relativePath: params.relativePath,
|
|
@@ -783,7 +786,7 @@ async function removePathInRoot(root, params) {
|
|
|
783
786
|
}
|
|
784
787
|
async function mkdirPathInRoot(root, params) {
|
|
785
788
|
const privateMode = resolveCreationPermissions(params, true).private;
|
|
786
|
-
|
|
789
|
+
validatePinnedRelativePath(params.relativePath);
|
|
787
790
|
const policy = params.denyMutations === undefined && params.mutationSymlinks === undefined
|
|
788
791
|
? undefined
|
|
789
792
|
: snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks);
|
|
@@ -862,7 +865,7 @@ async function commitPinnedWriteInRoot(root, pinned, params, input) {
|
|
|
862
865
|
overwrite: params.overwrite,
|
|
863
866
|
rejectFinalSymlink: params.mutationSymlinks !== undefined,
|
|
864
867
|
input,
|
|
865
|
-
maxBytes: params.maxBytes,
|
|
868
|
+
maxBytes: input.kind === "buffer" ? undefined : params.maxBytes,
|
|
866
869
|
rootIdentity: root.rootIdentity,
|
|
867
870
|
mutationAdmission: pinned.mutationAdmission,
|
|
868
871
|
assertBeforeMutation: params.assertBeforeMutation,
|
|
@@ -892,9 +895,7 @@ async function commitPinnedWriteInRoot(root, pinned, params, input) {
|
|
|
892
895
|
throw error;
|
|
893
896
|
}
|
|
894
897
|
if (params.overwrite === false && isAlreadyExistsError(error)) {
|
|
895
|
-
throw new FsSafeError("already-exists", "file already exists",
|
|
896
|
-
cause: error instanceof Error ? error : undefined,
|
|
897
|
-
});
|
|
898
|
+
throw new FsSafeError("already-exists", "file already exists", errorCauseOptions(error));
|
|
898
899
|
}
|
|
899
900
|
throw normalizePinnedWriteError(error);
|
|
900
901
|
}
|
|
@@ -1173,9 +1174,7 @@ async function movePathFallback(root, params) {
|
|
|
1173
1174
|
throw fileNotFoundError(error instanceof Error ? error : undefined);
|
|
1174
1175
|
}
|
|
1175
1176
|
if (hasNodeErrorCode(error, "EEXIST")) {
|
|
1176
|
-
throw new FsSafeError("already-exists", "destination exists",
|
|
1177
|
-
cause: error instanceof Error ? error : undefined,
|
|
1178
|
-
});
|
|
1177
|
+
throw new FsSafeError("already-exists", "destination exists", errorCauseOptions(error));
|
|
1179
1178
|
}
|
|
1180
1179
|
throw error;
|
|
1181
1180
|
}
|
|
@@ -1193,7 +1192,11 @@ async function writeFileFallback(root, params) {
|
|
|
1193
1192
|
const policy = snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks);
|
|
1194
1193
|
if (policy)
|
|
1195
1194
|
params = { ...params, ...policy };
|
|
1196
|
-
const { rootReal, resolved } = await resolveGuardedWritePathInRoot(root,
|
|
1195
|
+
const { rootReal, resolved } = await resolveGuardedWritePathInRoot(root, {
|
|
1196
|
+
relativePath: params.relativePath,
|
|
1197
|
+
denyMutations: params.denyMutations,
|
|
1198
|
+
mutationSymlinks: params.mutationSymlinks,
|
|
1199
|
+
});
|
|
1197
1200
|
await withRootFallbackCompatibilityLock({
|
|
1198
1201
|
rootPath: rootReal, rootIdentity: root.rootIdentity, targetPath: resolved, assertBeforeMutation: params.assertBeforeMutation,
|
|
1199
1202
|
}, async ({ targetPath, ...binding }) => await writeFileFallbackUnlocked(root, { ...params, ...binding }, targetPath));
|
|
@@ -1422,9 +1425,7 @@ async function writeMissingFileFallback(root, params, expectedWritePath) {
|
|
|
1422
1425
|
if (verifyingPublication)
|
|
1423
1426
|
throw err;
|
|
1424
1427
|
if (hasNodeErrorCode(err, "EEXIST")) {
|
|
1425
|
-
throw new FsSafeError("already-exists", "file already exists",
|
|
1426
|
-
cause: err instanceof Error ? err : undefined,
|
|
1427
|
-
});
|
|
1428
|
+
throw new FsSafeError("already-exists", "file already exists", errorCauseOptions(err));
|
|
1428
1429
|
}
|
|
1429
1430
|
throw err;
|
|
1430
1431
|
}
|
|
@@ -9,7 +9,7 @@ import { isNotFoundPathError } from "./path.js";
|
|
|
9
9
|
import { assertRootIdentityCurrent, assertRootIdentityCurrentSync } from "./root-context.js";
|
|
10
10
|
import { resolveRootPathSync } from "./root-path.js";
|
|
11
11
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
12
|
-
import { fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, outsideWorkspaceError, } from "./root-errors.js";
|
|
12
|
+
import { errorCauseOptions, fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, outsideWorkspaceError, } from "./root-errors.js";
|
|
13
13
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
14
14
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
15
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
@@ -44,17 +44,13 @@ function normalizeMoveError(error) {
|
|
|
44
44
|
return fileNotFoundError(error instanceof Error ? error : undefined);
|
|
45
45
|
}
|
|
46
46
|
if (nativePrimitiveUnavailable(error)) {
|
|
47
|
-
return new FsSafeError("helper-unavailable", "native no-replace move is unavailable",
|
|
48
|
-
cause: error instanceof Error ? error : undefined,
|
|
49
|
-
});
|
|
47
|
+
return new FsSafeError("helper-unavailable", "native no-replace move is unavailable", errorCauseOptions(error));
|
|
50
48
|
}
|
|
51
49
|
return error;
|
|
52
50
|
}
|
|
53
51
|
function normalizeRenameNoReplaceError(error) {
|
|
54
52
|
if (error?.code === "EINVAL") {
|
|
55
|
-
return new FsSafeError("helper-unavailable", "native no-replace move is unavailable on this filesystem",
|
|
56
|
-
cause: error instanceof Error ? error : undefined,
|
|
57
|
-
});
|
|
53
|
+
return new FsSafeError("helper-unavailable", "native no-replace move is unavailable on this filesystem", errorCauseOptions(error));
|
|
58
54
|
}
|
|
59
55
|
return normalizeMoveError(error);
|
|
60
56
|
}
|
|
@@ -157,9 +153,7 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
157
153
|
}
|
|
158
154
|
catch (error) {
|
|
159
155
|
if (isAlreadyExistsError(error) || error?.code === "ENOTEMPTY") {
|
|
160
|
-
throw new FsSafeError("already-exists", "destination exists",
|
|
161
|
-
cause: error instanceof Error ? error : undefined,
|
|
162
|
-
});
|
|
156
|
+
throw new FsSafeError("already-exists", "destination exists", errorCauseOptions(error));
|
|
163
157
|
}
|
|
164
158
|
throw normalizeRenameNoReplaceError(error);
|
|
165
159
|
}
|
|
@@ -7,7 +7,7 @@ import { admitPathInsideRoot } from "./root-boundary.js";
|
|
|
7
7
|
import { assertRootPathObservationReceiptCurrent } from "./root-directory-list.js";
|
|
8
8
|
import { RootPathObservationError, resolveRootPathWithObservation, } from "./root-path.js";
|
|
9
9
|
import { createRootObservationGuard, ensureTrailingSep, expandRelativePathWithHome, } from "./root-context.js";
|
|
10
|
-
import { fileNotFoundError, outsideWorkspaceError } from "./root-errors.js";
|
|
10
|
+
import { errorCauseOptions, fileNotFoundError, outsideWorkspaceError } from "./root-errors.js";
|
|
11
11
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
12
12
|
export async function resolvePinnedObservedPathInRoot(root, relativePath, kind) {
|
|
13
13
|
const rootGuard = await createRootObservationGuard(root);
|
|
@@ -50,9 +50,7 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
|
|
|
50
50
|
if (kind === "stat") {
|
|
51
51
|
throw fileNotFoundError(error.error instanceof Error ? error.error : undefined);
|
|
52
52
|
}
|
|
53
|
-
throw new FsSafeError("not-found", "directory not found",
|
|
54
|
-
cause: error.error instanceof Error ? error.error : undefined,
|
|
55
|
-
});
|
|
53
|
+
throw new FsSafeError("not-found", "directory not found", errorCauseOptions(error.error));
|
|
56
54
|
}
|
|
57
55
|
throw error.error;
|
|
58
56
|
}
|
package/dist/root-options.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DenyMutationPolicy } from "./deny-mutations.js";
|
|
2
|
-
import type { RenameIdentityPolicy } from "./pinned-write.js";
|
|
2
|
+
import type { RenameIdentityPolicy } from "./pinned-write-types.js";
|
|
3
3
|
import type { MutationSymlinkPolicy, SymlinkPolicy } from "./root-symlink-policy.js";
|
|
4
4
|
import type { CopyCloneMode } from "./copy-policy.js";
|
|
5
5
|
import type { RootCopyPublicationReceipt } from "./copy-publication.js";
|
|
@@ -6,6 +6,7 @@ import { isNotFoundPathError, isPathInside, isSymlinkOpenError } from "./path.js
|
|
|
6
6
|
import { realpathSync } from "./realpath.js";
|
|
7
7
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathFromBasePreservingWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
8
8
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
9
|
+
import { errorCauseOptions } from "./root-errors.js";
|
|
9
10
|
export function absolutePathWithRawSegments(candidate) {
|
|
10
11
|
if (path.sep !== "\\") {
|
|
11
12
|
if (path.isAbsolute(candidate))
|
|
@@ -144,9 +145,7 @@ export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved) {
|
|
|
144
145
|
}
|
|
145
146
|
catch (error) {
|
|
146
147
|
if (isSymlinkOpenError(error) || (rejectUnresolved && isNotFoundPathError(error))) {
|
|
147
|
-
throw new FsSafeError("symlink", "symlink path could not be resolved",
|
|
148
|
-
cause: error instanceof Error ? error : undefined,
|
|
149
|
-
});
|
|
148
|
+
throw new FsSafeError("symlink", "symlink path could not be resolved", errorCauseOptions(error));
|
|
150
149
|
}
|
|
151
150
|
if (!isNotFoundPathError(error))
|
|
152
151
|
throw error;
|
package/dist/root-path-stat.js
CHANGED
|
@@ -4,7 +4,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
4
4
|
import { fileObservation, recordFileObservationFailure } from "./file-observation.js";
|
|
5
5
|
import { hasNodeErrorCode, isNotFoundPathError } from "./path.js";
|
|
6
6
|
import { assertRootDirectoryObservationGuard, assertRootPathObservationReceiptCurrent, createRootDirectoryObservationGuard, pathStatFromStats, } from "./root-directory-list.js";
|
|
7
|
-
import { fileNotFoundError } from "./root-errors.js";
|
|
7
|
+
import { errorCauseOptions, fileNotFoundError } from "./root-errors.js";
|
|
8
8
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
9
9
|
import { assertStatObservationSync } from "./stat-observation.js";
|
|
10
10
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
@@ -32,9 +32,7 @@ async function captureChangedObservedFile(error, pathname, before, observed, ass
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
async function missingObservedFileError(cause, pathname, before, assertParents, proof) {
|
|
35
|
-
const failure = new FsSafeError("path-mismatch", "file changed during operation",
|
|
36
|
-
cause: cause instanceof Error ? cause : undefined,
|
|
37
|
-
});
|
|
35
|
+
const failure = new FsSafeError("path-mismatch", "file changed during operation", errorCauseOptions(cause));
|
|
38
36
|
if (hasNodeErrorCode(cause, "ENOENT") && before?.isFile() && !before.isSymbolicLink() &&
|
|
39
37
|
(before.nlink === 1 || before.nlink === 1n)) {
|
|
40
38
|
try {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { OpenResult, WritableOpenResult, } from "./root-impl.js";
|
|
2
|
+
export type { HardlinkPolicy, RootAppendOptions, RootCopyOptions, RootCopySource, RootCreateJsonOptions, RootCreateOptions, RootCreateStreamOptions, RootDefaults, RootMkdirOptions, RootMoveOptions, RootOpenOptions, RootOpenWritableOptions, RootOptions, RootReadOptions, RootRemoveOptions, RootWriteJsonOptions, RootWriteOptions, WritableOpenMode, } from "./root-options.js";
|
|
3
|
+
export type { DenyMutationPolicy } from "./deny-mutations.js";
|
|
4
|
+
export type { RenameIdentityPolicy } from "./pinned-write-types.js";
|
|
5
|
+
export type { MutationSymlinkPolicy, SymlinkPolicy, } from "./root-symlink-policy.js";
|
|
6
|
+
export type { ReadResult } from "./read-opened-file.js";
|
|
7
|
+
export type { RootCopyPublicationReceipt } from "./copy-publication.js";
|
|
8
|
+
export type { CopyCloneMode } from "./copy-policy.js";
|
|
9
|
+
export type { RootEntriesOptions } from "./root-entries.js";
|
|
10
|
+
export type { ContainmentGuarantee } from "./containment.js";
|
|
11
|
+
export type { RootWalkDataEntry, RootWalkDataEntryKind, RootWalkDirectoryErrorBehavior, RootWalkEntry, RootWalkEntryFilter, RootWalkEntryFilterResult, RootWalkEntryKind, RootWalkLimitBehavior, RootWalkOptions, RootWalkSymlinkPolicy, } from "./root-walk.js";
|
|
@@ -2,12 +2,10 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { realpathSync } from "./realpath.js";
|
|
5
|
-
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
5
|
+
import { directoryComponentNotDirectoryError, errorCauseOptions } from "./root-errors.js";
|
|
6
6
|
const MAX_SAFE_DIRECTORY_IDENTITY = BigInt(Number.MAX_SAFE_INTEGER);
|
|
7
7
|
function identityMismatch(cause) {
|
|
8
|
-
return new FsSafeError("path-mismatch", "removal ancestor identity changed or could not be verified",
|
|
9
|
-
cause: cause instanceof Error ? cause : undefined,
|
|
10
|
-
});
|
|
8
|
+
return new FsSafeError("path-mismatch", "removal ancestor identity changed or could not be verified", errorCauseOptions(cause));
|
|
11
9
|
}
|
|
12
10
|
function safeIdentityNumber(value) {
|
|
13
11
|
if (value < 0n || value > MAX_SAFE_DIRECTORY_IDENTITY)
|
package/dist/root-remove.js
CHANGED
|
@@ -8,7 +8,7 @@ import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
|
8
8
|
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
9
9
|
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
10
10
|
import { assertRootIdentityCurrent } from "./root-context.js";
|
|
11
|
-
import { normalizeRemoveGuardError, normalizeRemovePathError, rootPathChangedError } from "./root-errors.js";
|
|
11
|
+
import { errorCauseOptions, normalizeRemoveGuardError, normalizeRemovePathError, rootPathChangedError } from "./root-errors.js";
|
|
12
12
|
import { assertRemovalDirectoryCurrent, createRemovalDirectoryAssertion, } from "./root-remove-identity.js";
|
|
13
13
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
14
14
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
@@ -44,9 +44,7 @@ function assertNotAborted(signal) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
function removalAncestorChanged(error) {
|
|
47
|
-
return new FsSafeError("path-mismatch", "removal ancestor changed during operation",
|
|
48
|
-
cause: error instanceof Error ? error : undefined,
|
|
49
|
-
});
|
|
47
|
+
return new FsSafeError("path-mismatch", "removal ancestor changed during operation", errorCauseOptions(error));
|
|
50
48
|
}
|
|
51
49
|
function sameCanonicalDirectory(left, right) {
|
|
52
50
|
return left === right || (isPathInside(left, right) && isPathInside(right, left));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
3
3
|
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
|
-
import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
|
|
4
|
+
import type { PinnedWriteMutationAdmission } from "./pinned-write-types.js";
|
|
5
5
|
import type { RootContext } from "./root-context.js";
|
|
6
6
|
import { resolvePathInRoot } from "./root-context.js";
|
|
7
7
|
import { type MutationSymlinkPolicy } from "./root-symlink-policy.js";
|
|
@@ -8,7 +8,7 @@ import { assertNoPathAliasEscape } from "./path-policy.js";
|
|
|
8
8
|
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy, } from "./pinned-mutation-admission.js";
|
|
9
9
|
import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
|
|
10
10
|
import { resolvePathInRoot } from "./root-context.js";
|
|
11
|
-
import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
11
|
+
import { errorCauseOptions, hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
12
12
|
import { inheritWriteTargetMode } from "./root-write-mode.js";
|
|
13
13
|
import { mutationSymlinkResolution, } from "./root-symlink-policy.js";
|
|
14
14
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
@@ -23,9 +23,7 @@ export function createRootWriteSelectionForFd(selection, fd) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
function writeSelectionChanged(cause) {
|
|
26
|
-
return new FsSafeError("path-mismatch", "write target changed during operation",
|
|
27
|
-
cause: cause instanceof Error ? cause : undefined,
|
|
28
|
-
});
|
|
26
|
+
return new FsSafeError("path-mismatch", "write target changed during operation", errorCauseOptions(cause));
|
|
29
27
|
}
|
|
30
28
|
function inspectRegularSelectionPath(pathname, expected, follow) {
|
|
31
29
|
let observed;
|
|
@@ -186,9 +184,7 @@ export async function resolveGuardedWritePathInRoot(root, params) {
|
|
|
186
184
|
});
|
|
187
185
|
}
|
|
188
186
|
catch (error) {
|
|
189
|
-
throw new FsSafeError("path-alias", "path alias escape blocked",
|
|
190
|
-
cause: error instanceof Error ? error : undefined,
|
|
191
|
-
});
|
|
187
|
+
throw new FsSafeError("path-alias", "path alias escape blocked", errorCauseOptions(error));
|
|
192
188
|
}
|
|
193
189
|
}
|
|
194
190
|
return resolvedPath;
|
|
@@ -8,13 +8,12 @@ import { realpathSync } from "./realpath.js";
|
|
|
8
8
|
import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
|
|
9
9
|
import { ordinaryWindowsSegments } from "./pinned-mutation-shared-route.js";
|
|
10
10
|
import { prepareRootWriteTarget } from "./root-directory-creation.js";
|
|
11
|
+
import { errorCauseOptions } from "./root-errors.js";
|
|
11
12
|
import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
|
|
12
13
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
13
14
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
14
15
|
function writeSelectionChanged(cause) {
|
|
15
|
-
return new FsSafeError("path-mismatch", "write target changed during operation",
|
|
16
|
-
cause: cause instanceof Error ? cause : undefined,
|
|
17
|
-
});
|
|
16
|
+
return new FsSafeError("path-mismatch", "write target changed during operation", errorCauseOptions(cause));
|
|
18
17
|
}
|
|
19
18
|
function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
|
|
20
19
|
if (process.versions.bun || relativePath === "" || relativePath.startsWith("~") ||
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
2
|
-
import type { PublishedWriteIdentity } from "./pinned-write.js";
|
|
2
|
+
import type { PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
3
3
|
import { type RootContext } from "./root-context.js";
|
|
4
4
|
export declare function verifyAtomicWriteResult(params: {
|
|
5
5
|
root: RootContext;
|
package/dist/root.d.ts
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type
|
|
2
|
-
export type
|
|
3
|
-
export type { CopyCloneMode } from "./copy-policy.js";
|
|
4
|
-
export type { RootEntriesOptions } from "./root-entries.js";
|
|
5
|
-
export type { ContainmentGuarantee } from "./containment.js";
|
|
6
|
-
export type { RootWalkDataEntry, RootWalkDataEntryKind, RootWalkDirectoryErrorBehavior, RootWalkEntry, RootWalkEntryFilter, RootWalkEntryFilterResult, RootWalkEntryKind, RootWalkLimitBehavior, RootWalkOptions, RootWalkSymlinkPolicy, } from "./root-walk.js";
|
|
1
|
+
export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type Root, } from "./root-impl.js";
|
|
2
|
+
export type * from "./root-public-types.js";
|
package/dist/secret-file.js
CHANGED
|
@@ -8,7 +8,6 @@ import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryI
|
|
|
8
8
|
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
9
9
|
import { assertOwnedDirectory } from "./directory-mode-owner.js";
|
|
10
10
|
import { FsSafeError } from "./errors.js";
|
|
11
|
-
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
12
11
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
13
12
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
14
13
|
import { ensureTrailingSep } from "./root-context.js";
|
|
@@ -14,9 +14,13 @@ export function captureSecureTempRepairAdapter(supplied, hasLegacyFsHooks) {
|
|
|
14
14
|
}
|
|
15
15
|
return { lstatSync, fstatSync, openSync, fchmodSync, closeSync, flags: O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_NONBLOCK };
|
|
16
16
|
}
|
|
17
|
+
// Node exposes stat identity through BigInt64Array; a high OS bit can be negative.
|
|
18
|
+
// Preserve raw values and existing positive adapter receipts, never normalize them.
|
|
19
|
+
const MIN_NODE_STAT_IDENTITY = -(1n << 63n);
|
|
17
20
|
export function secureTempDirectoryReceipt(stat, uid) {
|
|
18
21
|
const { dev, ino, uid: owner, mode } = stat;
|
|
19
|
-
if (typeof dev !== "bigint" || dev <
|
|
22
|
+
if (typeof dev !== "bigint" || dev < MIN_NODE_STAT_IDENTITY ||
|
|
23
|
+
typeof ino !== "bigint" || ino < MIN_NODE_STAT_IDENTITY || ino === 0n ||
|
|
20
24
|
typeof owner !== "bigint" || owner !== BigInt(uid) || typeof mode !== "bigint" ||
|
|
21
25
|
mode < 0n || mode > 0xffffffffn || (mode & 61440n) !== 16384n ||
|
|
22
26
|
stat.isDirectory() !== true || stat.isSymbolicLink() !== false) {
|
|
@@ -69,7 +69,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
69
69
|
if (activeDescendant)
|
|
70
70
|
throw sidecarLockTimeout(lockPath, normalizedTargetPath);
|
|
71
71
|
const admission = createSidecarAdmissionController(admissionAncestry, context.admissions, normalizedTargetPath);
|
|
72
|
-
const startedAt =
|
|
72
|
+
const startedAt = performance.now();
|
|
73
73
|
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
74
74
|
let reclaimGuard;
|
|
75
75
|
const releaseReclaimGuard = async () => {
|
|
@@ -90,7 +90,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
90
90
|
const withinDenialBudget = () => ++transientDenials <= maxTransientLockDenials;
|
|
91
91
|
const waitForRetry = async () => {
|
|
92
92
|
admission.release();
|
|
93
|
-
const delay = sidecarLockRetryDelay(retry, timeoutMs === Number.POSITIVE_INFINITY ? undefined : timeoutMs,
|
|
93
|
+
const delay = sidecarLockRetryDelay(retry, timeoutMs === Number.POSITIVE_INFINITY ? undefined : timeoutMs, performance.now() - startedAt, attempt);
|
|
94
94
|
if (delay === undefined)
|
|
95
95
|
throw sidecarLockTimeout(lockPath, normalizedTargetPath);
|
|
96
96
|
attempt += 1;
|
|
@@ -357,8 +357,9 @@ export async function acquireSidecarLock(options, context) {
|
|
|
357
357
|
try {
|
|
358
358
|
if (createdHeld)
|
|
359
359
|
stopSidecarLockMonitoring(createdHeld);
|
|
360
|
+
let failedSnapshot = createdSnapshot;
|
|
360
361
|
if (handle) {
|
|
361
|
-
|
|
362
|
+
failedSnapshot ??= { payload: null };
|
|
362
363
|
try {
|
|
363
364
|
failedSnapshot.stat = fsSync.fstatSync(handle.fd);
|
|
364
365
|
}
|
|
@@ -370,14 +371,10 @@ export async function acquireSidecarLock(options, context) {
|
|
|
370
371
|
context.held.delete(normalizedTargetPath);
|
|
371
372
|
}
|
|
372
373
|
await handle.close().catch(() => undefined);
|
|
373
|
-
// Root records use the creator receipt; partial raw writes use fd identity.
|
|
374
|
-
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
375
|
-
lockRoot,
|
|
376
|
-
parsePayload: conditionalSidecarLockParser(parserState, admission, () => admission.hasToken() && !context.held.has(normalizedTargetPath)),
|
|
377
|
-
});
|
|
378
374
|
}
|
|
379
|
-
|
|
380
|
-
|
|
375
|
+
// Root records use the creator receipt; partial raw writes use fd identity.
|
|
376
|
+
if (failedSnapshot) {
|
|
377
|
+
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
381
378
|
lockRoot,
|
|
382
379
|
parsePayload: conditionalSidecarLockParser(parserState, admission, () => admission.hasToken() && !context.held.has(normalizedTargetPath)),
|
|
383
380
|
});
|
|
@@ -5,6 +5,7 @@ export declare function validateSidecarLockStaleMs(staleMs: number | undefined):
|
|
|
5
5
|
export declare function validateSidecarLockCompromiseCheckIntervalMs(intervalMs: number | undefined): void;
|
|
6
6
|
export declare function computeSidecarLockDelayMs(retry: SidecarLockRetryOptions, attempt: number): number;
|
|
7
7
|
export declare function sidecarLockTimeout(lockPath: string, normalizedTargetPath: string): Error;
|
|
8
|
+
export declare function sidecarLockStale(lockPath: string, normalizedTargetPath: string): Error;
|
|
8
9
|
/** Undefined means the deadline or retry count is exhausted. */
|
|
9
10
|
export declare function sidecarLockRetryDelay(retry: SidecarLockRetryOptions, timeoutMs: number | undefined, elapsed: number, attempt: number): number | undefined;
|
|
10
11
|
export declare const maxTransientLockDenials = 8;
|
|
@@ -62,6 +62,13 @@ export function sidecarLockTimeout(lockPath, normalizedTargetPath) {
|
|
|
62
62
|
normalizedTargetPath,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
+
export function sidecarLockStale(lockPath, normalizedTargetPath) {
|
|
66
|
+
return Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
|
|
67
|
+
code: "file_lock_stale",
|
|
68
|
+
lockPath,
|
|
69
|
+
normalizedTargetPath,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
65
72
|
/** Undefined means the deadline or retry count is exhausted. */
|
|
66
73
|
export function sidecarLockRetryDelay(retry, timeoutMs, elapsed, attempt) {
|
|
67
74
|
if ((timeoutMs !== undefined && elapsed >= timeoutMs) ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaultSidecarLockShouldReclaim, isTransientLockFileDenial } from "./sidecar-lock-policy.js";
|
|
1
|
+
import { defaultSidecarLockShouldReclaim, isTransientLockFileDenial, sidecarLockStale } from "./sidecar-lock-policy.js";
|
|
2
2
|
import { readSidecarLockRawSnapshot, removeStaleSidecarLockIfAllowed, sidecarLockSnapshotStillPresent, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
|
|
3
3
|
import { observeSidecarLockParser, parseSidecarLockSnapshotUnderAdmission, scopedSidecarLockParser, } from "./sidecar-lock-admission-parser.js";
|
|
4
4
|
/** Handle one historical EEXIST attempt while its canonical admission is held. */
|
|
@@ -224,9 +224,5 @@ export async function handleStaleSidecarAdmission(params) {
|
|
|
224
224
|
}
|
|
225
225
|
await params.releaseReclaimGuard();
|
|
226
226
|
}
|
|
227
|
-
throw
|
|
228
|
-
code: "file_lock_stale",
|
|
229
|
-
lockPath,
|
|
230
|
-
normalizedTargetPath,
|
|
231
|
-
});
|
|
227
|
+
throw sidecarLockStale(lockPath, normalizedTargetPath);
|
|
232
228
|
}
|
|
@@ -12,27 +12,35 @@ export type SidecarLockCompromisedInfo = {
|
|
|
12
12
|
lockPath: string;
|
|
13
13
|
normalizedTargetPath: string;
|
|
14
14
|
};
|
|
15
|
-
export type
|
|
16
|
-
|
|
15
|
+
export type SidecarLockOptionFields<T> = {
|
|
16
|
+
[K in keyof T]: T[K];
|
|
17
|
+
};
|
|
18
|
+
export type SidecarLockCommonAcquireOptions = {
|
|
17
19
|
lockPath?: string;
|
|
18
|
-
staleMs: number;
|
|
19
20
|
timeoutMs?: number;
|
|
20
21
|
retry?: SidecarLockRetryOptions;
|
|
21
22
|
staleRecovery?: SidecarLockStaleRecovery;
|
|
22
23
|
reentrantOwner?: string;
|
|
24
|
+
parsePayload?: (raw: string) => unknown;
|
|
25
|
+
lockRoot?: Root;
|
|
26
|
+
onCompromised?: (info: SidecarLockCompromisedInfo) => void;
|
|
27
|
+
compromiseCheckIntervalMs?: number;
|
|
28
|
+
};
|
|
29
|
+
export type SidecarLockReclaimParams<Held extends boolean = boolean> = {
|
|
30
|
+
lockPath: string;
|
|
31
|
+
normalizedTargetPath: string;
|
|
32
|
+
payload: unknown;
|
|
33
|
+
staleMs: number;
|
|
34
|
+
nowMs: number;
|
|
35
|
+
heldByThisProcess: Held;
|
|
36
|
+
};
|
|
37
|
+
export type SidecarLockAcquireOptions<TPayload extends Record<string, unknown>> = SidecarLockOptionFields<SidecarLockCommonAcquireOptions & {
|
|
38
|
+
targetPath: string;
|
|
39
|
+
staleMs: number;
|
|
23
40
|
payload: () => TPayload | Promise<TPayload>;
|
|
24
|
-
shouldReclaim?: (params:
|
|
25
|
-
lockPath: string;
|
|
26
|
-
normalizedTargetPath: string;
|
|
27
|
-
payload: unknown;
|
|
28
|
-
staleMs: number;
|
|
29
|
-
nowMs: number;
|
|
30
|
-
heldByThisProcess: boolean;
|
|
31
|
-
}) => boolean | Promise<boolean>;
|
|
41
|
+
shouldReclaim?: (params: SidecarLockReclaimParams) => boolean | Promise<boolean>;
|
|
32
42
|
shouldRemoveStaleLock?: (snapshot: SidecarLockStaleSnapshot) => boolean | Promise<boolean>;
|
|
33
43
|
metadata?: Record<string, unknown>;
|
|
34
|
-
parsePayload?: (raw: string) => unknown;
|
|
35
|
-
lockRoot?: Root;
|
|
36
44
|
/**
|
|
37
45
|
* Keep the lock file when the process exits naturally. Default `false`:
|
|
38
46
|
* process-exit handlers release held locks. Set only for deliberately
|
|
@@ -40,9 +48,7 @@ export type SidecarLockAcquireOptions<TPayload extends Record<string, unknown>>
|
|
|
40
48
|
* liveness is governed by the caller's own stale policy.
|
|
41
49
|
*/
|
|
42
50
|
retainOnExit?: boolean;
|
|
43
|
-
|
|
44
|
-
compromiseCheckIntervalMs?: number;
|
|
45
|
-
};
|
|
51
|
+
}>;
|
|
46
52
|
export type SidecarLockHandle = {
|
|
47
53
|
lockPath: string;
|
|
48
54
|
normalizedTargetPath: string;
|
|
@@ -2,14 +2,12 @@ import fsSync, {} from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { inspectDirectoryIdentitySync, observeDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
|
-
import { admitTempWorkspaceChild, admitTempWorkspaceChildSync, } from "./temp-workspace-child-admission.js";
|
|
5
|
+
import { admitTempWorkspaceChild, admitTempWorkspaceChildSync, assertTrustedTempWorkspaceDirectory, inspectTempWorkspaceDescriptorIdentitySync, projectTempWorkspaceNumericIdentity, TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY, } from "./temp-workspace-child-admission.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { recordFileObservationFailure } from "./file-observation.js";
|
|
8
8
|
import { realpathSync } from "./realpath.js";
|
|
9
9
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
10
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
11
|
-
import { inspectTempWorkspaceDescriptorIdentitySync, projectTempWorkspaceNumericIdentity, TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY, } from "./temp-workspace-identity.js";
|
|
12
|
-
import { assertTrustedTempWorkspaceDirectory } from "./temp-workspace-permissions.js";
|
|
13
11
|
const WINDOWS = process.platform === "win32";
|
|
14
12
|
function effectiveOwner() {
|
|
15
13
|
// Windows mode/uid fields do not describe ACL authority. The supplied root's
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
2
|
import type { TempWorkspaceRootAdmission } from "./temp-workspace-admission.js";
|
|
3
|
-
|
|
3
|
+
export type TempWorkspaceIdentity = Readonly<{
|
|
4
|
+
dev: bigint;
|
|
5
|
+
ino: bigint;
|
|
6
|
+
}>;
|
|
7
|
+
export type TempWorkspaceNumericIdentity = Readonly<{
|
|
8
|
+
dev: number;
|
|
9
|
+
ino: number;
|
|
10
|
+
}>;
|
|
11
|
+
export type TempWorkspaceIdentityStat = BigIntStats | Stats;
|
|
12
|
+
export declare const TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY: boolean;
|
|
13
|
+
export declare function projectTempWorkspaceNumericIdentity(identity: TempWorkspaceIdentity): TempWorkspaceNumericIdentity | undefined;
|
|
14
|
+
export declare function inspectTempWorkspaceDescriptorIdentitySync(fd: number, expected: TempWorkspaceIdentity, numeric: TempWorkspaceNumericIdentity | undefined): TempWorkspaceIdentityStat;
|
|
15
|
+
export declare function inspectTempWorkspaceDirectoryIdentitySync(dir: string, expected: TempWorkspaceIdentity, numeric: TempWorkspaceNumericIdentity | undefined): TempWorkspaceIdentityStat;
|
|
16
|
+
export declare function validateTempWorkspaceDirMode(mode: number): void;
|
|
17
|
+
export declare function assertTrustedTempWorkspaceDirectory(stat: Pick<BigIntStats, "uid" | "mode"> | Pick<Stats, "uid" | "mode">, uid: number | undefined, child?: boolean): void;
|
|
4
18
|
export declare function assertTempWorkspaceChildState(stat: BigIntStats | Stats, ownerUid: number | undefined): void;
|
|
5
19
|
export declare function validateInitialTempWorkspaceChild(stat: BigIntStats, ownerUid: number | undefined, mode: number): boolean;
|
|
6
20
|
export declare function childHasRequestedMode(stat: BigIntStats | Stats, mode: number): boolean;
|