@gajae-code/coding-agent 0.12.5 → 0.12.6
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.
- package/CHANGELOG.md +32 -0
- package/README.md +4 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +10 -8
- package/dist/types/extensibility/extensions/runner.d.ts +9 -7
- package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
- package/dist/types/gjc-runtime/team-store.d.ts +0 -4
- package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
- package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +18 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -3
- package/dist/types/sdk/bus/index.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
- package/dist/types/sdk/host/host.d.ts +23 -2
- package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
- package/dist/types/session/agent-session.d.ts +5 -4
- package/dist/types/session/artifacts.d.ts +8 -5
- package/dist/types/session/attempt-record-store.d.ts +19 -0
- package/dist/types/session/client-bridge.d.ts +14 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +5 -2
- package/dist/types/session/messages.d.ts +4 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +32 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +178 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +3 -0
- package/dist/types/tools/output-meta.d.ts +37 -5
- package/dist/types/tools/tool-result.d.ts +1 -0
- package/dist/types/utils/artifact-id.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/capture-sticky-viewport-showcase.ts +131 -5
- package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
- package/scripts/verify-sticky-viewport-showcase.ts +620 -93
- package/src/config/model-profiles.ts +8 -0
- package/src/config/settings-schema.ts +17 -3
- package/src/cursor.ts +20 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +425 -151
- package/src/extensibility/extensions/runner.ts +80 -6
- package/src/extensibility/extensions/wrapper.ts +22 -15
- package/src/extensibility/runtime-skill-discovery.ts +4 -6
- package/src/gjc-runtime/launch-tmux.ts +309 -293
- package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
- package/src/gjc-runtime/psmux-detect.ts +52 -6
- package/src/gjc-runtime/team-launch.ts +50 -2
- package/src/gjc-runtime/team-runtime.ts +716 -251
- package/src/gjc-runtime/team-store.ts +25 -7
- package/src/gjc-runtime/team-workers.ts +161 -1
- package/src/gjc-runtime/tmux-common.ts +16 -4
- package/src/gjc-runtime/tmux-gc.ts +37 -5
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
- package/src/gjc-runtime/tmux-provider-context.ts +451 -0
- package/src/gjc-runtime/tmux-sessions.ts +409 -156
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
- package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
- package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/internal-urls/agent-protocol.ts +98 -38
- package/src/internal-urls/artifact-protocol.ts +28 -17
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/local-protocol.ts +39 -18
- package/src/internal-urls/registry-helpers.ts +62 -0
- package/src/modes/acp/acp-agent.ts +51 -13
- package/src/modes/acp/acp-client-bridge.ts +154 -11
- package/src/modes/components/gjc-bundle-settings.ts +2 -0
- package/src/modes/components/plugin-settings.ts +5 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/monitor.md +4 -4
- package/src/sdk/acp/adapter.ts +200 -9
- package/src/sdk/broker/broker.ts +93 -10
- package/src/sdk/broker/lifecycle-ledger.ts +227 -11
- package/src/sdk/broker/lifecycle.ts +691 -92
- package/src/sdk/bus/chat-daemon-control.ts +32 -6
- package/src/sdk/bus/index.ts +347 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
- package/src/sdk/bus/telegram-daemon.ts +283 -44
- package/src/sdk/bus/topic-registry.ts +133 -10
- package/src/sdk/host/host.ts +187 -9
- package/src/sdk/host/reverse-leases.ts +249 -42
- package/src/sdk/session.ts +7 -6
- package/src/session/agent-session.ts +930 -354
- package/src/session/artifacts.ts +211 -74
- package/src/session/attempt-record-store.ts +106 -0
- package/src/session/client-bridge.ts +15 -0
- package/src/session/internal/managed-session-scope.ts +252 -67
- package/src/session/internal/managed-session-storage.ts +105 -5
- package/src/session/messages.ts +32 -7
- package/src/session/session-manager.ts +19 -21
- package/src/session/session-storage.ts +146 -24
- package/src/session/streaming-output.ts +198 -40
- package/src/task/index.ts +45 -0
- package/src/task/provider-retry-status.ts +4 -1
- package/src/task/receipt.ts +4 -0
- package/src/task/render.ts +30 -0
- package/src/task/types.ts +34 -0
- package/src/tools/bash.ts +818 -239
- package/src/tools/eval.ts +99 -4
- package/src/tools/fetch.ts +77 -15
- package/src/tools/find.ts +7 -3
- package/src/tools/monitor.ts +7 -1
- package/src/tools/output-meta.ts +706 -116
- package/src/tools/search.ts +9 -4
- package/src/tools/tool-result.ts +1 -0
- package/src/utils/artifact-id.ts +6 -0
|
@@ -692,6 +692,7 @@ function inspectCandidate(filePath: string, provenance: "v2" | "legacy"): Manage
|
|
|
692
692
|
named.isSymbolicLink() ||
|
|
693
693
|
named.dev !== snapshot.identity.dev ||
|
|
694
694
|
named.ino !== snapshot.identity.ino ||
|
|
695
|
+
named.nlink !== 1n ||
|
|
695
696
|
Number(named.size) !== snapshot.identity.size ||
|
|
696
697
|
named.mtimeNs !== snapshot.identity.mtimeNs
|
|
697
698
|
)
|
|
@@ -706,6 +707,7 @@ function inspectCandidate(filePath: string, provenance: "v2" | "legacy"): Manage
|
|
|
706
707
|
canonicalPath: path.resolve(filePath),
|
|
707
708
|
sessionId: header.id,
|
|
708
709
|
...snapshot.identity,
|
|
710
|
+
nlink: named.nlink,
|
|
709
711
|
mtimeMs: Number(named.mtimeMs),
|
|
710
712
|
sha256: createHash("sha256").update(snapshot.bytes).digest("hex"),
|
|
711
713
|
},
|
|
@@ -1138,6 +1140,7 @@ function sameCandidate(left: ManagedCandidate, right: ManagedCandidate): boolean
|
|
|
1138
1140
|
left.path === right.path &&
|
|
1139
1141
|
left.identity.dev === right.identity.dev &&
|
|
1140
1142
|
left.identity.ino === right.identity.ino &&
|
|
1143
|
+
left.identity.nlink === right.identity.nlink &&
|
|
1141
1144
|
left.identity.size === right.identity.size &&
|
|
1142
1145
|
left.identity.mtimeNs === right.identity.mtimeNs &&
|
|
1143
1146
|
left.identity.sha256 === right.identity.sha256
|
|
@@ -1150,6 +1153,7 @@ function matchesExpectedResumeIdentity(candidate: ManagedCandidate, expected: Re
|
|
|
1150
1153
|
candidate.identity.sessionId === expected.sessionId &&
|
|
1151
1154
|
candidate.identity.dev === expected.dev &&
|
|
1152
1155
|
candidate.identity.ino === expected.ino &&
|
|
1156
|
+
candidate.identity.nlink === expected.nlink &&
|
|
1153
1157
|
candidate.identity.size === expected.size &&
|
|
1154
1158
|
candidate.identity.mtimeMs === expected.mtimeMs &&
|
|
1155
1159
|
candidate.identity.mtimeNs === expected.mtimeNs &&
|
|
@@ -1364,7 +1368,27 @@ function preparedReceiptMatches(
|
|
|
1364
1368
|
}
|
|
1365
1369
|
}
|
|
1366
1370
|
|
|
1367
|
-
type RetiredTarget = ManagedCandidate;
|
|
1371
|
+
type RetiredTarget = ManagedCandidate & { transcriptParentIdentity: { dev: bigint; ino: bigint } };
|
|
1372
|
+
|
|
1373
|
+
function authorizeRetiredTarget(scope: ManagedScope, candidate: ManagedCandidate): RetiredTarget | undefined {
|
|
1374
|
+
try {
|
|
1375
|
+
const before = fs.lstatSync(path.dirname(candidate.path), { bigint: true });
|
|
1376
|
+
if (!before.isDirectory()) return undefined;
|
|
1377
|
+
const verified = validateCandidateForScope(scope, candidate);
|
|
1378
|
+
const after = fs.lstatSync(path.dirname(candidate.path), { bigint: true });
|
|
1379
|
+
if (
|
|
1380
|
+
!verified ||
|
|
1381
|
+
!sameCandidate(verified, candidate) ||
|
|
1382
|
+
!after.isDirectory() ||
|
|
1383
|
+
after.dev !== before.dev ||
|
|
1384
|
+
after.ino !== before.ino
|
|
1385
|
+
)
|
|
1386
|
+
return undefined;
|
|
1387
|
+
return { ...candidate, transcriptParentIdentity: { dev: before.dev, ino: before.ino } };
|
|
1388
|
+
} catch {
|
|
1389
|
+
return undefined;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1368
1392
|
|
|
1369
1393
|
function retiredTargets(scope: ManagedScope, pathname: string): readonly RetiredTarget[] | undefined {
|
|
1370
1394
|
try {
|
|
@@ -1385,6 +1409,14 @@ function retiredTargets(scope: ManagedScope, pathname: string): readonly Retired
|
|
|
1385
1409
|
const identity = item.identity;
|
|
1386
1410
|
if (!identity || typeof identity !== "object" || Array.isArray(identity)) return undefined;
|
|
1387
1411
|
const fields = identity as Record<string, unknown>;
|
|
1412
|
+
const transcriptParentIdentity = item.transcriptParentIdentity;
|
|
1413
|
+
if (
|
|
1414
|
+
!transcriptParentIdentity ||
|
|
1415
|
+
typeof transcriptParentIdentity !== "object" ||
|
|
1416
|
+
Array.isArray(transcriptParentIdentity)
|
|
1417
|
+
)
|
|
1418
|
+
return undefined;
|
|
1419
|
+
const parentFields = transcriptParentIdentity as Record<string, unknown>;
|
|
1388
1420
|
if (
|
|
1389
1421
|
typeof item.path !== "string" ||
|
|
1390
1422
|
typeof item.sessionId !== "string" ||
|
|
@@ -1394,10 +1426,13 @@ function retiredTargets(scope: ManagedScope, pathname: string): readonly Retired
|
|
|
1394
1426
|
typeof fields.canonicalPath !== "string" ||
|
|
1395
1427
|
typeof fields.dev !== "string" ||
|
|
1396
1428
|
typeof fields.ino !== "string" ||
|
|
1429
|
+
typeof fields.nlink !== "string" ||
|
|
1397
1430
|
typeof fields.size !== "number" ||
|
|
1398
1431
|
typeof fields.mtimeMs !== "number" ||
|
|
1399
1432
|
typeof fields.mtimeNs !== "string" ||
|
|
1400
|
-
typeof fields.sha256 !== "string"
|
|
1433
|
+
typeof fields.sha256 !== "string" ||
|
|
1434
|
+
typeof parentFields.dev !== "string" ||
|
|
1435
|
+
typeof parentFields.ino !== "string"
|
|
1401
1436
|
)
|
|
1402
1437
|
return undefined;
|
|
1403
1438
|
const provenance =
|
|
@@ -1416,12 +1451,17 @@ function retiredTargets(scope: ManagedScope, pathname: string): readonly Retired
|
|
|
1416
1451
|
canonicalPath: fields.canonicalPath,
|
|
1417
1452
|
dev: BigInt(fields.dev),
|
|
1418
1453
|
ino: BigInt(fields.ino),
|
|
1454
|
+
nlink: BigInt(fields.nlink as string),
|
|
1419
1455
|
size: fields.size,
|
|
1420
1456
|
mtimeMs: fields.mtimeMs,
|
|
1421
1457
|
mtimeNs: BigInt(fields.mtimeNs),
|
|
1422
1458
|
sha256: fields.sha256,
|
|
1423
1459
|
sessionId: item.sessionId,
|
|
1424
1460
|
},
|
|
1461
|
+
transcriptParentIdentity: {
|
|
1462
|
+
dev: BigInt(parentFields.dev as string),
|
|
1463
|
+
ino: BigInt(parentFields.ino as string),
|
|
1464
|
+
},
|
|
1425
1465
|
});
|
|
1426
1466
|
}
|
|
1427
1467
|
return targets;
|
|
@@ -1498,7 +1538,9 @@ function retainedArtifactsRootMatches(record: Record<string, unknown>): boolean
|
|
|
1498
1538
|
type CleanupReceipt = {
|
|
1499
1539
|
attempt: number;
|
|
1500
1540
|
target: RetiredTarget;
|
|
1541
|
+
transcriptParentIdentity: { dev: bigint; ino: bigint };
|
|
1501
1542
|
expectedArtifactsIdentity?: SessionStorageFileIdentity;
|
|
1543
|
+
artifactsAbsentAtAuthorization?: true;
|
|
1502
1544
|
expectedArtifactsTree?: NativeDirectoryTreeSnapshot;
|
|
1503
1545
|
detachedArtifactsPath?: string;
|
|
1504
1546
|
detachedTranscriptPath?: string;
|
|
@@ -1538,7 +1580,12 @@ function cleanupReceipt(scope: ManagedScope, tombstone: string, receipt: Cleanup
|
|
|
1538
1580
|
cwd: receipt.target.cwd,
|
|
1539
1581
|
identity: receipt.target.identity,
|
|
1540
1582
|
},
|
|
1583
|
+
transcriptParentIdentity: {
|
|
1584
|
+
dev: receipt.transcriptParentIdentity.dev.toString(),
|
|
1585
|
+
ino: receipt.transcriptParentIdentity.ino.toString(),
|
|
1586
|
+
},
|
|
1541
1587
|
...(receipt.expectedArtifactsIdentity ? { expectedArtifactsIdentity: receipt.expectedArtifactsIdentity } : {}),
|
|
1588
|
+
...(receipt.artifactsAbsentAtAuthorization ? { artifactsAbsentAtAuthorization: true as const } : {}),
|
|
1542
1589
|
...(receipt.expectedArtifactsTree ? { expectedArtifactsTree: receipt.expectedArtifactsTree } : {}),
|
|
1543
1590
|
...(receipt.detachedArtifactsPath ? { detachedArtifactsPath: receipt.detachedArtifactsPath } : {}),
|
|
1544
1591
|
...(receipt.detachedTranscriptPath ? { detachedTranscriptPath: receipt.detachedTranscriptPath } : {}),
|
|
@@ -1670,15 +1717,49 @@ function transcriptRootMatchesTarget(pathname: string, target: RetiredTarget): b
|
|
|
1670
1717
|
}
|
|
1671
1718
|
}
|
|
1672
1719
|
|
|
1673
|
-
function
|
|
1720
|
+
function cleanupTranscriptParentMatches(target: RetiredTarget, pending: CleanupReceipt): boolean {
|
|
1721
|
+
try {
|
|
1722
|
+
const stat = fs.lstatSync(path.dirname(target.path), { bigint: true });
|
|
1723
|
+
return (
|
|
1724
|
+
stat.isDirectory() &&
|
|
1725
|
+
stat.dev === pending.transcriptParentIdentity.dev &&
|
|
1726
|
+
stat.ino === pending.transcriptParentIdentity.ino
|
|
1727
|
+
);
|
|
1728
|
+
} catch {
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function cleanupRootsAbsent(
|
|
1734
|
+
tombstone: string,
|
|
1735
|
+
target: RetiredTarget,
|
|
1736
|
+
pending: CleanupReceipt,
|
|
1737
|
+
includeTranscriptAuthority = true,
|
|
1738
|
+
): boolean {
|
|
1739
|
+
const transcriptParent = path.dirname(target.path);
|
|
1740
|
+
const transcriptParentMatches = (): boolean => {
|
|
1741
|
+
try {
|
|
1742
|
+
const stat = fs.lstatSync(transcriptParent, { bigint: true });
|
|
1743
|
+
return (
|
|
1744
|
+
stat.isDirectory() &&
|
|
1745
|
+
stat.dev === pending.transcriptParentIdentity.dev &&
|
|
1746
|
+
stat.ino === pending.transcriptParentIdentity.ino
|
|
1747
|
+
);
|
|
1748
|
+
} catch {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
if (!transcriptParentMatches()) return false;
|
|
1674
1753
|
const prefix = `${path.basename(tombstone, ".json")}.${stableOperationName(target)}.cleanup-pending-`;
|
|
1675
|
-
const activeTranscriptRoots =
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1754
|
+
const activeTranscriptRoots = includeTranscriptAuthority
|
|
1755
|
+
? new Set(
|
|
1756
|
+
[pending.plannedTranscriptPath, pending.detachedTranscriptPath].filter((pathname): pathname is string =>
|
|
1757
|
+
isQuarantinePath(target, pathname),
|
|
1758
|
+
),
|
|
1759
|
+
)
|
|
1760
|
+
: new Set<string>();
|
|
1680
1761
|
const roots = new Set<string>([
|
|
1681
|
-
target.path,
|
|
1762
|
+
...(includeTranscriptAuthority ? [target.path] : []),
|
|
1682
1763
|
target.path.slice(0, -6),
|
|
1683
1764
|
pending.plannedArtifactsPath,
|
|
1684
1765
|
deterministicRemovalRoot(pending.plannedArtifactsPath),
|
|
@@ -1746,7 +1827,52 @@ function cleanupRootsAbsent(tombstone: string, target: RetiredTarget, pending: C
|
|
|
1746
1827
|
if (!transcriptRootMatchesTarget(pathname, target)) throw new Error("durability_failed");
|
|
1747
1828
|
return false;
|
|
1748
1829
|
}
|
|
1749
|
-
return true;
|
|
1830
|
+
if (!includeTranscriptAuthority) return true;
|
|
1831
|
+
const pendingDirectories = [transcriptParent];
|
|
1832
|
+
const snapshots: Array<{ path: string; stat: fs.BigIntStats }> = [];
|
|
1833
|
+
let entryCount = 0;
|
|
1834
|
+
try {
|
|
1835
|
+
while (pendingDirectories.length > 0) {
|
|
1836
|
+
const directory = pendingDirectories.pop();
|
|
1837
|
+
if (!directory) return false;
|
|
1838
|
+
const before = fs.lstatSync(directory, { bigint: true });
|
|
1839
|
+
if (!before.isDirectory()) return false;
|
|
1840
|
+
if (
|
|
1841
|
+
directory === transcriptParent &&
|
|
1842
|
+
(before.dev !== pending.transcriptParentIdentity.dev || before.ino !== pending.transcriptParentIdentity.ino)
|
|
1843
|
+
)
|
|
1844
|
+
return false;
|
|
1845
|
+
snapshots.push({ path: directory, stat: before });
|
|
1846
|
+
const entries = fs.readdirSync(directory);
|
|
1847
|
+
entryCount += entries.length;
|
|
1848
|
+
if (entryCount > MANAGED_ARTIFACT_MAX_FILES) return false;
|
|
1849
|
+
for (const name of entries) {
|
|
1850
|
+
const pathname = path.join(directory, name);
|
|
1851
|
+
let stat: fs.BigIntStats;
|
|
1852
|
+
try {
|
|
1853
|
+
stat = fs.lstatSync(pathname, { bigint: true });
|
|
1854
|
+
} catch {
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
if (stat.dev === target.identity.dev && stat.ino === target.identity.ino) return false;
|
|
1858
|
+
if (stat.isDirectory()) pendingDirectories.push(pathname);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
for (const snapshot of snapshots) {
|
|
1862
|
+
const after = fs.lstatSync(snapshot.path, { bigint: true });
|
|
1863
|
+
if (
|
|
1864
|
+
!after.isDirectory() ||
|
|
1865
|
+
after.dev !== snapshot.stat.dev ||
|
|
1866
|
+
after.ino !== snapshot.stat.ino ||
|
|
1867
|
+
after.mtimeNs !== snapshot.stat.mtimeNs ||
|
|
1868
|
+
after.ctimeNs !== snapshot.stat.ctimeNs
|
|
1869
|
+
)
|
|
1870
|
+
return false;
|
|
1871
|
+
}
|
|
1872
|
+
return true;
|
|
1873
|
+
} catch {
|
|
1874
|
+
return false;
|
|
1875
|
+
}
|
|
1750
1876
|
}
|
|
1751
1877
|
|
|
1752
1878
|
function sameArtifactRootIdentity(left: SessionStorageFileIdentity, right: SessionStorageFileIdentity): boolean {
|
|
@@ -1757,7 +1883,16 @@ function sameArtifactTreeSnapshot(left: NativeDirectoryTreeSnapshot, right: Nati
|
|
|
1757
1883
|
if (left.rootDev !== right.rootDev || left.rootIno !== right.rootIno || left.entries.length !== right.entries.length)
|
|
1758
1884
|
return false;
|
|
1759
1885
|
const entryKey = (entry: NativeDirectoryTreeSnapshot["entries"][number]): string =>
|
|
1760
|
-
JSON.stringify([
|
|
1886
|
+
JSON.stringify([
|
|
1887
|
+
entry.relativePath,
|
|
1888
|
+
entry.kind,
|
|
1889
|
+
entry.dev,
|
|
1890
|
+
entry.ino,
|
|
1891
|
+
entry.nlink,
|
|
1892
|
+
entry.size,
|
|
1893
|
+
entry.mtimeNs,
|
|
1894
|
+
entry.sha256,
|
|
1895
|
+
]);
|
|
1761
1896
|
const leftEntries = left.entries.map(entryKey).sort();
|
|
1762
1897
|
const rightEntries = right.entries.map(entryKey).sort();
|
|
1763
1898
|
return leftEntries.every((entry, index) => entry === rightEntries[index]);
|
|
@@ -1839,6 +1974,7 @@ function artifactTreeSnapshot(value: unknown): NativeDirectoryTreeSnapshot | und
|
|
|
1839
1974
|
(item.kind === "file" || item.kind === "directory") &&
|
|
1840
1975
|
typeof item.dev === "string" &&
|
|
1841
1976
|
typeof item.ino === "string" &&
|
|
1977
|
+
typeof item.nlink === "string" &&
|
|
1842
1978
|
typeof item.size === "string" &&
|
|
1843
1979
|
typeof item.mtimeNs === "string" &&
|
|
1844
1980
|
typeof item.ctimeNs === "string" &&
|
|
@@ -1886,6 +2022,14 @@ function pendingCleanupReceipt(
|
|
|
1886
2022
|
const attempt = record.attempt;
|
|
1887
2023
|
const recorded = record.target as Record<string, unknown> | undefined;
|
|
1888
2024
|
const identity = recorded?.identity as Record<string, unknown> | undefined;
|
|
2025
|
+
const transcriptParentIdentity = record.transcriptParentIdentity as Record<string, unknown> | undefined;
|
|
2026
|
+
if (
|
|
2027
|
+
typeof transcriptParentIdentity?.dev === "string" &&
|
|
2028
|
+
typeof transcriptParentIdentity?.ino === "string" &&
|
|
2029
|
+
(transcriptParentIdentity.dev !== target.transcriptParentIdentity.dev.toString() ||
|
|
2030
|
+
transcriptParentIdentity.ino !== target.transcriptParentIdentity.ino.toString())
|
|
2031
|
+
)
|
|
2032
|
+
throw new Error("durability_failed");
|
|
1889
2033
|
if (
|
|
1890
2034
|
record.schemaVersion !== 2 ||
|
|
1891
2035
|
record.state !== "cleanup_pending" ||
|
|
@@ -1899,9 +2043,20 @@ function pendingCleanupReceipt(
|
|
|
1899
2043
|
recorded.cwd !== target.cwd ||
|
|
1900
2044
|
identity?.dev !== String(target.identity.dev) ||
|
|
1901
2045
|
identity.ino !== String(target.identity.ino) ||
|
|
2046
|
+
identity.nlink !== String(target.identity.nlink) ||
|
|
1902
2047
|
identity.size !== target.identity.size ||
|
|
1903
2048
|
identity.mtimeNs !== String(target.identity.mtimeNs) ||
|
|
1904
2049
|
identity.sha256 !== target.identity.sha256 ||
|
|
2050
|
+
!transcriptParentIdentity ||
|
|
2051
|
+
typeof transcriptParentIdentity.dev !== "string" ||
|
|
2052
|
+
typeof transcriptParentIdentity.ino !== "string" ||
|
|
2053
|
+
!/^[0-9]+$/.test(transcriptParentIdentity.dev) ||
|
|
2054
|
+
!/^[0-9]+$/.test(transcriptParentIdentity.ino) ||
|
|
2055
|
+
transcriptParentIdentity.dev !== target.transcriptParentIdentity.dev.toString() ||
|
|
2056
|
+
transcriptParentIdentity.ino !== target.transcriptParentIdentity.ino.toString() ||
|
|
2057
|
+
(latest !== undefined &&
|
|
2058
|
+
(transcriptParentIdentity.dev !== latest.transcriptParentIdentity.dev.toString() ||
|
|
2059
|
+
transcriptParentIdentity.ino !== latest.transcriptParentIdentity.ino.toString())) ||
|
|
1905
2060
|
!isQuarantinePath(target, record.plannedArtifactsPath) ||
|
|
1906
2061
|
!isQuarantinePath(target, record.plannedTranscriptPath) ||
|
|
1907
2062
|
record.plannedArtifactsPath === record.plannedTranscriptPath ||
|
|
@@ -1923,6 +2078,8 @@ function pendingCleanupReceipt(
|
|
|
1923
2078
|
!isRetainedNativePath(target, record.retainedTranscriptUnknownPath))
|
|
1924
2079
|
)
|
|
1925
2080
|
throw new Error("durability_failed");
|
|
2081
|
+
if (record.artifactsAbsentAtAuthorization !== undefined && record.artifactsAbsentAtAuthorization !== true)
|
|
2082
|
+
throw new Error("durability_failed");
|
|
1926
2083
|
const artifact = record.expectedArtifactsIdentity as Record<string, unknown> | undefined;
|
|
1927
2084
|
const expectedArtifactsIdentity = artifact
|
|
1928
2085
|
? typeof artifact.dev === "string" &&
|
|
@@ -1940,6 +2097,8 @@ function pendingCleanupReceipt(
|
|
|
1940
2097
|
: undefined
|
|
1941
2098
|
: undefined;
|
|
1942
2099
|
if (artifact && !expectedArtifactsIdentity) throw new Error("durability_failed");
|
|
2100
|
+
if (record.artifactsAbsentAtAuthorization === true && expectedArtifactsIdentity)
|
|
2101
|
+
throw new Error("durability_failed");
|
|
1943
2102
|
const expectedArtifactsTree =
|
|
1944
2103
|
record.expectedArtifactsTree === undefined ? undefined : artifactTreeSnapshot(record.expectedArtifactsTree);
|
|
1945
2104
|
if (record.expectedArtifactsTree !== undefined && !expectedArtifactsTree) throw new Error("durability_failed");
|
|
@@ -1958,7 +2117,12 @@ function pendingCleanupReceipt(
|
|
|
1958
2117
|
latest = {
|
|
1959
2118
|
attempt,
|
|
1960
2119
|
target,
|
|
2120
|
+
transcriptParentIdentity: {
|
|
2121
|
+
dev: BigInt(transcriptParentIdentity.dev as string),
|
|
2122
|
+
ino: BigInt(transcriptParentIdentity.ino as string),
|
|
2123
|
+
},
|
|
1961
2124
|
expectedArtifactsIdentity,
|
|
2125
|
+
artifactsAbsentAtAuthorization: record.artifactsAbsentAtAuthorization === true ? true : undefined,
|
|
1962
2126
|
expectedArtifactsTree,
|
|
1963
2127
|
detachedArtifactsPath: record.detachedArtifactsPath as string | undefined,
|
|
1964
2128
|
detachedTranscriptPath: record.detachedTranscriptPath as string | undefined,
|
|
@@ -1983,7 +2147,13 @@ function artifactIdentityForCleanup(target: RetiredTarget): SessionStorageFileId
|
|
|
1983
2147
|
try {
|
|
1984
2148
|
const stat = fs.lstatSync(target.path.slice(0, -6), { bigint: true });
|
|
1985
2149
|
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("unsafe_artifacts");
|
|
1986
|
-
return {
|
|
2150
|
+
return {
|
|
2151
|
+
dev: stat.dev,
|
|
2152
|
+
ino: stat.ino,
|
|
2153
|
+
size: Number(stat.size),
|
|
2154
|
+
mtimeNs: stat.mtimeNs,
|
|
2155
|
+
sha256: "",
|
|
2156
|
+
};
|
|
1987
2157
|
} catch (error) {
|
|
1988
2158
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
1989
2159
|
throw error;
|
|
@@ -2005,6 +2175,7 @@ function snapshotArtifactTree(pathname: string): NativeDirectoryTreeSnapshot {
|
|
|
2005
2175
|
function nextCleanupReceipt(target: RetiredTarget, pending: CleanupReceipt | undefined): CleanupReceipt {
|
|
2006
2176
|
const attempt = (pending?.attempt ?? 0) + 1;
|
|
2007
2177
|
const directory = path.dirname(target.path);
|
|
2178
|
+
const transcriptParentIdentity = pending?.transcriptParentIdentity ?? target.transcriptParentIdentity;
|
|
2008
2179
|
const operation = stableOperationName(target);
|
|
2009
2180
|
const expectedArtifactsIdentity = pending?.expectedArtifactsIdentity ?? artifactIdentityForCleanup(target);
|
|
2010
2181
|
const expectedArtifactsTree =
|
|
@@ -2014,7 +2185,10 @@ function nextCleanupReceipt(target: RetiredTarget, pending: CleanupReceipt | und
|
|
|
2014
2185
|
return {
|
|
2015
2186
|
attempt,
|
|
2016
2187
|
target,
|
|
2188
|
+
transcriptParentIdentity,
|
|
2017
2189
|
expectedArtifactsIdentity,
|
|
2190
|
+
artifactsAbsentAtAuthorization:
|
|
2191
|
+
pending?.artifactsAbsentAtAuthorization ?? (expectedArtifactsIdentity ? undefined : true),
|
|
2018
2192
|
expectedArtifactsTree,
|
|
2019
2193
|
detachedArtifactsPath: pending?.detachedArtifactsPath,
|
|
2020
2194
|
retainedArtifactsSuccessorPath: pending?.retainedArtifactsSuccessorPath,
|
|
@@ -2407,6 +2581,7 @@ function sameArtifactTree(
|
|
|
2407
2581
|
entry.kind,
|
|
2408
2582
|
entry.dev,
|
|
2409
2583
|
entry.ino,
|
|
2584
|
+
entry.nlink,
|
|
2410
2585
|
entry.size,
|
|
2411
2586
|
entry.mtimeNs,
|
|
2412
2587
|
entry.ctimeNs,
|
|
@@ -2889,11 +3064,8 @@ export async function reconcileManagedTombstones(
|
|
|
2889
3064
|
if (
|
|
2890
3065
|
cleanupArtifactsRemoved(scope, tombstone, target, observedPending.attempt) &&
|
|
2891
3066
|
cleanupRootsAbsent(tombstone, target, observedPending)
|
|
2892
|
-
)
|
|
2893
|
-
fsyncManagedParent(target.path);
|
|
2894
|
-
await publishCleanupCompleted(scope, tombstone, target, lock);
|
|
3067
|
+
)
|
|
2895
3068
|
continue;
|
|
2896
|
-
}
|
|
2897
3069
|
} else throw error;
|
|
2898
3070
|
}
|
|
2899
3071
|
const verified = observedPending ? target : validateCandidateForScope(scope, target);
|
|
@@ -2908,13 +3080,16 @@ export async function reconcileManagedTombstones(
|
|
|
2908
3080
|
: (observedPending ?? nextCleanupReceipt(target, undefined));
|
|
2909
3081
|
if (!observedPending || discoveredDetach || requiresFreshCleanupPlan(observedPending))
|
|
2910
3082
|
await publishCleanupPending(scope, tombstone, active, lock);
|
|
3083
|
+
if (!cleanupTranscriptParentMatches(target, active)) continue;
|
|
2911
3084
|
let deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
2912
3085
|
sessionsRoot: scope.sessionsRoot,
|
|
2913
3086
|
transcriptPath: target.path,
|
|
2914
3087
|
sessionId: target.sessionId,
|
|
2915
3088
|
cwd: target.cwd,
|
|
2916
3089
|
transcriptIdentity: target.identity,
|
|
3090
|
+
transcriptParentIdentity: target.transcriptParentIdentity,
|
|
2917
3091
|
expectedArtifactsIdentity: active.expectedArtifactsIdentity,
|
|
3092
|
+
artifactsAbsentAtAuthorization: active.artifactsAbsentAtAuthorization,
|
|
2918
3093
|
expectedArtifactsTree: active.expectedArtifactsTree,
|
|
2919
3094
|
detachedArtifactsPath:
|
|
2920
3095
|
active.detachedArtifactsPath ??
|
|
@@ -2940,6 +3115,8 @@ export async function reconcileManagedTombstones(
|
|
|
2940
3115
|
: {}),
|
|
2941
3116
|
});
|
|
2942
3117
|
if (deletion.kind === "artifacts_removed") {
|
|
3118
|
+
if (!cleanupRootsAbsent(tombstone, target, active, false)) continue;
|
|
3119
|
+
if (!cleanupTranscriptParentMatches(target, active)) continue;
|
|
2943
3120
|
await publishCleanupArtifactsRemoved(scope, tombstone, active, lock);
|
|
2944
3121
|
deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
2945
3122
|
sessionsRoot: scope.sessionsRoot,
|
|
@@ -2947,6 +3124,7 @@ export async function reconcileManagedTombstones(
|
|
|
2947
3124
|
sessionId: target.sessionId,
|
|
2948
3125
|
cwd: target.cwd,
|
|
2949
3126
|
transcriptIdentity: target.identity,
|
|
3127
|
+
transcriptParentIdentity: target.transcriptParentIdentity,
|
|
2950
3128
|
plannedArtifactsPath: active.plannedArtifactsPath,
|
|
2951
3129
|
plannedTranscriptPath: active.plannedTranscriptPath,
|
|
2952
3130
|
detachedTranscriptPath: active.detachedTranscriptPath ?? observedPending?.detachedTranscriptPath,
|
|
@@ -3010,6 +3188,7 @@ export async function reconcileManagedTombstones(
|
|
|
3010
3188
|
);
|
|
3011
3189
|
continue;
|
|
3012
3190
|
}
|
|
3191
|
+
if (!cleanupRootsAbsent(tombstone, target, active)) continue;
|
|
3013
3192
|
fsyncManagedParent(target.path);
|
|
3014
3193
|
await publishCleanupCompleted(scope, tombstone, target, lock);
|
|
3015
3194
|
} catch (error) {
|
|
@@ -3432,7 +3611,11 @@ export async function deleteManagedSessionCandidate(
|
|
|
3432
3611
|
let targets = retiredTargets(scope, tombstone);
|
|
3433
3612
|
if (!targets) {
|
|
3434
3613
|
if (!current) throw new Error("source_changed");
|
|
3435
|
-
|
|
3614
|
+
const authorizedTargets = [current, ...(paired ? [paired] : [])].map(target =>
|
|
3615
|
+
authorizeRetiredTarget(scope, target),
|
|
3616
|
+
);
|
|
3617
|
+
if (authorizedTargets.some(target => target === undefined)) throw new Error("source_changed");
|
|
3618
|
+
targets = authorizedTargets as RetiredTarget[];
|
|
3436
3619
|
lock.assertOwned();
|
|
3437
3620
|
try {
|
|
3438
3621
|
await publishManagedTombstone(
|
|
@@ -3447,6 +3630,10 @@ export async function deleteManagedSessionCandidate(
|
|
|
3447
3630
|
cwd: target.cwd,
|
|
3448
3631
|
provenance: target.provenance,
|
|
3449
3632
|
identity: target.identity,
|
|
3633
|
+
transcriptParentIdentity: {
|
|
3634
|
+
dev: target.transcriptParentIdentity.dev.toString(),
|
|
3635
|
+
ino: target.transcriptParentIdentity.ino.toString(),
|
|
3636
|
+
},
|
|
3450
3637
|
})),
|
|
3451
3638
|
},
|
|
3452
3639
|
lock.assertOwned,
|
|
@@ -3462,6 +3649,7 @@ export async function deleteManagedSessionCandidate(
|
|
|
3462
3649
|
lock.assertOwned();
|
|
3463
3650
|
const pending = pendingCleanupReceipt(scope, tombstone, target);
|
|
3464
3651
|
const observedPending = pending ? probePlannedCleanupDetach(target, pending) : undefined;
|
|
3652
|
+
if (cleanupCompleted(scope, tombstone, target)) continue;
|
|
3465
3653
|
try {
|
|
3466
3654
|
fs.lstatSync(target.path);
|
|
3467
3655
|
} catch (error) {
|
|
@@ -3470,14 +3658,16 @@ export async function deleteManagedSessionCandidate(
|
|
|
3470
3658
|
if (
|
|
3471
3659
|
cleanupArtifactsRemoved(scope, tombstone, target, observedPending.attempt) &&
|
|
3472
3660
|
cleanupRootsAbsent(tombstone, target, observedPending)
|
|
3473
|
-
)
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3661
|
+
)
|
|
3662
|
+
return {
|
|
3663
|
+
kind: "cleanup_pending",
|
|
3664
|
+
tombstonePath: tombstone,
|
|
3665
|
+
phase: "transcript",
|
|
3666
|
+
message:
|
|
3667
|
+
"Exact cleanup remains pending because transcript authority disappeared without native deletion proof.",
|
|
3668
|
+
};
|
|
3478
3669
|
} else throw error;
|
|
3479
3670
|
}
|
|
3480
|
-
if (cleanupCompleted(scope, tombstone, target)) continue;
|
|
3481
3671
|
deletedAny = true;
|
|
3482
3672
|
const verified = observedPending ? target : validateCandidateForScope(scope, target);
|
|
3483
3673
|
if (!verified || !sameCandidate(verified, target)) throw new Error("source_changed");
|
|
@@ -3491,13 +3681,24 @@ export async function deleteManagedSessionCandidate(
|
|
|
3491
3681
|
: (observedPending ?? nextCleanupReceipt(target, undefined));
|
|
3492
3682
|
if (!observedPending || discoveredDetach || requiresFreshCleanupPlan(observedPending))
|
|
3493
3683
|
await publishCleanupPending(scope, tombstone, active, lock);
|
|
3684
|
+
if (!cleanupTranscriptParentMatches(target, active))
|
|
3685
|
+
return {
|
|
3686
|
+
kind: "cleanup_pending",
|
|
3687
|
+
tombstonePath: tombstone,
|
|
3688
|
+
phase: cleanupArtifactsRemoved(scope, tombstone, target, pending?.attempt ?? active.attempt)
|
|
3689
|
+
? "transcript"
|
|
3690
|
+
: "artifacts",
|
|
3691
|
+
message: "Exact cleanup remains pending because transcript parent identity changed before mutation.",
|
|
3692
|
+
};
|
|
3494
3693
|
let deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
3495
3694
|
sessionsRoot: scope.sessionsRoot,
|
|
3496
3695
|
transcriptPath: target.path,
|
|
3497
3696
|
sessionId: target.sessionId,
|
|
3498
3697
|
cwd: target.cwd,
|
|
3499
3698
|
transcriptIdentity: target.identity,
|
|
3699
|
+
transcriptParentIdentity: target.transcriptParentIdentity,
|
|
3500
3700
|
expectedArtifactsIdentity: active.expectedArtifactsIdentity,
|
|
3701
|
+
artifactsAbsentAtAuthorization: active.artifactsAbsentAtAuthorization,
|
|
3501
3702
|
expectedArtifactsTree: active.expectedArtifactsTree,
|
|
3502
3703
|
detachedArtifactsPath: active.detachedArtifactsPath ?? observedPending?.detachedArtifactsPath,
|
|
3503
3704
|
detachedTranscriptPath:
|
|
@@ -3517,6 +3718,20 @@ export async function deleteManagedSessionCandidate(
|
|
|
3517
3718
|
: {}),
|
|
3518
3719
|
});
|
|
3519
3720
|
if (deletion.kind === "artifacts_removed") {
|
|
3721
|
+
if (!cleanupRootsAbsent(tombstone, target, active, false))
|
|
3722
|
+
return {
|
|
3723
|
+
kind: "cleanup_pending",
|
|
3724
|
+
tombstonePath: tombstone,
|
|
3725
|
+
phase: "artifacts",
|
|
3726
|
+
message: "Exact cleanup remains pending because artifact or side-path authority is retained.",
|
|
3727
|
+
};
|
|
3728
|
+
if (!cleanupTranscriptParentMatches(target, active))
|
|
3729
|
+
return {
|
|
3730
|
+
kind: "cleanup_pending",
|
|
3731
|
+
tombstonePath: tombstone,
|
|
3732
|
+
phase: "transcript",
|
|
3733
|
+
message: "Exact cleanup remains pending because transcript parent identity changed before mutation.",
|
|
3734
|
+
};
|
|
3520
3735
|
await publishCleanupArtifactsRemoved(scope, tombstone, active, lock);
|
|
3521
3736
|
deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
3522
3737
|
sessionsRoot: scope.sessionsRoot,
|
|
@@ -3524,6 +3739,7 @@ export async function deleteManagedSessionCandidate(
|
|
|
3524
3739
|
sessionId: target.sessionId,
|
|
3525
3740
|
cwd: target.cwd,
|
|
3526
3741
|
transcriptIdentity: target.identity,
|
|
3742
|
+
transcriptParentIdentity: target.transcriptParentIdentity,
|
|
3527
3743
|
plannedArtifactsPath: active.plannedArtifactsPath,
|
|
3528
3744
|
plannedTranscriptPath: active.plannedTranscriptPath,
|
|
3529
3745
|
detachedTranscriptPath: active.detachedTranscriptPath ?? observedPending?.detachedTranscriptPath,
|
|
@@ -3549,51 +3765,20 @@ export async function deleteManagedSessionCandidate(
|
|
|
3549
3765
|
throw new Error("durability_failed");
|
|
3550
3766
|
const retry = nextCleanupReceipt(target, active);
|
|
3551
3767
|
await publishCleanupPending(scope, tombstone, cleanupPendingEvidence(retry, active, deletion), lock);
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
transcriptPath: target.path,
|
|
3559
|
-
sessionId: target.sessionId,
|
|
3560
|
-
cwd: target.cwd,
|
|
3561
|
-
transcriptIdentity: target.identity,
|
|
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,
|
|
3571
|
-
artifactsRemoved: true,
|
|
3572
|
-
});
|
|
3573
|
-
if (deletion.kind === "cleanup_pending") {
|
|
3574
|
-
const followup = nextCleanupReceipt(target, active);
|
|
3575
|
-
await publishCleanupPending(
|
|
3576
|
-
scope,
|
|
3577
|
-
tombstone,
|
|
3578
|
-
cleanupPendingEvidence(followup, active, deletion),
|
|
3579
|
-
lock,
|
|
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
|
-
};
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
// A typed retained transcript quarantine is durable canonical-absence
|
|
3595
|
-
// evidence — never a terminal byte-deletion claim; complete the receipt.
|
|
3768
|
+
return {
|
|
3769
|
+
kind: "cleanup_pending",
|
|
3770
|
+
tombstonePath: tombstone,
|
|
3771
|
+
phase: deletion.phase,
|
|
3772
|
+
message: "Exact cleanup remains pending because descriptor-bound final deletion is unavailable.",
|
|
3773
|
+
};
|
|
3596
3774
|
}
|
|
3775
|
+
if (!cleanupRootsAbsent(tombstone, target, active))
|
|
3776
|
+
return {
|
|
3777
|
+
kind: "cleanup_pending",
|
|
3778
|
+
tombstonePath: tombstone,
|
|
3779
|
+
phase: "transcript",
|
|
3780
|
+
message: "Exact cleanup remains pending because transcript or artifact authority is retained.",
|
|
3781
|
+
};
|
|
3597
3782
|
fsyncManagedParent(target.path);
|
|
3598
3783
|
await publishCleanupCompleted(scope, tombstone, target, lock);
|
|
3599
3784
|
}
|