@cortexkit/opencode-antigravity-auth 3.0.3 → 3.0.5

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 (99) hide show
  1. package/README.md +7 -7
  2. package/dist/cli.js +1722 -484
  3. package/dist/cli.js.map +4 -4
  4. package/dist/cli.meta.json +440 -120
  5. package/dist/index.js +2157 -803
  6. package/dist/index.js.map +4 -4
  7. package/dist/index.meta.json +476 -141
  8. package/dist/server.js +2248 -841
  9. package/dist/server.js.map +4 -4
  10. package/dist/server.meta.json +1 -1
  11. package/dist/src/common-auth-embedded/claustrum/enrollment.d.ts +3 -2
  12. package/dist/src/common-auth-embedded/claustrum/enrollment.js +84 -42
  13. package/dist/src/common-auth-embedded/claustrum/roster.js +2 -2
  14. package/dist/src/common-auth-embedded/fs/atomic-write.d.ts +5 -0
  15. package/dist/src/common-auth-embedded/fs/atomic-write.js +21 -0
  16. package/dist/src/common-auth-embedded/fs/index.d.ts +2 -0
  17. package/dist/src/common-auth-embedded/fs/index.js +1 -0
  18. package/dist/src/common-auth-embedded/fs/private-files.d.ts +66 -0
  19. package/dist/src/common-auth-embedded/fs/private-files.js +302 -0
  20. package/dist/src/common-auth-embedded/fs/refresh-file-lock.d.ts +1 -1
  21. package/dist/src/common-auth-embedded/fs/refresh-file-lock.js +254 -37
  22. package/dist/src/common-auth-embedded/fs/with-lock.d.ts +3 -0
  23. package/dist/src/common-auth-embedded/fs/with-lock.js +4 -0
  24. package/dist/src/common-auth-embedded/logger/engine.d.ts +6 -8
  25. package/dist/src/common-auth-embedded/logger/engine.js +126 -11
  26. package/dist/src/common-auth-embedded/pi-slot/slot.d.ts +2 -0
  27. package/dist/src/common-auth-embedded/pi-slot/slot.js +34 -6
  28. package/dist/src/common-auth-embedded/rpc/port-file.js +30 -8
  29. package/dist/src/common-auth-embedded/rpc/rpc-client.js +15 -1
  30. package/dist/src/common-auth-embedded/rpc/rpc-server.js +8 -3
  31. package/dist/src/common-auth-embedded/source-output.json +92 -76
  32. package/dist/src/common-auth-embedded/store/mutate.js +2 -2
  33. package/dist/src/common-auth-embedded/store/pool.d.ts +1 -0
  34. package/dist/src/common-auth-embedded/store/pool.js +3 -0
  35. package/dist/src/common-auth-embedded/store/refresh-lock.d.ts +4 -0
  36. package/dist/src/common-auth-embedded/store/refresh-lock.js +5 -0
  37. package/dist/src/ga/rpc/protocol.d.ts +2 -3
  38. package/dist/src/ga/rpc/protocol.d.ts.map +1 -1
  39. package/dist/src/ga/rpc/protocol.js +60 -0
  40. package/dist/src/ga/rpc/protocol.js.map +1 -1
  41. package/dist/src/ga/server/index.d.ts +3 -2
  42. package/dist/src/ga/server/index.d.ts.map +1 -1
  43. package/dist/src/ga/server/index.js +23 -7
  44. package/dist/src/ga/server/index.js.map +1 -1
  45. package/dist/src/plugin/request.d.ts.map +1 -1
  46. package/dist/src/plugin/request.js +8 -14
  47. package/dist/src/plugin/request.js.map +1 -1
  48. package/dist/src/plugin/vault-custody.d.ts +6 -0
  49. package/dist/src/plugin/vault-custody.d.ts.map +1 -1
  50. package/dist/src/plugin/vault-custody.js +81 -2
  51. package/dist/src/plugin/vault-custody.js.map +1 -1
  52. package/dist/tui-build-map.json +29 -13
  53. package/dist/tui-ga-build-map.json +1 -1
  54. package/dist/types/src/common-auth-embedded/fs/refresh-file-lock.d.ts +1 -1
  55. package/dist/types/src/common-auth-embedded/logger/engine.d.ts +6 -8
  56. package/dist/types/src/common-auth-embedded/store/pool.d.ts +1 -0
  57. package/dist/types/src/common-auth-embedded/store/refresh-lock.d.ts +4 -0
  58. package/dist/types/src/ga/rpc/protocol.d.ts +2 -3
  59. package/dist/types/src/ga/rpc/protocol.d.ts.map +1 -1
  60. package/dist/types/src/ga/server/index.d.ts +3 -2
  61. package/dist/types/src/ga/server/index.d.ts.map +1 -1
  62. package/dist/types/src/plugin/request.d.ts.map +1 -1
  63. package/dist/types/src/plugin/vault-custody.d.ts +6 -0
  64. package/dist/types/src/plugin/vault-custody.d.ts.map +1 -1
  65. package/package.json +5 -4
  66. package/src/common-auth-embedded/claustrum/enrollment.d.ts +3 -2
  67. package/src/common-auth-embedded/claustrum/enrollment.js +84 -42
  68. package/src/common-auth-embedded/claustrum/roster.js +2 -2
  69. package/src/common-auth-embedded/fs/atomic-write.d.ts +5 -0
  70. package/src/common-auth-embedded/fs/atomic-write.js +21 -0
  71. package/src/common-auth-embedded/fs/index.d.ts +2 -0
  72. package/src/common-auth-embedded/fs/index.js +1 -0
  73. package/src/common-auth-embedded/fs/private-files.d.ts +66 -0
  74. package/src/common-auth-embedded/fs/private-files.js +302 -0
  75. package/src/common-auth-embedded/fs/refresh-file-lock.d.ts +1 -1
  76. package/src/common-auth-embedded/fs/refresh-file-lock.js +254 -37
  77. package/src/common-auth-embedded/fs/with-lock.d.ts +3 -0
  78. package/src/common-auth-embedded/fs/with-lock.js +4 -0
  79. package/src/common-auth-embedded/logger/engine.d.ts +6 -8
  80. package/src/common-auth-embedded/logger/engine.js +126 -11
  81. package/src/common-auth-embedded/pi-slot/slot.d.ts +2 -0
  82. package/src/common-auth-embedded/pi-slot/slot.js +34 -6
  83. package/src/common-auth-embedded/rpc/port-file.js +30 -8
  84. package/src/common-auth-embedded/rpc/rpc-client.js +15 -1
  85. package/src/common-auth-embedded/rpc/rpc-server.js +8 -3
  86. package/src/common-auth-embedded/source-output.json +92 -76
  87. package/src/common-auth-embedded/store/mutate.js +2 -2
  88. package/src/common-auth-embedded/store/pool.d.ts +1 -0
  89. package/src/common-auth-embedded/store/pool.js +3 -0
  90. package/src/common-auth-embedded/store/refresh-lock.d.ts +4 -0
  91. package/src/common-auth-embedded/store/refresh-lock.js +5 -0
  92. package/src/tui-compiled/common-auth-embedded/fs/private-files.js +302 -0
  93. package/src/tui-compiled/common-auth-embedded/logger/engine.js +126 -11
  94. package/src/tui-compiled/common-auth-embedded/rpc/port-file.js +30 -8
  95. package/src/tui-compiled/common-auth-embedded/rpc/rpc-client.js +15 -1
  96. package/src/tui-raw/common-auth-embedded/fs/private-files.js +302 -0
  97. package/src/tui-raw/common-auth-embedded/logger/engine.js +126 -11
  98. package/src/tui-raw/common-auth-embedded/rpc/port-file.js +30 -8
  99. package/src/tui-raw/common-auth-embedded/rpc/rpc-client.js +15 -1
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { mkdir, open, readFile, rename, rm, stat, writeFile, } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
+ import { createPrivateBytes, createPrivateDirectoryExclusive, ensurePrivateDirectory, PrivateFileError, privateOperation, readPrivateText, verifyPrivate, windowsPrivateFiles, writePrivateAtomic, } from './private-files.js';
4
5
  import { LockOwnershipError, lockPathFor } from './with-lock.js';
5
6
  const setRefreshLockRenewalTimeout = globalThis.setTimeout.bind(globalThis);
6
7
  const clearRefreshLockRenewalTimeout = globalThis.clearTimeout.bind(globalThis);
@@ -10,7 +11,8 @@ const clearRefreshLockRenewalTimeout = globalThis.clearTimeout.bind(globalThis);
10
11
  // to hold — a lost race the caller should retry, not a fatal lock error.
11
12
  export function isLostMarkerRaceError(error) {
12
13
  const code = error?.code;
13
- return code === 'ENOENT' || code === 'EINVAL' || code === 'ENOTDIR';
14
+ return (code === 'ENOENT' ||
15
+ (!windowsPrivateFiles && (code === 'EINVAL' || code === 'ENOTDIR')));
14
16
  }
15
17
  export async function acquireRefreshFileLock(options) {
16
18
  const lockPath = lockPathFor(options.path, options.name);
@@ -19,6 +21,7 @@ export async function acquireRefreshFileLock(options) {
19
21
  const now = options.now ?? Date.now;
20
22
  let renewTimer = null;
21
23
  let released = false;
24
+ let releaseConfirmed = false;
22
25
  let loss;
23
26
  let resolveLoss;
24
27
  const lostPromise = new Promise((resolve) => {
@@ -61,6 +64,7 @@ export async function acquireRefreshFileLock(options) {
61
64
  cancelRenewalTimer();
62
65
  resolveLoss(loss);
63
66
  }
67
+ let terminalIOError;
64
68
  let renewalInFlight = null;
65
69
  // Only the owner of this exclusively-created marker may remove or renew a
66
70
  // lock. A contender recovering a stale marker can accidentally rename a newer
@@ -72,6 +76,18 @@ export async function acquireRefreshFileLock(options) {
72
76
  const EVICT_TTL = 5_000;
73
77
  const MAX_STEAL_ATTEMPTS = 8;
74
78
  async function readOwner() {
79
+ if (windowsPrivateFiles) {
80
+ try {
81
+ return JSON.parse(await readPrivateText(lockPath, true));
82
+ }
83
+ catch (error) {
84
+ if (error.code !== 'EISDIR' &&
85
+ error.reason !== 'wrong-kind')
86
+ throw error;
87
+ await verifyPrivate(lockPath, 'directory', true);
88
+ return JSON.parse(await readPrivateText(legacyOwnerPath, true));
89
+ }
90
+ }
75
91
  try {
76
92
  return JSON.parse(await readFile(lockPath, 'utf8'));
77
93
  }
@@ -83,6 +99,25 @@ export async function acquireRefreshFileLock(options) {
83
99
  }
84
100
  }
85
101
  async function writeOwner() {
102
+ if (windowsPrivateFiles) {
103
+ const owner = await readOwner();
104
+ const remaining = Number(owner?.expiresAt) - now();
105
+ await writePrivateAtomic(lockPath, `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, {
106
+ deadline: performance.now() + Math.max(0, remaining),
107
+ beforeRename: async () => {
108
+ const current = await readOwner();
109
+ if (current?.ownerId !== ownerId ||
110
+ !(Number(current?.expiresAt) > now()) ||
111
+ !(await ownsEvictionMarker())) {
112
+ throw new LockOwnershipError({
113
+ target: options.path,
114
+ name: options.name,
115
+ });
116
+ }
117
+ },
118
+ });
119
+ return;
120
+ }
86
121
  // Readers must see a complete lease, even while renewal is writing.
87
122
  const tempPath = `${lockPath}.${randomUUID()}.tmp`;
88
123
  let handle;
@@ -94,6 +129,15 @@ export async function acquireRefreshFileLock(options) {
94
129
  await handle.chmod(0o600);
95
130
  await handle.close();
96
131
  handle = undefined;
132
+ const current = await readOwner();
133
+ if (current?.ownerId !== ownerId ||
134
+ !(Number(current?.expiresAt) > now()) ||
135
+ !(await ownsEvictionMarker())) {
136
+ throw new LockOwnershipError({
137
+ target: options.path,
138
+ name: options.name,
139
+ });
140
+ }
97
141
  await rename(tempPath, lockPath);
98
142
  created = false;
99
143
  }
@@ -103,25 +147,56 @@ export async function acquireRefreshFileLock(options) {
103
147
  await rm(tempPath, { force: true }).catch(() => { });
104
148
  }
105
149
  }
150
+ async function createLease() {
151
+ const bytes = `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`;
152
+ if (windowsPrivateFiles)
153
+ await createPrivateBytes(lockPath, bytes);
154
+ else
155
+ await writeFile(lockPath, bytes, {
156
+ encoding: 'utf8',
157
+ mode: 0o600,
158
+ flag: 'wx',
159
+ });
160
+ }
161
+ async function leaseContention(error) {
162
+ const code = error.code;
163
+ if (code === 'EEXIST') {
164
+ if (windowsPrivateFiles)
165
+ await readOwner();
166
+ return true;
167
+ }
168
+ if (code === 'EISDIR') {
169
+ if (windowsPrivateFiles) {
170
+ await verifyPrivate(lockPath, 'directory', true);
171
+ await readPrivateText(legacyOwnerPath, true);
172
+ }
173
+ return true;
174
+ }
175
+ return false;
176
+ }
106
177
  async function tryAcquire() {
178
+ if (windowsPrivateFiles)
179
+ await ensurePrivateDirectory(dirname(lockPath));
107
180
  try {
108
- await writeFile(lockPath, `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
181
+ await createLease();
109
182
  return true;
110
183
  }
111
184
  catch (error) {
112
185
  const code = error.code;
113
- if (code === 'EEXIST' || code === 'EISDIR')
186
+ if (await leaseContention(error))
114
187
  return false;
115
188
  if (code === 'ENOENT') {
116
189
  // Reboots can clear runtime temp directories before the next lock acquisition.
117
- await mkdir(dirname(lockPath), { recursive: true });
190
+ if (windowsPrivateFiles)
191
+ await ensurePrivateDirectory(dirname(lockPath));
192
+ else
193
+ await mkdir(dirname(lockPath), { recursive: true });
118
194
  try {
119
- await writeFile(lockPath, `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
195
+ await createLease();
120
196
  return true;
121
197
  }
122
198
  catch (retryError) {
123
- const retryCode = retryError.code;
124
- if (retryCode === 'EEXIST' || retryCode === 'EISDIR')
199
+ if (await leaseContention(retryError))
125
200
  return false;
126
201
  throw retryError;
127
202
  }
@@ -132,41 +207,108 @@ export async function acquireRefreshFileLock(options) {
132
207
  async function backoff() {
133
208
  await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 4)));
134
209
  }
135
- async function lockIsLive() {
210
+ async function readLockState(notifyReady = true) {
211
+ if (notifyReady && options.onStep)
212
+ await options.onStep('lock-read-ready');
213
+ let owner;
136
214
  try {
137
- const currentOwner = await readOwner();
138
- return Number(currentOwner?.expiresAt) > now();
215
+ owner = await readOwner();
139
216
  }
140
- catch {
141
- try {
142
- const current = await stat(lockPath);
143
- return current.mtimeMs + options.ttlMs > now();
144
- }
145
- catch {
146
- // Lock doesn't exist — safe to acquire.
147
- return false;
217
+ catch (error) {
218
+ if (windowsPrivateFiles &&
219
+ error.code !== 'ENOENT')
220
+ throw error;
221
+ }
222
+ const expiresAt = Number(owner?.expiresAt);
223
+ if (Number.isFinite(expiresAt) && expiresAt > now())
224
+ return { status: 'live' };
225
+ if (typeof owner?.ownerId === 'string' && Number.isFinite(expiresAt)) {
226
+ return {
227
+ status: 'stale',
228
+ identity: { kind: 'owner', ownerId: owner.ownerId, expiresAt },
229
+ };
230
+ }
231
+ try {
232
+ const current = await stat(lockPath);
233
+ const legacyExpiry = Number.isFinite(expiresAt)
234
+ ? expiresAt
235
+ : current.mtimeMs + options.ttlMs;
236
+ if (legacyExpiry > now())
237
+ return { status: 'live' };
238
+ return {
239
+ status: 'stale',
240
+ identity: {
241
+ kind: 'stat',
242
+ dev: current.dev,
243
+ ino: current.ino,
244
+ mtimeMs: current.mtimeMs,
245
+ expiresAt: legacyExpiry,
246
+ },
247
+ };
248
+ }
249
+ catch (error) {
250
+ if (error.code === 'ENOENT') {
251
+ if (options.onStep)
252
+ await options.onStep('lock-missing');
253
+ return { status: 'missing' };
148
254
  }
255
+ throw error;
256
+ }
257
+ }
258
+ function sameStaleLock(observed, current) {
259
+ if (current.status !== 'stale')
260
+ return false;
261
+ const identity = current.identity;
262
+ if (observed.kind === 'owner' && identity.kind === 'owner') {
263
+ return (observed.ownerId === identity.ownerId &&
264
+ observed.expiresAt === identity.expiresAt);
149
265
  }
266
+ return (observed.kind === 'stat' &&
267
+ identity.kind === 'stat' &&
268
+ observed.dev === identity.dev &&
269
+ observed.ino === identity.ino &&
270
+ observed.mtimeMs === identity.mtimeMs &&
271
+ observed.expiresAt === identity.expiresAt);
150
272
  }
151
273
  // Fail-closed: any read error means we do NOT own the marker.
152
274
  async function ownsEvictionMarker() {
153
275
  try {
154
- const owner = JSON.parse(await readFile(evictOwnerPath, 'utf8'));
276
+ const owner = JSON.parse(await readPrivateText(evictOwnerPath, windowsPrivateFiles));
155
277
  return owner?.ownerId === evictOwnerId;
156
278
  }
157
- catch {
279
+ catch (error) {
280
+ if (windowsPrivateFiles &&
281
+ error.code !== 'ENOENT')
282
+ throw error;
158
283
  return false;
159
284
  }
160
285
  }
286
+ async function removeLeaseObject(path) {
287
+ if (windowsPrivateFiles)
288
+ await privateOperation(path, 'remove', () => rm(path, { recursive: true, force: true }));
289
+ else
290
+ await rm(path, { recursive: true, force: true }).catch(() => { });
291
+ }
161
292
  async function releaseEvictionMarker() {
162
293
  if (await ownsEvictionMarker()) {
163
- await rm(evictPath, { recursive: true, force: true }).catch(() => { });
294
+ await removeLeaseObject(evictPath);
164
295
  }
165
296
  }
166
297
  async function tryAcquireEvictionMarker() {
167
- await mkdir(evictPath);
298
+ if (windowsPrivateFiles)
299
+ await createPrivateDirectoryExclusive(evictPath);
300
+ else
301
+ await mkdir(evictPath);
168
302
  try {
169
- await writeFile(evictOwnerPath, `${JSON.stringify({ ownerId: evictOwnerId, createdAt: now() })}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
303
+ const bytes = `${JSON.stringify({ ownerId: evictOwnerId, createdAt: now() })}\n`;
304
+ if (windowsPrivateFiles)
305
+ await createPrivateBytes(evictOwnerPath, bytes);
306
+ else
307
+ await writeFile(evictOwnerPath, bytes, {
308
+ encoding: 'utf8',
309
+ mode: 0o600,
310
+ flag: 'wx',
311
+ });
170
312
  }
171
313
  catch (error) {
172
314
  // A competing contender can rename our just-created marker directory
@@ -193,6 +335,8 @@ export async function acquireRefreshFileLock(options) {
193
335
  return 'missing';
194
336
  throw error;
195
337
  }
338
+ if (windowsPrivateFiles)
339
+ await verifyPrivate(evictPath, 'directory', true);
196
340
  if (evictStat.mtimeMs + EVICT_TTL > now())
197
341
  return 'fresh';
198
342
  if (options.onStep)
@@ -208,7 +352,7 @@ export async function acquireRefreshFileLock(options) {
208
352
  }
209
353
  if (options.onStep)
210
354
  await options.onStep('stale-marker-claimed');
211
- await rm(claimedPath, { recursive: true, force: true }).catch(() => { });
355
+ await removeLeaseObject(claimedPath);
212
356
  return 'recovered';
213
357
  }
214
358
  async function withEvictionMarker(action) {
@@ -217,8 +361,19 @@ export async function acquireRefreshFileLock(options) {
217
361
  return false;
218
362
  }
219
363
  catch (error) {
220
- if (error.code === 'EEXIST')
364
+ if (error.code === 'EEXIST') {
365
+ if (windowsPrivateFiles) {
366
+ await verifyPrivate(evictPath, 'directory', true);
367
+ try {
368
+ await readPrivateText(evictOwnerPath, true);
369
+ }
370
+ catch (ownerError) {
371
+ if (ownerError.code !== 'ENOENT')
372
+ throw ownerError;
373
+ }
374
+ }
221
375
  return false;
376
+ }
222
377
  throw error;
223
378
  }
224
379
  try {
@@ -241,7 +396,9 @@ export async function acquireRefreshFileLock(options) {
241
396
  try {
242
397
  owner = await readOwner();
243
398
  }
244
- catch {
399
+ catch (error) {
400
+ if (windowsPrivateFiles)
401
+ throw error;
245
402
  return;
246
403
  }
247
404
  if (owner?.ownerId !== ownerId)
@@ -250,7 +407,9 @@ export async function acquireRefreshFileLock(options) {
250
407
  await rm(lockPath, { recursive: true, force: true });
251
408
  return;
252
409
  }
253
- catch {
410
+ catch (error) {
411
+ if (windowsPrivateFiles)
412
+ throw error;
254
413
  await backoff();
255
414
  }
256
415
  }
@@ -306,6 +465,8 @@ export async function acquireRefreshFileLock(options) {
306
465
  return;
307
466
  }
308
467
  await writeOwner();
468
+ if (options.onStep)
469
+ await options.onStep('renewal-write-finished');
309
470
  if (!(await ownsEvictionMarker())) {
310
471
  // If marker ownership cannot be read, stop claiming the lease
311
472
  // and remove only a record that still carries our owner id.
@@ -319,7 +480,12 @@ export async function acquireRefreshFileLock(options) {
319
480
  await options.onStep('renewal-marker-unavailable');
320
481
  }
321
482
  }
322
- catch {
483
+ catch (error) {
484
+ if (windowsPrivateFiles && error instanceof PrivateFileError) {
485
+ terminalIOError = error;
486
+ recordLoss('renewal-failed');
487
+ shouldReschedule = false;
488
+ }
323
489
  // Retry transient failures only while the lease can still be verified.
324
490
  try {
325
491
  const owner = await readOwner();
@@ -376,8 +542,12 @@ export async function acquireRefreshFileLock(options) {
376
542
  acquired = await tryAcquire();
377
543
  if (acquired)
378
544
  break;
379
- if (await lockIsLive())
545
+ const observed = await readLockState();
546
+ if (observed.status === 'live')
380
547
  return contended();
548
+ // Absence is a create opportunity, never evidence of an expired lease.
549
+ if (observed.status === 'missing')
550
+ continue;
381
551
  if (options.onStep)
382
552
  await options.onStep('stale-lock-observed');
383
553
  try {
@@ -397,8 +567,11 @@ export async function acquireRefreshFileLock(options) {
397
567
  continue;
398
568
  }
399
569
  try {
400
- if (await lockIsLive())
570
+ const confirmed = await readLockState();
571
+ if (confirmed.status === 'live')
401
572
  return contended();
573
+ if (!sameStaleLock(observed.identity, confirmed))
574
+ continue;
402
575
  // Verify marker ownership before removing a lock found not live.
403
576
  if (!(await ownsEvictionMarker()))
404
577
  return null;
@@ -408,7 +581,18 @@ export async function acquireRefreshFileLock(options) {
408
581
  // our marker, so verify ownership again after the callback.
409
582
  if (!(await ownsEvictionMarker()))
410
583
  return null;
411
- await rm(lockPath, { recursive: true, force: true }).catch(() => { });
584
+ // Do not await a callback between this ownership check and the lock read.
585
+ const current = await readLockState(false);
586
+ if (!sameStaleLock(observed.identity, current))
587
+ continue;
588
+ // The final read and removal are not atomic. While this stale file
589
+ // exists, wx cannot create a successor; the marker excludes other
590
+ // evictors and release/renewal. An expired holder cannot renew: renewal
591
+ // rechecks expiry and marker ownership immediately before publishing.
592
+ // With the marker still owned, cooperating users cannot replace it in
593
+ // this window. Arbitrary suspension across marker expiry or filesystem
594
+ // changes still need a kernel lock for an atomic guarantee.
595
+ await removeLeaseObject(lockPath);
412
596
  // Fence check 3: re-verify ownership after removing the stale lock.
413
597
  if (!(await ownsEvictionMarker()))
414
598
  return null;
@@ -442,6 +626,8 @@ export async function acquireRefreshFileLock(options) {
442
626
  whenLost: () => lostPromise,
443
627
  hasLost: () => loss !== undefined,
444
628
  assertOwned: async () => {
629
+ if (terminalIOError)
630
+ throw terminalIOError;
445
631
  let observed;
446
632
  try {
447
633
  const owner = await readOwner();
@@ -453,9 +639,11 @@ export async function acquireRefreshFileLock(options) {
453
639
  return;
454
640
  recordLoss(owner?.ownerId !== ownerId ? 'taken-over' : 'expired', owner);
455
641
  }
456
- catch {
642
+ catch (error) {
457
643
  // Unreadable ownership is not evidence of a valid lease.
458
644
  recordLoss('unreadable');
645
+ if (windowsPrivateFiles && error instanceof PrivateFileError)
646
+ throw error;
459
647
  }
460
648
  throw new LockOwnershipError({
461
649
  target: options.path,
@@ -470,32 +658,61 @@ export async function acquireRefreshFileLock(options) {
470
658
  });
471
659
  },
472
660
  release: async () => {
661
+ if (windowsPrivateFiles && releaseConfirmed)
662
+ return;
473
663
  released = true;
474
664
  cancelRenewalTimer();
475
665
  await renewalInFlight;
476
666
  for (let attempt = 0; attempt < MAX_STEAL_ATTEMPTS; attempt++) {
477
667
  try {
668
+ let removalConfirmed = false;
478
669
  const markerAcquired = await withEvictionMarker(async () => {
479
- const owner = await readOwner();
480
- if (owner?.ownerId !== ownerId)
670
+ let owner;
671
+ try {
672
+ owner = await readOwner();
673
+ }
674
+ catch (error) {
675
+ if (windowsPrivateFiles &&
676
+ error.code === 'ENOENT') {
677
+ removalConfirmed = true;
678
+ return;
679
+ }
680
+ throw error;
681
+ }
682
+ if (owner?.ownerId !== ownerId) {
683
+ removalConfirmed = true;
481
684
  return;
685
+ }
482
686
  if (options.onStep)
483
687
  await options.onStep('release-owner-confirmed');
484
688
  if (!(await ownsEvictionMarker()))
485
689
  return;
486
- await rm(lockPath, { recursive: true, force: true }).catch(() => { });
690
+ await removeLeaseObject(lockPath);
691
+ removalConfirmed = true;
487
692
  });
488
- if (markerAcquired)
693
+ if (markerAcquired) {
694
+ if (windowsPrivateFiles && !removalConfirmed)
695
+ throw new PrivateFileError(lockPath, 'release-not-confirmed', {
696
+ code: 'EIO',
697
+ });
698
+ releaseConfirmed = removalConfirmed;
489
699
  return;
700
+ }
490
701
  await recoverStaleEvictionMarker();
491
702
  }
492
- catch {
703
+ catch (error) {
704
+ if (windowsPrivateFiles)
705
+ throw error;
493
706
  return;
494
707
  }
495
708
  await backoff();
496
709
  }
497
710
  // Do not delete by pathname without the marker: bounded retries leave the
498
711
  // lease to expire rather than risking removal of a successor's lock.
712
+ if (windowsPrivateFiles)
713
+ throw new PrivateFileError(lockPath, 'release-not-confirmed', {
714
+ code: 'ETIMEDOUT',
715
+ });
499
716
  },
500
717
  };
501
718
  }
@@ -1,3 +1,4 @@
1
+ import { acquireRefreshFileLock } from './refresh-file-lock.js';
1
2
  export declare function lockPathFor(target: string, name: string): string;
2
3
  export declare class LockContentionError extends Error {
3
4
  readonly details: {
@@ -27,6 +28,8 @@ export interface LockOptions {
27
28
  ttlMs: number;
28
29
  timeoutMs: number;
29
30
  renew?: boolean;
31
+ onStep?: Parameters<typeof acquireRefreshFileLock>[0]['onStep'];
32
+ onRenewalTimer?: Parameters<typeof acquireRefreshFileLock>[0]['onRenewalTimer'];
30
33
  }
31
34
  export declare function withLock<T>(target: string, options: LockOptions, fn: (lock: {
32
35
  assertOwned(): Promise<void>;
@@ -26,6 +26,10 @@ export async function withLock(target, options, fn) {
26
26
  name: options.name,
27
27
  ttlMs: options.ttlMs,
28
28
  renew: options.renew ?? false,
29
+ ...(options.onStep ? { onStep: options.onStep } : {}),
30
+ ...(options.onRenewalTimer
31
+ ? { onRenewalTimer: options.onRenewalTimer }
32
+ : {}),
29
33
  });
30
34
  if (lock) {
31
35
  try {
@@ -62,11 +62,10 @@ export interface LoggerInstance {
62
62
  /** Operator override of the level floor; undefined removes it. */
63
63
  setLogLevel(level: Level | undefined): void;
64
64
  /**
65
- * Write whatever is buffered. Safe to call synchronously from a
66
- * process-exit handler, which is how each host drains the buffer on
67
- * shutdown.
65
+ * On Windows this returns a promise and must be awaited before shutdown;
66
+ * synchronous process-exit flushing remains available on POSIX only.
68
67
  */
69
- flushLogs(): void;
68
+ flushLogs(): void | Promise<void>;
70
69
  }
71
70
  /**
72
71
  * A logger instance of its own, configured with `options`. A plugin whose
@@ -84,11 +83,10 @@ export declare function createLoggerInstance(options: LoggerOptions): LoggerInst
84
83
  export declare function initLogger(options: LoggerOptions): LoggerInstance;
85
84
  export declare function setLogLevel(l: Level | undefined): void;
86
85
  /**
87
- * Write whatever the default logger has buffered. Safe to call synchronously
88
- * from a process-exit handler, which is how each host drains the buffer on
89
- * shutdown.
86
+ * Write whatever the default logger has buffered. On Windows await the
87
+ * promise before shutdown; POSIX keeps synchronous process-exit flushing.
90
88
  */
91
- export declare function flushLogs(): void;
89
+ export declare function flushLogs(): void | Promise<void>;
92
90
  export declare function createLogger(channel: string): ChannelLogger;
93
91
  export declare function flushForTest(): Promise<void>;
94
92
  /**