@gajae-code/coding-agent 0.18.6 → 0.18.7

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 (58) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/config/settings.d.ts +4 -0
  4. package/dist/types/discovery/helpers.d.ts +2 -2
  5. package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
  6. package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
  7. package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
  8. package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
  9. package/dist/types/sdk/host/host.d.ts +6 -0
  10. package/dist/types/sdk/router/session-router.d.ts +7 -0
  11. package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
  12. package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
  13. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
  14. package/dist/types/session/session-retirement.d.ts +47 -0
  15. package/dist/types/session/session-storage.d.ts +0 -46
  16. package/dist/types/session/session-transcript-header.d.ts +2 -0
  17. package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
  18. package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
  19. package/dist/types/session/task-artifact-owner.d.ts +10 -0
  20. package/package.json +7 -7
  21. package/scripts/resident-memory-bench.ts +174 -20
  22. package/src/capability/index.ts +1 -0
  23. package/src/capability/types.ts +4 -0
  24. package/src/cli/config-cli.ts +8 -18
  25. package/src/cli-main.ts +7 -8
  26. package/src/commands/sdk.ts +112 -21
  27. package/src/config/settings.ts +44 -4
  28. package/src/discovery/helpers.ts +9 -3
  29. package/src/discovery/ssh.ts +8 -3
  30. package/src/gjc-runtime/gc-runtime.ts +2 -1
  31. package/src/hindsight/config.ts +9 -3
  32. package/src/internal-urls/docs-index.generated.ts +2 -2
  33. package/src/modes/acp/acp-agent.ts +75 -3
  34. package/src/runtime-mcp/client.ts +13 -4
  35. package/src/runtime-mcp/elicitation.ts +142 -0
  36. package/src/sdk/broker/broker.ts +8 -1
  37. package/src/sdk/broker/lifecycle.ts +109 -7
  38. package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
  39. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  40. package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
  41. package/src/sdk/bus/index.ts +3 -3
  42. package/src/sdk/bus/telegram-daemon.ts +4 -2
  43. package/src/sdk/host/host.ts +6 -0
  44. package/src/sdk/host/session-runtime.test.ts +43 -2
  45. package/src/sdk/host/session-runtime.ts +2 -2
  46. package/src/sdk/mcp/server.ts +15 -3
  47. package/src/sdk/router/session-router.ts +19 -2
  48. package/src/sdk/session.ts +14 -0
  49. package/src/session/agent-session.ts +57 -6
  50. package/src/session/internal/managed-session-storage.ts +107 -1
  51. package/src/session/internal/task-artifact-owner-access.ts +229 -0
  52. package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
  53. package/src/session/session-retirement.ts +155 -0
  54. package/src/session/session-storage.ts +1 -160
  55. package/src/session/session-transcript-header.ts +17 -0
  56. package/src/session/task-artifact-owner-codec.ts +578 -0
  57. package/src/session/task-artifact-owner-retirement.ts +475 -0
  58. package/src/session/task-artifact-owner.ts +289 -0
@@ -0,0 +1,475 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { NativeDirectoryTreeSnapshot, NativeExactUnlinkResult } from "@gajae-code/natives";
4
+ import type { ManagedSessionDescendantStore } from "./internal/managed-session-storage";
5
+ import {
6
+ captureOwnerTreeIfPresent,
7
+ captureValidatedOwnerTree,
8
+ newSessionRootStore,
9
+ ownerTreeHasPendingManagedPublication,
10
+ } from "./internal/task-artifact-owner-access";
11
+ import {
12
+ EMPTY_PAYLOAD_SHA256,
13
+ isKnownNativeSidePath,
14
+ isOwnerRetainedRoot,
15
+ OWNER_DIRECTORY,
16
+ OWNER_RETIREMENT_SCHEMA_VERSION,
17
+ ownerAbsolutePath,
18
+ parseTaskArtifactOwnerDeletionEvidence,
19
+ parseTaskArtifactOwnerRetirementContinuation,
20
+ parseTaskArtifactOwnerRetirementOutcome,
21
+ retainedTreeDoesNotExpandAuthority,
22
+ sameOwnerParentIdentity,
23
+ type TaskArtifactOwnerDeletionEvidence,
24
+ type TaskArtifactOwnerRetirementContinuation,
25
+ type TaskArtifactOwnerRetirementOutcome,
26
+ type TaskArtifactOwnerStorageContext,
27
+ } from "./task-artifact-owner-codec";
28
+
29
+ function mergeUniqueStrings(
30
+ previous: readonly string[] | undefined,
31
+ next: string | undefined,
32
+ maximum: number,
33
+ ): string[] | undefined {
34
+ const values = new Set(previous ?? []);
35
+ if (next !== undefined) values.add(next);
36
+ if (values.size > maximum) throw new Error("task_artifact_owner_native_diagnostic_limit_exceeded");
37
+ return values.size > 0 ? [...values] : undefined;
38
+ }
39
+
40
+ function retainedSidePaths(
41
+ context: TaskArtifactOwnerStorageContext,
42
+ evidence: TaskArtifactOwnerDeletionEvidence,
43
+ previous: readonly string[] | undefined,
44
+ next: string | undefined,
45
+ ): string[] | undefined {
46
+ const values = mergeUniqueStrings(previous, next, 256) ?? [];
47
+ const ownerParent = path.dirname(ownerAbsolutePath(context, evidence.locator.ownerId));
48
+ const retained: string[] = [];
49
+ for (const pathname of values) {
50
+ if (!isKnownNativeSidePath(context, evidence.locator.ownerId, pathname))
51
+ throw new Error("task_artifact_owner_native_side_path_unrecognized");
52
+ if (path.dirname(pathname) !== ownerParent) {
53
+ retained.push(pathname);
54
+ continue;
55
+ }
56
+ try {
57
+ fs.lstatSync(pathname, { bigint: true });
58
+ retained.push(pathname);
59
+ } catch (error) {
60
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") retained.push(pathname);
61
+ }
62
+ }
63
+ return retained.length > 0 ? retained : undefined;
64
+ }
65
+
66
+ function continuationRecord(
67
+ context: TaskArtifactOwnerStorageContext,
68
+ evidence: TaskArtifactOwnerDeletionEvidence,
69
+ retainedRootPath: string,
70
+ retainedTreeSnapshot: NativeDirectoryTreeSnapshot,
71
+ previous?: TaskArtifactOwnerRetirementContinuation,
72
+ result?: NativeExactUnlinkResult,
73
+ ): TaskArtifactOwnerRetirementContinuation {
74
+ const detachedPaths = mergeUniqueStrings(previous?.detachedPaths, result?.detachedPath, 2);
75
+ if (detachedPaths?.some(pathname => !isOwnerRetainedRoot(context, evidence.locator.ownerId, pathname)))
76
+ throw new Error("task_artifact_owner_native_retained_root_unrecognized");
77
+ const nativeCodes = mergeUniqueStrings(previous?.nativeCodes, result?.code, 64);
78
+ if (nativeCodes?.some(code => !/^[A-Za-z0-9_.:-]{1,128}$/u.test(code)))
79
+ throw new Error("task_artifact_owner_native_code_unrecognized");
80
+ const retainedSuccessorPaths = retainedSidePaths(
81
+ context,
82
+ evidence,
83
+ previous?.retainedSuccessorPaths,
84
+ result?.retainedSuccessorPath,
85
+ );
86
+ const retainedPlaceholderPaths = retainedSidePaths(
87
+ context,
88
+ evidence,
89
+ previous?.retainedPlaceholderPaths,
90
+ result?.retainedPlaceholderPath,
91
+ );
92
+ const retainedUnknownPaths = retainedSidePaths(
93
+ context,
94
+ evidence,
95
+ previous?.retainedUnknownPaths,
96
+ result?.retainedUnknownPath,
97
+ );
98
+ const windowsErrorCodes = mergeUniqueStrings(previous?.windowsErrorCodes, result?.windowsErrorCode, 64);
99
+ if (windowsErrorCodes?.some(code => !/^0x[0-9A-Fa-f]{8}$/u.test(code)))
100
+ throw new Error("task_artifact_owner_native_windows_code_unrecognized");
101
+ const payloadDurable =
102
+ previous?.payloadDurable === true || result?.payloadDurable === true
103
+ ? true
104
+ : (result?.payloadDurable ?? previous?.payloadDurable);
105
+ return parseTaskArtifactOwnerRetirementContinuation(context, evidence, {
106
+ schemaVersion: OWNER_RETIREMENT_SCHEMA_VERSION,
107
+ parentIdentity: evidence.parentIdentity,
108
+ retainedRootPath,
109
+ retainedTreeSnapshot,
110
+ ...(detachedPaths !== undefined ? { detachedPaths } : {}),
111
+ ...(nativeCodes !== undefined ? { nativeCodes } : {}),
112
+ ...(payloadDurable !== undefined ? { payloadDurable } : {}),
113
+ ...(retainedSuccessorPaths !== undefined ? { retainedSuccessorPaths } : {}),
114
+ ...(retainedPlaceholderPaths !== undefined ? { retainedPlaceholderPaths } : {}),
115
+ ...(retainedUnknownPaths !== undefined ? { retainedUnknownPaths } : {}),
116
+ ...(windowsErrorCodes !== undefined ? { windowsErrorCodes } : {}),
117
+ });
118
+ }
119
+
120
+ function defaultContinuation(
121
+ context: TaskArtifactOwnerStorageContext,
122
+ evidence: TaskArtifactOwnerDeletionEvidence,
123
+ previous?: TaskArtifactOwnerRetirementContinuation,
124
+ ): TaskArtifactOwnerRetirementContinuation {
125
+ return (
126
+ previous ??
127
+ continuationRecord(
128
+ context,
129
+ evidence,
130
+ `${ownerAbsolutePath(context, evidence.locator.ownerId)}.removing`,
131
+ evidence.treeSnapshot,
132
+ )
133
+ );
134
+ }
135
+
136
+ function nativeResultSidePathsRemain(
137
+ context: TaskArtifactOwnerStorageContext,
138
+ rootStore: ManagedSessionDescendantStore,
139
+ evidence: TaskArtifactOwnerDeletionEvidence,
140
+ continuation: TaskArtifactOwnerRetirementContinuation,
141
+ ): boolean {
142
+ const ownerParent = path.dirname(ownerAbsolutePath(context, evidence.locator.ownerId));
143
+ try {
144
+ if (!sameOwnerParentIdentity(rootStore.captureDirectoryIdentity(OWNER_DIRECTORY), evidence.parentIdentity))
145
+ return true;
146
+ } catch {
147
+ return true;
148
+ }
149
+ let remains = false;
150
+ for (const pathname of [
151
+ ...(continuation.retainedSuccessorPaths ?? []),
152
+ ...(continuation.retainedPlaceholderPaths ?? []),
153
+ ...(continuation.retainedUnknownPaths ?? []),
154
+ ]) {
155
+ if (path.dirname(pathname) !== ownerParent) {
156
+ remains = true;
157
+ continue;
158
+ }
159
+ try {
160
+ fs.lstatSync(pathname, { bigint: true });
161
+ remains = true;
162
+ } catch (error) {
163
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") remains = true;
164
+ }
165
+ }
166
+ try {
167
+ return (
168
+ remains ||
169
+ !sameOwnerParentIdentity(rootStore.captureDirectoryIdentity(OWNER_DIRECTORY), evidence.parentIdentity)
170
+ );
171
+ } catch {
172
+ return true;
173
+ }
174
+ }
175
+
176
+ /** Confirm physical completion from a managed native proof, never from canonical absence alone. */
177
+ export function verifyTaskArtifactOwnerPhysicalRetirement(
178
+ context: TaskArtifactOwnerStorageContext,
179
+ evidence: TaskArtifactOwnerDeletionEvidence,
180
+ value: unknown,
181
+ ): void {
182
+ const outcome = parseTaskArtifactOwnerRetirementOutcome(context, evidence, value);
183
+ if (outcome.kind !== "completed") throw new Error("task_artifact_owner_physical_retirement_unverified");
184
+ const store = newSessionRootStore(context);
185
+ try {
186
+ if (!sameOwnerParentIdentity(store.captureDirectoryIdentity(OWNER_DIRECTORY), evidence.parentIdentity))
187
+ throw new Error("task_artifact_owner_parent_identity_mismatch");
188
+ const canonical = ownerAbsolutePath(context, evidence.locator.ownerId);
189
+ if (
190
+ captureOwnerTreeIfPresent(context, store, evidence.locator.ownerId, canonical) ||
191
+ captureOwnerTreeIfPresent(context, store, evidence.locator.ownerId, `${canonical}.removing`)
192
+ )
193
+ throw new Error("task_artifact_owner_physical_retirement_unverified");
194
+ } finally {
195
+ store.close();
196
+ }
197
+ }
198
+
199
+ /** Revalidate a persisted remnant through managed authority without mutation or new payload proof. */
200
+ export function verifyTaskArtifactOwnerRetirementContinuation(
201
+ context: TaskArtifactOwnerStorageContext,
202
+ evidenceValue: TaskArtifactOwnerDeletionEvidence,
203
+ value: unknown,
204
+ ): TaskArtifactOwnerRetirementContinuation {
205
+ const evidence = parseTaskArtifactOwnerDeletionEvidence(evidenceValue);
206
+ const continuation = parseTaskArtifactOwnerRetirementContinuation(context, evidence, value);
207
+ const store = newSessionRootStore(context);
208
+ try {
209
+ if (!sameOwnerParentIdentity(store.captureDirectoryIdentity(OWNER_DIRECTORY), evidence.parentIdentity))
210
+ throw new Error("task_artifact_owner_parent_identity_mismatch");
211
+ const snapshot = captureOwnerTreeIfPresent(
212
+ context,
213
+ store,
214
+ evidence.locator.ownerId,
215
+ continuation.retainedRootPath,
216
+ );
217
+ if (!snapshot || !retainedTreeDoesNotExpandAuthority(continuation.retainedTreeSnapshot, snapshot))
218
+ throw new Error("task_artifact_owner_retained_tree_mismatch");
219
+ if (
220
+ continuation.payloadDurable === true &&
221
+ snapshot.entries.some(
222
+ entry => entry.kind === "file" && (entry.size !== "0" || entry.sha256 !== EMPTY_PAYLOAD_SHA256),
223
+ )
224
+ )
225
+ throw new Error("task_artifact_owner_retired_payload_changed");
226
+ return continuation;
227
+ } finally {
228
+ store.close();
229
+ }
230
+ }
231
+
232
+ /** Continue exact native removal without widening the original owner-tree authority. */
233
+ export function retireTaskArtifactOwner(
234
+ context: TaskArtifactOwnerStorageContext,
235
+ evidenceValue: TaskArtifactOwnerDeletionEvidence,
236
+ continuationValue?: unknown,
237
+ ): TaskArtifactOwnerRetirementOutcome {
238
+ const evidence = parseTaskArtifactOwnerDeletionEvidence(evidenceValue);
239
+ const previous =
240
+ continuationValue === undefined
241
+ ? undefined
242
+ : parseTaskArtifactOwnerRetirementContinuation(context, evidence, continuationValue);
243
+ const fallback = defaultContinuation(context, evidence, previous);
244
+ const rootStore = newSessionRootStore(context);
245
+ try {
246
+ try {
247
+ rootStore.verifyRootSecurity();
248
+ } catch {
249
+ return {
250
+ kind: "uncertain",
251
+ evidence,
252
+ continuation: fallback,
253
+ reason: "task_artifact_owner_namespace_unverified",
254
+ };
255
+ }
256
+ let currentParent: { readonly dev: string; readonly ino: string };
257
+ try {
258
+ currentParent = rootStore.captureDirectoryIdentity(OWNER_DIRECTORY);
259
+ } catch {
260
+ return {
261
+ kind: "uncertain",
262
+ evidence,
263
+ continuation: fallback,
264
+ reason: "task_artifact_owner_parent_unavailable",
265
+ };
266
+ }
267
+ if (!sameOwnerParentIdentity(currentParent, evidence.parentIdentity))
268
+ return {
269
+ kind: "uncertain",
270
+ evidence,
271
+ continuation: fallback,
272
+ reason: "task_artifact_owner_parent_identity_changed",
273
+ };
274
+
275
+ // Publication markers can hold writable descriptors; scrubbing cannot revoke them.
276
+ if (ownerTreeHasPendingManagedPublication(evidence.treeSnapshot))
277
+ return {
278
+ kind: "uncertain",
279
+ evidence,
280
+ continuation: fallback,
281
+ reason: "task_artifact_owner_writer_not_quiescent",
282
+ };
283
+
284
+ const ownerPath = ownerAbsolutePath(context, evidence.locator.ownerId);
285
+ const removingPath = `${ownerPath}.removing`;
286
+ const baseline = previous?.retainedTreeSnapshot ?? evidence.treeSnapshot;
287
+ const candidates = [
288
+ ...new Set(
289
+ [previous?.retainedRootPath, removingPath, ownerPath].filter(
290
+ (pathname): pathname is string => pathname !== undefined,
291
+ ),
292
+ ),
293
+ ];
294
+ let retainedRootPath: string | undefined;
295
+ let retainedTreeSnapshot: NativeDirectoryTreeSnapshot | undefined;
296
+ for (const candidate of candidates) {
297
+ let snapshot: NativeDirectoryTreeSnapshot | undefined;
298
+ try {
299
+ snapshot = captureOwnerTreeIfPresent(context, rootStore, evidence.locator.ownerId, candidate);
300
+ } catch {
301
+ return {
302
+ kind: "uncertain",
303
+ evidence,
304
+ continuation: fallback,
305
+ reason: "task_artifact_owner_retained_tree_unavailable",
306
+ };
307
+ }
308
+ if (snapshot && ownerTreeHasPendingManagedPublication(snapshot))
309
+ return {
310
+ kind: "uncertain",
311
+ evidence,
312
+ continuation: fallback,
313
+ reason: "task_artifact_owner_writer_not_quiescent",
314
+ };
315
+ if (
316
+ !snapshot ||
317
+ snapshot.rootDev !== evidence.locator.directoryDev ||
318
+ snapshot.rootIno !== evidence.locator.directoryIno ||
319
+ !retainedTreeDoesNotExpandAuthority(baseline, snapshot)
320
+ )
321
+ continue;
322
+ if (candidate === ownerPath && JSON.stringify(snapshot) === JSON.stringify(evidence.treeSnapshot)) {
323
+ try {
324
+ const validated = captureValidatedOwnerTree(context, rootStore, evidence.sessionId, evidence.locator);
325
+ if (JSON.stringify(validated) !== JSON.stringify(evidence.treeSnapshot))
326
+ return {
327
+ kind: "uncertain",
328
+ evidence,
329
+ continuation: fallback,
330
+ reason: "task_artifact_owner_changed_since_capture",
331
+ };
332
+ } catch {
333
+ return {
334
+ kind: "uncertain",
335
+ evidence,
336
+ continuation: fallback,
337
+ reason: "task_artifact_owner_identity_unverified",
338
+ };
339
+ }
340
+ }
341
+ retainedRootPath = candidate;
342
+ retainedTreeSnapshot = snapshot;
343
+ break;
344
+ }
345
+ if (!retainedRootPath || !retainedTreeSnapshot)
346
+ return {
347
+ kind: "uncertain",
348
+ evidence,
349
+ continuation: fallback,
350
+ reason: "task_artifact_owner_retained_root_not_found_or_unverified",
351
+ };
352
+
353
+ let nativeOutcome: NativeExactUnlinkResult;
354
+ try {
355
+ nativeOutcome = rootStore.removeTreeExpectedWithParentIdentity(
356
+ path.relative(context.sessionsRoot, retainedRootPath).split(path.sep).join("/"),
357
+ retainedTreeSnapshot,
358
+ { dev: BigInt(evidence.parentIdentity.dev), ino: BigInt(evidence.parentIdentity.ino) },
359
+ );
360
+ } catch {
361
+ return {
362
+ kind: "uncertain",
363
+ evidence,
364
+ continuation: fallback,
365
+ reason: "task_artifact_owner_native_removal_uncertain",
366
+ };
367
+ }
368
+ let parentStillBound = false;
369
+ try {
370
+ parentStillBound = sameOwnerParentIdentity(
371
+ rootStore.captureDirectoryIdentity(OWNER_DIRECTORY),
372
+ evidence.parentIdentity,
373
+ );
374
+ } catch {
375
+ parentStillBound = false;
376
+ }
377
+ if (!parentStillBound)
378
+ return {
379
+ kind: "uncertain",
380
+ evidence,
381
+ continuation: fallback,
382
+ reason: "task_artifact_owner_parent_identity_changed_after_removal",
383
+ nativeOutcome,
384
+ };
385
+
386
+ const reportedRoot = nativeOutcome.detachedPath;
387
+ const reportedRootKnown =
388
+ reportedRoot === undefined || isOwnerRetainedRoot(context, evidence.locator.ownerId, reportedRoot);
389
+ const nextRoot = reportedRootKnown && reportedRoot !== undefined ? reportedRoot : retainedRootPath;
390
+ let residualTree: NativeDirectoryTreeSnapshot | undefined;
391
+ let residualValid = reportedRootKnown;
392
+ try {
393
+ residualTree = captureOwnerTreeIfPresent(context, rootStore, evidence.locator.ownerId, nextRoot);
394
+ if (
395
+ residualTree &&
396
+ (residualTree.rootDev !== evidence.locator.directoryDev ||
397
+ residualTree.rootIno !== evidence.locator.directoryIno ||
398
+ !retainedTreeDoesNotExpandAuthority(retainedTreeSnapshot, residualTree))
399
+ )
400
+ residualValid = false;
401
+ } catch {
402
+ residualValid = false;
403
+ }
404
+ const nextTree = residualValid && residualTree ? residualTree : retainedTreeSnapshot;
405
+ let nextContinuation: TaskArtifactOwnerRetirementContinuation;
406
+ try {
407
+ nextContinuation = continuationRecord(context, evidence, nextRoot, nextTree, previous, nativeOutcome);
408
+ } catch {
409
+ return {
410
+ kind: "uncertain",
411
+ evidence,
412
+ continuation: continuationRecord(context, evidence, retainedRootPath, retainedTreeSnapshot, previous),
413
+ reason: "task_artifact_owner_native_retained_state_unrecognized",
414
+ nativeOutcome,
415
+ };
416
+ }
417
+ if (!residualValid)
418
+ return {
419
+ kind: "uncertain",
420
+ evidence,
421
+ continuation: nextContinuation,
422
+ reason: "task_artifact_owner_native_retained_state_unverified",
423
+ nativeOutcome,
424
+ };
425
+
426
+ let ownerRootRemains = false;
427
+ for (const candidate of [ownerPath, removingPath]) {
428
+ let snapshot: NativeDirectoryTreeSnapshot | undefined;
429
+ try {
430
+ snapshot = captureOwnerTreeIfPresent(context, rootStore, evidence.locator.ownerId, candidate);
431
+ } catch {
432
+ ownerRootRemains = true;
433
+ break;
434
+ }
435
+ if (snapshot) {
436
+ ownerRootRemains = true;
437
+ break;
438
+ }
439
+ }
440
+ const sidePathsRemain = nativeResultSidePathsRemain(context, rootStore, evidence, nextContinuation);
441
+ if (nativeOutcome.ok && !ownerRootRemains && !sidePathsRemain)
442
+ return { kind: "completed", evidence, nativeOutcome };
443
+ if (
444
+ !nativeOutcome.ok &&
445
+ nativeOutcome.code === "cleanup_pending" &&
446
+ nativeOutcome.payloadDurable === true &&
447
+ residualTree &&
448
+ nextRoot === removingPath &&
449
+ !sidePathsRemain &&
450
+ residualTree.entries.every(
451
+ entry => entry.kind === "directory" || (entry.size === "0" && entry.sha256 === EMPTY_PAYLOAD_SHA256),
452
+ )
453
+ )
454
+ return {
455
+ kind: "payload_retired",
456
+ namespace: "retained",
457
+ evidence,
458
+ continuation: nextContinuation,
459
+ nativeOutcome,
460
+ };
461
+ if (!nativeOutcome.ok && nativeOutcome.code === "cleanup_pending")
462
+ return { kind: "cleanup_pending", evidence, continuation: nextContinuation, nativeOutcome };
463
+ return {
464
+ kind: "uncertain",
465
+ evidence,
466
+ continuation: nextContinuation,
467
+ reason: nativeOutcome.ok
468
+ ? "task_artifact_owner_native_success_retained_authority"
469
+ : `task_artifact_owner_native_removal_${nativeOutcome.code ?? "uncertain"}`,
470
+ nativeOutcome,
471
+ };
472
+ } finally {
473
+ rootStore.close();
474
+ }
475
+ }