@openclaw/fs-safe 0.13.1 → 0.15.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 (256) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/README.md +10 -4
  3. package/dist/advanced.d.ts +1 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -1
  6. package/dist/archive-merge.d.ts.map +1 -1
  7. package/dist/archive-merge.js +113 -46
  8. package/dist/archive-read.d.ts.map +1 -1
  9. package/dist/archive-read.js +4 -4
  10. package/dist/archive-zip-directory.d.ts +4 -0
  11. package/dist/archive-zip-directory.d.ts.map +1 -1
  12. package/dist/archive-zip-directory.js +2 -0
  13. package/dist/archive-zip-entry.d.ts +6 -2
  14. package/dist/archive-zip-entry.d.ts.map +1 -1
  15. package/dist/archive-zip-entry.js +23 -8
  16. package/dist/archive-zip-integrity.d.ts.map +1 -1
  17. package/dist/archive-zip-integrity.js +3 -4
  18. package/dist/archive-zip-loader.d.ts.map +1 -1
  19. package/dist/archive-zip-loader.js +107 -31
  20. package/dist/archive-zip-names.d.ts +1 -0
  21. package/dist/archive-zip-names.d.ts.map +1 -1
  22. package/dist/archive-zip-names.js +6 -0
  23. package/dist/archive.js +6 -5
  24. package/dist/bounded-read-stream.d.ts +0 -1
  25. package/dist/bounded-read-stream.d.ts.map +1 -1
  26. package/dist/bounded-read-stream.js +0 -6
  27. package/dist/copy-publication.d.ts +6 -0
  28. package/dist/copy-publication.d.ts.map +1 -1
  29. package/dist/copy-publication.js +3 -0
  30. package/dist/copy-tree-portable.d.ts.map +1 -1
  31. package/dist/copy-tree-portable.js +44 -24
  32. package/dist/copy.d.ts.map +1 -1
  33. package/dist/copy.js +29 -11
  34. package/dist/directory-mode-owner.js +5 -5
  35. package/dist/file-handle-transfer.d.ts +2 -0
  36. package/dist/file-handle-transfer.d.ts.map +1 -1
  37. package/dist/file-handle-transfer.js +57 -2
  38. package/dist/file-identity.d.ts.map +1 -1
  39. package/dist/file-identity.js +18 -4
  40. package/dist/file-lock-sync-admission.d.ts +19 -0
  41. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  42. package/dist/file-lock-sync-admission.js +93 -0
  43. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  44. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  45. package/dist/file-lock-sync-root-acquire.js +370 -0
  46. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  47. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  48. package/dist/file-lock-sync-root-arbitration.js +66 -0
  49. package/dist/file-lock-sync-root-held.d.ts +34 -0
  50. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  51. package/dist/file-lock-sync-root-held.js +393 -0
  52. package/dist/file-lock-sync-root-io.d.ts +44 -0
  53. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  54. package/dist/file-lock-sync-root-io.js +209 -0
  55. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  56. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  57. package/dist/file-lock-sync-root-mutation.js +277 -0
  58. package/dist/file-lock-sync-root-options.d.ts +20 -0
  59. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  60. package/dist/file-lock-sync-root-options.js +58 -0
  61. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  62. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  63. package/dist/file-lock-sync-root-registration.js +90 -0
  64. package/dist/file-lock-sync-root.d.ts +36 -0
  65. package/dist/file-lock-sync-root.d.ts.map +1 -0
  66. package/dist/file-lock-sync-root.js +361 -0
  67. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  68. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  69. package/dist/file-lock-sync-stale-admission.js +205 -0
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +245 -205
  72. package/dist/file-store-prune.d.ts.map +1 -1
  73. package/dist/file-store-prune.js +5 -1
  74. package/dist/file-store-sync-directory.d.ts.map +1 -1
  75. package/dist/file-store-sync-directory.js +14 -7
  76. package/dist/file-store-sync-write.js +3 -3
  77. package/dist/file-store.d.ts.map +1 -1
  78. package/dist/file-store.js +47 -12
  79. package/dist/guest-dispatch-python.js +1 -1
  80. package/dist/json-document-store.d.ts.map +1 -1
  81. package/dist/json-document-store.js +22 -15
  82. package/dist/json-durable-queue-ownership.d.ts +0 -1
  83. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  84. package/dist/json-durable-queue-ownership.js +0 -6
  85. package/dist/native-binding.d.ts +8 -0
  86. package/dist/native-binding.d.ts.map +1 -1
  87. package/dist/native-parent-admission.d.ts +3 -2
  88. package/dist/native-parent-admission.d.ts.map +1 -1
  89. package/dist/native-parent-admission.js +24 -5
  90. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  91. package/dist/native-pinned-write-windows.js +7 -7
  92. package/dist/native-pinned-write.d.ts.map +1 -1
  93. package/dist/native-pinned-write.js +202 -172
  94. package/dist/native-policy-directory-observation.d.ts +12 -0
  95. package/dist/native-policy-directory-observation.d.ts.map +1 -0
  96. package/dist/native-policy-directory-observation.js +61 -0
  97. package/dist/native-policy-parent-windows.d.ts +14 -0
  98. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  99. package/dist/native-policy-parent-windows.js +200 -0
  100. package/dist/native-rename-outcome.d.ts +4 -0
  101. package/dist/native-rename-outcome.d.ts.map +1 -0
  102. package/dist/native-rename-outcome.js +8 -0
  103. package/dist/native-staged-file.d.ts +2 -2
  104. package/dist/native-staged-file.d.ts.map +1 -1
  105. package/dist/native-staged-file.js +42 -40
  106. package/dist/output.d.ts.map +1 -1
  107. package/dist/output.js +12 -8
  108. package/dist/path-prefix.d.ts.map +1 -1
  109. package/dist/path-prefix.js +30 -8
  110. package/dist/path-suffix-aliases.d.ts +2 -0
  111. package/dist/path-suffix-aliases.d.ts.map +1 -1
  112. package/dist/path-suffix-aliases.js +25 -17
  113. package/dist/permission-exec.d.ts +2 -0
  114. package/dist/permission-exec.d.ts.map +1 -1
  115. package/dist/permission-exec.js +150 -21
  116. package/dist/permissions-windows.js +1 -1
  117. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  118. package/dist/pinned-mutation-admission.js +23 -29
  119. package/dist/pinned-mutation-observation.d.ts +7 -2
  120. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  121. package/dist/pinned-mutation-observation.js +81 -18
  122. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  123. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  124. package/dist/pinned-mutation-shared-route.js +1 -1
  125. package/dist/pinned-write-types.d.ts +2 -0
  126. package/dist/pinned-write-types.d.ts.map +1 -1
  127. package/dist/private-temp-workspace.d.ts.map +1 -1
  128. package/dist/private-temp-workspace.js +75 -121
  129. package/dist/regular-file.d.ts.map +1 -1
  130. package/dist/regular-file.js +1 -15
  131. package/dist/replace-directory.d.ts.map +1 -1
  132. package/dist/replace-directory.js +256 -18
  133. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  134. package/dist/replace-file-copy-fallback.js +62 -70
  135. package/dist/replace-file-copy-source.d.ts.map +1 -1
  136. package/dist/replace-file-copy-source.js +10 -12
  137. package/dist/replace-file-temp-owner.d.ts +5 -2
  138. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  139. package/dist/replace-file-temp-owner.js +65 -30
  140. package/dist/replace-file.js +6 -6
  141. package/dist/retained-directory-replacement.d.ts +26 -0
  142. package/dist/retained-directory-replacement.d.ts.map +1 -0
  143. package/dist/retained-directory-replacement.js +193 -0
  144. package/dist/root-boundary.d.ts +1 -0
  145. package/dist/root-boundary.d.ts.map +1 -1
  146. package/dist/root-boundary.js +4 -0
  147. package/dist/root-context.d.ts +0 -8
  148. package/dist/root-context.d.ts.map +1 -1
  149. package/dist/root-context.js +0 -3
  150. package/dist/root-create-input.d.ts +6 -0
  151. package/dist/root-create-input.d.ts.map +1 -1
  152. package/dist/root-create-input.js +5 -1
  153. package/dist/root-directory-creation.d.ts.map +1 -1
  154. package/dist/root-directory-creation.js +5 -4
  155. package/dist/root-directory-list.d.ts +1 -0
  156. package/dist/root-directory-list.d.ts.map +1 -1
  157. package/dist/root-directory-list.js +1 -0
  158. package/dist/root-impl.d.ts.map +1 -1
  159. package/dist/root-impl.js +20 -11
  160. package/dist/root-move-noreplace.d.ts.map +1 -1
  161. package/dist/root-move-noreplace.js +2 -2
  162. package/dist/root-path-errors.d.ts +1 -0
  163. package/dist/root-path-errors.d.ts.map +1 -1
  164. package/dist/root-path-errors.js +11 -2
  165. package/dist/root-path-existing.d.ts.map +1 -1
  166. package/dist/root-path-existing.js +12 -37
  167. package/dist/root-path.js +1 -13
  168. package/dist/root-remove.d.ts +1 -0
  169. package/dist/root-remove.d.ts.map +1 -1
  170. package/dist/root-remove.js +4 -0
  171. package/dist/root-walk.d.ts +1 -1
  172. package/dist/root-walk.d.ts.map +1 -1
  173. package/dist/root-walk.js +17 -2
  174. package/dist/root-write-admission.d.ts +0 -2
  175. package/dist/root-write-admission.d.ts.map +1 -1
  176. package/dist/root-write-admission.js +1 -15
  177. package/dist/root-write-compatibility.d.ts +1 -2
  178. package/dist/root-write-compatibility.d.ts.map +1 -1
  179. package/dist/root-write-compatibility.js +17 -68
  180. package/dist/root-write-complete-parent.d.ts.map +1 -1
  181. package/dist/root-write-complete-parent.js +10 -24
  182. package/dist/root-write-lock-binding.d.ts +15 -0
  183. package/dist/root-write-lock-binding.d.ts.map +1 -0
  184. package/dist/root-write-lock-binding.js +162 -0
  185. package/dist/root-write-verification.d.ts.map +1 -1
  186. package/dist/root-write-verification.js +29 -42
  187. package/dist/secret-file.d.ts.map +1 -1
  188. package/dist/secret-file.js +2 -24
  189. package/dist/secret-read-async.d.ts.map +1 -1
  190. package/dist/secret-read-async.js +3 -24
  191. package/dist/secret-read-policy.d.ts +6 -2
  192. package/dist/secret-read-policy.d.ts.map +1 -1
  193. package/dist/secret-read-policy.js +26 -2
  194. package/dist/sibling-temp.d.ts.map +1 -1
  195. package/dist/sibling-temp.js +23 -12
  196. package/dist/sidecar-lock-acquire.d.ts +2 -28
  197. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  198. package/dist/sidecar-lock-acquire.js +288 -199
  199. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  200. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  201. package/dist/sidecar-lock-admission-context.js +60 -0
  202. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  203. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  204. package/dist/sidecar-lock-admission-parser.js +113 -0
  205. package/dist/sidecar-lock-admission.d.ts +35 -0
  206. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  207. package/dist/sidecar-lock-admission.js +7 -0
  208. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  209. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  210. package/dist/sidecar-lock-reclaim.js +80 -25
  211. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  212. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  213. package/dist/sidecar-lock-stale-admission.js +232 -0
  214. package/dist/sidecar-lock-target.d.ts +8 -0
  215. package/dist/sidecar-lock-target.d.ts.map +1 -0
  216. package/dist/sidecar-lock-target.js +55 -0
  217. package/dist/sidecar-lock.d.ts.map +1 -1
  218. package/dist/sidecar-lock.js +100 -16
  219. package/dist/staged-directory.d.ts +5 -2
  220. package/dist/staged-directory.d.ts.map +1 -1
  221. package/dist/staged-directory.js +37 -1
  222. package/dist/temp-workspace-admission.d.ts.map +1 -1
  223. package/dist/temp-workspace-admission.js +28 -4
  224. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  225. package/dist/temp-workspace-descriptor.js +9 -27
  226. package/dist/temp-workspace-owner.d.ts.map +1 -1
  227. package/dist/temp-workspace-owner.js +8 -8
  228. package/dist/walk.d.ts +5 -1
  229. package/dist/walk.d.ts.map +1 -1
  230. package/dist/walk.js +19 -6
  231. package/dist/windows-owner.d.ts.map +1 -1
  232. package/dist/windows-owner.js +2 -2
  233. package/docs/advanced.md +1 -1
  234. package/docs/archive.md +36 -9
  235. package/docs/atomic.md +85 -8
  236. package/docs/copy.md +35 -0
  237. package/docs/file-store.md +19 -0
  238. package/docs/guest.md +1 -1
  239. package/docs/json-store.md +5 -0
  240. package/docs/native-helper.md +10 -3
  241. package/docs/native.md +8 -0
  242. package/docs/output.md +6 -0
  243. package/docs/path-prefix.md +10 -0
  244. package/docs/path-suffix-aliases.md +51 -6
  245. package/docs/permissions.md +13 -0
  246. package/docs/public-api.md +3 -2
  247. package/docs/root.md +22 -3
  248. package/docs/sidecar-lock.md +109 -4
  249. package/docs/staged-file.md +7 -3
  250. package/docs/temp.md +20 -3
  251. package/docs/walk.md +67 -1
  252. package/docs/writing.md +11 -1
  253. package/package.json +10 -10
  254. package/dist/darwin-acl.d.ts +0 -4
  255. package/dist/darwin-acl.d.ts.map +0 -1
  256. package/dist/darwin-acl.js +0 -24
@@ -0,0 +1,232 @@
1
+ import { defaultSidecarLockShouldReclaim, isTransientLockFileDenial } from "./sidecar-lock-policy.js";
2
+ import { readSidecarLockRawSnapshot, removeStaleSidecarLockIfAllowed, sidecarLockSnapshotStillPresent, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
3
+ import { observeSidecarLockParser, parseSidecarLockSnapshotUnderAdmission, scopedSidecarLockParser, } from "./sidecar-lock-admission-parser.js";
4
+ /** Handle one historical EEXIST attempt while its canonical admission is held. */
5
+ export async function handleStaleSidecarAdmission(params) {
6
+ const { admission, lockPath, normalizedTargetPath, options } = params;
7
+ const holderChanged = {};
8
+ const assertUnheld = () => {
9
+ params.assertToken();
10
+ if (params.currentHeld())
11
+ throw holderChanged;
12
+ };
13
+ const retryHolderChange = async (error) => {
14
+ if (error !== holderChanged)
15
+ return false;
16
+ await params.waitForRetry();
17
+ return true;
18
+ };
19
+ const retryChangedBoundary = async () => {
20
+ try {
21
+ assertUnheld();
22
+ return false;
23
+ }
24
+ catch (error) {
25
+ if (await retryHolderChange(error))
26
+ return true;
27
+ throw error;
28
+ }
29
+ };
30
+ let rawSnapshot;
31
+ let lockFileOpenDenied = false;
32
+ try {
33
+ rawSnapshot = await admission.run(async () => await readSidecarLockRawSnapshot(lockPath, {
34
+ lockRoot: params.lockRoot,
35
+ rejectNonFile: true,
36
+ discardObservation: "changed",
37
+ onOpenFailure: (error) => {
38
+ lockFileOpenDenied = isTransientLockFileDenial(error, lockPath);
39
+ },
40
+ }));
41
+ }
42
+ catch (error) {
43
+ if (await retryChangedBoundary())
44
+ return;
45
+ if (!lockFileOpenDenied || !params.withinDenialBudget())
46
+ throw error;
47
+ await params.retryOrRethrowDenial(error);
48
+ return;
49
+ }
50
+ params.assertToken();
51
+ if (params.currentHeld()) {
52
+ await params.waitForRetry();
53
+ return;
54
+ }
55
+ let snapshot;
56
+ try {
57
+ observeSidecarLockParser(params.parserState, admission, () => options.parsePayload, assertUnheld);
58
+ snapshot = parseSidecarLockSnapshotUnderAdmission(rawSnapshot, params.parserState, admission, assertUnheld);
59
+ assertUnheld();
60
+ }
61
+ catch (error) {
62
+ if (await retryHolderChange(error))
63
+ return;
64
+ if (await retryChangedBoundary())
65
+ return;
66
+ throw error;
67
+ }
68
+ if (!snapshot) {
69
+ await params.waitForRetry();
70
+ return;
71
+ }
72
+ const staleOptions = params.staleOptionsState;
73
+ try {
74
+ if (!staleOptions.shouldReclaimObserved) {
75
+ assertUnheld();
76
+ staleOptions.shouldReclaim = admission.run(() => options.shouldReclaim);
77
+ staleOptions.shouldReclaimObserved = true;
78
+ assertUnheld();
79
+ }
80
+ if (!staleOptions.shouldRemoveObserved) {
81
+ assertUnheld();
82
+ staleOptions.shouldRemove = admission.run(() => options.shouldRemoveStaleLock);
83
+ staleOptions.shouldRemoveObserved = true;
84
+ assertUnheld();
85
+ }
86
+ if (!staleOptions.staleRecoveryObserved) {
87
+ assertUnheld();
88
+ staleOptions.staleRecovery = admission.run(() => options.staleRecovery);
89
+ staleOptions.staleRecoveryObserved = true;
90
+ assertUnheld();
91
+ }
92
+ }
93
+ catch (error) {
94
+ if (await retryHolderChange(error))
95
+ return;
96
+ if (await retryChangedBoundary())
97
+ return;
98
+ throw error;
99
+ }
100
+ const shouldReclaim = staleOptions.shouldReclaim ?? defaultSidecarLockShouldReclaim;
101
+ let reclaim;
102
+ try {
103
+ reclaim = await admission.run(async () => {
104
+ assertUnheld();
105
+ try {
106
+ return await Reflect.apply(shouldReclaim, undefined, [{
107
+ lockPath,
108
+ normalizedTargetPath,
109
+ payload: snapshot.payload ?? null,
110
+ staleMs: params.staleMs,
111
+ nowMs: Date.now(),
112
+ heldByThisProcess: false,
113
+ }]);
114
+ }
115
+ finally {
116
+ assertUnheld();
117
+ }
118
+ });
119
+ assertUnheld();
120
+ }
121
+ catch (error) {
122
+ if (await retryHolderChange(error))
123
+ return;
124
+ if (await retryChangedBoundary())
125
+ return;
126
+ throw error;
127
+ }
128
+ if (!reclaim) {
129
+ await params.waitForRetry();
130
+ return;
131
+ }
132
+ let stillPresent;
133
+ try {
134
+ stillPresent = await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
135
+ lockRoot: params.lockRoot,
136
+ parsePayload: scopedSidecarLockParser(params.parserState, admission, assertUnheld),
137
+ });
138
+ assertUnheld();
139
+ }
140
+ catch (error) {
141
+ if (await retryHolderChange(error))
142
+ return;
143
+ if (await retryChangedBoundary())
144
+ return;
145
+ throw error;
146
+ }
147
+ if (!stillPresent) {
148
+ await params.waitForRetry();
149
+ return;
150
+ }
151
+ if ((staleOptions.staleRecovery ?? "fail-closed") === "remove-if-unchanged") {
152
+ const acquiredGuard = await admission.run(() => tryAcquireSidecarReclaimGuard(params.reclaimGuards, params.reclaimGuardPath, params.lockRoot));
153
+ if (acquiredGuard)
154
+ params.setReclaimGuard(acquiredGuard);
155
+ params.assertToken();
156
+ if (params.currentHeld()) {
157
+ if (acquiredGuard) {
158
+ await params.releaseReclaimGuard();
159
+ }
160
+ await params.waitForRetry();
161
+ return;
162
+ }
163
+ if (!acquiredGuard) {
164
+ await params.waitForRetry();
165
+ return;
166
+ }
167
+ const shouldRemove = staleOptions.shouldRemove;
168
+ const assertRemovalAuthorized = assertUnheld;
169
+ const guardedShouldRemove = shouldRemove && function (staleSnapshot) {
170
+ return admission.run(async () => {
171
+ assertRemovalAuthorized();
172
+ try {
173
+ return await Reflect.apply(shouldRemove, this, [staleSnapshot]);
174
+ }
175
+ finally {
176
+ assertRemovalAuthorized();
177
+ }
178
+ });
179
+ };
180
+ let removal;
181
+ try {
182
+ removal = await removeStaleSidecarLockIfAllowed({
183
+ lockPath,
184
+ normalizedTargetPath,
185
+ snapshot,
186
+ assertGuardHeld: acquiredGuard.assertHeld && (() => admission.run(async () => {
187
+ assertUnheld();
188
+ await acquiredGuard.assertHeld();
189
+ assertUnheld();
190
+ })),
191
+ shouldRemoveStaleLock: guardedShouldRemove,
192
+ lockRoot: params.lockRoot,
193
+ parsePayload: scopedSidecarLockParser(params.parserState, admission, assertRemovalAuthorized),
194
+ assertAuthorized: assertRemovalAuthorized,
195
+ });
196
+ }
197
+ catch (error) {
198
+ if (error !== holderChanged) {
199
+ try {
200
+ assertRemovalAuthorized();
201
+ }
202
+ catch (boundaryError) {
203
+ if (boundaryError !== holderChanged)
204
+ throw boundaryError;
205
+ }
206
+ if (!params.currentHeld())
207
+ throw error;
208
+ }
209
+ await params.releaseReclaimGuard();
210
+ await params.waitForRetry();
211
+ return;
212
+ }
213
+ params.assertToken();
214
+ if (params.currentHeld()) {
215
+ await params.releaseReclaimGuard();
216
+ await params.waitForRetry();
217
+ return;
218
+ }
219
+ if (removal === "removed")
220
+ return;
221
+ if (removal === "changed") {
222
+ await params.waitForRetry();
223
+ return;
224
+ }
225
+ await params.releaseReclaimGuard();
226
+ }
227
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
228
+ code: "file_lock_stale",
229
+ lockPath,
230
+ normalizedTargetPath,
231
+ });
232
+ }
@@ -0,0 +1,8 @@
1
+ import type { Root } from "./root-impl.js";
2
+ export declare function isCwdIndependentAbsolutePath(filePath: string): boolean;
3
+ export declare function resolveSidecarTargetPath(resolved: string, lockRoot?: Root): Promise<string>;
4
+ export declare function resolveSidecarLockPaths(targetPath: string, explicitLockPath: string | undefined, lockRoot: Root | undefined): Promise<{
5
+ lockPath: string;
6
+ normalizedTargetPath: string;
7
+ }>;
8
+ //# sourceMappingURL=sidecar-lock-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar-lock-target.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-target.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAG3C,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAItE;AAED,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBjG;AAED,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,QAAQ,EAAE,IAAI,GAAG,SAAS,GACzB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB7D"}
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
4
+ import { realpathSync } from "./realpath.js";
5
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
6
+ import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
7
+ export function isCwdIndependentAbsolutePath(filePath) {
8
+ if (!path.isAbsolute(filePath))
9
+ return false;
10
+ // A leading separator on Windows is rooted on the process's current drive.
11
+ return process.platform !== "win32" || path.parse(filePath).root.length > 1;
12
+ }
13
+ export async function resolveSidecarTargetPath(resolved, lockRoot) {
14
+ assertNoWindowsPathAlias(resolved);
15
+ const dir = path.dirname(resolved);
16
+ if (lockRoot) {
17
+ // The target is an arbitration key, not necessarily inside the lock Root.
18
+ await lockRoot.resolve(".");
19
+ const parent = await canonicalPathFromExistingAncestor(dir);
20
+ await lockRoot.resolve(".");
21
+ assertNoWindowsPathAlias(parent);
22
+ const normalized = path.join(parent, path.basename(resolved));
23
+ assertNoWindowsPathAlias(normalized);
24
+ return normalized;
25
+ }
26
+ await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
27
+ let parent;
28
+ try {
29
+ parent = realpathSync.native(dir);
30
+ }
31
+ catch {
32
+ return resolved;
33
+ }
34
+ assertNoWindowsPathAlias(parent);
35
+ const normalized = path.join(parent, path.basename(resolved));
36
+ assertNoWindowsPathAlias(normalized);
37
+ return normalized;
38
+ }
39
+ export async function resolveSidecarLockPaths(targetPath, explicitLockPath, lockRoot) {
40
+ const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
41
+ assertNoWindowsPathAlias(anchoredTargetPath);
42
+ const resolvedTargetPath = path.resolve(anchoredTargetPath);
43
+ let resolvedLockPath;
44
+ if (explicitLockPath !== undefined) {
45
+ const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
46
+ assertNoWindowsPathAlias(anchoredLockPath);
47
+ resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
48
+ ? explicitLockPath
49
+ : path.resolve(anchoredLockPath);
50
+ }
51
+ const normalizedTargetPath = await resolveSidecarTargetPath(resolvedTargetPath, lockRoot);
52
+ const lockPath = resolvedLockPath ?? `${normalizedTargetPath}.lock`;
53
+ assertNoWindowsPathAlias(lockPath);
54
+ return { lockPath, normalizedTargetPath };
55
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAmIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAyFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
1
+ {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AA2KjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AA8HD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAuB3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAaL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
@@ -8,7 +8,10 @@ import { createSuppressedError } from "./suppressed-error.js";
8
8
  const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
9
9
  const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
10
10
  const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
11
+ const GLOBAL_CLEANUP_REGISTRATION_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistration");
11
12
  const GLOBAL_BEFORE_EXIT_KEY = Symbol.for("fsSafe.sidecarLockBeforeExitCleanup");
13
+ const GLOBAL_BEFORE_EXIT_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockBeforeExitCleanupHandler");
14
+ const GLOBAL_BEFORE_EXIT_REGISTRATION_KEY = Symbol.for("fsSafe.sidecarLockBeforeExitCleanupRegistration");
12
15
  // Set by copies whose exit handlers honor retainOnExit. When an older package
13
16
  // copy registered the handlers first, this marker stays absent and retained
14
17
  // acquisitions must fail closed rather than silently lose the guarantee.
@@ -27,6 +30,7 @@ function resolveManagerState(key) {
27
30
  state = {
28
31
  cleanupRegistered: false,
29
32
  held: new Map(),
33
+ admissions: new Map(),
30
34
  reclaimCleanupRegistered: false,
31
35
  reclaimGuards: new Set(),
32
36
  };
@@ -34,7 +38,9 @@ function resolveManagerState(key) {
34
38
  }
35
39
  else {
36
40
  // The global manager symbol is shared across package copies and hot reloads.
37
- // Backfill state created by fs-safe versions that predate reclaim guards.
41
+ // Backfill state created by fs-safe versions that predate admission tokens
42
+ // or reclaim guards without placing an incomplete value in the held map.
43
+ state.admissions ??= new Map();
38
44
  state.reclaimCleanupRegistered ??= false;
39
45
  state.reclaimGuards ??= new Set();
40
46
  }
@@ -107,21 +113,55 @@ function releaseAllLocksSync(state, options) {
107
113
  }
108
114
  state.held.delete(normalizedTargetPath);
109
115
  }
116
+ state.admissions?.clear();
110
117
  releaseAllReclaimGuardsSync(state);
111
118
  }
112
119
  function ensureGlobalExitCleanupRegistered() {
113
120
  const globalWithCleanup = globalThis;
114
121
  if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
115
122
  return;
116
- globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
117
- globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] = true;
123
+ if (globalWithCleanup[GLOBAL_CLEANUP_REGISTRATION_KEY]) {
124
+ throw new FsSafeError("helper-unavailable", "sidecar lock exit cleanup registration is already in progress");
125
+ }
126
+ const registration = {};
118
127
  const cleanup = () => {
119
128
  for (const state of getGlobalManagers().values()) {
120
129
  releaseAllLocksSync(state, { preserveRetained: true });
121
130
  }
122
131
  };
123
- globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
124
- process.on("exit", cleanup);
132
+ globalWithCleanup[GLOBAL_CLEANUP_REGISTRATION_KEY] = registration;
133
+ try {
134
+ process.on("exit", cleanup);
135
+ globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
136
+ globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] = true;
137
+ globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
138
+ }
139
+ catch (error) {
140
+ if (process.listeners("exit").includes(cleanup)) {
141
+ try {
142
+ process.off("exit", cleanup);
143
+ }
144
+ catch {
145
+ // Reconcile the marker with the actual listener below.
146
+ }
147
+ }
148
+ if (process.listeners("exit").includes(cleanup)) {
149
+ globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
150
+ globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] = true;
151
+ globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
152
+ }
153
+ else {
154
+ if (globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] === cleanup) {
155
+ delete globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY];
156
+ }
157
+ }
158
+ throw error;
159
+ }
160
+ finally {
161
+ if (globalWithCleanup[GLOBAL_CLEANUP_REGISTRATION_KEY] === registration) {
162
+ delete globalWithCleanup[GLOBAL_CLEANUP_REGISTRATION_KEY];
163
+ }
164
+ }
125
165
  }
126
166
  /** True when a retain-unaware package copy registered the process-exit handlers first. */
127
167
  export function exitCleanupCannotRetain() {
@@ -133,10 +173,13 @@ function ensureGlobalBeforeExitCleanupRegistered() {
133
173
  if (globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY]) {
134
174
  return globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY];
135
175
  }
176
+ if (globalWithCleanup[GLOBAL_BEFORE_EXIT_REGISTRATION_KEY]) {
177
+ throw new FsSafeError("helper-unavailable", "sidecar lock before-exit cleanup registration is already in progress");
178
+ }
136
179
  // Independent of the exit registration, which an older package copy may own.
137
180
  const lifecycle = { armed: false };
138
- globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY] = lifecycle;
139
- process.on("beforeExit", () => {
181
+ const registration = {};
182
+ const cleanup = () => {
140
183
  if (!lifecycle.armed)
141
184
  return;
142
185
  // Cleanup itself schedules I/O; retry only after another acquisition.
@@ -148,8 +191,42 @@ function ensureGlobalBeforeExitCleanupRegistered() {
148
191
  }
149
192
  }
150
193
  }
151
- });
152
- return lifecycle;
194
+ };
195
+ globalWithCleanup[GLOBAL_BEFORE_EXIT_REGISTRATION_KEY] = registration;
196
+ try {
197
+ process.on("beforeExit", cleanup);
198
+ globalWithCleanup[GLOBAL_BEFORE_EXIT_HANDLER_KEY] = cleanup;
199
+ globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY] = lifecycle;
200
+ return lifecycle;
201
+ }
202
+ catch (error) {
203
+ if (process.listeners("beforeExit").includes(cleanup)) {
204
+ try {
205
+ process.off("beforeExit", cleanup);
206
+ }
207
+ catch {
208
+ // Reconcile the marker with the actual listener below.
209
+ }
210
+ }
211
+ if (process.listeners("beforeExit").includes(cleanup)) {
212
+ globalWithCleanup[GLOBAL_BEFORE_EXIT_HANDLER_KEY] = cleanup;
213
+ globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY] = lifecycle;
214
+ }
215
+ else {
216
+ if (globalWithCleanup[GLOBAL_BEFORE_EXIT_HANDLER_KEY] === cleanup) {
217
+ delete globalWithCleanup[GLOBAL_BEFORE_EXIT_HANDLER_KEY];
218
+ }
219
+ if (globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY] === lifecycle) {
220
+ delete globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY];
221
+ }
222
+ }
223
+ throw error;
224
+ }
225
+ finally {
226
+ if (globalWithCleanup[GLOBAL_BEFORE_EXIT_REGISTRATION_KEY] === registration) {
227
+ delete globalWithCleanup[GLOBAL_BEFORE_EXIT_REGISTRATION_KEY];
228
+ }
229
+ }
153
230
  }
154
231
  async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
155
232
  const current = state.held.get(normalizedTargetPath);
@@ -194,7 +271,6 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
194
271
  }
195
272
  }
196
273
  function handleForHeldLock(state, normalizedTargetPath, held) {
197
- ensureGlobalBeforeExitCleanupRegistered().armed = true;
198
274
  return createHeldSidecarLockHandle({
199
275
  normalizedTargetPath,
200
276
  held,
@@ -204,21 +280,29 @@ function handleForHeldLock(state, normalizedTargetPath, held) {
204
280
  export function createSidecarLockManager(key) {
205
281
  const state = resolveManagerState(key);
206
282
  function ensureExitCleanupRegistered() {
283
+ ensureGlobalExitCleanupRegistered();
284
+ const lifecycle = ensureGlobalBeforeExitCleanupRegistered();
207
285
  state.cleanupRegistered = true;
208
286
  state.reclaimCleanupRegistered = true;
209
- ensureGlobalExitCleanupRegistered();
210
- ensureGlobalBeforeExitCleanupRegistered().armed = true;
287
+ return lifecycle;
288
+ }
289
+ function armExitCleanup(lifecycle) {
290
+ lifecycle.armed = true;
291
+ }
292
+ function assertRetainOnExitSupported(retainOnExit) {
293
+ if (retainOnExit !== true || !exitCleanupCannotRetain())
294
+ return;
295
+ throw new FsSafeError("helper-unavailable", "retainOnExit requires this process's exit handlers to be retain-aware; an older package copy registered them first");
211
296
  }
212
297
  async function acquire(options) {
213
- if (options.retainOnExit === true && exitCleanupCannotRetain()) {
214
- throw new FsSafeError("helper-unavailable", "retainOnExit requires this process's exit handlers to be retain-aware; an older package copy registered them first");
215
- }
216
298
  return await acquireSidecarLock(options, {
217
299
  held: state.held,
300
+ admissions: state.admissions,
218
301
  reclaimGuards: state.reclaimGuards,
219
302
  ensureExitCleanupRegistered,
303
+ armExitCleanup,
304
+ assertRetainOnExitSupported,
220
305
  handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
221
- releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
222
306
  });
223
307
  }
224
308
  async function withLock(options, fn) {
@@ -1,13 +1,16 @@
1
1
  import { type BigIntStats } from "node:fs";
2
2
  import type { DirectoryReceipt } from "./directory-durability.js";
3
3
  import type { FileIdentityStat } from "./file-identity.js";
4
- import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
4
+ import type { NativeBinding } from "./native-binding.js";
5
+ import { type MutationDirectoryObservation, type MutationDirectoryObserver } from "./pinned-mutation-observation.js";
5
6
  import type { StagedFileReceipt } from "./staged-file-types.js";
6
7
  export type StagedDirectorySnapshot = StagedFileReceipt["directory"];
7
8
  export type PolicyStagedDirectory = Readonly<{
8
9
  directory: StagedDirectorySnapshot;
9
10
  observation: MutationDirectoryObservation;
10
11
  stat: BigIntStats;
12
+ observeCurrent?: MutationDirectoryObserver;
13
+ disposeObservation?(): void;
11
14
  }>;
12
15
  export declare function exactIdentityMatches(expected: FileIdentityStat, actual: Readonly<{
13
16
  dev: bigint;
@@ -15,7 +18,7 @@ export declare function exactIdentityMatches(expected: FileIdentityStat, actual:
15
18
  }>): boolean;
16
19
  export declare function describeStagedDirectory(fd: number, pathname: string): StagedDirectorySnapshot;
17
20
  export declare function assertStagedDirectoryCurrent(receipt: StagedDirectorySnapshot): BigIntStats;
18
- export declare function describePolicyStagedDirectory(fd: number, pathname: string): PolicyStagedDirectory;
21
+ export declare function describePolicyStagedDirectory(fd: number, pathname: string, binding?: NativeBinding): PolicyStagedDirectory;
19
22
  export declare function assertPolicyStagedDirectoryCurrent(captured: PolicyStagedDirectory): BigIntStats;
20
23
  export declare function refreshPolicyStagedDirectoryObservation(captured: PolicyStagedDirectory): MutationDirectoryObservation;
21
24
  export declare function openStagedDirectory(directory: string | DirectoryReceipt): {
@@ -1 +1 @@
1
- {"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,uBAAuB,CAAC;IACnC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,CAAC;AAEH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,uBAAuB,CAY7F;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAS1F;AASD,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,qBAAqB,CAwBvB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,qBAAqB,GAC9B,WAAW,CAOb;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,qBAAqB,GAC9B,4BAA4B,CAU9B;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,uBAAuB,CAAC;CAClC,CAiCA"}
1
+ {"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAE/B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,uBAAuB,CAAC;IACnC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;IAClB,cAAc,CAAC,EAAE,yBAAyB,CAAC;IAC3C,kBAAkB,CAAC,IAAI,IAAI,CAAC;CAC7B,CAAC,CAAC;AAEH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,uBAAuB,CAY7F;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAS1F;AASD,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,aAAa,GACtB,qBAAqB,CA2CvB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,qBAAqB,GAC9B,WAAW,CAeb;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,qBAAqB,GAC9B,4BAA4B,CAoB9B;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,uBAAuB,CAAC;CAClC,CAiCA"}
@@ -2,6 +2,7 @@ import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { inspectDirectoryIdentitySync } from "./directory-guard.js";
5
+ import { nativePolicyDirectoryObserver, NativePolicyDirectoryMismatch } from "./native-policy-directory-observation.js";
5
6
  import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
6
7
  import { realpathSync } from "./realpath.js";
7
8
  import { resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
@@ -38,12 +39,31 @@ function sameDirectoryMetadata(left, right) {
38
39
  }
39
40
  // The policy hot path is Node/POSIX-only. It deliberately uses native
40
41
  // canonicalization so a successful capture contains no JS realpath work.
41
- export function describePolicyStagedDirectory(fd, pathname) {
42
+ export function describePolicyStagedDirectory(fd, pathname, binding) {
42
43
  const resolved = path.resolve(pathname);
43
44
  const descriptor = fs.fstatSync(fd, { bigint: true });
44
45
  if (!descriptor.isDirectory()) {
45
46
  throw new FsSafeError("not-file", "staging parent must be a directory");
46
47
  }
48
+ const observeCurrent = nativePolicyDirectoryObserver(binding, fd, resolved);
49
+ const observed = observeCurrent?.();
50
+ if (observed) {
51
+ if (!sameDirectoryMetadata(descriptor, observed.identity)) {
52
+ throw new NativePolicyDirectoryMismatch();
53
+ }
54
+ const directory = Object.freeze({
55
+ path: resolved,
56
+ realPath: observed.canonicalPath,
57
+ identity: Object.freeze({ dev: descriptor.dev, ino: descriptor.ino }),
58
+ });
59
+ return Object.freeze({
60
+ directory,
61
+ observation: checkedMutationDirectory(resolved, observed.canonicalPath, observed.identity, observeCurrent),
62
+ stat: descriptor,
63
+ observeCurrent,
64
+ disposeObservation: observeCurrent.dispose,
65
+ });
66
+ }
47
67
  const before = inspectDirectoryIdentitySync(resolved);
48
68
  const canonicalPath = path.resolve(realpathSync.native(resolved));
49
69
  const after = inspectDirectoryIdentitySync(resolved, descriptor);
@@ -64,6 +84,14 @@ export function describePolicyStagedDirectory(fd, pathname) {
64
84
  });
65
85
  }
66
86
  export function assertPolicyStagedDirectoryCurrent(captured) {
87
+ const observed = captured.observeCurrent?.();
88
+ if (observed) {
89
+ if (!sameDirectoryMetadata(captured.stat, observed.identity) ||
90
+ observed.canonicalPath !== captured.directory.realPath) {
91
+ throw new FsSafeError("path-mismatch", "staging directory pathname changed");
92
+ }
93
+ return captured.stat;
94
+ }
67
95
  const current = inspectDirectoryIdentitySync(captured.directory.path, captured.directory.identity);
68
96
  if (!sameDirectoryMetadata(captured.stat, current) ||
69
97
  path.resolve(realpathSync.native(captured.directory.path)) !== captured.directory.realPath) {
@@ -72,6 +100,14 @@ export function assertPolicyStagedDirectoryCurrent(captured) {
72
100
  return current;
73
101
  }
74
102
  export function refreshPolicyStagedDirectoryObservation(captured) {
103
+ const observed = captured.observeCurrent?.();
104
+ if (observed) {
105
+ if (!exactIdentityMatches(captured.directory.identity, observed.identity) ||
106
+ observed.canonicalPath !== captured.directory.realPath) {
107
+ throw new FsSafeError("path-mismatch", "staging directory pathname changed");
108
+ }
109
+ return checkedMutationDirectory(captured.directory.path, observed.canonicalPath, observed.identity, captured.observeCurrent);
110
+ }
75
111
  const current = inspectDirectoryIdentitySync(captured.directory.path, captured.directory.identity);
76
112
  const canonicalPath = path.resolve(realpathSync.native(captured.directory.path));
77
113
  if (canonicalPath !== captured.directory.realPath) {
@@ -1 +1 @@
1
- {"version":3,"file":"temp-workspace-admission.d.ts","sourceRoot":"","sources":["../src/temp-workspace-admission.ts"],"names":[],"mappings":"AA6BA,KAAK,aAAa,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAiB5D,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,oBAAoB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,aAAa,IAAI,IAAI,CAAC;IACtB,cAAc,IAAI,IAAI,CAAC;IACvB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C,CAAC;AA0UF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,CAM1F;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAyBjG;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,CAsBtF"}
1
+ {"version":3,"file":"temp-workspace-admission.d.ts","sourceRoot":"","sources":["../src/temp-workspace-admission.ts"],"names":[],"mappings":"AA6BA,KAAK,aAAa,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAiB5D,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,oBAAoB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,aAAa,IAAI,IAAI,CAAC;IACtB,cAAc,IAAI,IAAI,CAAC;IACvB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C,CAAC;AAsWF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,CAM1F;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAyBjG;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,CAsBtF"}