@openclaw/fs-safe 0.18.0 → 0.18.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/CHANGELOG.md +18 -0
- package/dist/advanced.d.ts +4 -2
- package/dist/advanced.js +1 -1
- package/dist/archive-durability.d.ts +1 -1
- package/dist/archive-merge.js +0 -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-staging.d.ts +1 -2
- package/dist/archive-staging.js +0 -2
- 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-zip-preflight.d.ts +0 -2
- package/dist/archive-zip-preflight.js +0 -1
- package/dist/archive.d.ts +14 -8
- package/dist/archive.js +127 -12
- 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-guard.d.ts +0 -1
- package/dist/directory-guard.js +0 -3
- package/dist/directory-mode-node.d.ts +20 -2
- package/dist/directory-mode-node.js +89 -3
- package/dist/durability.d.ts +2 -1
- package/dist/file-lock-sync-acquisition.js +3 -3
- package/dist/file-lock-sync-root-acquire.js +2 -6
- package/dist/file-lock-sync-root-options.d.ts +2 -10
- 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-ownership.d.ts +1 -5
- package/dist/json-durable-queue-read.js +7 -3
- package/dist/json-durable-queue.d.ts +0 -1
- package/dist/json-durable-queue.js +1 -3
- 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/permission-exec.d.ts +6 -0
- package/dist/permissions-public.d.ts +2 -1
- package/dist/permissions-windows.d.ts +3 -6
- package/dist/permissions.d.ts +3 -10
- package/dist/permissions.js +0 -1
- package/dist/pinned-mutation-admission.d.ts +1 -1
- package/dist/pinned-open.d.ts +1 -0
- package/dist/pinned-open.js +7 -2
- 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 +6 -19
- package/dist/replace-file-copy-fallback.d.ts +1 -8
- package/dist/replace-file-descriptor.d.ts +3 -11
- package/dist/replace-file-descriptor.js +1 -1
- 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/retained-directory-replacement.d.ts +1 -0
- package/dist/retained-directory-replacement.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-file-final-admission.d.ts +1 -1
- package/dist/root-file-final-admission.js +1 -6
- package/dist/root-file.js +2 -5
- 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 +11 -17
- package/dist/secret-read-policy.js +1 -1
- package/dist/secure-file-windows.js +4 -9
- package/dist/secure-temp-dir.js +2 -7
- package/dist/secure-temp-repair.js +5 -1
- package/dist/sidecar-lock-acquire.js +8 -11
- package/dist/sidecar-lock-policy.d.ts +1 -0
- package/dist/sidecar-lock-policy.js +7 -0
- package/dist/sidecar-lock-reclaim.js +5 -6
- package/dist/sidecar-lock-stale-admission.js +2 -6
- package/dist/sidecar-lock-types.d.ts +22 -16
- package/dist/stat-observation.js +3 -10
- package/dist/strict-file-identity.d.ts +2 -0
- package/dist/strict-file-identity.js +6 -6
- package/dist/temp-target.js +3 -8
- package/dist/temp-workspace-admission.js +7 -15
- package/dist/temp-workspace-child-admission.d.ts +16 -2
- package/dist/temp-workspace-child-admission.js +65 -3
- package/dist/temp-workspace-descriptor.d.ts +1 -1
- package/dist/temp-workspace-descriptor.js +2 -4
- package/dist/text-atomic.js +1 -1
- package/dist/windows-owner.d.ts +3 -6
- 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/dist/windows-security-bridge.cs +0 -2
- package/docs/advanced.md +3 -1
- package/docs/secret-file.md +13 -0
- package/docs/sidecar-lock.md +6 -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/directory-mode-owner.d.ts +0 -20
- package/dist/directory-mode-owner.js +0 -78
- 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
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
|
+
import fsAsync from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertDirectoryIdentitySync, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
|
+
import { nodeDarwinDirectoryMetadataFlags, nodeDirectorySearchOnlyFlags } from "./directory-mode-node.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
8
|
+
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
9
|
+
import { getNativeBinding } from "./native.js";
|
|
10
|
+
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
11
|
+
import { warnNativeFallback } from "./native-fallback-warning.js";
|
|
12
|
+
import { assertNoNulPathInput } from "./path.js";
|
|
13
|
+
import { realpathSync } from "./realpath.js";
|
|
14
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
15
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
|
|
16
|
+
import { inspectWindowsDirectoryCommand, inspectWindowsDirectoryCommandSync, protectPrivateWindowsFileCommand, protectPrivateWindowsFileCommandSync, verifyPrivateWindowsFileCommand, verifyPrivateWindowsFileCommandSync, hasUnsettledWindowsSecurityCommand, } from "./windows-security-command.js";
|
|
17
|
+
export function resolveCreationPermissions(options, directory) {
|
|
18
|
+
const privatePath = options.private;
|
|
19
|
+
const mode = options.mode;
|
|
20
|
+
if (privatePath !== undefined && typeof privatePath !== "boolean") {
|
|
21
|
+
throw new TypeError("private must be a boolean");
|
|
22
|
+
}
|
|
23
|
+
if (mode !== undefined && (!Number.isInteger(mode) || mode < 0 || mode > 0o7777)) {
|
|
24
|
+
throw new RangeError("mode must contain only permission bits");
|
|
25
|
+
}
|
|
26
|
+
if (privatePath && mode !== undefined && (mode & 0o7077) !== 0) {
|
|
27
|
+
throw new FsSafeError("insecure-permissions", "private creation requires owner-only permission bits");
|
|
28
|
+
}
|
|
29
|
+
if (privatePath)
|
|
30
|
+
assertDarwinPrivateCreationAvailable();
|
|
31
|
+
if (privatePath && directory)
|
|
32
|
+
assertDarwinPrivateDirectoryMode(mode ?? 0o700);
|
|
33
|
+
return { private: privatePath === true, mode: mode ?? (privatePath ? directory ? 0o700 : 0o600 : undefined) };
|
|
34
|
+
}
|
|
35
|
+
function assertPrivatePosixDirectory(stat) {
|
|
36
|
+
if (typeof process.getuid !== "function" || stat.uid !== BigInt(process.getuid())) {
|
|
37
|
+
throw new FsSafeError("not-owned", "private directory must belong to the current user");
|
|
38
|
+
}
|
|
39
|
+
if ((stat.mode & 3647n) !== 0n) {
|
|
40
|
+
throw new FsSafeError("insecure-permissions", "existing directory is not private");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function unavailable() {
|
|
44
|
+
throw new FsSafeError("helper-unavailable", "private Windows creation requires an up-to-date native helper");
|
|
45
|
+
}
|
|
46
|
+
export function assertPrivateFileCreationAvailable() {
|
|
47
|
+
const native = getNativeBinding();
|
|
48
|
+
if (process.platform !== "win32" || getFsSafeNativeConfig().mode !== "require")
|
|
49
|
+
return;
|
|
50
|
+
if (typeof native?.createPrivateDirectoryWithParentIdentity !== "function" ||
|
|
51
|
+
typeof native.inspectWindowsDirectory !== "function" ||
|
|
52
|
+
typeof native.protectPrivateWindowsFile !== "function" ||
|
|
53
|
+
typeof native.verifyPrivateWindowsFile !== "function")
|
|
54
|
+
unavailable();
|
|
55
|
+
}
|
|
56
|
+
function commandFallback() {
|
|
57
|
+
if (getFsSafeNativeConfig().mode === "require")
|
|
58
|
+
unavailable();
|
|
59
|
+
warnNativeFallback("windows-private-creation", "Private Windows creation uses a slower built-in system command.");
|
|
60
|
+
}
|
|
61
|
+
function creationOperation(name) {
|
|
62
|
+
const native = getNativeBinding();
|
|
63
|
+
const operation = native?.[name];
|
|
64
|
+
if (typeof operation === "function")
|
|
65
|
+
return operation.bind(native);
|
|
66
|
+
commandFallback();
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Full Windows identity, with security validation when requested; no repairs. */
|
|
70
|
+
export function inspectCreationDirectorySync(targetPath, privatePath) {
|
|
71
|
+
assertNoWindowsPathAlias(targetPath, "filesystem");
|
|
72
|
+
const inspect = creationOperation("inspectWindowsDirectory");
|
|
73
|
+
return (inspect ? inspect(targetPath, privatePath) : inspectWindowsDirectoryCommandSync(targetPath, privatePath)).identity;
|
|
74
|
+
}
|
|
75
|
+
export async function inspectCreationDirectory(targetPath, privatePath) {
|
|
76
|
+
assertNoWindowsPathAlias(targetPath, "filesystem");
|
|
77
|
+
const inspect = creationOperation("inspectWindowsDirectory");
|
|
78
|
+
return (inspect ? inspect(targetPath, privatePath) : await inspectWindowsDirectoryCommand(targetPath, privatePath)).identity;
|
|
79
|
+
}
|
|
80
|
+
export function assertPrivateDirectorySync(targetPath) {
|
|
81
|
+
const stat = inspectDirectoryIdentitySync(targetPath);
|
|
82
|
+
if (process.platform === "win32")
|
|
83
|
+
inspectCreationDirectorySync(targetPath, true);
|
|
84
|
+
else {
|
|
85
|
+
assertPrivatePosixDirectory(stat);
|
|
86
|
+
assertDarwinCreationDirectoryAcl(targetPath, stat);
|
|
87
|
+
}
|
|
88
|
+
inspectDirectoryIdentitySync(targetPath, stat);
|
|
89
|
+
}
|
|
90
|
+
export async function assertPrivateDirectory(targetPath) {
|
|
91
|
+
const stat = inspectDirectoryIdentitySync(targetPath);
|
|
92
|
+
if (process.platform === "win32")
|
|
93
|
+
await inspectCreationDirectory(targetPath, true);
|
|
94
|
+
else {
|
|
95
|
+
assertPrivatePosixDirectory(stat);
|
|
96
|
+
assertDarwinCreationDirectoryAcl(targetPath, stat);
|
|
97
|
+
}
|
|
98
|
+
inspectDirectoryIdentitySync(targetPath, stat);
|
|
99
|
+
}
|
|
100
|
+
export function protectCreatedFileSync(fd, targetPath, parentIdentity) {
|
|
101
|
+
const protect = creationOperation("protectPrivateWindowsFile");
|
|
102
|
+
return (protect ? protect(fd, targetPath, parentIdentity)
|
|
103
|
+
: protectPrivateWindowsFileCommandSync(fd, targetPath, parentIdentity)).identity;
|
|
104
|
+
}
|
|
105
|
+
export async function protectCreatedFile(fd, targetPath, parentIdentity) {
|
|
106
|
+
const protect = creationOperation("protectPrivateWindowsFile");
|
|
107
|
+
return (protect ? protect(fd, targetPath, parentIdentity)
|
|
108
|
+
: await protectPrivateWindowsFileCommand(fd, targetPath, parentIdentity)).identity;
|
|
109
|
+
}
|
|
110
|
+
export function verifyCreatedFileSync(fd, targetPath, identity, parentIdentity, links = 1) {
|
|
111
|
+
const verify = creationOperation("verifyPrivateWindowsFile");
|
|
112
|
+
if (verify)
|
|
113
|
+
verify(fd, targetPath, identity, parentIdentity, links);
|
|
114
|
+
else
|
|
115
|
+
verifyPrivateWindowsFileCommandSync(fd, targetPath, identity, parentIdentity, links);
|
|
116
|
+
}
|
|
117
|
+
export async function verifyCreatedFile(fd, targetPath, identity, parentIdentity, links = 1) {
|
|
118
|
+
const verify = creationOperation("verifyPrivateWindowsFile");
|
|
119
|
+
if (verify)
|
|
120
|
+
verify(fd, targetPath, identity, parentIdentity, links);
|
|
121
|
+
else
|
|
122
|
+
await verifyPrivateWindowsFileCommand(fd, targetPath, identity, parentIdentity, links);
|
|
123
|
+
}
|
|
124
|
+
function inspector() {
|
|
125
|
+
const native = getNativeBinding();
|
|
126
|
+
if (typeof native?.inspectDarwinAcl !== "function") {
|
|
127
|
+
throw new FsSafeError("helper-unavailable", "private Darwin creation requires native descriptor ACL inspection");
|
|
128
|
+
}
|
|
129
|
+
return native.inspectDarwinAcl.bind(native);
|
|
130
|
+
}
|
|
131
|
+
export function assertDarwinPrivateCreationAvailable() {
|
|
132
|
+
if (process.platform === "darwin")
|
|
133
|
+
inspector();
|
|
134
|
+
}
|
|
135
|
+
export function assertDarwinPrivateDirectoryMode(mode) {
|
|
136
|
+
if (process.platform === "darwin" && ((mode & ~process.umask()) & 0o500) === 0) {
|
|
137
|
+
throw new FsSafeError("helper-unavailable", "private Darwin directory ACL inspection requires owner read or search permission");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function assertDarwinCreationAcl(fd, inheritanceTarget) {
|
|
141
|
+
if (process.platform !== "darwin")
|
|
142
|
+
return;
|
|
143
|
+
const inspect = inspector();
|
|
144
|
+
const state = inspect(fd, inheritanceTarget)?.state;
|
|
145
|
+
if (state === "present") {
|
|
146
|
+
throw new FsSafeError("insecure-permissions", inheritanceTarget
|
|
147
|
+
? "private creation parent has inheritable Darwin ACL entries"
|
|
148
|
+
: "private creation object has Darwin ACL entries");
|
|
149
|
+
}
|
|
150
|
+
if (state !== "absent" && state !== "empty") {
|
|
151
|
+
throw new FsSafeError("helper-unavailable", "native Darwin ACL inspection returned incomplete facts");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function assertDarwinCreationDirectoryAcl(pathname, expected, inheritanceTarget) {
|
|
155
|
+
if (process.platform !== "darwin")
|
|
156
|
+
return;
|
|
157
|
+
assertDarwinPrivateCreationAvailable();
|
|
158
|
+
let fd;
|
|
159
|
+
try {
|
|
160
|
+
fd = fs.openSync(pathname, nodeDarwinDirectoryMetadataFlags());
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error.code !== "EACCES")
|
|
164
|
+
throw error;
|
|
165
|
+
const search = nodeDirectorySearchOnlyFlags();
|
|
166
|
+
if (!search)
|
|
167
|
+
throw error;
|
|
168
|
+
try {
|
|
169
|
+
fd = fs.openSync(pathname, search.flags | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
170
|
+
}
|
|
171
|
+
catch (cause) {
|
|
172
|
+
if (cause.code !== "EACCES")
|
|
173
|
+
throw cause;
|
|
174
|
+
throw new FsSafeError("helper-unavailable", "private Darwin directory ACL inspection requires owner read or search permission", { cause });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const opened = inspectFileIdentitySync(() => fs.fstatSync(fd, { bigint: true }), expected);
|
|
179
|
+
if (!opened.isDirectory())
|
|
180
|
+
throw new FsSafeError("path-mismatch", "ACL target is not the admitted directory");
|
|
181
|
+
assertDarwinCreationAcl(fd, inheritanceTarget);
|
|
182
|
+
inspectDirectoryIdentitySync(pathname, expected);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
fs.closeSync(fd);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export function assertCreationFile(fd, pathname, expected) {
|
|
189
|
+
const opened = inspectFileIdentitySync(() => fs.fstatSync(fd, { bigint: true }), expected);
|
|
190
|
+
const named = inspectFileIdentitySync(() => fs.lstatSync(pathname, { bigint: true }), opened);
|
|
191
|
+
if (!opened.isFile() || !named.isFile() || named.isSymbolicLink() || opened.nlink !== 1n || named.nlink !== 1n) {
|
|
192
|
+
throw new FsSafeError("path-mismatch", "created file is not the expected single-linked regular file");
|
|
193
|
+
}
|
|
194
|
+
return opened;
|
|
195
|
+
}
|
|
196
|
+
export function assertPrivateCreationFile(stat, fd) {
|
|
197
|
+
if (process.platform === "win32")
|
|
198
|
+
return;
|
|
199
|
+
if (typeof process.getuid !== "function" || stat.uid !== BigInt(process.getuid())) {
|
|
200
|
+
throw new FsSafeError("not-owned", "created private file must belong to the current user");
|
|
201
|
+
}
|
|
202
|
+
if ((stat.mode & 3647n) !== 0n) {
|
|
203
|
+
throw new FsSafeError("insecure-permissions", "created private file is not owner-only");
|
|
204
|
+
}
|
|
205
|
+
assertDarwinCreationAcl(fd);
|
|
206
|
+
}
|
|
207
|
+
function assertRecordedFile(current, identity) {
|
|
208
|
+
if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentityForCleanup(current, identity)) {
|
|
209
|
+
throw new FsSafeError("path-mismatch", "created file cleanup preserved a replacement");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export function removeRecordedCreationFileSync(pathname, identity, assertParent) {
|
|
213
|
+
assertParent();
|
|
214
|
+
let current;
|
|
215
|
+
try {
|
|
216
|
+
current = fs.lstatSync(pathname, { bigint: true });
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error?.code === "ENOENT")
|
|
220
|
+
return;
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
assertRecordedFile(current, identity);
|
|
224
|
+
fs.unlinkSync(pathname);
|
|
225
|
+
}
|
|
226
|
+
export async function removeRecordedCreationFile(pathname, identity, assertParent) {
|
|
227
|
+
assertParent();
|
|
228
|
+
let current;
|
|
229
|
+
try {
|
|
230
|
+
current = await fsAsync.lstat(pathname, { bigint: true });
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (error?.code === "ENOENT")
|
|
234
|
+
return;
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
assertParent();
|
|
238
|
+
assertRecordedFile(current, identity);
|
|
239
|
+
// Recheck after the awaited observation before dispatching the unlink.
|
|
240
|
+
assertRecordedFile(fs.lstatSync(pathname, { bigint: true }), identity);
|
|
241
|
+
await fsAsync.unlink(pathname);
|
|
242
|
+
}
|
|
243
|
+
export function creationPublicationAfterFailure(error, recorded) {
|
|
244
|
+
if (error instanceof FsSafeError) {
|
|
245
|
+
const publication = error.details?.publication;
|
|
246
|
+
if (publication && typeof publication === "object" && "status" in publication &&
|
|
247
|
+
publication.status === "indeterminate")
|
|
248
|
+
return "indeterminate";
|
|
249
|
+
}
|
|
250
|
+
return recorded;
|
|
251
|
+
}
|
|
252
|
+
export function hasPreservedCreationArtifacts(error) {
|
|
253
|
+
if (!(error instanceof FsSafeError))
|
|
254
|
+
return false;
|
|
255
|
+
if (error.details?.cleanup === "preserved" || error.details?.cleanup === "failed")
|
|
256
|
+
return true;
|
|
257
|
+
const publication = error.details?.publication;
|
|
258
|
+
return publication !== null && typeof publication === "object" && "status" in publication &&
|
|
259
|
+
(publication.status === "published" || publication.status === "indeterminate");
|
|
260
|
+
}
|
|
261
|
+
export function rethrowPrivateStageCreationFailure(error, path, stageDirectory) {
|
|
262
|
+
if (error instanceof FsSafeError && error.code === "already-exists") {
|
|
263
|
+
throw new FsSafeError("helper-failed", "private file staging path already exists", {
|
|
264
|
+
cause: error, details: { publication: { status: "not-published" }, path, stageDirectory },
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const publication = error instanceof FsSafeError ? error.details?.publication : undefined;
|
|
268
|
+
const created = error instanceof FsSafeError && error.details?.path === stageDirectory &&
|
|
269
|
+
publication !== null && typeof publication === "object" && "status" in publication &&
|
|
270
|
+
publication.status === "published";
|
|
271
|
+
let unconfirmed = hasUnsettledWindowsSecurityCommand(error);
|
|
272
|
+
if (error !== null && (typeof error === "object" || typeof error === "function")) {
|
|
273
|
+
try {
|
|
274
|
+
const outcome = Object.getOwnPropertyDescriptor(error, "creationOutcome");
|
|
275
|
+
unconfirmed ||= Boolean(outcome && "value" in outcome && outcome.value === "unconfirmed");
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
unconfirmed = true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (created || unconfirmed) {
|
|
282
|
+
throw privateFileSettlementFailure({
|
|
283
|
+
primary: error, cleanup: [], publication: "not-published", path, stageDirectory, preserved: true,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
export function privateFileSettlementFailure(params) {
|
|
289
|
+
return new FsSafeError("helper-failed", "file creation or staging settlement failed", {
|
|
290
|
+
cause: params.cleanup.length
|
|
291
|
+
? new AggregateError([params.primary, ...params.cleanup], "file creation and cleanup failed")
|
|
292
|
+
: params.primary,
|
|
293
|
+
details: {
|
|
294
|
+
publication: { status: params.publication }, path: params.path, stageDirectory: params.stageDirectory,
|
|
295
|
+
cleanup: params.preserved || params.publication === "indeterminate" ? "preserved" : params.cleanup.length ? "failed" : "removed",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
export function assertBeforeCreation(selected, permissions, assertion, kind) {
|
|
300
|
+
if (permissions.private)
|
|
301
|
+
assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, kind);
|
|
302
|
+
assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
|
|
303
|
+
selected.assertParent();
|
|
304
|
+
if (permissions.private && kind === "directory")
|
|
305
|
+
assertDarwinPrivateDirectoryMode(permissions.mode);
|
|
306
|
+
if (permissions.private)
|
|
307
|
+
assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, kind);
|
|
308
|
+
}
|
|
309
|
+
export function creationAdmissionFromParent(parent) {
|
|
310
|
+
const { dev, ino } = parent.stat;
|
|
311
|
+
if (typeof dev !== "bigint" || typeof ino !== "bigint") {
|
|
312
|
+
throw new FsSafeError("path-mismatch", "private creation requires an exact parent identity");
|
|
313
|
+
}
|
|
314
|
+
const identity = inspectDirectoryIdentitySync(parent.dir, { dev, ino });
|
|
315
|
+
return { expectedParentIdentity: { dev: identity.dev, ino: identity.ino, realPath: parent.realPath } };
|
|
316
|
+
}
|
|
317
|
+
export function prepareCreationPath(input, expectedParent) {
|
|
318
|
+
expectedParent = expectedParent && { dev: expectedParent.dev, ino: expectedParent.ino, realPath: expectedParent.realPath };
|
|
319
|
+
assertNoNulPathInput(input);
|
|
320
|
+
assertNoWindowsPathAlias(input, "filesystem");
|
|
321
|
+
if (process.platform === "win32" && input.replaceAll("/", "\\").split("\\")
|
|
322
|
+
.some(component => component.endsWith(".") || component.endsWith(" "))) {
|
|
323
|
+
throw new FsSafeError("invalid-path", "creation path contains an ambiguous Windows component");
|
|
324
|
+
}
|
|
325
|
+
const target = resolvePathPreservingWindowsRoot(input);
|
|
326
|
+
const directory = path.dirname(target);
|
|
327
|
+
if (target === directory || input.length === 0) {
|
|
328
|
+
throw new FsSafeError("invalid-path", "creation requires a child name");
|
|
329
|
+
}
|
|
330
|
+
if (expectedParent)
|
|
331
|
+
assertDirectoryIdentitySync(directory, expectedParent);
|
|
332
|
+
const stat = inspectDirectoryIdentitySync(directory);
|
|
333
|
+
const realPath = realpathSync.native(directory);
|
|
334
|
+
const parent = { dir: directory, realPath, stat };
|
|
335
|
+
const assertParent = () => {
|
|
336
|
+
assertDirectoryIdentitySync(directory, { dev: stat.dev, ino: stat.ino, realPath });
|
|
337
|
+
if (expectedParent)
|
|
338
|
+
assertDirectoryIdentitySync(directory, expectedParent);
|
|
339
|
+
};
|
|
340
|
+
assertParent();
|
|
341
|
+
return { target, parent, assertParent };
|
|
342
|
+
}
|
|
343
|
+
export function creationCollision(error) {
|
|
344
|
+
if (error?.code === "EEXIST") {
|
|
345
|
+
return new FsSafeError("already-exists", "creation target already exists", { cause: error });
|
|
346
|
+
}
|
|
347
|
+
return error;
|
|
348
|
+
}
|
|
349
|
+
/** Remove only the recorded empty stage; never traverse a replaced directory. */
|
|
350
|
+
export function removeCreationDirectory(directory, identity, assertParent) {
|
|
351
|
+
assertParent();
|
|
352
|
+
try {
|
|
353
|
+
assertDirectoryIdentitySync(directory, identity);
|
|
354
|
+
fs.rmdirSync(directory);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
if (error?.code !== "ENOENT")
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
export async function removeCreationDirectoryAsync(directory, identity, assertParent) {
|
|
362
|
+
assertParent();
|
|
363
|
+
try {
|
|
364
|
+
assertDirectoryIdentitySync(directory, identity);
|
|
365
|
+
await fsAsync.rmdir(directory);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
if (error?.code !== "ENOENT")
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -36,7 +36,6 @@ export declare function createNearestExistingDirectoryGuard(rootReal: string, ta
|
|
|
36
36
|
export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
|
|
37
37
|
bigint: boolean;
|
|
38
38
|
}): Promise<AnyAsyncDirectoryGuard>;
|
|
39
|
-
export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
|
|
40
39
|
export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
|
|
41
40
|
export declare function observeDirectoryIdentitySync(dir: string, options: {
|
|
42
41
|
bigint: true;
|
package/dist/directory-guard.js
CHANGED
|
@@ -84,9 +84,6 @@ function assertDirectoryGuard(guard, mode) {
|
|
|
84
84
|
export async function createNearestExistingDirectoryGuard(rootReal, targetPath, options = { bigint: false }) {
|
|
85
85
|
return nearestExistingDirectoryGuard(rootReal, targetPath, "native", options);
|
|
86
86
|
}
|
|
87
|
-
export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
88
|
-
return nearestExistingDirectoryGuard(rootReal, targetPath, "normalized");
|
|
89
|
-
}
|
|
90
87
|
function nearestExistingDirectoryGuard(rootReal, targetPath, mode, options) {
|
|
91
88
|
assertNoWindowsPathAlias(rootReal, "filesystem");
|
|
92
89
|
assertNoWindowsPathAlias(targetPath, "filesystem");
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import { type BigIntStats } from "node:fs";
|
|
2
|
-
|
|
1
|
+
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
|
+
export type DirectoryModeChecks = {
|
|
3
|
+
check?: () => void;
|
|
4
|
+
beforeChmod?: () => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export type DirectoryModeOwner = {
|
|
7
|
+
verify(check?: () => void): Promise<void>;
|
|
8
|
+
apply(mode: number, checks?: DirectoryModeChecks): Promise<void>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export declare function assertOwnedDirectory(expected: Stats | BigIntStats, actual: Stats | BigIntStats): void;
|
|
12
|
+
/** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
|
|
13
|
+
export declare function ownDirectoryMode(params: {
|
|
14
|
+
inspect: () => Promise<number>;
|
|
15
|
+
chmod: (mode: number) => Promise<void>;
|
|
16
|
+
prepareChmod?: () => Promise<void>;
|
|
17
|
+
verifyChmod?: () => Promise<void>;
|
|
18
|
+
close: () => Promise<void>;
|
|
19
|
+
ignoreChmodError?: boolean;
|
|
20
|
+
}): DirectoryModeOwner;
|
|
3
21
|
/** Darwin descriptor inspection avoids requesting directory-content reads. */
|
|
4
22
|
export declare function nodeDarwinDirectoryMetadataFlags(): number;
|
|
5
23
|
export declare function nodeDirectorySearchOnlyFlags(): {
|
|
@@ -3,7 +3,83 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
5
5
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
|
-
import {
|
|
6
|
+
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
7
|
+
export function assertOwnedDirectory(expected, actual) {
|
|
8
|
+
if (actual.isSymbolicLink() || !actual.isDirectory()) {
|
|
9
|
+
throw new FsSafeError("not-file", "directory mode target must be a real directory");
|
|
10
|
+
}
|
|
11
|
+
if (!sameFileIdentityForCleanup(expected, actual)) {
|
|
12
|
+
throw new FsSafeError("path-mismatch", "directory changed before its mode could be applied");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
|
|
16
|
+
export function ownDirectoryMode(params) {
|
|
17
|
+
let pending = Promise.resolve();
|
|
18
|
+
let closing;
|
|
19
|
+
const enqueue = (run) => {
|
|
20
|
+
if (closing)
|
|
21
|
+
return Promise.reject(new FsSafeError("path-mismatch", "directory mode owner is closed"));
|
|
22
|
+
const operation = pending.then(run);
|
|
23
|
+
pending = operation.catch(() => undefined);
|
|
24
|
+
return operation;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
verify: (check) => enqueue(async () => {
|
|
28
|
+
check?.();
|
|
29
|
+
await params.inspect();
|
|
30
|
+
check?.();
|
|
31
|
+
}),
|
|
32
|
+
apply: (mode, checks = {}) => enqueue(async () => {
|
|
33
|
+
// inspect() reports permission bits only; chmod ignores file-type bits,
|
|
34
|
+
// so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
|
|
35
|
+
mode &= 0o7777;
|
|
36
|
+
checks.check?.();
|
|
37
|
+
const currentMode = await params.inspect();
|
|
38
|
+
checks.check?.();
|
|
39
|
+
if (currentMode === mode && !checks.beforeChmod && !checks.check)
|
|
40
|
+
return;
|
|
41
|
+
if (currentMode !== mode) {
|
|
42
|
+
await params.prepareChmod?.();
|
|
43
|
+
checks.check?.();
|
|
44
|
+
}
|
|
45
|
+
await checks.beforeChmod?.();
|
|
46
|
+
checks.check?.();
|
|
47
|
+
// Hooks/ancestor checks can yield; recheck the original named association.
|
|
48
|
+
await params.inspect();
|
|
49
|
+
checks.check?.();
|
|
50
|
+
let dispatchDeadlineFailure;
|
|
51
|
+
if (currentMode !== mode) {
|
|
52
|
+
checks.check?.();
|
|
53
|
+
try {
|
|
54
|
+
await params.chmod(mode);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!params.ignoreChmodError)
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
// Expiry cannot release the fd while post-dispatch verification is pending.
|
|
61
|
+
try {
|
|
62
|
+
checks.check?.();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
dispatchDeadlineFailure = { error };
|
|
66
|
+
}
|
|
67
|
+
await params.verifyChmod?.();
|
|
68
|
+
}
|
|
69
|
+
const finalMode = await params.inspect();
|
|
70
|
+
if (dispatchDeadlineFailure)
|
|
71
|
+
throw dispatchDeadlineFailure.error;
|
|
72
|
+
checks.check?.();
|
|
73
|
+
if (!params.ignoreChmodError && finalMode !== mode) {
|
|
74
|
+
throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
close() {
|
|
78
|
+
closing ??= pending.then(params.close);
|
|
79
|
+
return closing;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
7
83
|
/** Darwin descriptor inspection avoids requesting directory-content reads. */
|
|
8
84
|
export function nodeDarwinDirectoryMetadataFlags() {
|
|
9
85
|
if (process.platform !== "darwin" || (process.arch !== "x64" && process.arch !== "arm64")) {
|
|
@@ -95,7 +171,12 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
|
|
|
95
171
|
return owner;
|
|
96
172
|
}
|
|
97
173
|
catch (error) {
|
|
98
|
-
|
|
174
|
+
try {
|
|
175
|
+
await handle.close();
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Preserve the initial admission failure before ownership transfers.
|
|
179
|
+
}
|
|
99
180
|
throw error;
|
|
100
181
|
}
|
|
101
182
|
}
|
|
@@ -193,7 +274,12 @@ export function pinNodeDirectoryForModeSync(dirPath, options = {}) {
|
|
|
193
274
|
};
|
|
194
275
|
}
|
|
195
276
|
catch (error) {
|
|
196
|
-
|
|
277
|
+
try {
|
|
278
|
+
fsSync.closeSync(fd);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Preserve the initial admission failure before ownership transfers.
|
|
282
|
+
}
|
|
197
283
|
throw error;
|
|
198
284
|
}
|
|
199
285
|
}
|
package/dist/durability.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
|
|
2
|
-
export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy,
|
|
2
|
+
export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, } from "./publish-file.js";
|
|
3
|
+
export type { PublishFileExclusiveCleanup, PublishFileExclusiveDirectorySyncFailure, PublishFileExclusiveFailureDetails, PublishFileExclusiveFailurePhase, PublishFileExclusiveSyncFailurePolicy, } from "./publish-file-failure.js";
|
|
3
4
|
export { sha256File, sha256FileSync, type Sha256FileInput, type Sha256FileSyncInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
|
|
@@ -10,7 +10,7 @@ export class SyncLockAcquisition {
|
|
|
10
10
|
#admissions = getSyncLockAdmissions();
|
|
11
11
|
#token = {};
|
|
12
12
|
#owns = false;
|
|
13
|
-
#startedAt =
|
|
13
|
+
#startedAt = performance.now();
|
|
14
14
|
#attempt = 0;
|
|
15
15
|
#transientDenials = 0;
|
|
16
16
|
constructor(lockPath, normalizedTargetPath, retry, timeoutMs) {
|
|
@@ -51,7 +51,7 @@ export class SyncLockAcquisition {
|
|
|
51
51
|
}
|
|
52
52
|
waitForRetry() {
|
|
53
53
|
this.release();
|
|
54
|
-
const delay = sidecarLockRetryDelay(this.retry, this.timeoutMs,
|
|
54
|
+
const delay = sidecarLockRetryDelay(this.retry, this.timeoutMs, performance.now() - this.#startedAt, this.#attempt);
|
|
55
55
|
if (delay === undefined)
|
|
56
56
|
throw sidecarLockTimeout(this.lockPath, this.normalizedTargetPath);
|
|
57
57
|
sleepSync(delay);
|
|
@@ -66,7 +66,7 @@ export class SyncLockAcquisition {
|
|
|
66
66
|
}
|
|
67
67
|
catch (waitError) {
|
|
68
68
|
// Exhausted backoff must preserve the filesystem denial diagnosis.
|
|
69
|
-
if (waitError
|
|
69
|
+
if (waitError?.code === "file_lock_timeout")
|
|
70
70
|
throw error;
|
|
71
71
|
throw waitError;
|
|
72
72
|
}
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { captureRootSyncAcquireOptions } from "./file-lock-sync-root-options.js";
|
|
5
5
|
import { defaultSyncShouldReclaim, foreignSyncHeldLock } from "./file-lock-sync-admission.js";
|
|
6
|
-
import { isTransientLockFileDenial } from "./sidecar-lock-policy.js";
|
|
6
|
+
import { isTransientLockFileDenial, sidecarLockStale } from "./sidecar-lock-policy.js";
|
|
7
7
|
import { serializeSidecarLockPayload, } from "./sidecar-lock-reclaim.js";
|
|
8
8
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
9
9
|
import { SyncLockAcquisition } from "./file-lock-sync-acquisition.js";
|
|
@@ -280,11 +280,7 @@ export function acquireFileLockSyncWithRoot(targetPath, inputOptions, lockRoot)
|
|
|
280
280
|
}
|
|
281
281
|
releaseReclaimGuard();
|
|
282
282
|
}
|
|
283
|
-
throw
|
|
284
|
-
code: "file_lock_stale",
|
|
285
|
-
lockPath,
|
|
286
|
-
normalizedTargetPath,
|
|
287
|
-
});
|
|
283
|
+
throw sidecarLockStale(lockPath, normalizedTargetPath);
|
|
288
284
|
}
|
|
289
285
|
acquisition.waitForRetry();
|
|
290
286
|
}
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import type { FileLockSyncAcquireOptions } from "./file-lock-sync.js";
|
|
2
|
-
import type {
|
|
3
|
-
type CapturedRootSyncAcquireOptions<TPayload extends Record<string, unknown>> = Readonly<{
|
|
4
|
-
compromiseCheckIntervalMs?: number;
|
|
5
|
-
lockPath?: string;
|
|
6
|
-
onCompromised?: (info: SidecarLockCompromisedInfo) => void;
|
|
2
|
+
import type { SidecarLockRetryOptions } from "./sidecar-lock-types.js";
|
|
3
|
+
type CapturedRootSyncAcquireOptions<TPayload extends Record<string, unknown>> = Readonly<Pick<FileLockSyncAcquireOptions<TPayload>, "compromiseCheckIntervalMs" | "lockPath" | "onCompromised" | "parsePayload" | "payload" | "reentrantOwner" | "staleRecovery" | "timeoutMs"> & {
|
|
7
4
|
optionsReceiver: FileLockSyncAcquireOptions<TPayload>;
|
|
8
|
-
parsePayload?: (raw: string) => unknown;
|
|
9
|
-
payload: () => TPayload;
|
|
10
|
-
reentrantOwner?: string;
|
|
11
5
|
retry: Readonly<SidecarLockRetryOptions>;
|
|
12
6
|
shouldReclaim?: FileLockSyncAcquireOptions<TPayload>["shouldReclaim"];
|
|
13
7
|
shouldRemoveStaleLock?: FileLockSyncAcquireOptions<TPayload>["shouldRemoveStaleLock"];
|
|
14
8
|
staleMs: number;
|
|
15
|
-
staleRecovery?: SidecarLockStaleRecovery;
|
|
16
|
-
timeoutMs?: number;
|
|
17
9
|
}>;
|
|
18
10
|
export declare function captureRootSyncAcquireOptions<TPayload extends Record<string, unknown>>(options: FileLockSyncAcquireOptions<TPayload>): CapturedRootSyncAcquireOptions<TPayload>;
|
|
19
11
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RootContext } from "./root-context.js";
|
|
2
|
-
import type { Root
|
|
2
|
+
import type { Root } from "./root-impl.js";
|
|
3
|
+
import type { RootDefaults } from "./root-options.js";
|
|
3
4
|
/** @internal Registers only instances constructed by RootHandle itself. */
|
|
4
5
|
export declare function registerFileLockSyncRootAdapter(handle: object, context: RootContext, defaults: RootDefaults): void;
|
|
5
6
|
export type FileLockSyncRootAuthority = Readonly<{
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import { defaultSyncShouldReclaim } from "./file-lock-sync-admission.js";
|
|
3
3
|
import { parseSidecarLockSnapshot, readSidecarLockRawSnapshotSync, removeSidecarLockIfUnchangedSync, } from "./sidecar-lock-reclaim.js";
|
|
4
4
|
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
5
|
+
import { sidecarLockStale } from "./sidecar-lock-policy.js";
|
|
5
6
|
/** Handle one exclusive-create conflict without surrendering canonical admission. */
|
|
6
7
|
export function handleSyncStaleAdmission(acquisition, params) {
|
|
7
8
|
const holderChanged = {};
|
|
@@ -149,11 +150,7 @@ export function handleSyncStaleAdmission(acquisition, params) {
|
|
|
149
150
|
throw error;
|
|
150
151
|
}
|
|
151
152
|
if (!approved) {
|
|
152
|
-
throw
|
|
153
|
-
code: "file_lock_stale",
|
|
154
|
-
lockPath: acquisition.lockPath,
|
|
155
|
-
normalizedTargetPath: acquisition.normalizedTargetPath,
|
|
156
|
-
});
|
|
153
|
+
throw sidecarLockStale(acquisition.lockPath, acquisition.normalizedTargetPath);
|
|
157
154
|
}
|
|
158
155
|
try {
|
|
159
156
|
fs.mkdirSync(params.reclaimGuardPath);
|
|
@@ -198,9 +195,5 @@ export function handleSyncStaleAdmission(acquisition, params) {
|
|
|
198
195
|
return;
|
|
199
196
|
}
|
|
200
197
|
}
|
|
201
|
-
throw
|
|
202
|
-
code: "file_lock_stale",
|
|
203
|
-
lockPath: acquisition.lockPath,
|
|
204
|
-
normalizedTargetPath: acquisition.normalizedTargetPath,
|
|
205
|
-
});
|
|
198
|
+
throw sidecarLockStale(acquisition.lockPath, acquisition.normalizedTargetPath);
|
|
206
199
|
}
|