@openclaw/fs-safe 0.5.1 → 0.5.3

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.
Files changed (231) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +19 -6
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +20 -12
  5. package/dist/archive-entry.d.ts +1 -0
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +37 -6
  8. package/dist/archive-errors.d.ts +1 -0
  9. package/dist/archive-errors.d.ts.map +1 -1
  10. package/dist/archive-errors.js +4 -0
  11. package/dist/archive-input.d.ts +7 -0
  12. package/dist/archive-input.d.ts.map +1 -1
  13. package/dist/archive-input.js +11 -1
  14. package/dist/archive-limits.js +1 -1
  15. package/dist/archive-native.d.ts.map +1 -1
  16. package/dist/archive-native.js +12 -6
  17. package/dist/archive-policy.d.ts.map +1 -1
  18. package/dist/archive-policy.js +2 -1
  19. package/dist/archive-read.d.ts.map +1 -1
  20. package/dist/archive-read.js +62 -37
  21. package/dist/archive-staging.d.ts.map +1 -1
  22. package/dist/archive-staging.js +3 -2
  23. package/dist/archive-tar-meta.d.ts.map +1 -1
  24. package/dist/archive-tar-meta.js +15 -1
  25. package/dist/archive-tar-runtime.d.ts +1 -0
  26. package/dist/archive-tar-runtime.d.ts.map +1 -1
  27. package/dist/archive-tar-runtime.js +11 -0
  28. package/dist/archive-tar.d.ts.map +1 -1
  29. package/dist/archive-tar.js +8 -4
  30. package/dist/archive-zip-entry.d.ts +7 -1
  31. package/dist/archive-zip-entry.d.ts.map +1 -1
  32. package/dist/archive-zip-entry.js +11 -1
  33. package/dist/archive-zip-integrity.d.ts +5 -0
  34. package/dist/archive-zip-integrity.d.ts.map +1 -0
  35. package/dist/archive-zip-integrity.js +54 -0
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +12 -1
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -5
  40. package/dist/bounded-read-stream.d.ts.map +1 -1
  41. package/dist/bounded-read-stream.js +13 -2
  42. package/dist/deny-mutations.d.ts +1 -0
  43. package/dist/deny-mutations.d.ts.map +1 -1
  44. package/dist/deny-mutations.js +6 -39
  45. package/dist/device-path.d.ts +1 -0
  46. package/dist/device-path.d.ts.map +1 -1
  47. package/dist/device-path.js +6 -6
  48. package/dist/directory-guard.d.ts.map +1 -1
  49. package/dist/directory-guard.js +5 -4
  50. package/dist/error-detail.d.ts +2 -0
  51. package/dist/error-detail.d.ts.map +1 -0
  52. package/dist/error-detail.js +4 -0
  53. package/dist/errors.d.ts +1 -1
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +4 -0
  56. package/dist/file-hash.d.ts.map +1 -1
  57. package/dist/file-hash.js +4 -1
  58. package/dist/file-lock-sync.d.ts.map +1 -1
  59. package/dist/file-lock-sync.js +88 -35
  60. package/dist/file-store-boundary.d.ts +6 -0
  61. package/dist/file-store-boundary.d.ts.map +1 -1
  62. package/dist/file-store-boundary.js +20 -5
  63. package/dist/file-store-sync-write.d.ts +9 -0
  64. package/dist/file-store-sync-write.d.ts.map +1 -0
  65. package/dist/file-store-sync-write.js +97 -0
  66. package/dist/file-store.d.ts.map +1 -1
  67. package/dist/file-store.js +62 -139
  68. package/dist/filename.d.ts.map +1 -1
  69. package/dist/filename.js +27 -0
  70. package/dist/guarded-mkdir.d.ts.map +1 -1
  71. package/dist/guarded-mkdir.js +5 -6
  72. package/dist/install-path.js +1 -1
  73. package/dist/json-document-store.d.ts.map +1 -1
  74. package/dist/json-document-store.js +19 -13
  75. package/dist/json.d.ts.map +1 -1
  76. package/dist/json.js +1 -3
  77. package/dist/local-file-access.d.ts +3 -2
  78. package/dist/local-file-access.d.ts.map +1 -1
  79. package/dist/local-file-access.js +12 -6
  80. package/dist/local-roots.d.ts.map +1 -1
  81. package/dist/local-roots.js +35 -30
  82. package/dist/move-path.d.ts.map +1 -1
  83. package/dist/move-path.js +51 -12
  84. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  85. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  86. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  87. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  88. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  89. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  90. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  91. package/dist/native-binding.d.ts +1 -0
  92. package/dist/native-binding.d.ts.map +1 -1
  93. package/dist/native-operations.d.ts.map +1 -1
  94. package/dist/native-operations.js +14 -1
  95. package/dist/native-pinned-write.d.ts.map +1 -1
  96. package/dist/native-pinned-write.js +44 -17
  97. package/dist/output-sibling.d.ts.map +1 -1
  98. package/dist/output-sibling.js +6 -5
  99. package/dist/path-policy.d.ts.map +1 -1
  100. package/dist/path-policy.js +1 -7
  101. package/dist/path.d.ts.map +1 -1
  102. package/dist/path.js +4 -0
  103. package/dist/permission-exec.d.ts +6 -0
  104. package/dist/permission-exec.d.ts.map +1 -0
  105. package/dist/permission-exec.js +28 -0
  106. package/dist/permissions-windows.d.ts +56 -0
  107. package/dist/permissions-windows.d.ts.map +1 -0
  108. package/dist/permissions-windows.js +285 -0
  109. package/dist/permissions.d.ts +3 -46
  110. package/dist/permissions.d.ts.map +1 -1
  111. package/dist/permissions.js +17 -328
  112. package/dist/pinned-open.d.ts.map +1 -1
  113. package/dist/pinned-open.js +21 -4
  114. package/dist/pinned-write.js +2 -1
  115. package/dist/private-temp-workspace.d.ts.map +1 -1
  116. package/dist/private-temp-workspace.js +45 -4
  117. package/dist/read-error.d.ts +2 -0
  118. package/dist/read-error.d.ts.map +1 -0
  119. package/dist/read-error.js +11 -0
  120. package/dist/regular-file.d.ts.map +1 -1
  121. package/dist/regular-file.js +21 -2
  122. package/dist/replace-directory.d.ts.map +1 -1
  123. package/dist/replace-directory.js +10 -1
  124. package/dist/replace-file-copy-fallback.d.ts +3 -0
  125. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  126. package/dist/replace-file-copy-fallback.js +18 -11
  127. package/dist/replace-file-descriptor.d.ts +33 -0
  128. package/dist/replace-file-descriptor.d.ts.map +1 -0
  129. package/dist/replace-file-descriptor.js +96 -0
  130. package/dist/replace-file.d.ts +9 -2
  131. package/dist/replace-file.d.ts.map +1 -1
  132. package/dist/replace-file.js +64 -72
  133. package/dist/root-context.d.ts +8 -0
  134. package/dist/root-context.d.ts.map +1 -1
  135. package/dist/root-context.js +43 -2
  136. package/dist/root-errors.d.ts +7 -0
  137. package/dist/root-errors.d.ts.map +1 -1
  138. package/dist/root-errors.js +46 -1
  139. package/dist/root-file.d.ts +1 -0
  140. package/dist/root-file.d.ts.map +1 -1
  141. package/dist/root-file.js +2 -0
  142. package/dist/root-impl.d.ts.map +1 -1
  143. package/dist/root-impl.js +216 -169
  144. package/dist/root-path-symlink.d.ts +3 -0
  145. package/dist/root-path-symlink.d.ts.map +1 -0
  146. package/dist/root-path-symlink.js +35 -0
  147. package/dist/root-path.d.ts +1 -0
  148. package/dist/root-path.d.ts.map +1 -1
  149. package/dist/root-path.js +129 -190
  150. package/dist/root-walk.d.ts.map +1 -1
  151. package/dist/root-walk.js +15 -0
  152. package/dist/safe-path-segment.d.ts +2 -0
  153. package/dist/safe-path-segment.d.ts.map +1 -1
  154. package/dist/safe-path-segment.js +12 -0
  155. package/dist/secret-file.d.ts.map +1 -1
  156. package/dist/secret-file.js +29 -6
  157. package/dist/secret-read-async.d.ts.map +1 -1
  158. package/dist/secret-read-async.js +5 -1
  159. package/dist/secure-temp-dir.d.ts.map +1 -1
  160. package/dist/secure-temp-dir.js +6 -1
  161. package/dist/short-path.d.ts +2 -0
  162. package/dist/short-path.d.ts.map +1 -0
  163. package/dist/short-path.js +7 -0
  164. package/dist/sidecar-lock-acquire.d.ts +30 -0
  165. package/dist/sidecar-lock-acquire.d.ts.map +1 -0
  166. package/dist/sidecar-lock-acquire.js +260 -0
  167. package/dist/sidecar-lock-policy.d.ts +3 -0
  168. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  169. package/dist/sidecar-lock-policy.js +20 -3
  170. package/dist/sidecar-lock-reclaim.d.ts +5 -1
  171. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  172. package/dist/sidecar-lock-reclaim.js +59 -9
  173. package/dist/sidecar-lock.d.ts.map +1 -1
  174. package/dist/sidecar-lock.js +27 -226
  175. package/dist/symlink-parents.d.ts.map +1 -1
  176. package/dist/symlink-parents.js +9 -8
  177. package/dist/temp-target.d.ts.map +1 -1
  178. package/dist/temp-target.js +15 -4
  179. package/dist/test-hooks.d.ts +1 -0
  180. package/dist/test-hooks.d.ts.map +1 -1
  181. package/dist/timing.d.ts +2 -0
  182. package/dist/timing.d.ts.map +1 -1
  183. package/dist/timing.js +9 -0
  184. package/dist/trash.d.ts.map +1 -1
  185. package/dist/trash.js +10 -1
  186. package/dist/walk.d.ts.map +1 -1
  187. package/dist/walk.js +15 -0
  188. package/docs/advanced.md +7 -1
  189. package/docs/archive.md +27 -20
  190. package/docs/assets/readme-banner.jpg +0 -0
  191. package/docs/atomic.md +48 -19
  192. package/docs/errors.md +34 -8
  193. package/docs/file-store.md +14 -1
  194. package/docs/filename.md +17 -16
  195. package/docs/index.md +1 -0
  196. package/docs/install-path.md +9 -6
  197. package/docs/json.md +8 -2
  198. package/docs/local-roots.md +79 -103
  199. package/docs/native-helper.md +11 -5
  200. package/docs/native.md +2 -2
  201. package/docs/output.md +8 -3
  202. package/docs/path-scope.md +4 -1
  203. package/docs/path.md +7 -3
  204. package/docs/permissions.md +8 -0
  205. package/docs/public-api.md +109 -0
  206. package/docs/quickstart.md +6 -2
  207. package/docs/reading.md +13 -10
  208. package/docs/root.md +26 -4
  209. package/docs/secret-file.md +21 -4
  210. package/docs/security-model.md +21 -4
  211. package/docs/sidecar-lock.md +15 -5
  212. package/docs/temp.md +42 -5
  213. package/docs/test-hooks.md +2 -0
  214. package/docs/types.md +32 -15
  215. package/docs/writing.md +25 -12
  216. package/package.json +15 -4
  217. package/dist/archive-file-io.d.ts +0 -9
  218. package/dist/archive-file-io.d.ts.map +0 -1
  219. package/dist/archive-file-io.js +0 -11
  220. package/dist/file-store-source.d.ts +0 -5
  221. package/dist/file-store-source.d.ts.map +0 -1
  222. package/dist/file-store-source.js +0 -30
  223. package/dist/file-url.d.ts +0 -2
  224. package/dist/file-url.d.ts.map +0 -1
  225. package/dist/file-url.js +0 -1
  226. package/dist/path-stat.d.ts +0 -4
  227. package/dist/path-stat.d.ts.map +0 -1
  228. package/dist/path-stat.js +0 -15
  229. package/dist/windows-permissions-native.d.ts +0 -9
  230. package/dist/windows-permissions-native.d.ts.map +0 -1
  231. package/dist/windows-permissions-native.js +0 -31
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
4
5
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
5
6
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
6
7
  import { FsSafeError } from "./errors.js";
@@ -8,6 +9,7 @@ import { sameFileIdentity } from "./file-identity.js";
8
9
  import { resolveHomeRelativePath } from "./home-dir.js";
9
10
  import { openPinnedFileSync } from "./pinned-open.js";
10
11
  import { runPinnedWriteHelper } from "./pinned-write.js";
12
+ import { serializePathWrite } from "./write-queue.js";
11
13
  export const DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024;
12
14
  export const PRIVATE_SECRET_DIR_MODE = 0o700;
13
15
  export const PRIVATE_SECRET_FILE_MODE = 0o600;
@@ -89,7 +91,6 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
89
91
  filePath: resolvedPath,
90
92
  rejectPathSymlink: options.rejectSymlink,
91
93
  rejectHardlinks: options.rejectHardlinks !== false,
92
- maxBytes,
93
94
  });
94
95
  if (!opened.ok) {
95
96
  const error = normalizeSecretReadError(opened.reason === "validation" ? new Error("security validation failed") : opened.error);
@@ -101,6 +102,15 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
101
102
  };
102
103
  }
103
104
  try {
105
+ if (!sameFileIdentity(previewStat, opened.stat)) {
106
+ const error = new FsSafeError("path-mismatch", "security validation failed");
107
+ return {
108
+ ok: false,
109
+ code: "path-mismatch",
110
+ error,
111
+ message: `Failed to read ${label} file at ${resolvedPath}: ${String(error)}`,
112
+ };
113
+ }
104
114
  const raw = readFileDescriptorBoundedSync(opened.fd, maxBytes).toString("utf8");
105
115
  const secret = raw.trim();
106
116
  if (!secret) {
@@ -116,9 +126,7 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
116
126
  const normalized = normalizeSecretReadError(error);
117
127
  return {
118
128
  ok: false,
119
- code: error instanceof FsSafeError && error.code === "too-large"
120
- ? "too-large"
121
- : "invalid-path",
129
+ code: error instanceof FsSafeError ? error.code : "read-failed",
122
130
  error: normalized,
123
131
  message: `Failed to read ${label} file at ${resolvedPath}: ${String(normalized)}`,
124
132
  };
@@ -257,6 +265,15 @@ async function ensurePrivateDirectory(rootDir, targetDir, mode) {
257
265
  }
258
266
  return { rootGuard, targetReal: await fsp.realpath(resolvedTarget) };
259
267
  }
268
+ async function secretFileWriteQueueKey(filePath) {
269
+ try {
270
+ return await canonicalPathFromExistingAncestor(filePath);
271
+ }
272
+ catch {
273
+ // Keep validation and its public error shape owned by the write path below.
274
+ return path.resolve(filePath);
275
+ }
276
+ }
260
277
  async function materializeSecretFileAtomic(params, createOnly) {
261
278
  const mode = params.mode ?? PRIVATE_SECRET_FILE_MODE;
262
279
  const dirMode = params.dirMode ?? PRIVATE_SECRET_DIR_MODE;
@@ -304,11 +321,17 @@ async function materializeSecretFileAtomic(params, createOnly) {
304
321
  await enforcePrivateFileIdentityAndMode(finalFilePath, identity, mode);
305
322
  }
306
323
  export async function writeSecretFileAtomic(params) {
307
- await materializeSecretFileAtomic(params, false);
324
+ const canonicalPath = await secretFileWriteQueueKey(params.filePath);
325
+ await serializePathWrite(canonicalPath, async () => {
326
+ await materializeSecretFileAtomic(params, false);
327
+ });
308
328
  }
309
329
  export async function createSecretFileAtomic(params) {
310
330
  try {
311
- await materializeSecretFileAtomic(params, true);
331
+ const canonicalPath = await secretFileWriteQueueKey(params.filePath);
332
+ await serializePathWrite(canonicalPath, async () => {
333
+ await materializeSecretFileAtomic(params, true);
334
+ });
312
335
  }
313
336
  catch (error) {
314
337
  if ((error instanceof FsSafeError && error.code === "already-exists") ||
@@ -1 +1 @@
1
- {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AAqF1B,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7B"}
1
+ {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AA0F1B,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7B"}
@@ -68,7 +68,11 @@ async function readSecretFileOutcome(filePath, label, options) {
68
68
  const normalized = error instanceof Error ? error : new Error(String(error));
69
69
  return {
70
70
  ok: false,
71
- code: error instanceof FsSafeError ? error.code : pathErrorCode(error) === "not-found" ? "not-found" : "path-mismatch",
71
+ code: error instanceof FsSafeError
72
+ ? error.code
73
+ : pathErrorCode(error) === "not-found"
74
+ ? "not-found"
75
+ : "read-failed",
72
76
  error: normalized,
73
77
  message: `Failed to read ${label} file at ${resolvedPath}: ${String(normalized)}`,
74
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"secure-temp-dir.d.ts","sourceRoot":"","sources":["../src/secure-temp-dir.ts"],"names":[],"mappings":"AAMA,KAAK,aAAa,GAAG;IACnB,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAWF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,CA0JnF"}
1
+ {"version":3,"file":"secure-temp-dir.d.ts","sourceRoot":"","sources":["../src/secure-temp-dir.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG;IACnB,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAWF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,CA8JnF"}
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import { tmpdir as getOsTmpDir } from "node:os";
3
3
  import path from "node:path";
4
+ import { assertSafePathSegment } from "./safe-path-segment.js";
4
5
  function isNodeErrorWithCode(err, code) {
5
6
  return (typeof err === "object" &&
6
7
  err !== null &&
@@ -8,6 +9,10 @@ function isNodeErrorWithCode(err, code) {
8
9
  err.code === code);
9
10
  }
10
11
  export function resolveSecureTempRoot(options) {
12
+ const fallbackPrefix = assertSafePathSegment(options.fallbackPrefix, {
13
+ allowDotPrefix: true,
14
+ label: "fallback temp prefix",
15
+ });
11
16
  const TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK;
12
17
  const accessSync = options.accessSync ?? fs.accessSync;
13
18
  const chmodSync = options.chmodSync ?? fs.chmodSync;
@@ -42,7 +47,7 @@ export function resolveSecureTempRoot(options) {
42
47
  };
43
48
  const fallback = () => {
44
49
  const base = tmpdir();
45
- const suffix = uid === undefined ? options.fallbackPrefix : `${options.fallbackPrefix}-${uid}`;
50
+ const suffix = uid === undefined ? fallbackPrefix : `${fallbackPrefix}-${uid}`;
46
51
  const joiner = platform === "win32" ? path.win32.join : path.join;
47
52
  return joiner(base, suffix);
48
53
  };
@@ -0,0 +1,2 @@
1
+ export declare function shortPath(value: string): string;
2
+ //# sourceMappingURL=short-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"short-path.d.ts","sourceRoot":"","sources":["../src/short-path.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C"}
@@ -0,0 +1,7 @@
1
+ import os from "node:os";
2
+ import { formatErrorDetail } from "./error-detail.js";
3
+ export function shortPath(value) {
4
+ const home = os.homedir();
5
+ const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
6
+ return formatErrorDetail(shortened);
7
+ }
@@ -0,0 +1,30 @@
1
+ import { type NativeFileHandle } from "./native-operations.js";
2
+ import type { Root } from "./root-impl.js";
3
+ import { type SidecarLockSnapshot } from "./sidecar-lock-reclaim.js";
4
+ import type { SidecarLockAcquireOptions, SidecarLockHandle } from "./sidecar-lock-types.js";
5
+ type SidecarFileHandle = Pick<NativeFileHandle, "close" | "stat" | "writeFile">;
6
+ export type HeldSidecarLock = {
7
+ refCount: number;
8
+ reentrantOwner?: string;
9
+ handle: SidecarFileHandle;
10
+ lockPath: string;
11
+ snapshot: SidecarLockSnapshot;
12
+ acquiredAt: number;
13
+ metadata: Record<string, unknown>;
14
+ releasePromise?: Promise<void>;
15
+ lockRoot?: Root;
16
+ parsePayload?: (raw: string) => unknown;
17
+ compromiseTimer?: NodeJS.Timeout;
18
+ };
19
+ type SidecarLockAcquisitionContext = {
20
+ held: Map<string, HeldSidecarLock>;
21
+ reclaimGuards: Set<string>;
22
+ ensureExitCleanupRegistered(): void;
23
+ handleForHeldLock(normalizedTargetPath: string, held: HeldSidecarLock): SidecarLockHandle;
24
+ releaseHeldLock(normalizedTargetPath: string, held: HeldSidecarLock, options?: {
25
+ force?: boolean;
26
+ }): Promise<boolean>;
27
+ };
28
+ export declare function acquireSidecarLock<TPayload extends Record<string, unknown>>(options: SidecarLockAcquireOptions<TPayload>, context: SidecarLockAcquisitionContext): Promise<SidecarLockHandle>;
29
+ export {};
30
+ //# sourceMappingURL=sidecar-lock-acquire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAGA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAO3C,OAAO,EAUL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAaF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAmP5B"}
@@ -0,0 +1,260 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { createNativeExclusiveFile } from "./native-operations.js";
5
+ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, } from "./sidecar-lock-policy.js";
6
+ import { readSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
7
+ async function resolveNormalizedTargetPath(targetPath) {
8
+ const resolved = path.resolve(targetPath);
9
+ const dir = path.dirname(resolved);
10
+ await fs.mkdir(dir, { recursive: true });
11
+ try {
12
+ return path.join(await fs.realpath(dir), path.basename(resolved));
13
+ }
14
+ catch {
15
+ return resolved;
16
+ }
17
+ }
18
+ export async function acquireSidecarLock(options, context) {
19
+ context.ensureExitCleanupRegistered();
20
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
21
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
22
+ const held = context.held.get(normalizedTargetPath);
23
+ if (held &&
24
+ options.reentrantOwner !== undefined &&
25
+ held.reentrantOwner !== undefined &&
26
+ options.reentrantOwner === held.reentrantOwner) {
27
+ held.refCount += 1;
28
+ return context.handleForHeldLock(normalizedTargetPath, held);
29
+ }
30
+ const startedAt = Date.now();
31
+ const retry = options.retry ?? {};
32
+ const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
33
+ const reclaimGuardPath = `${lockPath}.reclaim`;
34
+ let ownsReclaimGuard = false;
35
+ let attempt = 0;
36
+ // Bounded so a genuine denial still surfaces as EPERM, not a lock timeout.
37
+ let transientDenials = 0;
38
+ const withinDenialBudget = () => ++transientDenials <= maxTransientLockDenials;
39
+ const waitForRetry = async () => {
40
+ const elapsed = Date.now() - startedAt;
41
+ if ((options.timeoutMs !== undefined &&
42
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
43
+ elapsed >= options.timeoutMs) ||
44
+ (maxRetries !== undefined && attempt >= maxRetries)) {
45
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
46
+ code: "file_lock_timeout",
47
+ lockPath,
48
+ normalizedTargetPath,
49
+ });
50
+ }
51
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
52
+ ? Number.POSITIVE_INFINITY
53
+ : Math.max(0, options.timeoutMs - elapsed);
54
+ const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
55
+ attempt += 1;
56
+ await new Promise((resolve) => setTimeout(resolve, delay));
57
+ };
58
+ // Waiting can fail on the caller's own retry or deadline limits. Classifying
59
+ // a denial as contention must not cost them the original diagnosis, so hand
60
+ // the denial back when no further attempt can be scheduled.
61
+ const retryOrRethrowDenial = async (denial) => {
62
+ try {
63
+ await waitForRetry();
64
+ }
65
+ catch (waitError) {
66
+ if (waitError.code === "file_lock_timeout")
67
+ throw denial;
68
+ throw waitError;
69
+ }
70
+ };
71
+ try {
72
+ while (true) {
73
+ if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
74
+ await waitForRetry();
75
+ continue;
76
+ }
77
+ let handle = null;
78
+ let createdSnapshot = null;
79
+ let lockFileCreateDenied = false;
80
+ try {
81
+ const payload = await options.payload();
82
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
83
+ if (options.lockRoot) {
84
+ const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
85
+ try {
86
+ await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
87
+ }
88
+ catch (error) {
89
+ if (error instanceof FsSafeError && error.code === "already-exists") {
90
+ throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
91
+ }
92
+ throw error;
93
+ }
94
+ createdSnapshot = { raw, payload, ownershipToken };
95
+ handle = (await options.lockRoot.open(relativeLockPath)).handle;
96
+ }
97
+ else {
98
+ try {
99
+ handle =
100
+ (await createNativeExclusiveFile(lockPath, 0o600)) ??
101
+ (await fs.open(lockPath, "wx"));
102
+ }
103
+ catch (createError) {
104
+ lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
105
+ throw createError;
106
+ }
107
+ await handle.writeFile(raw, "utf8");
108
+ }
109
+ const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
110
+ const createdHeld = {
111
+ refCount: 1,
112
+ reentrantOwner: options.reentrantOwner,
113
+ handle,
114
+ lockPath,
115
+ snapshot,
116
+ acquiredAt: Date.now(),
117
+ metadata: options.metadata ?? {},
118
+ lockRoot: options.lockRoot,
119
+ parsePayload: options.parsePayload,
120
+ };
121
+ context.held.set(normalizedTargetPath, createdHeld);
122
+ if (ownsReclaimGuard) {
123
+ try {
124
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
125
+ ownsReclaimGuard = false;
126
+ }
127
+ catch (err) {
128
+ await context.releaseHeldLock(normalizedTargetPath, createdHeld, { force: true });
129
+ throw err;
130
+ }
131
+ }
132
+ const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
133
+ const interval = options.compromiseCheckIntervalMs;
134
+ if (options.onCompromised && interval !== undefined && interval > 0) {
135
+ createdHeld.compromiseTimer = setInterval(() => {
136
+ void returnedHandle.verifyStillHeld().then((stillHeld) => {
137
+ if (!stillHeld && createdHeld.compromiseTimer) {
138
+ clearInterval(createdHeld.compromiseTimer);
139
+ createdHeld.compromiseTimer = undefined;
140
+ options.onCompromised?.({ lockPath, normalizedTargetPath });
141
+ }
142
+ });
143
+ }, interval);
144
+ createdHeld.compromiseTimer.unref();
145
+ }
146
+ return returnedHandle;
147
+ }
148
+ catch (err) {
149
+ if (handle) {
150
+ const failedSnapshot = { payload: null };
151
+ try {
152
+ failedSnapshot.stat = await handle.stat();
153
+ }
154
+ catch {
155
+ // Best-effort cleanup of a failed exclusive create.
156
+ }
157
+ const current = context.held.get(normalizedTargetPath);
158
+ if (current?.handle === handle) {
159
+ context.held.delete(normalizedTargetPath);
160
+ }
161
+ await handle.close().catch(() => undefined);
162
+ // The file may be empty or partial JSON, so remove by the identity
163
+ // captured from our exclusive handle rather than by pathname alone.
164
+ await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
165
+ lockRoot: options.lockRoot,
166
+ parsePayload: options.parsePayload,
167
+ });
168
+ }
169
+ else if (createdSnapshot) {
170
+ await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
171
+ lockRoot: options.lockRoot,
172
+ parsePayload: options.parsePayload,
173
+ });
174
+ }
175
+ if (lockFileCreateDenied && withinDenialBudget()) {
176
+ await retryOrRethrowDenial(err);
177
+ continue;
178
+ }
179
+ if (err.code !== "EEXIST") {
180
+ throw err;
181
+ }
182
+ if (ownsReclaimGuard) {
183
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
184
+ ownsReclaimGuard = false;
185
+ continue;
186
+ }
187
+ const nowMs = Date.now();
188
+ let snapshot;
189
+ try {
190
+ snapshot = await readSidecarLockSnapshot(lockPath, {
191
+ lockRoot: options.lockRoot,
192
+ parsePayload: options.parsePayload,
193
+ rejectNonFile: true,
194
+ });
195
+ }
196
+ catch (readErr) {
197
+ if (!isTransientLockFileDenial(readErr, lockPath) || !withinDenialBudget())
198
+ throw readErr;
199
+ await retryOrRethrowDenial(readErr);
200
+ continue;
201
+ }
202
+ if (!snapshot) {
203
+ continue;
204
+ }
205
+ if (context.held.has(normalizedTargetPath)) {
206
+ await waitForRetry();
207
+ continue;
208
+ }
209
+ const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
210
+ if (await shouldReclaim({
211
+ lockPath,
212
+ normalizedTargetPath,
213
+ payload: snapshot?.payload ?? null,
214
+ staleMs: options.staleMs,
215
+ nowMs,
216
+ heldByThisProcess: context.held.has(normalizedTargetPath),
217
+ })) {
218
+ if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
219
+ lockRoot: options.lockRoot,
220
+ parsePayload: options.parsePayload,
221
+ }))) {
222
+ continue;
223
+ }
224
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
225
+ if (staleRecovery === "remove-if-unchanged") {
226
+ if (!(await tryAcquireSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath))) {
227
+ await waitForRetry();
228
+ continue;
229
+ }
230
+ ownsReclaimGuard = true;
231
+ const removal = await removeStaleSidecarLockIfAllowed({
232
+ lockPath,
233
+ normalizedTargetPath,
234
+ snapshot,
235
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
236
+ lockRoot: options.lockRoot,
237
+ parsePayload: options.parsePayload,
238
+ });
239
+ if (removal === "removed" || removal === "changed") {
240
+ continue;
241
+ }
242
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
243
+ ownsReclaimGuard = false;
244
+ }
245
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
246
+ code: "file_lock_stale",
247
+ lockPath,
248
+ normalizedTargetPath,
249
+ });
250
+ }
251
+ await waitForRetry();
252
+ }
253
+ }
254
+ }
255
+ finally {
256
+ if (ownsReclaimGuard) {
257
+ await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
258
+ }
259
+ }
260
+ }
@@ -1,6 +1,9 @@
1
1
  import type { SidecarLockRetryOptions } from "./sidecar-lock-types.js";
2
2
  export declare function computeSidecarLockDelayMs(retry: SidecarLockRetryOptions, attempt: number): number;
3
+ export declare const maxTransientLockDenials = 8;
4
+ export declare function isTransientLockFileDenial(error: unknown, lockPath: string): boolean;
3
5
  export declare function sidecarLockPayloadIsStale(payload: unknown, staleMs: number, nowMs: number): boolean;
6
+ export declare function sidecarLockPayloadCreatedAtMs(payload: unknown): number | null;
4
7
  export declare function defaultSidecarLockShouldReclaim(params: {
5
8
  lockPath: string;
6
9
  payload: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEvE,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAOjG;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAUT;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnB"}
1
+ {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEvE,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAOjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAGT;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
@@ -7,7 +7,23 @@ export function computeSidecarLockDelayMs(retry, attempt) {
7
7
  const jitter = retry.randomize ? 1 + Math.random() : 1;
8
8
  return Math.min(maxTimeout, Math.round(base * jitter));
9
9
  }
10
+ // Windows denies access to a lock file while a just-unlinked directory entry
11
+ // is still being torn down, so a contended acquire sees EPERM on a name that is
12
+ // already gone -- both when creating it exclusively and when reading the
13
+ // holder's snapshot. The next attempt succeeds, so this is contention rather
14
+ // than a permission failure. The error must name the lock file itself: the
15
+ // exclusive-create helper opens the parent directory first, and a denial from
16
+ // that setup step carries no teardown evidence and has to reach the caller.
17
+ export const maxTransientLockDenials = 8;
18
+ export function isTransientLockFileDenial(error, lockPath) {
19
+ const denial = error;
20
+ return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
21
+ }
10
22
  export function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
23
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
24
+ return createdAtMs !== null && nowMs - createdAtMs > staleMs;
25
+ }
26
+ export function sidecarLockPayloadCreatedAtMs(payload) {
11
27
  const createdAt = payload &&
12
28
  typeof payload === "object" &&
13
29
  "createdAt" in payload &&
@@ -15,11 +31,12 @@ export function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
15
31
  ? payload.createdAt
16
32
  : "";
17
33
  const createdAtMs = Date.parse(createdAt);
18
- return Number.isFinite(createdAtMs) && nowMs - createdAtMs > staleMs;
34
+ return Number.isFinite(createdAtMs) ? createdAtMs : null;
19
35
  }
20
36
  export async function defaultSidecarLockShouldReclaim(params) {
21
- if (sidecarLockPayloadIsStale(params.payload, params.staleMs, params.nowMs))
22
- return true;
37
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
38
+ if (createdAtMs !== null)
39
+ return params.nowMs - createdAtMs > params.staleMs;
23
40
  try {
24
41
  return params.nowMs - (await fs.stat(params.lockPath)).mtimeMs > params.staleMs;
25
42
  }
@@ -22,8 +22,12 @@ export declare function parseSidecarLockPayload(raw: string, parser?: (raw: stri
22
22
  export declare function readSidecarLockSnapshot(lockPath: string, options?: {
23
23
  lockRoot?: Root;
24
24
  parsePayload?: (raw: string) => unknown;
25
+ rejectNonFile?: boolean;
26
+ allowDescriptorIdentityDrift?: boolean;
25
27
  }): Promise<SidecarLockSnapshot | null>;
26
- export declare function readSidecarLockSnapshotSync(lockPath: string, parsePayload?: (raw: string) => unknown): SidecarLockSnapshot | null;
28
+ export declare function readSidecarLockSnapshotSync(lockPath: string, parsePayload?: (raw: string) => unknown, options?: {
29
+ rejectNonFile?: boolean;
30
+ }): SidecarLockSnapshot | null;
27
31
  export declare function removeSidecarLockIfUnchangedSync(lockPath: string, observed: SidecarLockSnapshot): boolean;
28
32
  export declare function sidecarLockSnapshotMatches(current: SidecarLockSnapshot, observed: SidecarLockSnapshot): boolean;
29
33
  export declare function removeSidecarLockIfUnchanged(lockPath: string, observed: SidecarLockSnapshot | null, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-reclaim.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-reclaim.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sidecar-lock-reclaim.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-reclaim.ts"],"names":[],"mappings":"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"}