@jini-ai/platform 0.5.9 → 0.6.10

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 (79) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/asset-cache.js +26 -25
  3. package/dist/blob-storage.d.ts +7 -2
  4. package/dist/blob-storage.js +99 -59
  5. package/dist/child-environment.d.ts +36 -0
  6. package/dist/child-environment.js +68 -0
  7. package/dist/download.js +192 -148
  8. package/dist/fs/atomic-write.js +105 -93
  9. package/dist/fs/boot-identity.d.ts +16 -0
  10. package/dist/fs/boot-identity.js +38 -17
  11. package/dist/fs/directory-home.js +6 -0
  12. package/dist/fs/directory-registry.d.ts +1 -1
  13. package/dist/fs/directory-registry.js +23 -16
  14. package/dist/fs/directory-running-lock.d.ts +1 -1
  15. package/dist/fs/directory-running-lock.js +18 -7
  16. package/dist/fs/directory-sync.d.ts +1 -1
  17. package/dist/fs/directory-sync.js +64 -51
  18. package/dist/fs/directory-user-config.js +108 -67
  19. package/dist/fs/directory-workspace.d.ts +11 -0
  20. package/dist/fs/directory-workspace.js +16 -11
  21. package/dist/fs/durable-json.js +49 -37
  22. package/dist/fs/file-lock.js +214 -149
  23. package/dist/fs/guarded-reader.js +47 -35
  24. package/dist/fs/user-data-directories.d.ts +1 -1
  25. package/dist/fs/user-data-directories.js +25 -9
  26. package/dist/fs/verified-directory-move.d.ts +13 -11
  27. package/dist/fs/verified-directory-move.js +105 -68
  28. package/dist/fs/verified-file-relocation.d.ts +1 -1
  29. package/dist/fs/verified-file-relocation.js +111 -94
  30. package/dist/http/guarded/client.d.ts +3 -4
  31. package/dist/http/guarded/client.js +50 -60
  32. package/dist/http/guarded/fetch-adapter.d.ts +2 -3
  33. package/dist/http/guarded/fetch-adapter.js +26 -15
  34. package/dist/mail/builtin-providers.d.ts +39 -0
  35. package/dist/mail/builtin-providers.js +60 -0
  36. package/dist/mail/feedback.d.ts +84 -0
  37. package/dist/mail/feedback.js +90 -0
  38. package/dist/mail/index.d.ts +11 -0
  39. package/dist/mail/index.js +6 -0
  40. package/dist/mail/policy.d.ts +77 -0
  41. package/dist/mail/policy.js +155 -0
  42. package/dist/mail/ports.d.ts +56 -7
  43. package/dist/mail/provider.d.ts +117 -0
  44. package/dist/mail/provider.js +74 -0
  45. package/dist/mail/rate-limit.d.ts +33 -0
  46. package/dist/mail/rate-limit.js +35 -0
  47. package/dist/mail/sender-identity.d.ts +68 -0
  48. package/dist/mail/sender-identity.js +50 -0
  49. package/dist/mail/types.d.ts +9 -0
  50. package/dist/net/address.d.ts +9 -23
  51. package/dist/net/address.js +28 -133
  52. package/dist/net/endpoint-policy.d.ts +26 -2
  53. package/dist/net/endpoint-policy.js +159 -56
  54. package/dist/object-store/index.d.ts +2 -0
  55. package/dist/object-store/index.js +2 -0
  56. package/dist/object-store/local-fs.d.ts +17 -0
  57. package/dist/object-store/local-fs.js +146 -0
  58. package/dist/process.js +2 -0
  59. package/dist/proxy-env.js +131 -135
  60. package/dist/secrets/credential-sets/contracts.d.ts +1 -0
  61. package/dist/secrets/credential-sets/contracts.js +1 -0
  62. package/dist/secrets/credential-sets/index.d.ts +1 -0
  63. package/dist/secrets/credential-sets/index.js +1 -0
  64. package/dist/secrets/credential-sets/provider-matches.d.ts +61 -0
  65. package/dist/secrets/credential-sets/provider-matches.js +61 -0
  66. package/dist/secrets/credential-sets/types.d.ts +7 -0
  67. package/dist/secrets/credential-sets/vault.meta.js +58 -20
  68. package/dist/secrets/credential-sets/vault.repo.memory.d.ts +7 -0
  69. package/dist/secrets/credential-sets/vault.repo.memory.js +43 -36
  70. package/dist/secrets/credential-sets/vault.service.d.ts +9 -0
  71. package/dist/secrets/credential-sets/vault.service.js +131 -61
  72. package/dist/secrets/credential-sets/vault.types.d.ts +11 -3
  73. package/dist/secrets/credential-sets/vault.types.js +22 -8
  74. package/dist/secrets/keyring.env.js +14 -10
  75. package/dist/toolchain.js +119 -91
  76. package/dist/web/html-document.js +73 -69
  77. package/dist/web/html-to-markdown.js +127 -102
  78. package/dist/web/web-page.js +160 -80
  79. package/package.json +12 -4
package/dist/download.js CHANGED
@@ -166,8 +166,15 @@ async function directoryIsEmpty(path) {
166
166
  const entries = await readdir(path);
167
167
  return entries.length === 0;
168
168
  }
169
+ // Every caller feeds this a `JSON.parse` result, and a parsed JSON array can
170
+ // never carry the named string keys (`kind`, `algorithm`, `pid`, ...) each
171
+ // guard goes on to require, so arrays are rejected without a separate
172
+ // `Array.isArray` check.
173
+ function isPlainObject(value) {
174
+ return typeof value === "object" && value != null;
175
+ }
169
176
  function isStoreSentinel(value) {
170
- if (typeof value !== "object" || value == null || Array.isArray(value))
177
+ if (!isPlainObject(value))
171
178
  return false;
172
179
  const record = value;
173
180
  return record.kind === STORE_KIND && record.schemaVersion === STORE_SCHEMA_VERSION && typeof record.createdAt === "string";
@@ -216,30 +223,33 @@ async function ensureManagedBase(basePath) {
216
223
  await ensureStoreDirs(basePath);
217
224
  }
218
225
  function isChecksum(value) {
219
- if (typeof value !== "object" || value == null || Array.isArray(value))
226
+ if (!isPlainObject(value))
220
227
  return false;
221
228
  const checksum = value;
222
229
  return ((checksum.algorithm === "sha256" || checksum.algorithm === "sha512") &&
223
230
  typeof checksum.value === "string");
224
231
  }
232
+ const MANIFEST_STRING_FIELDS = [
233
+ "bucket",
234
+ "fileName",
235
+ "targetKey",
236
+ "identityDigest",
237
+ "urlDigest",
238
+ "createdAt",
239
+ "updatedAt",
240
+ ];
225
241
  function isManifest(value) {
226
- if (typeof value !== "object" || value == null || Array.isArray(value))
242
+ if (!isPlainObject(value))
227
243
  return false;
228
244
  const record = value;
229
245
  return (record.kind === MANIFEST_KIND &&
230
246
  record.schemaVersion === MANIFEST_SCHEMA_VERSION &&
231
- typeof record.bucket === "string" &&
232
- typeof record.fileName === "string" &&
233
- typeof record.targetKey === "string" &&
234
- typeof record.identityDigest === "string" &&
235
- typeof record.urlDigest === "string" &&
247
+ MANIFEST_STRING_FIELDS.every((field) => typeof record[field] === "string") &&
236
248
  isChecksum(record.checksum) &&
237
- (record.state === "complete" || record.state === "partial") &&
238
- typeof record.createdAt === "string" &&
239
- typeof record.updatedAt === "string");
249
+ (record.state === "complete" || record.state === "partial"));
240
250
  }
241
251
  function isDownloadLockFile(value) {
242
- if (typeof value !== "object" || value == null || Array.isArray(value))
252
+ if (!isPlainObject(value))
243
253
  return false;
244
254
  const record = value;
245
255
  return typeof record.createdAt === "string" &&
@@ -326,7 +336,8 @@ async function statFileSize(path) {
326
336
  }
327
337
  async function emitExistingProgress(path, totalBytes, emit) {
328
338
  // `totalBytes` is required (not `number | undefined`): its only real call
329
- // site (`tryResumeDownload`) always resolves one, via a `??` chain whose
339
+ // site (`tryResumeDownload`) always resolves one, via a `??` chain (in
340
+ // `acceptedResumeTotalBytes`) whose
330
341
  // last resort is arithmetic (`partialBytes + (contentLength(response) ??
331
342
  // 0)`) — never `undefined`.
332
343
  const existing = await statFileSize(path);
@@ -361,16 +372,21 @@ function parseContentRange(value) {
361
372
  if (value == null)
362
373
  return null;
363
374
  const match = /^bytes\s+(\d+)-(\d+)\/(\d+|\*)$/i.exec(value.trim());
364
- if (match?.[1] == null || match[2] == null || match[3] == null)
375
+ // Every group in the pattern is mandatory, so a match always carries all three.
376
+ if (match == null)
365
377
  return null;
378
+ // `\d+` always parses to an integer — except a digit run long enough to
379
+ // overflow to Infinity, which `Number.isInteger` rejects.
366
380
  const start = Number(match[1]);
367
381
  const end = Number(match[2]);
368
- const totalBytes = match[3] === "*" ? undefined : Number(match[3]);
369
382
  if (!Number.isInteger(start) || !Number.isInteger(end) || end < start)
370
383
  return null;
371
- if (totalBytes != null && (!Number.isInteger(totalBytes) || totalBytes <= end))
384
+ if (match[3] === "*")
385
+ return { start, end };
386
+ const totalBytes = Number(match[3]);
387
+ if (!Number.isInteger(totalBytes) || totalBytes <= end)
372
388
  return null;
373
- return { start, end, ...(totalBytes == null ? {} : { totalBytes }) };
389
+ return { start, end, totalBytes };
374
390
  }
375
391
  async function writeResponseBodyToPartial(response, target, options) {
376
392
  if (response.body == null)
@@ -391,24 +407,36 @@ async function writeResponseBodyToPartial(response, target, options) {
391
407
  });
392
408
  await pipeline(Readable.fromWeb(response.body), meter, createWriteStream(target.partialPath, { flags: options.startBytes > 0 ? "a" : "w" }));
393
409
  }
394
- async function tryResumeDownload(target, manifest, fetchImpl, emit, requestHeaders) {
410
+ function resumeRequestHeaders(input, options) {
411
+ const etag = input.manifest.validators?.etag;
412
+ return {
413
+ ...(options.requestHeaders ?? {}),
414
+ ...(etag == null ? {} : { "If-Range": etag }),
415
+ Range: `bytes=${input.partialBytes}-`,
416
+ };
417
+ }
418
+ /** The resumed total size, or `null` when the response cannot continue the saved partial. */
419
+ function acceptedResumeTotalBytes(input) {
420
+ const { manifest, partialBytes, response } = input;
421
+ if (response.status !== 206)
422
+ return null;
423
+ const range = parseContentRange(response.headers.get("content-range"));
424
+ if (range == null || range.start !== partialBytes || validatorsConflict(manifest.validators, response)) {
425
+ return null;
426
+ }
427
+ return range.totalBytes ?? manifest.totalBytes ?? partialBytes + (contentLength(response) ?? 0);
428
+ }
429
+ async function tryResumeDownload(input, options) {
430
+ const { emit, fetchImpl, manifest, target } = input;
395
431
  const partialBytes = await statFileSize(target.partialPath);
396
432
  if (partialBytes == null || partialBytes <= 0)
397
433
  return "restart";
398
434
  const response = await fetchImpl(target.url, {
399
- headers: {
400
- ...(requestHeaders ?? {}),
401
- ...(manifest.validators?.etag == null ? {} : { "If-Range": manifest.validators.etag }),
402
- Range: `bytes=${partialBytes}-`,
403
- },
435
+ headers: resumeRequestHeaders({ manifest, partialBytes }, options),
404
436
  });
405
- if (response.status !== 206)
406
- return "restart";
407
- const range = parseContentRange(response.headers.get("content-range"));
408
- if (range == null || range.start !== partialBytes || validatorsConflict(manifest.validators, response)) {
437
+ const totalBytes = acceptedResumeTotalBytes({ manifest, partialBytes, response });
438
+ if (totalBytes == null)
409
439
  return "restart";
410
- }
411
- const totalBytes = range.totalBytes ?? manifest.totalBytes ?? partialBytes + (contentLength(response) ?? 0);
412
440
  await emitExistingProgress(target.partialPath, totalBytes, emit);
413
441
  await writeJson(target.manifestPath, {
414
442
  ...manifest,
@@ -420,7 +448,9 @@ async function tryResumeDownload(target, manifest, fetchImpl, emit, requestHeade
420
448
  await writeResponseBodyToPartial(response, target, { emit, startBytes: partialBytes, totalBytes });
421
449
  return { resumed: true, totalBytes };
422
450
  }
423
- async function downloadFromZero(target, fetchImpl, emit, requestHeaders) {
451
+ async function downloadFromZero(input, options) {
452
+ const { emit, fetchImpl, target } = input;
453
+ const { requestHeaders } = options;
424
454
  await rm(target.partialPath, { force: true }).catch(() => undefined);
425
455
  const response = await fetchImpl(target.url, {
426
456
  ...(requestHeaders == null ? {} : { headers: requestHeaders }),
@@ -436,63 +466,66 @@ async function downloadFromZero(target, fetchImpl, emit, requestHeaders) {
436
466
  async function downloadWithRetries(target, manifest, options) {
437
467
  let lastError;
438
468
  let nextManifest = manifest;
439
- let resumed = false;
469
+ const deps = { emit: options.emit, fetchImpl: options.fetchImpl };
440
470
  for (let attempt = 1; attempt <= options.maxAttempts; attempt += 1) {
441
471
  try {
442
472
  if (nextManifest?.state === "partial") {
443
- const resume = await tryResumeDownload(target, nextManifest, options.fetchImpl, options.emit, options.requestHeaders);
473
+ const resume = await tryResumeDownload({ ...deps, manifest: nextManifest, target }, options);
444
474
  if (resume !== "restart")
445
- return { ...resume, resumed: true };
475
+ return resume;
446
476
  await rm(target.partialPath, { force: true }).catch(() => undefined);
447
477
  nextManifest = null;
448
478
  }
449
- const full = await downloadFromZero(target, options.fetchImpl, options.emit, options.requestHeaders);
450
- resumed = resumed || full.resumed;
451
- return { ...full, resumed };
479
+ return await downloadFromZero({ ...deps, target }, options);
452
480
  }
453
481
  catch (error) {
454
482
  lastError = error;
455
- const partialBytes = await statFileSize(target.partialPath);
456
- if (partialBytes != null && partialBytes > 0) {
457
- nextManifest = {
458
- ...(nextManifest ?? createManifest(target, "partial")),
459
- state: "partial",
460
- updatedAt: new Date().toISOString(),
461
- };
462
- await writeJson(target.manifestPath, nextManifest).catch(() => undefined);
463
- }
483
+ nextManifest = await manifestAfterFailedAttempt({ manifest: nextManifest, target });
464
484
  }
465
485
  }
466
486
  throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.NETWORK_EXHAUSTED, `download failed after ${options.maxAttempts} attempts: ${errorMessage(lastError)}`);
467
487
  }
488
+ /** After a failed attempt, mark any bytes already on disk as a resumable partial (best-effort persisted). */
489
+ async function manifestAfterFailedAttempt(input) {
490
+ const { manifest, target } = input;
491
+ const partialBytes = await statFileSize(target.partialPath);
492
+ if (partialBytes == null || partialBytes <= 0)
493
+ return manifest;
494
+ const next = {
495
+ ...(manifest ?? createManifest(target, "partial")),
496
+ state: "partial",
497
+ updatedAt: new Date().toISOString(),
498
+ };
499
+ await writeJson(target.manifestPath, next).catch(() => undefined);
500
+ return next;
501
+ }
468
502
  async function acquireLock(target) {
469
503
  await mkdir(dirname(target.lockPath), { recursive: true });
470
- // The `continue` below is only reachable on `attempt === 0`, so by the time
471
- // `attempt` would reach 2 (the old loop bound), the branch above it has
472
- // already thrown — there's no way to fall through to a third iteration, so
473
- // no trailing fallback throw is needed. Every path out of this loop body is
474
- // a `return` or a `throw`; V8 still instruments the loop's closing brace as
475
- // a branch (whether execution "falls off" the loop normally), but that
476
- // outcome is structurally impossible here — see archived provenance ledger's
477
- // 2026-07-22 entry.
478
- for (let attempt = 0;; attempt += 1) {
479
- try {
480
- await writeFile(target.lockPath, `${JSON.stringify({
481
- createdAt: new Date().toISOString(),
482
- pid: process.pid,
483
- processStartedAt: new Date(PROCESS_STARTED_AT_MS).toISOString(),
484
- })}\n`, { flag: "wx" });
485
- return { path: target.lockPath };
486
- }
487
- catch (error) {
488
- if (errorCode(error) === "EEXIST") {
489
- const staleLockCleared = attempt === 0 && await clearStaleLock(target);
490
- if (staleLockCleared)
491
- continue;
492
- throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.TARGET_LOCKED, `download target is locked: ${target.bucket}/${target.fileName}`);
493
- }
494
- throw error;
495
- }
504
+ // At most two create attempts: the second only after the first hit an
505
+ // existing lock that `clearStaleLock` proved stale and removed; a second
506
+ // EEXIST is real contention. (This was formerly a `for (;;)` loop whose
507
+ // fall-through branch V8 instrumented but no path could take — see archived
508
+ // provenance ledger's 2026-07-22 entry; the straight-line form has none.)
509
+ if (await tryCreateLockFile(target))
510
+ return { path: target.lockPath };
511
+ if (await clearStaleLock(target) && await tryCreateLockFile(target))
512
+ return { path: target.lockPath };
513
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.TARGET_LOCKED, `download target is locked: ${target.bucket}/${target.fileName}`);
514
+ }
515
+ /** Exclusively create the lock file; `false` when one already exists, any other failure is rethrown. */
516
+ async function tryCreateLockFile(target) {
517
+ try {
518
+ await writeFile(target.lockPath, `${JSON.stringify({
519
+ createdAt: new Date().toISOString(),
520
+ pid: process.pid,
521
+ processStartedAt: new Date(PROCESS_STARTED_AT_MS).toISOString(),
522
+ })}\n`, { flag: "wx" });
523
+ return true;
524
+ }
525
+ catch (error) {
526
+ if (errorCode(error) === "EEXIST")
527
+ return false;
528
+ throw error;
496
529
  }
497
530
  }
498
531
  async function clearStaleLock(target) {
@@ -519,88 +552,52 @@ async function loadReusableState(target) {
519
552
  return await suspiciousReset(target);
520
553
  }
521
554
  if (manifest == null) {
522
- if (finalExists || partialExists)
523
- return await suspiciousReset(target);
524
- return { manifest: null };
555
+ return finalExists || partialExists ? await suspiciousReset(target) : { manifest: null };
525
556
  }
526
557
  if (!manifestMatchesTarget(manifest, target)) {
527
558
  return await suspiciousReset(target);
528
559
  }
529
- if (manifest.state === "complete") {
530
- if (!finalExists)
531
- return await suspiciousReset(target);
532
- const actual = await hashFile(target.finalPath, target.checksum.algorithm).catch(() => null);
533
- if (actual !== target.checksum.value)
534
- return await suspiciousReset(target);
535
- const bytes = await statFileSize(target.finalPath);
536
- if (bytes == null)
537
- return await suspiciousReset(target);
538
- return {
539
- manifest,
540
- result: {
541
- bucket: target.bucket,
542
- bytes,
543
- checksum: target.checksum,
544
- fileName: target.fileName,
545
- path: target.finalPath,
546
- reusedComplete: true,
547
- resumed: false,
548
- urlDigest: target.urlDigest,
549
- },
550
- };
551
- }
560
+ if (manifest.state === "complete")
561
+ return await reusableCompleteState({ manifest, target });
552
562
  if (!partialExists)
553
563
  return await suspiciousReset(target);
554
564
  return { manifest };
555
565
  }
566
+ /** Reuse a complete download only when its final file exists and still hashes to the requested checksum. */
567
+ async function reusableCompleteState(input) {
568
+ const { manifest, target } = input;
569
+ // A missing final file needs no separate check: hashing it rejects, which
570
+ // maps to `null` and fails the checksum comparison into the same reset.
571
+ const actual = await hashFile(target.finalPath, target.checksum.algorithm).catch(() => null);
572
+ if (actual !== target.checksum.value)
573
+ return await suspiciousReset(target);
574
+ const bytes = await statFileSize(target.finalPath);
575
+ if (bytes == null)
576
+ return await suspiciousReset(target);
577
+ return {
578
+ manifest,
579
+ result: {
580
+ bucket: target.bucket,
581
+ bytes,
582
+ checksum: target.checksum,
583
+ fileName: target.fileName,
584
+ path: target.finalPath,
585
+ reusedComplete: true,
586
+ resumed: false,
587
+ urlDigest: target.urlDigest,
588
+ },
589
+ };
590
+ }
556
591
  async function runManagedDownload(target, options) {
557
592
  await ensureManagedBase(target.basePath);
558
593
  await pruneManagedDownloads({ basePath: target.basePath }).catch(() => undefined);
559
- let lock = await acquireLock(target);
594
+ const held = { lock: await acquireLock(target) };
560
595
  try {
561
- let state = await loadReusableState(target);
596
+ const state = await loadStateAllowingOneReset({ held, target });
562
597
  if (state.result != null)
563
598
  return state.result;
564
- if (state.reset === true) {
565
- // A reset removes the lock as part of the managed base cleanup, so
566
- // reacquire it before writing the fresh target state.
567
- await releaseLock(lock);
568
- lock = null;
569
- await ensureManagedBase(target.basePath);
570
- lock = await acquireLock(target);
571
- state = await loadReusableState(target);
572
- if (state.result != null)
573
- return state.result;
574
- if (state.reset === true) {
575
- throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "download state kept resetting after base cleanup");
576
- }
577
- }
578
599
  const download = await downloadWithRetries(target, state.manifest, options);
579
- const actual = await hashFile(target.partialPath, target.checksum.algorithm).catch((error) => {
580
- throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, `downloaded partial could not be hashed: ${errorMessage(error)}`);
581
- });
582
- if (actual !== target.checksum.value) {
583
- await resetOwnedBase(target.basePath).catch(() => undefined);
584
- throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.CHECKSUM_MISMATCH, "downloaded file checksum did not match requested payload", {
585
- actual,
586
- expected: target.checksum.value,
587
- });
588
- }
589
- await mkdir(dirname(target.finalPath), { recursive: true });
590
- if (await pathExists(target.finalPath)) {
591
- const existing = await hashFile(target.finalPath, target.checksum.algorithm).catch(() => null);
592
- if (existing !== target.checksum.value) {
593
- await resetOwnedBase(target.basePath).catch(() => undefined);
594
- throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "existing complete file did not match requested payload");
595
- }
596
- await rm(target.partialPath, { force: true }).catch(() => undefined);
597
- }
598
- else {
599
- await rename(target.partialPath, target.finalPath);
600
- }
601
- const bytes = await statFileSize(target.finalPath);
602
- if (bytes == null)
603
- throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "complete file is missing after promotion");
600
+ const bytes = await promoteVerifiedPartial(target);
604
601
  await writeJson(target.manifestPath, createManifest(target, "complete", { totalBytes: download.totalBytes }));
605
602
  return {
606
603
  bucket: target.bucket,
@@ -614,8 +611,57 @@ async function runManagedDownload(target, options) {
614
611
  };
615
612
  }
616
613
  finally {
617
- await releaseLock(lock);
614
+ await releaseLock(held.lock);
615
+ }
616
+ }
617
+ /** Load reusable state, re-locking and reloading once if the first load had to reset the managed base. */
618
+ async function loadStateAllowingOneReset(input) {
619
+ const { held, target } = input;
620
+ const state = await loadReusableState(target);
621
+ if (state.reset !== true)
622
+ return state;
623
+ // A reset removes the lock as part of the managed base cleanup, so
624
+ // reacquire it before writing the fresh target state. `held.lock` is
625
+ // nulled first so a failed re-acquire never releases (deletes) a lock
626
+ // another process may have taken in the meantime.
627
+ await releaseLock(held.lock);
628
+ held.lock = null;
629
+ await ensureManagedBase(target.basePath);
630
+ held.lock = await acquireLock(target);
631
+ const reloaded = await loadReusableState(target);
632
+ if (reloaded.reset === true) {
633
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "download state kept resetting after base cleanup");
634
+ }
635
+ return reloaded;
636
+ }
637
+ /** Verify the downloaded partial's checksum, promote it to the final path, and return the final size. */
638
+ async function promoteVerifiedPartial(target) {
639
+ const actual = await hashFile(target.partialPath, target.checksum.algorithm).catch((error) => {
640
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, `downloaded partial could not be hashed: ${errorMessage(error)}`);
641
+ });
642
+ if (actual !== target.checksum.value) {
643
+ await resetOwnedBase(target.basePath).catch(() => undefined);
644
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.CHECKSUM_MISMATCH, "downloaded file checksum did not match requested payload", {
645
+ actual,
646
+ expected: target.checksum.value,
647
+ });
648
+ }
649
+ await mkdir(dirname(target.finalPath), { recursive: true });
650
+ if (await pathExists(target.finalPath)) {
651
+ const existing = await hashFile(target.finalPath, target.checksum.algorithm).catch(() => null);
652
+ if (existing !== target.checksum.value) {
653
+ await resetOwnedBase(target.basePath).catch(() => undefined);
654
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "existing complete file did not match requested payload");
655
+ }
656
+ await rm(target.partialPath, { force: true }).catch(() => undefined);
657
+ }
658
+ else {
659
+ await rename(target.partialPath, target.finalPath);
618
660
  }
661
+ const bytes = await statFileSize(target.finalPath);
662
+ if (bytes == null)
663
+ throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_CORRUPT, "complete file is missing after promotion");
664
+ return bytes;
619
665
  }
620
666
  function activeKey(target) {
621
667
  return `${target.basePath}\0${target.targetKey}\0${target.identityDigest}`;
@@ -768,14 +814,18 @@ export async function downloadCopyAndClear(options) {
768
814
  await releaseCopyLease(lease);
769
815
  }
770
816
  }
771
- export async function inspectManagedDownload(options) {
772
- const target = targetFromOptions({
817
+ /** Resolve a target's paths for callers (inspect/remove) that have no payload; the placeholder never reaches the network. */
818
+ function targetWithoutPayload(options) {
819
+ return targetFromOptions({
773
820
  ...options,
774
821
  payload: {
775
822
  checksum: { algorithm: "sha256", value: "0".repeat(64) },
776
823
  url: "https://example.invalid/",
777
824
  },
778
825
  });
826
+ }
827
+ export async function inspectManagedDownload(options) {
828
+ const target = targetWithoutPayload(options);
779
829
  await ensureManagedBase(target.basePath);
780
830
  const rawManifest = await readManifest(target.manifestPath);
781
831
  const manifest = rawManifest === "invalid" ? "unreadable" : rawManifest?.state ?? "missing";
@@ -788,13 +838,7 @@ export async function inspectManagedDownload(options) {
788
838
  };
789
839
  }
790
840
  export async function removeManagedDownload(options) {
791
- const target = targetFromOptions({
792
- ...options,
793
- payload: {
794
- checksum: { algorithm: "sha256", value: "0".repeat(64) },
795
- url: "https://example.invalid/",
796
- },
797
- });
841
+ const target = targetWithoutPayload(options);
798
842
  if (activeTargets.has(targetActiveKey(target))) {
799
843
  throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.TARGET_LOCKED, `download target is active: ${target.bucket}/${target.fileName}`);
800
844
  }