@gajae-code/coding-agent 0.12.4 → 0.12.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.
@@ -8,7 +8,7 @@ import {
8
8
  verifyOwnerOnlyPathSecurity,
9
9
  verifyOwnerOnlyPathSecurityExpected,
10
10
  } from "@gajae-code/natives";
11
- import { hasFsCode, logger, pathIsWithin } from "@gajae-code/utils";
11
+ import { logger, pathIsWithin } from "@gajae-code/utils";
12
12
  import type { ResumeSessionIdentity } from "../session-manager";
13
13
  import {
14
14
  FileSessionStorage,
@@ -31,7 +31,6 @@ import {
31
31
  type ManagedFileSnapshot,
32
32
  ManagedPublishError,
33
33
  ManagedSessionDescendantStore,
34
- ManagedSessionSecurityError,
35
34
  type ManagedSessionSecurityPolicy,
36
35
  type ManagedStorageLock,
37
36
  managedSecurityFailureClassification,
@@ -128,7 +127,7 @@ function configuredRootPath(scope: ManagedScope): string {
128
127
  const canonical = fs.realpathSync.native(candidate);
129
128
  return suffix.length === 0 ? canonical : path.join(canonical, ...suffix);
130
129
  } catch (error) {
131
- if (!hasFsCode(error, "ENOENT")) throw error;
130
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
132
131
  const parent = path.dirname(candidate);
133
132
  if (parent === candidate) throw new Error("Configured managed root is unavailable.");
134
133
  suffix.unshift(path.basename(candidate));
@@ -178,23 +177,6 @@ export type ManagedScopeResolution =
178
177
  cause?: { readonly classification: string; readonly diagnostic?: string };
179
178
  };
180
179
 
181
- function managedScopeFailureCause(error: unknown): { readonly classification: string } {
182
- return { classification: managedSecurityFailureClassification(error) ?? "binding_invalid" };
183
- }
184
-
185
- const managedScopeFailureCodes = new Set([
186
- "atomic_unavailable",
187
- "invalid_request",
188
- "durability_failed",
189
- "durability_not_provable",
190
- ]);
191
-
192
- function managedScopeFailureMessage(error: unknown, fallback: string): string {
193
- const classification = managedSecurityFailureClassification(error);
194
- if (classification) return classification;
195
- return error instanceof Error && managedScopeFailureCodes.has(error.message) ? error.message : fallback;
196
- }
197
-
198
180
  export interface ManagedCandidate {
199
181
  sessionId: string;
200
182
  path: string;
@@ -295,7 +277,7 @@ function identityFor(cwd: string): NativeIdentity {
295
277
  function verifyExistingManagedScopeDirectory(pathname: string) {
296
278
  if (process.platform !== "win32") return verifyOwnerOnlyPathSecurity(pathname, "directory");
297
279
  const expected = fs.lstatSync(pathname, { bigint: true });
298
- if (!expected.isDirectory() || expected.isSymbolicLink()) throw new ManagedSessionSecurityError("reparse_point");
280
+ if (!expected.isDirectory() || expected.isSymbolicLink()) throw new Error("Unsafe managed directory");
299
281
  const verified = verifyOwnerOnlyPathSecurityExpected(pathname, "directory", expected.dev, expected.ino);
300
282
  const current = fs.lstatSync(pathname, { bigint: true });
301
283
  if (
@@ -304,7 +286,7 @@ function verifyExistingManagedScopeDirectory(pathname: string) {
304
286
  current.dev !== expected.dev ||
305
287
  current.ino !== expected.ino
306
288
  )
307
- throw new ManagedSessionSecurityError("identity_mismatch");
289
+ throw new Error("Managed session directory changed");
308
290
  return verified;
309
291
  }
310
292
 
@@ -423,18 +405,8 @@ function validateExistingBinding(scope: ManagedScope): ManagedScopeResolution |
423
405
  try {
424
406
  raw = captureManagedFileNoFollow(bindingPath).bytes.toString("utf8");
425
407
  } catch (error) {
426
- if (hasFsCode(error, "ENOENT")) return undefined;
427
- const classification = hasFsCode(error, "EACCES")
428
- ? "EACCES"
429
- : hasFsCode(error, "EPERM")
430
- ? "EPERM"
431
- : "binding_invalid";
432
- return {
433
- kind: "error",
434
- code: "binding_invalid",
435
- message: "The managed scope binding is invalid JSON.",
436
- cause: { classification },
437
- };
408
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
409
+ return { kind: "error", code: "binding_invalid", message: "The managed scope binding is invalid JSON." };
438
410
  }
439
411
  return validateBindingRaw(scope, raw);
440
412
  }
@@ -457,16 +429,14 @@ function resolveManagedScopeInternal(
457
429
  kind: "error",
458
430
  code: "sessions_root_unavailable",
459
431
  message: "The sessions root is not a safe directory.",
460
- cause: { classification: "reparse_point" },
461
432
  };
462
433
  }
463
434
  } catch (error) {
464
- if (!hasFsCode(error, "ENOENT")) {
435
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
465
436
  return {
466
437
  kind: "error",
467
438
  code: "sessions_root_unavailable",
468
439
  message: "The sessions root could not be inspected.",
469
- cause: managedScopeFailureCause(error),
470
440
  };
471
441
  }
472
442
  }
@@ -492,28 +462,21 @@ function resolveManagedScopeInternal(
492
462
  kind: "error",
493
463
  code: "sessions_root_unavailable",
494
464
  message: "The sessions root is not a safe directory.",
495
- cause: { classification: "reparse_point" },
496
465
  };
497
466
  }
498
467
  } catch (error) {
499
- if (!hasFsCode(error, "ENOENT")) {
468
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
500
469
  return {
501
470
  kind: "error",
502
471
  code: "sessions_root_unavailable",
503
472
  message: "The sessions root could not be inspected.",
504
- cause: managedScopeFailureCause(error),
505
473
  };
506
474
  }
507
475
  }
508
476
  try {
509
477
  const directory = fs.lstatSync(scope.directoryPath);
510
478
  if (!directory.isDirectory() || directory.isSymbolicLink()) {
511
- return {
512
- kind: "error",
513
- code: "binding_invalid",
514
- message: "The managed scope path is not a safe directory.",
515
- cause: { classification: "reparse_point" },
516
- };
479
+ return { kind: "error", code: "binding_invalid", message: "The managed scope path is not a safe directory." };
517
480
  }
518
481
  const security = validateNativeSecurityResult(
519
482
  verifyExistingManagedScopeDirectory(scope.directoryPath),
@@ -525,17 +488,11 @@ function resolveManagedScopeInternal(
525
488
  kind: "error",
526
489
  code: "binding_invalid",
527
490
  message: "The managed scope security could not be verified.",
528
- cause: { classification: security.code ?? "acl_verify_failed" },
529
491
  };
530
492
  }
531
493
  } catch (error) {
532
- if (!hasFsCode(error, "ENOENT")) {
533
- return {
534
- kind: "error",
535
- code: "binding_invalid",
536
- message: "The managed scope path could not be inspected.",
537
- cause: managedScopeFailureCause(error),
538
- };
494
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
495
+ return { kind: "error", code: "binding_invalid", message: "The managed scope path could not be inspected." };
539
496
  }
540
497
  }
541
498
  return validateExistingBinding(scope) ?? { kind: "resolved", scope };
@@ -600,7 +557,7 @@ function fsyncManagedParent(pathname: string): void {
600
557
  try {
601
558
  descriptor = fs.openSync(parent, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
602
559
  } catch (error) {
603
- if (hasFsCode(error, "ENOENT") && path.dirname(parent) !== parent) {
560
+ if ((error as NodeJS.ErrnoException).code === "ENOENT" && path.dirname(parent) !== parent) {
604
561
  parent = path.dirname(parent);
605
562
  continue;
606
563
  }
@@ -822,7 +779,7 @@ export async function ensureManagedScope(
822
779
  try {
823
780
  await publishManagedFileNoReplace(bindingPath, new TextEncoder().encode(binding), undefined, root, policy);
824
781
  } catch (error) {
825
- if (!(error instanceof Error) || error.message !== "destination_conflict") throw error;
782
+ if ((error as Error).message !== "destination_conflict") throw error;
826
783
  bindingCollision = true;
827
784
  }
828
785
  const validated = validateExistingBinding(scope);
@@ -831,14 +788,14 @@ export async function ensureManagedScope(
831
788
 
832
789
  const preparedDirectory = fs.lstatSync(scope.directoryPath, { bigint: true });
833
790
  if (!preparedDirectory.isDirectory() || preparedDirectory.isSymbolicLink())
834
- throw new ManagedSessionSecurityError("reparse_point");
791
+ throw new Error("Managed session directory changed");
835
792
  managedDirectoryIdentities.set(scope, { dev: preparedDirectory.dev, ino: preparedDirectory.ino });
836
793
  return { kind: "resolved", scope };
837
794
  } catch (error) {
838
795
  const publication = error instanceof ManagedPublishError ? error : undefined;
839
796
  const message =
840
797
  publication?.classification ??
841
- managedScopeFailureMessage(error, "The managed scope could not be initialized.");
798
+ (error instanceof Error ? error.message : "The managed scope could not be initialized.");
842
799
  const code =
843
800
  message === "atomic_unavailable" ||
844
801
  message === "invalid_request" ||
@@ -846,13 +803,14 @@ export async function ensureManagedScope(
846
803
  message === "durability_not_provable"
847
804
  ? message
848
805
  : "binding_invalid";
806
+
849
807
  return {
850
808
  kind: "error",
851
809
  code,
852
810
  message,
853
- cause: publication
854
- ? { classification: publication.classification, diagnostic: publication.diagnostic }
855
- : managedScopeFailureCause(error),
811
+ ...(publication
812
+ ? { cause: { classification: publication.classification, diagnostic: publication.diagnostic } }
813
+ : { cause: { classification: code } }),
856
814
  };
857
815
  }
858
816
  }
@@ -988,7 +946,7 @@ export function prepareManagedSessionScopeForWriteSync(
988
946
  try {
989
947
  store.publishNoReplaceSync(MANAGED_SESSION_BINDING_FILE, binding);
990
948
  } catch (error) {
991
- if (!(error instanceof Error) || error.message !== "destination_conflict") throw error;
949
+ if ((error as Error).message !== "destination_conflict") throw error;
992
950
  }
993
951
  stage = "binding_read";
994
952
  const capturedBinding = store.readExpected(MANAGED_SESSION_BINDING_FILE);
@@ -1026,7 +984,8 @@ export function prepareManagedSessionScopeForWriteSync(
1026
984
  } catch (error) {
1027
985
  const publication = error instanceof ManagedPublishError ? error : undefined;
1028
986
  const message =
1029
- publication?.classification ?? managedScopeFailureMessage(error, "Managed write protocol setup failed.");
987
+ publication?.classification ??
988
+ (error instanceof Error ? error.message : "Managed write protocol setup failed.");
1030
989
  const code =
1031
990
  message === "atomic_unavailable" ||
1032
991
  message === "invalid_request" ||
@@ -1034,14 +993,14 @@ export function prepareManagedSessionScopeForWriteSync(
1034
993
  message === "durability_not_provable"
1035
994
  ? message
1036
995
  : "binding_invalid";
1037
-
996
+ const securityClassification = managedSecurityFailureClassification(error);
1038
997
  return {
1039
998
  kind: "error",
1040
999
  code,
1041
1000
  message,
1042
1001
  cause: publication
1043
1002
  ? { classification: publication.classification, diagnostic: publication.diagnostic }
1044
- : { ...managedScopeFailureCause(error), diagnostic: `prepare:${stage}` },
1003
+ : { classification: securityClassification ?? code, diagnostic: `prepare:${stage}` },
1045
1004
  };
1046
1005
  }
1047
1006
  }
@@ -1804,14 +1763,6 @@ function sameArtifactTreeSnapshot(left: NativeDirectoryTreeSnapshot, right: Nati
1804
1763
  return leftEntries.every((entry, index) => entry === rightEntries[index]);
1805
1764
  }
1806
1765
 
1807
- function artifactTreePayloadAbsent(snapshot: NativeDirectoryTreeSnapshot): boolean {
1808
- return (
1809
- snapshot.entries.length === 1 &&
1810
- snapshot.entries[0]?.relativePath === "" &&
1811
- snapshot.entries[0].kind === "directory"
1812
- );
1813
- }
1814
-
1815
1766
  function assertRetainedArtifactsAuthority(pending: CleanupReceipt): void {
1816
1767
  if (!pending.detachedArtifactsPath) return;
1817
1768
  if (!pending.expectedArtifactsIdentity || !pending.expectedArtifactsTree) throw new Error("durability_failed");
@@ -1822,12 +1773,10 @@ function assertRetainedArtifactsAuthority(pending: CleanupReceipt): void {
1822
1773
  throw new Error("durability_failed");
1823
1774
  }
1824
1775
  const observed = artifactIdentityAt(pending.detachedArtifactsPath);
1825
- const observedTree = snapshotArtifactTree(pending.detachedArtifactsPath);
1826
1776
  if (
1827
1777
  !observed ||
1828
1778
  !sameArtifactRootIdentity(observed, pending.expectedArtifactsIdentity) ||
1829
- (!sameArtifactTreeSnapshot(observedTree, pending.expectedArtifactsTree) &&
1830
- !artifactTreePayloadAbsent(observedTree))
1779
+ !sameArtifactTreeSnapshot(snapshotArtifactTree(pending.detachedArtifactsPath), pending.expectedArtifactsTree)
1831
1780
  )
1832
1781
  throw new Error("binding_invalid");
1833
1782
  }
@@ -2053,15 +2002,6 @@ function snapshotArtifactTree(pathname: string): NativeDirectoryTreeSnapshot {
2053
2002
  return result.snapshot;
2054
2003
  }
2055
2004
 
2056
- function retainedArtifactPayloadAbsent(pathname: string): boolean {
2057
- try {
2058
- return artifactTreePayloadAbsent(snapshotArtifactTree(pathname));
2059
- } catch (error) {
2060
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return true;
2061
- throw error;
2062
- }
2063
- }
2064
-
2065
2005
  function nextCleanupReceipt(target: RetiredTarget, pending: CleanupReceipt | undefined): CleanupReceipt {
2066
2006
  const attempt = (pending?.attempt ?? 0) + 1;
2067
2007
  const directory = path.dirname(target.path);
@@ -3022,44 +2962,53 @@ export async function reconcileManagedTombstones(
3022
2962
  }
3023
2963
  if (deletion.kind === "cleanup_pending") {
3024
2964
  const retry = nextCleanupReceipt(target, active);
3025
- const pendingEvidence = cleanupPendingEvidence(retry, active, deletion);
3026
- await publishCleanupPending(scope, tombstone, pendingEvidence, lock);
3027
- if (deletion.phase === "artifacts") {
3028
- if (!retainedArtifactPayloadAbsent(deletion.detachedArtifactsPath)) continue;
3029
- await publishCleanupArtifactsRemoved(scope, tombstone, pendingEvidence, lock);
3030
- deletion = await new FileSessionStorage().deleteSessionVerified({
3031
- sessionsRoot: scope.sessionsRoot,
3032
- transcriptPath: target.path,
3033
- sessionId: target.sessionId,
3034
- cwd: target.cwd,
3035
- transcriptIdentity: target.identity,
3036
- plannedArtifactsPath: pendingEvidence.plannedArtifactsPath,
3037
- plannedTranscriptPath: pendingEvidence.plannedTranscriptPath,
2965
+ await publishCleanupPending(
2966
+ scope,
2967
+ tombstone,
2968
+ {
2969
+ ...retry,
2970
+ expectedArtifactsIdentity:
2971
+ deletion.phase === "artifacts"
2972
+ ? deletion.artifactsIdentity
2973
+ : active.expectedArtifactsIdentity,
2974
+ expectedArtifactsTree:
2975
+ deletion.phase === "artifacts" ? deletion.artifactsTree : active.expectedArtifactsTree,
2976
+ detachedArtifactsPath:
2977
+ deletion.phase === "artifacts"
2978
+ ? deletion.detachedArtifactsPath
2979
+ : active.detachedArtifactsPath,
3038
2980
  detachedTranscriptPath:
3039
- pendingEvidence.detachedTranscriptPath ?? observedPending?.detachedTranscriptPath,
3040
- retainedArtifactsSuccessorPath: pendingEvidence.retainedArtifactsSuccessorPath,
3041
- retainedArtifactsPlaceholderPath: pendingEvidence.retainedArtifactsPlaceholderPath,
3042
- retainedArtifactsUnknownPath: pendingEvidence.retainedArtifactsUnknownPath,
3043
- retainedTranscriptSuccessorPath: pendingEvidence.retainedTranscriptSuccessorPath,
3044
- retainedTranscriptPlaceholderPath: pendingEvidence.retainedTranscriptPlaceholderPath,
3045
- retainedTranscriptUnknownPath: pendingEvidence.retainedTranscriptUnknownPath,
3046
- artifactsRemoved: true,
3047
- });
3048
- if (deletion.kind === "cleanup_pending") {
3049
- if (
3050
- deletion.phase !== "transcript" ||
3051
- deletion.detachedTranscriptPath !== pendingEvidence.plannedTranscriptPath
3052
- )
3053
- throw new Error("durability_failed");
3054
- const followup = nextCleanupReceipt(target, pendingEvidence);
3055
- await publishCleanupPending(
3056
- scope,
3057
- tombstone,
3058
- cleanupPendingEvidence(followup, pendingEvidence, deletion),
3059
- lock,
3060
- );
3061
- }
3062
- }
2981
+ deletion.phase === "transcript"
2982
+ ? deletion.detachedTranscriptPath
2983
+ : active.detachedTranscriptPath,
2984
+ retainedArtifactsSuccessorPath:
2985
+ deletion.phase === "artifacts"
2986
+ ? deletion.retainedSuccessorPath
2987
+ : active.retainedArtifactsSuccessorPath,
2988
+ retainedArtifactsPlaceholderPath:
2989
+ deletion.phase === "artifacts"
2990
+ ? deletion.retainedPlaceholderPath
2991
+ : active.retainedArtifactsPlaceholderPath,
2992
+ retainedArtifactsUnknownPath:
2993
+ deletion.phase === "artifacts"
2994
+ ? deletion.retainedUnknownPath
2995
+ : active.retainedArtifactsUnknownPath,
2996
+ retainedTranscriptSuccessorPath:
2997
+ deletion.phase === "transcript"
2998
+ ? deletion.retainedSuccessorPath
2999
+ : active.retainedTranscriptSuccessorPath,
3000
+ retainedTranscriptPlaceholderPath:
3001
+ deletion.phase === "transcript"
3002
+ ? deletion.retainedPlaceholderPath
3003
+ : active.retainedTranscriptPlaceholderPath,
3004
+ retainedTranscriptUnknownPath:
3005
+ deletion.phase === "transcript"
3006
+ ? deletion.retainedUnknownPath
3007
+ : active.retainedTranscriptUnknownPath,
3008
+ },
3009
+ lock,
3010
+ );
3011
+ continue;
3063
3012
  }
3064
3013
  fsyncManagedParent(target.path);
3065
3014
  await publishCleanupCompleted(scope, tombstone, target, lock);
@@ -3073,7 +3022,7 @@ export async function reconcileManagedTombstones(
3073
3022
  }
3074
3023
  }
3075
3024
  } finally {
3076
- if (lock) await lock.release();
3025
+ if (lock) await lock.release().catch(() => undefined);
3077
3026
  }
3078
3027
  }
3079
3028
  }
@@ -3102,7 +3051,8 @@ export async function prepareManagedSessionScopeForWrite(
3102
3051
  } catch (error) {
3103
3052
  const publication = error instanceof ManagedPublishError ? error : undefined;
3104
3053
  const message =
3105
- publication?.classification ?? managedScopeFailureMessage(error, "Managed write protocol setup failed.");
3054
+ publication?.classification ??
3055
+ (error instanceof Error ? error.message : "Managed write protocol setup failed.");
3106
3056
  const code =
3107
3057
  message === "atomic_unavailable" ||
3108
3058
  message === "invalid_request" ||
@@ -3114,9 +3064,9 @@ export async function prepareManagedSessionScopeForWrite(
3114
3064
  kind: "error",
3115
3065
  code,
3116
3066
  message,
3117
- cause: publication
3118
- ? { classification: publication.classification, diagnostic: publication.diagnostic }
3119
- : managedScopeFailureCause(error),
3067
+ ...(publication
3068
+ ? { cause: { classification: publication.classification, diagnostic: publication.diagnostic } }
3069
+ : { cause: { classification: code } }),
3120
3070
  };
3121
3071
  }
3122
3072
  }
@@ -3446,7 +3396,7 @@ export async function openManagedCandidateForWrite(
3446
3396
  : "Managed migration failed.",
3447
3397
  };
3448
3398
  } finally {
3449
- if (lock) await lock.release();
3399
+ if (lock) await lock.release().catch(() => undefined);
3450
3400
  }
3451
3401
  }
3452
3402
 
@@ -3598,52 +3548,51 @@ export async function deleteManagedSessionCandidate(
3598
3548
  )
3599
3549
  throw new Error("durability_failed");
3600
3550
  const retry = nextCleanupReceipt(target, active);
3601
- const pendingEvidence = cleanupPendingEvidence(retry, active, deletion);
3602
- await publishCleanupPending(scope, tombstone, pendingEvidence, lock);
3551
+ await publishCleanupPending(scope, tombstone, cleanupPendingEvidence(retry, active, deletion), lock);
3603
3552
  if (deletion.phase === "artifacts") {
3604
- if (!retainedArtifactPayloadAbsent(deletion.detachedArtifactsPath))
3605
- return {
3606
- kind: "cleanup_pending",
3607
- tombstonePath: tombstone,
3608
- phase: deletion.phase,
3609
- message: "Exact cleanup remains pending because descriptor-bound final deletion is unavailable.",
3610
- };
3611
- await publishCleanupArtifactsRemoved(scope, tombstone, pendingEvidence, lock);
3553
+ // A typed retained artifact root proves durable canonical absence; advance
3554
+ // to the transcript phase instead of stalling on the retained evidence.
3555
+ await publishCleanupArtifactsRemoved(scope, tombstone, active, lock);
3612
3556
  deletion = await new FileSessionStorage().deleteSessionVerified({
3613
3557
  sessionsRoot: scope.sessionsRoot,
3614
3558
  transcriptPath: target.path,
3615
3559
  sessionId: target.sessionId,
3616
3560
  cwd: target.cwd,
3617
3561
  transcriptIdentity: target.identity,
3618
- plannedArtifactsPath: pendingEvidence.plannedArtifactsPath,
3619
- plannedTranscriptPath: pendingEvidence.plannedTranscriptPath,
3620
- detachedTranscriptPath:
3621
- pendingEvidence.detachedTranscriptPath ?? observedPending?.detachedTranscriptPath,
3622
- retainedArtifactsSuccessorPath: pendingEvidence.retainedArtifactsSuccessorPath,
3623
- retainedArtifactsPlaceholderPath: pendingEvidence.retainedArtifactsPlaceholderPath,
3624
- retainedArtifactsUnknownPath: pendingEvidence.retainedArtifactsUnknownPath,
3625
- retainedTranscriptSuccessorPath: pendingEvidence.retainedTranscriptSuccessorPath,
3626
- retainedTranscriptPlaceholderPath: pendingEvidence.retainedTranscriptPlaceholderPath,
3627
- retainedTranscriptUnknownPath: pendingEvidence.retainedTranscriptUnknownPath,
3562
+ plannedArtifactsPath: active.plannedArtifactsPath,
3563
+ plannedTranscriptPath: active.plannedTranscriptPath,
3564
+ detachedTranscriptPath: active.detachedTranscriptPath ?? observedPending?.detachedTranscriptPath,
3565
+ retainedArtifactsSuccessorPath: active.retainedArtifactsSuccessorPath,
3566
+ retainedArtifactsPlaceholderPath: active.retainedArtifactsPlaceholderPath,
3567
+ retainedArtifactsUnknownPath: active.retainedArtifactsUnknownPath,
3568
+ retainedTranscriptSuccessorPath: active.retainedTranscriptSuccessorPath,
3569
+ retainedTranscriptPlaceholderPath: active.retainedTranscriptPlaceholderPath,
3570
+ retainedTranscriptUnknownPath: active.retainedTranscriptUnknownPath,
3628
3571
  artifactsRemoved: true,
3629
3572
  });
3630
3573
  if (deletion.kind === "cleanup_pending") {
3631
- if (
3632
- deletion.phase !== "transcript" ||
3633
- deletion.detachedTranscriptPath !== pendingEvidence.plannedTranscriptPath
3634
- )
3635
- throw new Error("durability_failed");
3636
- const followup = nextCleanupReceipt(target, pendingEvidence);
3574
+ const followup = nextCleanupReceipt(target, active);
3637
3575
  await publishCleanupPending(
3638
3576
  scope,
3639
3577
  tombstone,
3640
- cleanupPendingEvidence(followup, pendingEvidence, deletion),
3578
+ cleanupPendingEvidence(followup, active, deletion),
3641
3579
  lock,
3642
3580
  );
3581
+ if (
3582
+ deletion.phase !== "transcript" ||
3583
+ deletion.detachedTranscriptPath !== active.plannedTranscriptPath
3584
+ )
3585
+ return {
3586
+ kind: "cleanup_pending",
3587
+ tombstonePath: tombstone,
3588
+ phase: deletion.phase,
3589
+ message:
3590
+ "Exact cleanup remains pending because descriptor-bound final deletion is unavailable.",
3591
+ };
3643
3592
  }
3644
3593
  }
3645
- // A retained transcript quarantine proves canonical absence and remains
3646
- // identity-bound in the durable pending receipt.
3594
+ // A typed retained transcript quarantine is durable canonical-absence
3595
+ // evidence — never a terminal byte-deletion claim; complete the receipt.
3647
3596
  }
3648
3597
  fsyncManagedParent(target.path);
3649
3598
  await publishCleanupCompleted(scope, tombstone, target, lock);
@@ -3653,6 +3602,6 @@ export async function deleteManagedSessionCandidate(
3653
3602
  const code = expectedFailure(error);
3654
3603
  return { kind: "error", code, message: error instanceof Error ? error.message : "Managed deletion failed." };
3655
3604
  } finally {
3656
- if (lock) await lock.release();
3605
+ if (lock) await lock.release().catch(() => undefined);
3657
3606
  }
3658
3607
  }