@openclaw/fs-safe 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +38 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -1
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-merge.d.ts.map +1 -1
  10. package/dist/archive-merge.js +113 -46
  11. package/dist/archive-parser.wasm +0 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +10 -11
  14. package/dist/archive-tar-stream.d.ts +3 -0
  15. package/dist/archive-tar-stream.d.ts.map +1 -1
  16. package/dist/archive-tar-stream.js +56 -37
  17. package/dist/archive-tar-wasm.d.ts +16 -4
  18. package/dist/archive-tar-wasm.d.ts.map +1 -1
  19. package/dist/archive-tar-wasm.js +134 -34
  20. package/dist/archive-zip-directory.d.ts +4 -0
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +2 -0
  23. package/dist/archive-zip-entry.d.ts +6 -2
  24. package/dist/archive-zip-entry.d.ts.map +1 -1
  25. package/dist/archive-zip-entry.js +23 -8
  26. package/dist/archive-zip-integrity.d.ts.map +1 -1
  27. package/dist/archive-zip-integrity.js +3 -4
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +107 -31
  30. package/dist/archive-zip-names.d.ts +1 -0
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +6 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +11 -11
  35. package/dist/bounded-read-stream.d.ts +0 -1
  36. package/dist/bounded-read-stream.d.ts.map +1 -1
  37. package/dist/bounded-read-stream.js +0 -6
  38. package/dist/clone-metadata.d.ts +1 -0
  39. package/dist/clone-metadata.d.ts.map +1 -1
  40. package/dist/clone-metadata.js +6 -2
  41. package/dist/copy-publication.d.ts +6 -0
  42. package/dist/copy-publication.d.ts.map +1 -1
  43. package/dist/copy-publication.js +3 -0
  44. package/dist/copy-tree-portable.d.ts.map +1 -1
  45. package/dist/copy-tree-portable.js +44 -24
  46. package/dist/copy.d.ts.map +1 -1
  47. package/dist/copy.js +29 -11
  48. package/dist/create-directory.d.ts +20 -0
  49. package/dist/create-directory.d.ts.map +1 -0
  50. package/dist/create-directory.js +130 -0
  51. package/dist/create-file-async.d.ts +7 -0
  52. package/dist/create-file-async.d.ts.map +1 -0
  53. package/dist/create-file-async.js +121 -0
  54. package/dist/create-file.d.ts +8 -0
  55. package/dist/create-file.d.ts.map +1 -0
  56. package/dist/create-file.js +190 -0
  57. package/dist/create-owned-file.d.ts +8 -0
  58. package/dist/create-owned-file.d.ts.map +1 -0
  59. package/dist/create-owned-file.js +16 -0
  60. package/dist/create.d.ts +4 -0
  61. package/dist/create.d.ts.map +1 -0
  62. package/dist/create.js +2 -0
  63. package/dist/creation-darwin.d.ts +7 -0
  64. package/dist/creation-darwin.d.ts.map +1 -0
  65. package/dist/creation-darwin.js +79 -0
  66. package/dist/creation-file-state.d.ts +19 -0
  67. package/dist/creation-file-state.d.ts.map +1 -0
  68. package/dist/creation-file-state.js +118 -0
  69. package/dist/creation-path.d.ts +21 -0
  70. package/dist/creation-path.d.ts.map +1 -0
  71. package/dist/creation-path.js +71 -0
  72. package/dist/creation-permissions.d.ts +19 -0
  73. package/dist/creation-permissions.d.ts.map +1 -0
  74. package/dist/creation-permissions.js +125 -0
  75. package/dist/directory-durability.d.ts +1 -1
  76. package/dist/directory-durability.d.ts.map +1 -1
  77. package/dist/directory-durability.js +22 -80
  78. package/dist/directory-guard.d.ts +3 -0
  79. package/dist/directory-guard.d.ts.map +1 -1
  80. package/dist/directory-mode-node.d.ts +2 -0
  81. package/dist/directory-mode-node.d.ts.map +1 -1
  82. package/dist/directory-mode-node.js +8 -0
  83. package/dist/directory-mode-owner.js +5 -5
  84. package/dist/directory-receipt.d.ts +24 -0
  85. package/dist/directory-receipt.d.ts.map +1 -0
  86. package/dist/directory-receipt.js +127 -0
  87. package/dist/file-cleanup.d.ts +19 -0
  88. package/dist/file-cleanup.d.ts.map +1 -0
  89. package/dist/file-cleanup.js +78 -0
  90. package/dist/file-handle-transfer.d.ts +2 -0
  91. package/dist/file-handle-transfer.d.ts.map +1 -1
  92. package/dist/file-handle-transfer.js +57 -2
  93. package/dist/file-identity.d.ts.map +1 -1
  94. package/dist/file-identity.js +18 -4
  95. package/dist/file-lock-sync-admission.d.ts +19 -0
  96. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  97. package/dist/file-lock-sync-admission.js +93 -0
  98. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  99. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  100. package/dist/file-lock-sync-root-acquire.js +370 -0
  101. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  102. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  103. package/dist/file-lock-sync-root-arbitration.js +66 -0
  104. package/dist/file-lock-sync-root-held.d.ts +34 -0
  105. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  106. package/dist/file-lock-sync-root-held.js +393 -0
  107. package/dist/file-lock-sync-root-io.d.ts +44 -0
  108. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  109. package/dist/file-lock-sync-root-io.js +209 -0
  110. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  111. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  112. package/dist/file-lock-sync-root-mutation.js +277 -0
  113. package/dist/file-lock-sync-root-options.d.ts +20 -0
  114. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  115. package/dist/file-lock-sync-root-options.js +58 -0
  116. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  117. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  118. package/dist/file-lock-sync-root-registration.js +90 -0
  119. package/dist/file-lock-sync-root.d.ts +36 -0
  120. package/dist/file-lock-sync-root.d.ts.map +1 -0
  121. package/dist/file-lock-sync-root.js +361 -0
  122. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  123. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  124. package/dist/file-lock-sync-stale-admission.js +205 -0
  125. package/dist/file-lock-sync.d.ts.map +1 -1
  126. package/dist/file-lock-sync.js +245 -205
  127. package/dist/file-observation.d.ts +1 -1
  128. package/dist/file-observation.d.ts.map +1 -1
  129. package/dist/file-store-boundary.d.ts +2 -6
  130. package/dist/file-store-boundary.d.ts.map +1 -1
  131. package/dist/file-store-boundary.js +3 -9
  132. package/dist/file-store-prune.d.ts.map +1 -1
  133. package/dist/file-store-prune.js +5 -1
  134. package/dist/file-store-sync-write.d.ts.map +1 -1
  135. package/dist/file-store-sync-write.js +5 -8
  136. package/dist/file-store.d.ts.map +1 -1
  137. package/dist/file-store.js +47 -12
  138. package/dist/guarded-mkdir.d.ts +1 -0
  139. package/dist/guarded-mkdir.d.ts.map +1 -1
  140. package/dist/guarded-mkdir.js +36 -7
  141. package/dist/json-document-store.d.ts.map +1 -1
  142. package/dist/json-document-store.js +22 -15
  143. package/dist/json-durable-queue-ownership.d.ts +0 -1
  144. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  145. package/dist/json-durable-queue-ownership.js +0 -6
  146. package/dist/move-path.js +1 -1
  147. package/dist/native-binding.d.ts +13 -1
  148. package/dist/native-binding.d.ts.map +1 -1
  149. package/dist/native-fallback-warning.d.ts +4 -0
  150. package/dist/native-fallback-warning.d.ts.map +1 -0
  151. package/dist/native-fallback-warning.js +11 -0
  152. package/dist/native-operations.d.ts +0 -2
  153. package/dist/native-operations.d.ts.map +1 -1
  154. package/dist/native-operations.js +0 -24
  155. package/dist/native-parent-admission.d.ts +5 -2
  156. package/dist/native-parent-admission.d.ts.map +1 -1
  157. package/dist/native-parent-admission.js +27 -7
  158. package/dist/native-pinned-write-windows.d.ts +1 -1
  159. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  160. package/dist/native-pinned-write-windows.js +174 -29
  161. package/dist/native-pinned-write.d.ts.map +1 -1
  162. package/dist/native-pinned-write.js +26 -14
  163. package/dist/native-policy-parent-windows.d.ts +14 -0
  164. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  165. package/dist/native-policy-parent-windows.js +209 -0
  166. package/dist/native-rename-outcome.d.ts +4 -0
  167. package/dist/native-rename-outcome.d.ts.map +1 -0
  168. package/dist/native-rename-outcome.js +8 -0
  169. package/dist/native-staged-file.d.ts +3 -2
  170. package/dist/native-staged-file.d.ts.map +1 -1
  171. package/dist/native-staged-file.js +121 -72
  172. package/dist/output.d.ts.map +1 -1
  173. package/dist/output.js +12 -8
  174. package/dist/owner-dacl.d.ts.map +1 -1
  175. package/dist/owner-dacl.js +10 -4
  176. package/dist/path-prefix.d.ts.map +1 -1
  177. package/dist/path-prefix.js +30 -8
  178. package/dist/path-suffix-aliases.d.ts +2 -0
  179. package/dist/path-suffix-aliases.d.ts.map +1 -1
  180. package/dist/path-suffix-aliases.js +25 -17
  181. package/dist/permission-exec.d.ts +2 -0
  182. package/dist/permission-exec.d.ts.map +1 -1
  183. package/dist/permission-exec.js +150 -21
  184. package/dist/permissions-windows.js +1 -1
  185. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  186. package/dist/pinned-mutation-admission.js +10 -5
  187. package/dist/pinned-mutation-observation.d.ts +0 -1
  188. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  189. package/dist/pinned-mutation-observation.js +0 -19
  190. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  191. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  192. package/dist/pinned-mutation-shared-route.js +1 -1
  193. package/dist/pinned-write-input.d.ts +4 -0
  194. package/dist/pinned-write-input.d.ts.map +1 -0
  195. package/dist/pinned-write-input.js +25 -0
  196. package/dist/pinned-write-mode.d.ts +5 -0
  197. package/dist/pinned-write-mode.d.ts.map +1 -0
  198. package/dist/pinned-write-mode.js +24 -0
  199. package/dist/pinned-write-staged.d.ts +6 -0
  200. package/dist/pinned-write-staged.d.ts.map +1 -0
  201. package/dist/pinned-write-staged.js +187 -0
  202. package/dist/pinned-write-types.d.ts +5 -0
  203. package/dist/pinned-write-types.d.ts.map +1 -1
  204. package/dist/pinned-write.d.ts.map +1 -1
  205. package/dist/pinned-write.js +35 -145
  206. package/dist/private-directory.d.ts.map +1 -1
  207. package/dist/private-directory.js +18 -4
  208. package/dist/private-producer-handoff-sync.d.ts +14 -0
  209. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  210. package/dist/private-producer-handoff-sync.js +114 -0
  211. package/dist/private-producer-handoff.d.ts +22 -4
  212. package/dist/private-producer-handoff.d.ts.map +1 -1
  213. package/dist/private-producer-handoff.js +140 -77
  214. package/dist/private-temp-workspace.d.ts.map +1 -1
  215. package/dist/private-temp-workspace.js +75 -121
  216. package/dist/publish-copy-stage.d.ts +2 -1
  217. package/dist/publish-copy-stage.d.ts.map +1 -1
  218. package/dist/publish-copy-stage.js +16 -7
  219. package/dist/publish-file.d.ts.map +1 -1
  220. package/dist/publish-file.js +2 -2
  221. package/dist/regular-file.d.ts.map +1 -1
  222. package/dist/regular-file.js +1 -15
  223. package/dist/replace-directory.d.ts.map +1 -1
  224. package/dist/replace-directory.js +256 -18
  225. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  226. package/dist/replace-file-copy-fallback.js +62 -70
  227. package/dist/replace-file-copy-source.d.ts.map +1 -1
  228. package/dist/replace-file-copy-source.js +10 -12
  229. package/dist/replace-file-temp-owner.d.ts +5 -9
  230. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  231. package/dist/replace-file-temp-owner.js +56 -72
  232. package/dist/replace-file.js +6 -6
  233. package/dist/retained-directory-replacement.d.ts +26 -0
  234. package/dist/retained-directory-replacement.d.ts.map +1 -0
  235. package/dist/retained-directory-replacement.js +193 -0
  236. package/dist/root-boundary.d.ts +1 -0
  237. package/dist/root-boundary.d.ts.map +1 -1
  238. package/dist/root-boundary.js +4 -0
  239. package/dist/root-context.d.ts +0 -8
  240. package/dist/root-context.d.ts.map +1 -1
  241. package/dist/root-context.js +0 -3
  242. package/dist/root-create-input.d.ts +8 -1
  243. package/dist/root-create-input.d.ts.map +1 -1
  244. package/dist/root-create-input.js +17 -4
  245. package/dist/root-directory-creation.d.ts +3 -3
  246. package/dist/root-directory-creation.d.ts.map +1 -1
  247. package/dist/root-directory-creation.js +15 -3
  248. package/dist/root-directory-list.d.ts +1 -0
  249. package/dist/root-directory-list.d.ts.map +1 -1
  250. package/dist/root-directory-list.js +1 -0
  251. package/dist/root-impl.d.ts.map +1 -1
  252. package/dist/root-impl.js +46 -17
  253. package/dist/root-move-noreplace.d.ts.map +1 -1
  254. package/dist/root-move-noreplace.js +24 -15
  255. package/dist/root-options.d.ts +12 -4
  256. package/dist/root-options.d.ts.map +1 -1
  257. package/dist/root-path-errors.d.ts +1 -0
  258. package/dist/root-path-errors.d.ts.map +1 -1
  259. package/dist/root-path-errors.js +11 -2
  260. package/dist/root-path-existing.d.ts.map +1 -1
  261. package/dist/root-path-existing.js +11 -35
  262. package/dist/root-path-stat.d.ts.map +1 -1
  263. package/dist/root-path-stat.js +59 -7
  264. package/dist/root-path.js +1 -13
  265. package/dist/root-remove.d.ts +1 -0
  266. package/dist/root-remove.d.ts.map +1 -1
  267. package/dist/root-remove.js +4 -0
  268. package/dist/root-walk.d.ts +1 -1
  269. package/dist/root-walk.d.ts.map +1 -1
  270. package/dist/root-walk.js +17 -2
  271. package/dist/root-write-admission.d.ts +0 -2
  272. package/dist/root-write-admission.d.ts.map +1 -1
  273. package/dist/root-write-admission.js +1 -15
  274. package/dist/root-write-complete-parent.d.ts.map +1 -1
  275. package/dist/root-write-complete-parent.js +7 -23
  276. package/dist/root-write-publication.js +1 -1
  277. package/dist/root-write-verification.d.ts.map +1 -1
  278. package/dist/root-write-verification.js +29 -42
  279. package/dist/secret-file.d.ts.map +1 -1
  280. package/dist/secret-file.js +3 -24
  281. package/dist/secret-read-async.d.ts.map +1 -1
  282. package/dist/secret-read-async.js +3 -24
  283. package/dist/secret-read-policy.d.ts +6 -2
  284. package/dist/secret-read-policy.d.ts.map +1 -1
  285. package/dist/secret-read-policy.js +26 -2
  286. package/dist/secure-file-windows.d.ts +6 -0
  287. package/dist/secure-file-windows.d.ts.map +1 -1
  288. package/dist/secure-file-windows.js +34 -117
  289. package/dist/secure-file.js +2 -2
  290. package/dist/sibling-temp.d.ts.map +1 -1
  291. package/dist/sibling-temp.js +23 -12
  292. package/dist/sidecar-lock-acquire.d.ts +2 -28
  293. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  294. package/dist/sidecar-lock-acquire.js +288 -199
  295. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  296. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  297. package/dist/sidecar-lock-admission-context.js +60 -0
  298. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  299. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  300. package/dist/sidecar-lock-admission-parser.js +113 -0
  301. package/dist/sidecar-lock-admission.d.ts +35 -0
  302. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  303. package/dist/sidecar-lock-admission.js +7 -0
  304. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  305. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  306. package/dist/sidecar-lock-reclaim.js +80 -25
  307. package/dist/sidecar-lock-root.d.ts.map +1 -1
  308. package/dist/sidecar-lock-root.js +2 -1
  309. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  310. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  311. package/dist/sidecar-lock-stale-admission.js +232 -0
  312. package/dist/sidecar-lock-target.d.ts +8 -0
  313. package/dist/sidecar-lock-target.d.ts.map +1 -0
  314. package/dist/sidecar-lock-target.js +55 -0
  315. package/dist/sidecar-lock.d.ts.map +1 -1
  316. package/dist/sidecar-lock.js +100 -16
  317. package/dist/staged-directory.d.ts.map +1 -1
  318. package/dist/staged-directory.js +6 -6
  319. package/dist/staged-file-settlement.d.ts +17 -0
  320. package/dist/staged-file-settlement.d.ts.map +1 -0
  321. package/dist/staged-file-settlement.js +57 -0
  322. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  323. package/dist/temp-workspace-descriptor.js +9 -27
  324. package/dist/temp-workspace-owner.d.ts.map +1 -1
  325. package/dist/temp-workspace-owner.js +8 -8
  326. package/dist/walk.d.ts +5 -1
  327. package/dist/walk.d.ts.map +1 -1
  328. package/dist/walk.js +19 -6
  329. package/dist/windows-owner.d.ts.map +1 -1
  330. package/dist/windows-owner.js +4 -3
  331. package/dist/windows-security-bridge.cs +336 -0
  332. package/dist/windows-security-bridge.ps1 +15 -0
  333. package/dist/windows-security-command.d.ts +26 -0
  334. package/dist/windows-security-command.d.ts.map +1 -0
  335. package/dist/windows-security-command.js +363 -0
  336. package/dist/windows-security-facts.d.ts +6 -0
  337. package/dist/windows-security-facts.d.ts.map +1 -0
  338. package/dist/windows-security-facts.js +108 -0
  339. package/docs/advanced.md +4 -2
  340. package/docs/archive.md +97 -46
  341. package/docs/atomic.md +85 -8
  342. package/docs/config.md +6 -2
  343. package/docs/contributing.md +44 -4
  344. package/docs/copy.md +37 -0
  345. package/docs/creation.md +128 -0
  346. package/docs/durability.md +24 -0
  347. package/docs/file-store.md +19 -0
  348. package/docs/install.md +31 -7
  349. package/docs/json-store.md +5 -0
  350. package/docs/migrating-to-0.5.md +15 -6
  351. package/docs/migrating-to-0.6.md +9 -4
  352. package/docs/native-helper.md +32 -12
  353. package/docs/native.md +38 -7
  354. package/docs/output.md +6 -0
  355. package/docs/path-prefix.md +10 -0
  356. package/docs/path-suffix-aliases.md +51 -6
  357. package/docs/permissions.md +50 -14
  358. package/docs/public-api.md +3 -2
  359. package/docs/root.md +56 -3
  360. package/docs/secret-file.md +11 -2
  361. package/docs/secure-file.md +9 -4
  362. package/docs/sidecar-lock.md +114 -8
  363. package/docs/staged-file.md +12 -3
  364. package/docs/temp.md +20 -3
  365. package/docs/walk.md +67 -1
  366. package/docs/writing.md +76 -6
  367. package/package.json +19 -16
  368. package/dist/darwin-acl.d.ts +0 -4
  369. package/dist/darwin-acl.d.ts.map +0 -1
  370. package/dist/darwin-acl.js +0 -24
@@ -1,132 +1,109 @@
1
1
  import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
5
3
  import { FsSafeError } from "./errors.js";
6
4
  import { fileObservation } from "./file-observation.js";
7
5
  import { readFileHandleBounded } from "./bounded-read.js";
8
- import { realpathSync } from "./realpath.js";
9
- import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
10
6
  import { openSidecarRoot } from "./sidecar-lock-root.js";
11
7
  import { createNativeExclusiveFile } from "./native-operations.js";
12
- import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockRetryOptions, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
13
- import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
8
+ import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockRetryOptions, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
9
+ import { relativeSidecarLockPath, removeSidecarLockIfUnchanged, serializeSidecarLockPayload, sidecarReclaimGuardExists, } from "./sidecar-lock-reclaim.js";
10
+ import { awaitSidecarAdmissionBoundary, conditionalSidecarLockParser, observeHeldSidecarParser, observeSidecarLockParser, runSidecarAdmissionBoundary, } from "./sidecar-lock-admission-parser.js";
11
+ import { handleStaleSidecarAdmission, } from "./sidecar-lock-stale-admission.js";
12
+ import { ancestryHasSidecarAdmission, captureSidecarAdmissionAncestry, createSidecarAdmissionController, } from "./sidecar-lock-admission-context.js";
13
+ import { sidecarLockTimeout } from "./sidecar-lock-admission.js";
14
+ import { resolveSidecarLockPaths } from "./sidecar-lock-target.js";
14
15
  import { createSuppressedError } from "./suppressed-error.js";
15
16
  import { sleep } from "./timing.js";
16
- import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
17
- function isCwdIndependentAbsolutePath(filePath) {
18
- if (!path.isAbsolute(filePath))
19
- return false;
20
- // A leading separator on Windows is rooted on the process's current drive.
21
- // Drive-qualified, UNC, and namespace roots are longer and need no cwd state.
22
- return process.platform !== "win32" || path.parse(filePath).root.length > 1;
23
- }
24
- async function resolveNormalizedTargetPath(resolved, lockRoot) {
25
- assertNoWindowsPathAlias(resolved);
26
- const dir = path.dirname(resolved);
27
- if (lockRoot) {
28
- // The target is an arbitration key, not necessarily inside the lock Root.
29
- // Leave parent creation to the Root-backed sidecar write.
30
- await lockRoot.resolve(".");
31
- const parent = await canonicalPathFromExistingAncestor(dir);
32
- await lockRoot.resolve(".");
33
- assertNoWindowsPathAlias(parent);
34
- const normalized = path.join(parent, path.basename(resolved));
35
- assertNoWindowsPathAlias(normalized);
36
- return normalized;
37
- }
38
- await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
39
- let parent;
40
- try {
41
- parent = realpathSync.native(dir);
42
- }
43
- catch {
44
- return resolved;
45
- }
46
- assertNoWindowsPathAlias(parent);
47
- const normalized = path.join(parent, path.basename(resolved));
48
- assertNoWindowsPathAlias(normalized);
49
- return normalized;
50
- }
17
+ import { sidecarExclusiveCreate } from "./root-create-input.js";
51
18
  export async function acquireSidecarLock(options, context) {
19
+ const admissionAncestry = captureSidecarAdmissionAncestry();
20
+ const retainOnExit = options.retainOnExit;
21
+ context.assertRetainOnExitSupported(retainOnExit);
52
22
  const retry = options.retry ?? {};
23
+ const timeoutMs = options.timeoutMs;
24
+ const staleMs = options.staleMs;
25
+ const compromiseCheckIntervalMs = options.compromiseCheckIntervalMs;
53
26
  validateSidecarLockRetryOptions(retry);
54
- validateSidecarLockTimeoutMs(options.timeoutMs);
55
- validateSidecarLockStaleMs(options.staleMs);
56
- validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
27
+ validateSidecarLockTimeoutMs(timeoutMs);
28
+ validateSidecarLockStaleMs(staleMs);
29
+ validateSidecarLockCompromiseCheckIntervalMs(compromiseCheckIntervalMs);
57
30
  const targetPath = options.targetPath;
58
31
  const explicitLockPath = options.lockPath;
59
- const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
60
- assertNoWindowsPathAlias(anchoredTargetPath);
61
- const resolvedTargetPath = path.resolve(anchoredTargetPath);
62
- let resolvedLockPath;
63
- if (explicitLockPath !== undefined) {
64
- const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
65
- assertNoWindowsPathAlias(anchoredLockPath);
66
- resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
67
- ? explicitLockPath
68
- : path.resolve(anchoredLockPath);
69
- }
70
- context.ensureExitCleanupRegistered();
71
32
  const lockRoot = options.lockRoot;
72
- const normalizedTargetPath = await resolveNormalizedTargetPath(resolvedTargetPath, lockRoot);
73
- const lockPath = resolvedLockPath ?? `${normalizedTargetPath}.lock`;
74
- assertNoWindowsPathAlias(lockPath);
33
+ const requestedReentrantOwner = options.reentrantOwner;
34
+ const { lockPath, normalizedTargetPath } = await resolveSidecarLockPaths(targetPath, explicitLockPath, lockRoot);
35
+ const activeDescendant = ancestryHasSidecarAdmission(admissionAncestry, context.admissions, normalizedTargetPath);
75
36
  let held = context.held.get(normalizedTargetPath);
76
37
  if (held &&
77
- options.reentrantOwner !== undefined &&
38
+ requestedReentrantOwner !== undefined &&
78
39
  held.reentrantOwner !== undefined &&
79
- options.reentrantOwner === held.reentrantOwner) {
80
- // A final release may already have decremented the count to zero and be
81
- // removing the sidecar. Do not admit a new reentrant handle until that
82
- // cleanup settles: successful cleanup requires a fresh acquisition, while
83
- // failed cleanup leaves the existing sidecar held for a retry.
40
+ requestedReentrantOwner === held.reentrantOwner) {
41
+ // Join a final release before deciding whether this completed owner remains.
42
+ // Successful cleanup requires a fresh acquisition; failed cleanup can retry.
84
43
  if (held.releasePromise) {
44
+ if (activeDescendant)
45
+ throw sidecarLockTimeout(lockPath, normalizedTargetPath);
85
46
  await held.releasePromise.catch(() => undefined);
86
47
  held = context.held.get(normalizedTargetPath);
87
48
  }
88
49
  if (held &&
89
- options.reentrantOwner !== undefined &&
50
+ requestedReentrantOwner !== undefined &&
90
51
  held.reentrantOwner !== undefined &&
91
- options.reentrantOwner === held.reentrantOwner) {
92
- held.refCount = (held.refCount ?? 1) + 1;
93
- // Retention is monotonic: any same-owner request to keep the sidecar on
94
- // exit upgrades the held lock; a later default acquisition never revokes it.
95
- if (options.retainOnExit === true) {
96
- held.retainOnExit = true;
52
+ requestedReentrantOwner === held.reentrantOwner) {
53
+ const lifecycle = context.ensureExitCleanupRegistered();
54
+ context.assertRetainOnExitSupported(retainOnExit);
55
+ if (context.held.get(normalizedTargetPath) === held && !held.releasePromise) {
56
+ held.refCount = (held.refCount ?? 1) + 1;
57
+ // Retention is monotonic: any same-owner request to keep the sidecar on
58
+ // exit upgrades the held lock; a later default acquisition never revokes it.
59
+ if (retainOnExit === true) {
60
+ held.retainOnExit = true;
61
+ }
62
+ const returnedHandle = context.handleForHeldLock(normalizedTargetPath, held);
63
+ context.armExitCleanup(lifecycle);
64
+ return returnedHandle;
97
65
  }
98
- return context.handleForHeldLock(normalizedTargetPath, held);
99
66
  }
100
67
  }
68
+ if (activeDescendant)
69
+ throw sidecarLockTimeout(lockPath, normalizedTargetPath);
70
+ const admission = createSidecarAdmissionController(admissionAncestry, context.admissions, normalizedTargetPath);
101
71
  const startedAt = Date.now();
102
72
  const reclaimGuardPath = `${lockPath}.reclaim`;
103
- let ownsReclaimGuard = false;
73
+ let reclaimGuard;
74
+ const releaseReclaimGuard = async () => {
75
+ if (!reclaimGuard)
76
+ return;
77
+ await admission.run(() => reclaimGuard.release());
78
+ reclaimGuard = undefined;
79
+ };
104
80
  let attempt = 0;
105
81
  // Bounded so a genuine denial still surfaces as EPERM, not a lock timeout.
106
82
  let transientDenials = 0;
83
+ let payloadCallback;
84
+ let metadata = undefined;
85
+ let metadataObserved = false;
86
+ let onCompromised = undefined;
87
+ let onCompromisedObserved = false;
88
+ const staleOptionsState = {};
107
89
  const withinDenialBudget = () => ++transientDenials <= maxTransientLockDenials;
108
90
  const waitForRetry = async () => {
91
+ admission.release();
109
92
  const elapsed = Date.now() - startedAt;
110
- if ((options.timeoutMs !== undefined &&
111
- options.timeoutMs !== Number.POSITIVE_INFINITY &&
112
- elapsed >= options.timeoutMs) ||
93
+ if ((timeoutMs !== undefined &&
94
+ timeoutMs !== Number.POSITIVE_INFINITY &&
95
+ elapsed >= timeoutMs) ||
113
96
  (retry.retries !== undefined && attempt >= retry.retries)) {
114
- throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
115
- code: "file_lock_timeout",
116
- lockPath,
117
- normalizedTargetPath,
118
- });
97
+ throw sidecarLockTimeout(lockPath, normalizedTargetPath);
119
98
  }
120
- const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
99
+ const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
121
100
  ? Number.POSITIVE_INFINITY
122
- : Math.max(0, options.timeoutMs - elapsed);
101
+ : Math.max(0, timeoutMs - elapsed);
123
102
  const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
124
103
  attempt += 1;
125
104
  await sleep(delay);
126
105
  };
127
- // Waiting can fail on the caller's own retry or deadline limits. Classifying
128
- // a denial as contention must not cost them the original diagnosis, so hand
129
- // the denial back when no further attempt can be scheduled.
106
+ // Preserve a denial diagnosis if contention backoff has no remaining budget.
130
107
  const retryOrRethrowDenial = async (denial) => {
131
108
  try {
132
109
  await waitForRetry();
@@ -137,29 +114,166 @@ export async function acquireSidecarLock(options, context) {
137
114
  throw waitError;
138
115
  }
139
116
  };
117
+ const assertAdmissionToken = () => {
118
+ if (!admission.hasToken())
119
+ throw sidecarLockTimeout(lockPath, normalizedTargetPath);
120
+ };
140
121
  try {
141
122
  while (true) {
142
- if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
123
+ if (!admission.owns) {
124
+ held = context.held.get(normalizedTargetPath);
125
+ if (held &&
126
+ requestedReentrantOwner !== undefined &&
127
+ held.reentrantOwner !== undefined &&
128
+ requestedReentrantOwner === held.reentrantOwner) {
129
+ if (held.releasePromise) {
130
+ await held.releasePromise.catch(() => undefined);
131
+ held = context.held.get(normalizedTargetPath);
132
+ }
133
+ if (held &&
134
+ requestedReentrantOwner !== undefined &&
135
+ held.reentrantOwner !== undefined &&
136
+ requestedReentrantOwner === held.reentrantOwner) {
137
+ const lifecycle = context.ensureExitCleanupRegistered();
138
+ context.assertRetainOnExitSupported(retainOnExit);
139
+ if (context.held.get(normalizedTargetPath) !== held || held.releasePromise) {
140
+ continue;
141
+ }
142
+ held.refCount = (held.refCount ?? 1) + 1;
143
+ if (retainOnExit === true)
144
+ held.retainOnExit = true;
145
+ const returnedHandle = context.handleForHeldLock(normalizedTargetPath, held);
146
+ context.armExitCleanup(lifecycle);
147
+ return returnedHandle;
148
+ }
149
+ }
150
+ if (context.admissions.has(normalizedTargetPath)) {
151
+ await waitForRetry();
152
+ continue;
153
+ }
154
+ admission.reserve();
155
+ }
156
+ const attemptHeld = context.held.get(normalizedTargetPath);
157
+ const holderWasReplaced = () => {
158
+ const current = context.held.get(normalizedTargetPath);
159
+ return current !== undefined && current !== attemptHeld;
160
+ };
161
+ const lifecycle = admission.run(() => context.ensureExitCleanupRegistered());
162
+ assertAdmissionToken();
163
+ if (holderWasReplaced()) {
164
+ await waitForRetry();
165
+ continue;
166
+ }
167
+ admission.run(() => context.assertRetainOnExitSupported(retainOnExit));
168
+ assertAdmissionToken();
169
+ if (holderWasReplaced()) {
170
+ await waitForRetry();
171
+ continue;
172
+ }
173
+ if (reclaimGuard?.assertHeld)
174
+ await admission.run(() => reclaimGuard.assertHeld());
175
+ const reclaimGuardExists = !reclaimGuard && (await (lockRoot
176
+ ? admission.run(() => sidecarReclaimGuardExists(reclaimGuardPath, lockRoot))
177
+ : sidecarReclaimGuardExists(reclaimGuardPath)));
178
+ assertAdmissionToken();
179
+ if (holderWasReplaced()) {
180
+ await waitForRetry();
181
+ continue;
182
+ }
183
+ if (reclaimGuardExists) {
143
184
  await waitForRetry();
144
185
  continue;
145
186
  }
146
187
  let handle = null;
147
- let createdSnapshot = null;
188
+ let createdSnapshot = null, createdHeld;
189
+ let admissionConflict = false;
190
+ let exclusiveCreateConflict = false;
148
191
  let lockFileCreateDenied = false;
149
- const payload = await options.payload();
150
- const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
192
+ const parserState = { observed: false };
193
+ if (!payloadCallback)
194
+ payloadCallback = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => options.payload);
195
+ if (holderWasReplaced()) {
196
+ await waitForRetry();
197
+ continue;
198
+ }
199
+ const { value: payload } = await awaitSidecarAdmissionBoundary(admission, assertAdmissionToken, () => Reflect.apply(payloadCallback, options, []));
200
+ if (holderWasReplaced()) {
201
+ await waitForRetry();
202
+ continue;
203
+ }
204
+ const { raw, ownershipToken } = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => serializeSidecarLockPayload(payload));
205
+ if (holderWasReplaced()) {
206
+ await waitForRetry();
207
+ continue;
208
+ }
209
+ held = context.held.get(normalizedTargetPath);
210
+ if (held?.releasePromise) {
211
+ // Release cleanup never acquires this admission; retaining it here lets
212
+ // the serialized attempt continue without another callback or retry.
213
+ await held.releasePromise.catch(() => undefined);
214
+ assertAdmissionToken();
215
+ held = context.held.get(normalizedTargetPath);
216
+ }
217
+ if (held?.lockPath === lockPath) {
218
+ const observedHeld = held;
219
+ const observation = await observeHeldSidecarParser({
220
+ admission, assertToken: assertAdmissionToken,
221
+ currentHeld: () => context.held.get(normalizedTargetPath), held: observedHeld,
222
+ lockPath, lockRoot, parserState,
223
+ parserAccessor: () => options.parsePayload,
224
+ isTransientDenial: (error) => isTransientLockFileDenial(error, lockPath),
225
+ });
226
+ assertAdmissionToken();
227
+ held = context.held.get(normalizedTargetPath);
228
+ if (held !== observedHeld || observedHeld.releasePromise) {
229
+ await waitForRetry();
230
+ continue;
231
+ }
232
+ if (observation.kind === "read-error") {
233
+ if (!observation.transientDenial || !withinDenialBudget())
234
+ throw observation.error;
235
+ await retryOrRethrowDenial(observation.error);
236
+ continue;
237
+ }
238
+ }
239
+ if (held) {
240
+ await waitForRetry();
241
+ continue;
242
+ }
243
+ if (!metadataObserved) {
244
+ metadata = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => options.metadata);
245
+ metadataObserved = true;
246
+ if (context.held.has(normalizedTargetPath)) {
247
+ await waitForRetry();
248
+ continue;
249
+ }
250
+ }
251
+ if (!onCompromisedObserved) {
252
+ onCompromised = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => options.onCompromised);
253
+ onCompromisedObserved = true;
254
+ if (context.held.has(normalizedTargetPath)) {
255
+ await waitForRetry();
256
+ continue;
257
+ }
258
+ }
259
+ assertAdmissionToken();
260
+ if (context.held.has(normalizedTargetPath)) {
261
+ await waitForRetry();
262
+ continue;
263
+ }
151
264
  try {
152
265
  if (lockRoot) {
153
266
  const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
154
267
  const observation = fileObservation();
155
268
  try {
156
- await observation.run(() => lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 }));
269
+ await observation.run(() => lockRoot.create(relativeLockPath, raw, { ...sidecarExclusiveCreate, mkdir: true, mode: 0o600 }));
157
270
  }
158
271
  catch (error) {
159
272
  // Only this invocation's failed exclusive open grants denial retry authority.
160
273
  lockFileCreateDenied = observation.has(error, `exclusive-create:${lockPath}`) &&
161
274
  isTransientLockFileDenial(error, lockPath);
162
275
  if (error instanceof FsSafeError && error.code === "already-exists") {
276
+ exclusiveCreateConflict = true;
163
277
  throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
164
278
  }
165
279
  throw error;
@@ -191,45 +305,46 @@ export async function acquireSidecarLock(options, context) {
191
305
  }
192
306
  catch (createError) {
193
307
  lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
308
+ exclusiveCreateConflict = createError.code === "EEXIST";
194
309
  throw createError;
195
310
  }
196
311
  await handle.writeFile(raw, "utf8");
197
312
  }
198
313
  const snapshot = { raw, payload, stat: fsSync.fstatSync(handle.fd), ownershipToken };
314
+ createdSnapshot = snapshot;
199
315
  if (snapshot.stat.nlink === 0) {
200
316
  await handle.close();
201
317
  handle = null;
202
318
  await waitForRetry();
203
319
  continue;
204
320
  }
205
- const createdHeld = {
321
+ observeSidecarLockParser(parserState, admission, () => options.parsePayload, assertAdmissionToken);
322
+ if (context.held.has(normalizedTargetPath)) {
323
+ admissionConflict = true;
324
+ throw Object.assign(new Error("sidecar lock admission changed"), { code: "EEXIST" });
325
+ }
326
+ createdHeld = {
206
327
  refCount: 1,
207
- reentrantOwner: options.reentrantOwner,
328
+ reentrantOwner: requestedReentrantOwner,
208
329
  handle,
209
330
  lockPath,
210
331
  snapshot,
211
332
  acquiredAt: Date.now(),
212
- metadata: options.metadata ?? {},
333
+ metadata: metadata ?? {},
213
334
  lockRoot,
214
- retainOnExit: options.retainOnExit,
215
- parsePayload: options.parsePayload,
335
+ retainOnExit,
336
+ parsePayload: parserState.parser,
216
337
  };
217
- context.held.set(normalizedTargetPath, createdHeld);
218
- if (ownsReclaimGuard) {
219
- try {
220
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
221
- ownsReclaimGuard = false;
222
- }
223
- catch (err) {
224
- await context.releaseHeldLock(normalizedTargetPath, createdHeld, { force: true });
225
- throw err;
226
- }
227
- }
228
- const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
229
- const interval = options.compromiseCheckIntervalMs;
230
- if (options.onCompromised && interval !== undefined && interval > 0) {
338
+ const candidateHeld = createdHeld;
339
+ if (reclaimGuard)
340
+ await releaseReclaimGuard();
341
+ const returnedHandle = admission.run(() => context.handleForHeldLock(normalizedTargetPath, candidateHeld));
342
+ if (onCompromised &&
343
+ compromiseCheckIntervalMs !== undefined &&
344
+ compromiseCheckIntervalMs > 0) {
345
+ const compromisedCallback = onCompromised;
231
346
  let compromiseCheckInFlight = false;
232
- createdHeld.compromiseTimer = setInterval(() => {
347
+ candidateHeld.compromiseTimer = admission.run(() => setInterval(() => {
233
348
  if (compromiseCheckInFlight)
234
349
  return;
235
350
  compromiseCheckInFlight = true;
@@ -237,22 +352,34 @@ export async function acquireSidecarLock(options, context) {
237
352
  .verifyStillHeld()
238
353
  .catch(() => false)
239
354
  .then((stillHeld) => {
240
- if (!stillHeld && createdHeld.compromiseTimer) {
241
- clearInterval(createdHeld.compromiseTimer);
242
- createdHeld.compromiseTimer = undefined;
243
- options.onCompromised?.({ lockPath, normalizedTargetPath });
355
+ if (!stillHeld && candidateHeld.compromiseTimer) {
356
+ clearInterval(candidateHeld.compromiseTimer);
357
+ candidateHeld.compromiseTimer = undefined;
358
+ Reflect.apply(compromisedCallback, options, [{ lockPath, normalizedTargetPath }]);
244
359
  }
245
360
  })
246
361
  .finally(() => {
247
362
  compromiseCheckInFlight = false;
248
363
  });
249
- }, interval);
250
- createdHeld.compromiseTimer.unref();
364
+ }, compromiseCheckIntervalMs));
365
+ admission.run(() => candidateHeld.compromiseTimer?.unref());
366
+ }
367
+ if (!admission.hasToken() ||
368
+ context.held.has(normalizedTargetPath)) {
369
+ admissionConflict = true;
370
+ throw Object.assign(new Error("sidecar lock admission changed"), { code: "EEXIST" });
251
371
  }
372
+ context.held.set(normalizedTargetPath, candidateHeld);
373
+ admission.release();
374
+ context.armExitCleanup(lifecycle);
252
375
  return returnedHandle;
253
376
  }
254
377
  catch (err) {
255
378
  try {
379
+ if (createdHeld?.compromiseTimer) {
380
+ clearInterval(createdHeld.compromiseTimer);
381
+ createdHeld.compromiseTimer = undefined;
382
+ }
256
383
  if (handle) {
257
384
  const failedSnapshot = createdSnapshot ?? { payload: null };
258
385
  try {
@@ -262,118 +389,80 @@ export async function acquireSidecarLock(options, context) {
262
389
  // Best-effort cleanup of a failed exclusive create.
263
390
  }
264
391
  const current = context.held.get(normalizedTargetPath);
265
- if (current?.handle === handle) {
392
+ if (createdHeld && current === createdHeld) {
266
393
  context.held.delete(normalizedTargetPath);
267
394
  }
268
395
  await handle.close().catch(() => undefined);
269
- // Root-created records retain the creator's byte/token receipt;
270
- // partial direct writes use the exclusive descriptor's identity.
396
+ // Root records use the creator receipt; partial raw writes use fd identity.
271
397
  await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
272
398
  lockRoot,
273
- parsePayload: options.parsePayload,
399
+ parsePayload: conditionalSidecarLockParser(parserState, admission, () => admission.hasToken() && !context.held.has(normalizedTargetPath)),
274
400
  });
275
401
  }
276
402
  else if (createdSnapshot) {
277
403
  await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
278
404
  lockRoot,
279
- parsePayload: options.parsePayload,
405
+ parsePayload: conditionalSidecarLockParser(parserState, admission, () => admission.hasToken() && !context.held.has(normalizedTargetPath)),
280
406
  });
281
407
  }
282
408
  }
283
409
  catch (cleanupError) {
284
410
  throw createSuppressedError(cleanupError, err, "file lock acquisition and cleanup both failed");
285
411
  }
286
- if (lockFileCreateDenied && withinDenialBudget()) {
287
- await retryOrRethrowDenial(err);
288
- continue;
289
- }
290
- if (err.code !== "EEXIST") {
291
- throw err;
292
- }
293
- if (ownsReclaimGuard) {
294
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
295
- ownsReclaimGuard = false;
412
+ if (admissionConflict) {
296
413
  await waitForRetry();
297
414
  continue;
298
415
  }
299
- const nowMs = Date.now();
300
- let rawSnapshot;
301
- let lockFileOpenDenied = false;
302
- try {
303
- rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
304
- lockRoot,
305
- rejectNonFile: true,
306
- discardObservation: "changed",
307
- onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
308
- });
309
- }
310
- catch (readErr) {
311
- if (!lockFileOpenDenied || !withinDenialBudget())
312
- throw readErr;
313
- await retryOrRethrowDenial(readErr);
314
- continue;
416
+ if (lockFileCreateDenied) {
417
+ assertAdmissionToken();
418
+ if (context.held.has(normalizedTargetPath)) {
419
+ await waitForRetry();
420
+ continue;
421
+ }
422
+ if (withinDenialBudget()) {
423
+ await retryOrRethrowDenial(err);
424
+ continue;
425
+ }
426
+ throw err;
315
427
  }
316
- const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
317
- if (!snapshot) {
428
+ if (!exclusiveCreateConflict)
429
+ throw err;
430
+ assertAdmissionToken();
431
+ if (context.held.has(normalizedTargetPath)) {
318
432
  await waitForRetry();
319
433
  continue;
320
434
  }
321
- if (context.held.has(normalizedTargetPath)) {
435
+ if (reclaimGuard) {
436
+ await releaseReclaimGuard();
322
437
  await waitForRetry();
323
438
  continue;
324
439
  }
325
- const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
326
- if (await shouldReclaim({
440
+ await handleStaleSidecarAdmission({
441
+ admission,
442
+ assertToken: assertAdmissionToken,
443
+ currentHeld: () => context.held.get(normalizedTargetPath),
327
444
  lockPath,
445
+ lockRoot,
328
446
  normalizedTargetPath,
329
- payload: snapshot?.payload ?? null,
330
- staleMs: options.staleMs,
331
- nowMs,
332
- heldByThisProcess: context.held.has(normalizedTargetPath),
333
- })) {
334
- if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
335
- lockRoot,
336
- parsePayload: options.parsePayload,
337
- }))) {
338
- await waitForRetry();
339
- continue;
340
- }
341
- const staleRecovery = options.staleRecovery ?? "fail-closed";
342
- if (staleRecovery === "remove-if-unchanged") {
343
- if (!(await tryAcquireSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath))) {
344
- await waitForRetry();
345
- continue;
346
- }
347
- ownsReclaimGuard = true;
348
- const removal = await removeStaleSidecarLockIfAllowed({
349
- lockPath,
350
- normalizedTargetPath,
351
- snapshot,
352
- shouldRemoveStaleLock: options.shouldRemoveStaleLock,
353
- lockRoot,
354
- parsePayload: options.parsePayload,
355
- });
356
- if (removal === "removed" || removal === "changed") {
357
- if (removal === "changed")
358
- await waitForRetry();
359
- continue;
360
- }
361
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
362
- ownsReclaimGuard = false;
363
- }
364
- throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
365
- code: "file_lock_stale",
366
- lockPath,
367
- normalizedTargetPath,
368
- });
369
- }
370
- await waitForRetry();
447
+ options,
448
+ parserState,
449
+ reclaimGuardPath,
450
+ reclaimGuards: context.reclaimGuards,
451
+ setReclaimGuard: (guard) => { reclaimGuard = guard; },
452
+ releaseReclaimGuard,
453
+ staleOptionsState,
454
+ staleMs,
455
+ withinDenialBudget,
456
+ retryOrRethrowDenial,
457
+ waitForRetry,
458
+ });
459
+ continue;
371
460
  }
372
461
  }
373
462
  }
374
463
  finally {
375
- if (ownsReclaimGuard) {
376
- await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
377
- }
464
+ if (reclaimGuard)
465
+ await releaseReclaimGuard().catch(() => undefined);
466
+ admission.release();
378
467
  }
379
468
  }
@@ -0,0 +1,19 @@
1
+ type AdmissionIdentity = {
2
+ active: boolean;
3
+ admissions: Map<string, object>;
4
+ normalizedTargetPath: string;
5
+ token: object;
6
+ };
7
+ type SidecarAdmissionAncestry = readonly AdmissionIdentity[];
8
+ /** Capture active ancestry now; later lifecycle changes cannot reclassify this call. */
9
+ export declare function captureSidecarAdmissionAncestry(): SidecarAdmissionAncestry;
10
+ export declare function ancestryHasSidecarAdmission(ancestry: SidecarAdmissionAncestry, admissions: Map<string, object>, normalizedTargetPath: string): boolean;
11
+ export declare function createSidecarAdmissionController(ancestry: SidecarAdmissionAncestry, admissions: Map<string, object>, normalizedTargetPath: string): {
12
+ readonly owns: boolean;
13
+ hasToken: () => boolean;
14
+ release(): void;
15
+ reserve(): void;
16
+ run<T>(callback: () => T): T;
17
+ };
18
+ export {};
19
+ //# sourceMappingURL=sidecar-lock-admission-context.d.ts.map