@gajae-code/coding-agent 0.18.6 → 0.18.8

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 (302) hide show
  1. package/CHANGELOG.md +318 -1
  2. package/dist/types/async/job-manager.d.ts +1 -1
  3. package/dist/types/capability/mcp.d.ts +2 -0
  4. package/dist/types/capability/skill.d.ts +2 -0
  5. package/dist/types/capability/types.d.ts +6 -0
  6. package/dist/types/cli/public-command-errors.d.ts +3 -1
  7. package/dist/types/cli/setup-cli.d.ts +1 -1
  8. package/dist/types/commands/deep-interview.d.ts +3 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  10. package/dist/types/config/file-lock.d.ts +1 -0
  11. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  12. package/dist/types/config/model-profile-activation.d.ts +11 -3
  13. package/dist/types/config/model-registry.d.ts +4 -9
  14. package/dist/types/config/settings.d.ts +4 -0
  15. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  16. package/dist/types/cursor-delete-path.d.ts +1 -0
  17. package/dist/types/cursor-mcp-name.d.ts +8 -0
  18. package/dist/types/customization/inventory.d.ts +2 -0
  19. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  21. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  22. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  23. package/dist/types/discovery/helpers.d.ts +8 -3
  24. package/dist/types/export/html/css-color.d.ts +2 -0
  25. package/dist/types/export/html/template.generated.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  27. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  28. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  29. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  31. package/dist/types/extensibility/skill-management.d.ts +3 -1
  32. package/dist/types/extensibility/skills.d.ts +2 -0
  33. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  34. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  35. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  36. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  37. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  38. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  39. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  40. package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
  41. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  42. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  43. package/dist/types/lsp/workspace-path.d.ts +1 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  46. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  47. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  48. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  49. package/dist/types/modes/interactive-mode.d.ts +1 -0
  50. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  51. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  52. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  53. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  54. package/dist/types/progress/progress-contract.d.ts +154 -0
  55. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  56. package/dist/types/progress/project-progress.d.ts +141 -0
  57. package/dist/types/progress/render-progress.d.ts +28 -0
  58. package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
  59. package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
  60. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  61. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  62. package/dist/types/runtime-mcp/types.d.ts +6 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  64. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  65. package/dist/types/sdk/broker/broker.d.ts +18 -1
  66. package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
  67. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  68. package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
  69. package/dist/types/sdk/bus/index.d.ts +12 -1
  70. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  71. package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
  72. package/dist/types/sdk/client/client.d.ts +2 -0
  73. package/dist/types/sdk/host/host.d.ts +6 -0
  74. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  75. package/dist/types/sdk/index.d.ts +1 -0
  76. package/dist/types/sdk/router/session-router.d.ts +9 -0
  77. package/dist/types/sdk/session-list.d.ts +1 -1
  78. package/dist/types/sdk/session.d.ts +8 -0
  79. package/dist/types/session/agent-session.d.ts +38 -9
  80. package/dist/types/session/artifacts.d.ts +2 -2
  81. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  82. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  83. package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
  84. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  85. package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
  86. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
  87. package/dist/types/session/session-manager.d.ts +31 -5
  88. package/dist/types/session/session-retirement.d.ts +71 -0
  89. package/dist/types/session/session-storage.d.ts +62 -48
  90. package/dist/types/session/session-transcript-header.d.ts +2 -0
  91. package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
  92. package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
  93. package/dist/types/session/task-artifact-owner.d.ts +10 -0
  94. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  95. package/dist/types/setup/credential-import.d.ts +6 -4
  96. package/dist/types/skill-state/active-state.d.ts +38 -0
  97. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  98. package/dist/types/task/executor.d.ts +3 -0
  99. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  100. package/dist/types/tools/index.d.ts +3 -0
  101. package/dist/types/tools/python.d.ts +9 -1
  102. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  103. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  104. package/dist/types/tools/todo-write.d.ts +6 -0
  105. package/dist/types/utils/tools-checksum.d.ts +7 -0
  106. package/package.json +7 -7
  107. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  108. package/scripts/resident-memory-bench.ts +174 -20
  109. package/scripts/verify-managed-empty-stop.ts +28 -0
  110. package/src/async/job-manager.ts +228 -122
  111. package/src/capability/index.ts +18 -0
  112. package/src/capability/mcp.ts +2 -0
  113. package/src/capability/skill.ts +2 -0
  114. package/src/capability/types.ts +6 -0
  115. package/src/cli/auth-broker-cli.ts +1 -1
  116. package/src/cli/config-cli.ts +8 -18
  117. package/src/cli/customize-doctor.ts +15 -2
  118. package/src/cli/public-command-errors.ts +21 -1
  119. package/src/cli/setup-cli.ts +4 -4
  120. package/src/cli/skills-cli.ts +5 -0
  121. package/src/cli-main.ts +7 -8
  122. package/src/commands/deep-interview.ts +47 -8
  123. package/src/commands/sdk.ts +140 -29
  124. package/src/config/autorouting-tier-map.ts +117 -7
  125. package/src/config/file-lock.ts +307 -68
  126. package/src/config/mcp-schema.json +4 -0
  127. package/src/config/model-bindings-applier.ts +20 -0
  128. package/src/config/model-profile-activation.ts +240 -46
  129. package/src/config/model-registry.ts +47 -153
  130. package/src/config/settings.ts +44 -4
  131. package/src/config/skill-settings-defaults.ts +7 -0
  132. package/src/coordinator-mcp/policy.ts +3 -2
  133. package/src/cursor-delete-path.ts +10 -0
  134. package/src/cursor-mcp-name.ts +15 -0
  135. package/src/cursor.ts +13 -2
  136. package/src/customization/inventory.ts +3 -0
  137. package/src/debug/report-bundle-parent.ts +22 -0
  138. package/src/debug/report-bundle.ts +3 -0
  139. package/src/debug/system-info.ts +13 -1
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  141. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  142. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  143. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  144. package/src/defaults/gjc-defaults.test.ts +18 -1
  145. package/src/defaults/gjc-defaults.ts +3 -0
  146. package/src/defaults/gjc-skills.generated.ts +58 -36
  147. package/src/discovery/agents.ts +6 -1
  148. package/src/discovery/builtin.ts +10 -0
  149. package/src/discovery/helpers.ts +168 -23
  150. package/src/discovery/mcp-json.ts +6 -0
  151. package/src/discovery/ssh.ts +9 -4
  152. package/src/discovery/substitute-plugin-root.ts +3 -1
  153. package/src/edit/file-read-cache.ts +1 -1
  154. package/src/eval/py/executor.ts +380 -120
  155. package/src/export/html/css-color.ts +9 -0
  156. package/src/export/html/index.ts +5 -4
  157. package/src/export/html/template.generated.ts +1 -1
  158. package/src/extensibility/extensions/runner.ts +4 -0
  159. package/src/extensibility/extensions/types.ts +7 -0
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
  161. package/src/extensibility/plugins/manager.ts +3 -2
  162. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  163. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  164. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  165. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  166. package/src/extensibility/skill-management.ts +6 -2
  167. package/src/extensibility/skills.ts +6 -1
  168. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  169. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  170. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  171. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  172. package/src/gjc-runtime/gc-runtime.ts +206 -8
  173. package/src/gjc-runtime/launch-worktree.ts +14 -4
  174. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  175. package/src/gjc-runtime/state-argv.ts +2 -0
  176. package/src/gjc-runtime/state-migrations.ts +91 -29
  177. package/src/gjc-runtime/state-runtime.ts +3716 -135
  178. package/src/gjc-runtime/state-writer.ts +83 -8
  179. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  180. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  181. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  182. package/src/harness-control-plane/operate.ts +12 -4
  183. package/src/harness-control-plane/owner.ts +5 -1
  184. package/src/hashline/parser.ts +48 -9
  185. package/src/hashline/recovery.ts +13 -4
  186. package/src/hindsight/config.ts +9 -3
  187. package/src/internal-urls/docs-index.generated.ts +19 -18
  188. package/src/internal-urls/skill-path.ts +19 -0
  189. package/src/internal-urls/skill-protocol.ts +2 -0
  190. package/src/lsp/edits.ts +6 -0
  191. package/src/lsp/workspace-path.ts +51 -0
  192. package/src/main.ts +195 -31
  193. package/src/modes/acp/acp-agent.ts +163 -7
  194. package/src/modes/components/custom-editor.ts +14 -0
  195. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  196. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  197. package/src/modes/components/extensions/state-manager.ts +15 -5
  198. package/src/modes/components/model-selector.ts +1 -4
  199. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  200. package/src/modes/components/status-line/segments.ts +4 -0
  201. package/src/modes/controllers/event-controller.ts +11 -4
  202. package/src/modes/controllers/input-controller.ts +74 -29
  203. package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
  204. package/src/modes/controllers/selector-controller.ts +28 -15
  205. package/src/modes/interactive-mode.ts +4 -0
  206. package/src/modes/runtime-init.ts +4 -0
  207. package/src/modes/utils/abort-message.ts +3 -1
  208. package/src/modes/utils/interrupt-ownership.ts +12 -0
  209. package/src/modes/utils/keybinding-matchers.ts +4 -0
  210. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  211. package/src/progress/collect-project-progress.ts +153 -0
  212. package/src/progress/progress-contract.ts +172 -0
  213. package/src/progress/progress-snapshot.ts +140 -0
  214. package/src/progress/project-progress.ts +446 -0
  215. package/src/progress/render-progress.ts +209 -0
  216. package/src/prompts/system/master-mode.md +1 -0
  217. package/src/prompts/tools/skill-discovery.md +1 -1
  218. package/src/runtime-mcp/client.ts +13 -4
  219. package/src/runtime-mcp/config-writer.ts +10 -6
  220. package/src/runtime-mcp/config.ts +1 -0
  221. package/src/runtime-mcp/elicitation.ts +142 -0
  222. package/src/runtime-mcp/oauth-flow.ts +5 -0
  223. package/src/runtime-mcp/oauth-public-url.ts +8 -0
  224. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  225. package/src/runtime-mcp/smithery-registry.ts +22 -3
  226. package/src/runtime-mcp/transports/http.ts +2 -2
  227. package/src/runtime-mcp/types.ts +6 -0
  228. package/src/sdk/acp/adapter.ts +93 -8
  229. package/src/sdk/broker/broker-exit.ts +52 -23
  230. package/src/sdk/broker/broker.ts +83 -7
  231. package/src/sdk/broker/ensure.ts +2 -0
  232. package/src/sdk/broker/lifecycle.ts +820 -73
  233. package/src/sdk/broker/session-index.ts +191 -81
  234. package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
  235. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  236. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
  238. package/src/sdk/bus/index.ts +241 -46
  239. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  240. package/src/sdk/bus/telegram-daemon.ts +4 -2
  241. package/src/sdk/cli/session-cli.ts +17 -3
  242. package/src/sdk/client/client.ts +28 -8
  243. package/src/sdk/host/host.ts +6 -0
  244. package/src/sdk/host/query/handlers.ts +9 -1
  245. package/src/sdk/host/session-runtime.test.ts +2805 -411
  246. package/src/sdk/host/session-runtime.ts +428 -111
  247. package/src/sdk/host/surface-policy.ts +1 -0
  248. package/src/sdk/index.ts +8 -0
  249. package/src/sdk/lifecycle-session.ts +3 -1
  250. package/src/sdk/mcp/server.ts +15 -3
  251. package/src/sdk/prompt-deadline-manager.ts +15 -0
  252. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  253. package/src/sdk/protocol/operation-registry.ts +9 -2
  254. package/src/sdk/router/session-router.ts +94 -14
  255. package/src/sdk/session-list.ts +1 -0
  256. package/src/sdk/session.ts +81 -38
  257. package/src/session/agent-session.ts +674 -224
  258. package/src/session/artifacts.ts +174 -70
  259. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  260. package/src/session/internal/managed-session-scope.ts +3457 -462
  261. package/src/session/internal/managed-session-storage.ts +482 -61
  262. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  263. package/src/session/internal/task-artifact-owner-access.ts +245 -0
  264. package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
  265. package/src/session/session-manager.ts +939 -133
  266. package/src/session/session-retirement.ts +714 -0
  267. package/src/session/session-storage.ts +418 -174
  268. package/src/session/session-transcript-header.ts +17 -0
  269. package/src/session/task-artifact-owner-codec.ts +641 -0
  270. package/src/session/task-artifact-owner-retirement.ts +477 -0
  271. package/src/session/task-artifact-owner.ts +289 -0
  272. package/src/setup/credential-auto-import.ts +12 -3
  273. package/src/setup/credential-import.ts +176 -5
  274. package/src/setup/paseo/announce.ts +6 -1
  275. package/src/skill-state/active-state.ts +132 -2
  276. package/src/slash-commands/builtin-registry.ts +39 -9
  277. package/src/slash-commands/helpers/progress-report.ts +23 -0
  278. package/src/stt/setup.ts +3 -5
  279. package/src/task/commands.ts +3 -1
  280. package/src/task/executor.ts +4 -0
  281. package/src/task/index.ts +6 -0
  282. package/src/tools/ask.ts +205 -6
  283. package/src/tools/ast-edit.ts +4 -0
  284. package/src/tools/bash-allowed-prefixes.ts +15 -0
  285. package/src/tools/bash-skill-urls.ts +56 -3
  286. package/src/tools/bash.ts +48 -79
  287. package/src/tools/descriptors.ts +5 -1
  288. package/src/tools/eval.ts +56 -36
  289. package/src/tools/image-gen.ts +1 -1
  290. package/src/tools/index.ts +3 -0
  291. package/src/tools/python.ts +187 -66
  292. package/src/tools/recipe/index.ts +9 -0
  293. package/src/tools/recipe/recipe-guard.ts +19 -0
  294. package/src/tools/restricted-cwd.ts +10 -0
  295. package/src/tools/skill-discovery.ts +5 -0
  296. package/src/tools/skill.ts +28 -1
  297. package/src/tools/todo-write.ts +10 -1
  298. package/src/tools/tool-catalog.generated.ts +1 -1
  299. package/src/tools/write.ts +1 -0
  300. package/src/utils/tools-checksum.ts +34 -0
  301. package/src/utils/tools-manager.ts +24 -6
  302. package/src/web/search/providers/codex.ts +3 -2
@@ -0,0 +1,714 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as path from "node:path";
3
+ import * as util from "node:util";
4
+
5
+ import { toError } from "@gajae-code/utils";
6
+ import type {
7
+ ManagedGcSessionRetirementReceipt,
8
+ ManagedGcSessionRetirementTarget,
9
+ } from "./internal/managed-gc-retirement-codec";
10
+ import {
11
+ bindManagedGcSessionRetirementTarget,
12
+ type ManagedScope,
13
+ managedGcProtocolScopeInspectorForScope,
14
+ publishManagedGcSessionRetirementReceipt,
15
+ readManagedGcSessionRetirementReceiptReadOnly,
16
+ taskArtifactOwnerStorageContextForScope,
17
+ } from "./internal/managed-session-scope";
18
+ import { captureTaskArtifactOwnerDeletionEvidence } from "./internal/task-artifact-owner-access";
19
+ import {
20
+ hasSiblingTaskArtifactOwnerTranscript,
21
+ taskArtifactOwnerLocatorFromTranscriptBytes,
22
+ } from "./internal/task-artifact-owner-transcript";
23
+ import {
24
+ type FileSessionStorage,
25
+ SessionDeleteVerificationError,
26
+ type SessionStorageFileIdentity,
27
+ type SessionStorageSnapshot,
28
+ type VerifiedSessionDeleteTarget,
29
+ } from "./session-storage";
30
+ import { parseFirstJsonlLine } from "./session-transcript-header";
31
+ import type {
32
+ TaskArtifactOwnerDeletionEvidence,
33
+ TaskArtifactOwnerLocator,
34
+ TaskArtifactOwnerRetirementContinuation,
35
+ TaskArtifactOwnerRetirementOutcome,
36
+ TaskArtifactOwnerStorageContext,
37
+ } from "./task-artifact-owner-codec";
38
+ import {
39
+ retireTaskArtifactOwner,
40
+ verifyTaskArtifactOwnerPhysicalRetirement,
41
+ verifyTaskArtifactOwnerRetirementContinuation,
42
+ } from "./task-artifact-owner-retirement";
43
+
44
+ export interface SessionRetirementContinuation {
45
+ readonly managedScope: ManagedScope;
46
+ }
47
+
48
+ /**
49
+ * Outcome of a disk-retention retirement attempt.
50
+ *
51
+ * `kept` means the transcript survived and nothing of the session was
52
+ * destroyed, with the exact reason so `gjc gc --disk` can report it.
53
+ * `cleanup_pending` means the delete authority already detached or removed the
54
+ * session's artifact tree (or quarantined the transcript) before it stopped:
55
+ * the record survives, the session does not, and a caller must NOT report that
56
+ * as an ordinary keep.
57
+ */
58
+ export type SessionRetirementOutcome =
59
+ | { kind: "retired"; taskArtifactOwnerRetired?: true }
60
+ | { kind: "kept"; reason: string }
61
+ | {
62
+ kind: "cleanup_pending";
63
+ reason: string;
64
+ phase?: "artifacts" | "transcript";
65
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
66
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
67
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
68
+ taskArtifactOwnerPayloadRetired?: true;
69
+ taskArtifactOwnerNamespaceRetained?: true;
70
+ taskArtifactOwnerRetired?: true;
71
+ };
72
+
73
+ /** A retirement that cleared every precondition the retention pass itself owns. */
74
+ type SessionRetirementPlan =
75
+ | {
76
+ kind: "retirable";
77
+ target: VerifiedSessionDeleteTarget;
78
+ artifactsPresent: boolean;
79
+ taskArtifactOwnerLocator?: TaskArtifactOwnerLocator;
80
+ gcContinuationReceipt?: ManagedGcSessionRetirementReceipt;
81
+ }
82
+ | { kind: "owner_shared"; reason: string; gcContinuationReceipt?: ManagedGcSessionRetirementReceipt }
83
+ | { kind: "cleanup_pending"; reason: string; gcContinuationReceipt: ManagedGcSessionRetirementReceipt }
84
+ | { kind: "kept"; reason: string };
85
+
86
+ type OwnerRetirementFields = {
87
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
88
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
89
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
90
+ taskArtifactOwnerPayloadRetired?: true;
91
+ taskArtifactOwnerNamespaceRetained?: true;
92
+ taskArtifactOwnerRetired?: true;
93
+ };
94
+
95
+ export type SessionRetirementProbeOutcome =
96
+ | Extract<SessionRetirementOutcome, { kind: "cleanup_pending" }>
97
+ | {
98
+ kind: "retirable";
99
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
100
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
101
+ taskArtifactOwnerRetired?: true;
102
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
103
+ taskArtifactOwnerPayloadRetired?: true;
104
+ taskArtifactOwnerNamespaceRetained?: true;
105
+ }
106
+ | { kind: "kept"; reason: string };
107
+
108
+ function isWithinPath(root: string, target: string): boolean {
109
+ const relative = path.relative(path.resolve(root), path.resolve(target));
110
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
111
+ }
112
+
113
+ function sameTranscriptIdentity(left: SessionStorageFileIdentity, right: SessionStorageFileIdentity): boolean {
114
+ return (
115
+ left.dev === right.dev &&
116
+ left.ino === right.ino &&
117
+ left.nlink === right.nlink &&
118
+ left.size === right.size &&
119
+ left.mtimeNs === right.mtimeNs &&
120
+ left.sha256 === right.sha256
121
+ );
122
+ }
123
+
124
+ function sameManagedGcTarget(left: ManagedGcSessionRetirementTarget, right: ManagedGcSessionRetirementTarget): boolean {
125
+ return (
126
+ left.transcriptPath === right.transcriptPath &&
127
+ left.sessionId === right.sessionId &&
128
+ left.cwd === right.cwd &&
129
+ sameTranscriptIdentity(left.transcriptIdentity, right.transcriptIdentity) &&
130
+ util.isDeepStrictEqual(left.taskArtifactOwnerLocator, right.taskArtifactOwnerLocator)
131
+ );
132
+ }
133
+
134
+ function sameEvidence(left: TaskArtifactOwnerDeletionEvidence, right: TaskArtifactOwnerDeletionEvidence): boolean {
135
+ return util.isDeepStrictEqual(left, right);
136
+ }
137
+
138
+ function targetFromReceipt(receipt: ManagedGcSessionRetirementReceipt): ManagedGcSessionRetirementTarget {
139
+ return {
140
+ transcriptPath: receipt.transcriptPath,
141
+ sessionId: receipt.sessionId,
142
+ cwd: receipt.cwd,
143
+ transcriptIdentity: receipt.transcriptIdentity,
144
+ taskArtifactOwnerLocator: receipt.taskArtifactOwnerLocator,
145
+ };
146
+ }
147
+
148
+ function ownerOutcomeFields(
149
+ evidence: TaskArtifactOwnerDeletionEvidence,
150
+ outcome: TaskArtifactOwnerRetirementOutcome | undefined,
151
+ ownerRetired: boolean,
152
+ ): OwnerRetirementFields {
153
+ return {
154
+ taskArtifactOwnerDeletionEvidence: evidence,
155
+ ...(outcome ? { taskArtifactOwnerRetirementOutcome: outcome } : {}),
156
+ ...(outcome && outcome.kind !== "completed"
157
+ ? { taskArtifactOwnerRetirementContinuation: outcome.continuation }
158
+ : {}),
159
+ ...(outcome?.kind === "payload_retired"
160
+ ? { taskArtifactOwnerPayloadRetired: true as const, taskArtifactOwnerNamespaceRetained: true as const }
161
+ : {}),
162
+ ...(ownerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
163
+ };
164
+ }
165
+
166
+ function planRetirementOutcome(
167
+ plan: Extract<SessionRetirementPlan, { kind: "retirable" }>,
168
+ ): SessionRetirementProbeOutcome {
169
+ const receipt = plan.gcContinuationReceipt;
170
+ const outcome = receipt?.taskArtifactOwnerRetirementOutcome;
171
+ return {
172
+ kind: "retirable",
173
+ ...(receipt ? { taskArtifactOwnerDeletionEvidence: receipt.taskArtifactOwnerDeletionEvidence } : {}),
174
+ ...(outcome ? { taskArtifactOwnerRetirementOutcome: outcome } : {}),
175
+ ...(receipt?.state === "owner_retired" ? { taskArtifactOwnerRetired: true as const } : {}),
176
+ ...(outcome && outcome.kind !== "completed"
177
+ ? { taskArtifactOwnerRetirementContinuation: outcome.continuation }
178
+ : {}),
179
+ ...(outcome?.kind === "payload_retired"
180
+ ? { taskArtifactOwnerPayloadRetired: true as const, taskArtifactOwnerNamespaceRetained: true as const }
181
+ : {}),
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Evaluate the preconditions retirement owns — the transcript must be readable
187
+ * and carry a usable session header — and bind the delete target to the exact
188
+ * bytes just read. Nothing here mutates the store.
189
+ */
190
+ async function planSessionRetirement(
191
+ storage: FileSessionStorage,
192
+ sessionsRoot: string,
193
+ transcriptPath: string,
194
+ continuation?: SessionRetirementContinuation,
195
+ ): Promise<SessionRetirementPlan> {
196
+ let snapshot: SessionStorageSnapshot;
197
+ try {
198
+ snapshot = storage.readSnapshotSync(transcriptPath);
199
+ } catch (err) {
200
+ return { kind: "kept", reason: `transcript_unreadable: ${toError(err).message}` };
201
+ }
202
+
203
+ const header = parseFirstJsonlLine(snapshot.bytes);
204
+ if (header?.type !== "session" || typeof header.id !== "string" || typeof header.cwd !== "string") {
205
+ return { kind: "kept", reason: "transcript_header_unusable" };
206
+ }
207
+
208
+ // The verified delete authority only unlinks a single-link transcript, so a
209
+ // hard-linked one can never be retired. Refusing here keeps the dry run from
210
+ // promising bytes prune cannot release, and — because this runs before the
211
+ // artifact phase — keeps a doomed retirement from destroying its artifacts.
212
+ if (snapshot.stat.nlink === undefined || snapshot.stat.nlink !== 1n) {
213
+ return { kind: "kept", reason: "transcript_not_single_link" };
214
+ }
215
+
216
+ let ownerLocator: TaskArtifactOwnerLocator | undefined;
217
+ try {
218
+ ownerLocator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, header.id);
219
+ } catch (error) {
220
+ return { kind: "kept", reason: `task_artifact_owner_locator_invalid: ${toError(error).message}` };
221
+ }
222
+
223
+ let ownerContext: TaskArtifactOwnerStorageContext | undefined;
224
+ let ownerEvidence: TaskArtifactOwnerDeletionEvidence | undefined;
225
+ let gcContinuationReceipt: ManagedGcSessionRetirementReceipt | undefined;
226
+ let managedTarget: ManagedGcSessionRetirementTarget | undefined;
227
+ let partialReceipt: ManagedGcSessionRetirementReceipt | undefined;
228
+ const continuationRefusal = (reason: string): SessionRetirementPlan =>
229
+ partialReceipt
230
+ ? { kind: "cleanup_pending", reason, gcContinuationReceipt: partialReceipt }
231
+ : { kind: "kept", reason };
232
+ if (ownerLocator) {
233
+ if (!continuation) return { kind: "kept", reason: "task_artifact_owner_context_missing" };
234
+ try {
235
+ ownerContext = taskArtifactOwnerStorageContextForScope(continuation.managedScope);
236
+ if (path.resolve(ownerContext.sessionsRoot) !== path.resolve(sessionsRoot))
237
+ return { kind: "kept", reason: "task_artifact_owner_root_mismatch" };
238
+ managedTarget = bindManagedGcSessionRetirementTarget(continuation.managedScope, transcriptPath);
239
+ const snapshotIdentity: SessionStorageFileIdentity = {
240
+ dev: snapshot.stat.dev,
241
+ ino: snapshot.stat.ino,
242
+ nlink: snapshot.stat.nlink,
243
+ size: snapshot.stat.size,
244
+ mtimeNs: snapshot.stat.mtimeNs,
245
+ sha256: crypto.createHash("sha256").update(snapshot.bytes).digest("hex"),
246
+ };
247
+ if (
248
+ managedTarget.sessionId !== header.id ||
249
+ managedTarget.cwd !== header.cwd ||
250
+ !sameTranscriptIdentity(managedTarget.transcriptIdentity, snapshotIdentity) ||
251
+ !util.isDeepStrictEqual(managedTarget.taskArtifactOwnerLocator, ownerLocator)
252
+ )
253
+ return { kind: "kept", reason: "task_artifact_owner_continuation_transcript_replaced" };
254
+ gcContinuationReceipt = await readManagedGcSessionRetirementReceiptReadOnly(
255
+ continuation.managedScope,
256
+ transcriptPath,
257
+ );
258
+ if (gcContinuationReceipt) {
259
+ if (!sameManagedGcTarget(gcContinuationReceipt, managedTarget))
260
+ return { kind: "kept", reason: "task_artifact_owner_continuation_transcript_replaced" };
261
+ if (gcContinuationReceipt.artifactsRemoved) partialReceipt = gcContinuationReceipt;
262
+ ownerEvidence = gcContinuationReceipt.taskArtifactOwnerDeletionEvidence;
263
+ if (!ownerEvidence) return continuationRefusal("task_artifact_owner_continuation_evidence_missing");
264
+ if (gcContinuationReceipt.state === "prepared" || gcContinuationReceipt.state === "artifacts_removed") {
265
+ const currentEvidence = captureTaskArtifactOwnerDeletionEvidence(ownerContext, header.id, ownerLocator);
266
+ if (!currentEvidence || !sameEvidence(currentEvidence, ownerEvidence))
267
+ return continuationRefusal("task_artifact_owner_continuation_evidence_mismatch");
268
+ } else if (gcContinuationReceipt.state === "owner_pending") {
269
+ const outcome = gcContinuationReceipt.taskArtifactOwnerRetirementOutcome;
270
+ if (!outcome || outcome.kind === "completed")
271
+ return continuationRefusal("task_artifact_owner_continuation_outcome_mismatch");
272
+ verifyTaskArtifactOwnerRetirementContinuation(ownerContext, ownerEvidence, outcome.continuation);
273
+ } else {
274
+ const outcome = gcContinuationReceipt.taskArtifactOwnerRetirementOutcome;
275
+ if (outcome?.kind !== "completed")
276
+ return continuationRefusal("task_artifact_owner_continuation_outcome_mismatch");
277
+ verifyTaskArtifactOwnerPhysicalRetirement(ownerContext, ownerEvidence, outcome);
278
+ }
279
+ } else {
280
+ ownerEvidence = captureTaskArtifactOwnerDeletionEvidence(ownerContext, header.id, ownerLocator);
281
+ if (!ownerEvidence) return { kind: "kept", reason: "task_artifact_owner_continuation_evidence_missing" };
282
+ }
283
+ if (
284
+ await hasSiblingTaskArtifactOwnerTranscript(
285
+ storage,
286
+ transcriptPath,
287
+ ownerLocator,
288
+ ownerContext,
289
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
290
+ )
291
+ )
292
+ return {
293
+ kind: "owner_shared",
294
+ reason: "task_artifact_owner_shared_with_sibling_transcript",
295
+ ...(gcContinuationReceipt ? { gcContinuationReceipt } : {}),
296
+ };
297
+ } catch (error) {
298
+ return continuationRefusal(`task_artifact_owner_continuation_invalid: ${toError(error).message}`);
299
+ }
300
+ }
301
+
302
+ const directory = path.dirname(transcriptPath);
303
+ return {
304
+ kind: "retirable",
305
+ artifactsPresent: storage.existsSync(transcriptPath.slice(0, -".jsonl".length)),
306
+ ...(ownerLocator ? { taskArtifactOwnerLocator: ownerLocator } : {}),
307
+ ...(gcContinuationReceipt ? { gcContinuationReceipt } : {}),
308
+ target: {
309
+ ...(ownerContext ? { taskArtifactOwnerStorageContext: ownerContext } : {}),
310
+ ...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
311
+ ...(ownerLocator ? { deferTaskArtifactOwnerRetirement: true as const } : {}),
312
+ sessionsRoot,
313
+ transcriptPath,
314
+ sessionId: header.id,
315
+ cwd: header.cwd,
316
+ transcriptIdentity: {
317
+ dev: snapshot.stat.dev,
318
+ ino: snapshot.stat.ino,
319
+ nlink: snapshot.stat.nlink,
320
+ size: snapshot.stat.size,
321
+ mtimeNs: snapshot.stat.mtimeNs,
322
+ sha256: crypto.createHash("sha256").update(snapshot.bytes).digest("hex"),
323
+ },
324
+ plannedArtifactsPath: path.join(directory, `.gjc-delete-gc-${crypto.randomUUID()}-artifacts`),
325
+ plannedTranscriptPath: path.join(directory, `.gjc-delete-gc-${crypto.randomUUID()}-transcript`),
326
+ },
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Non-mutating projection of {@link retireSessionTranscript}: would the
332
+ * retention pass's own preconditions let this transcript be retired at all?
333
+ *
334
+ * `gjc gc --disk` runs it so a dry run reports the verdict a prune would reach
335
+ * instead of promising bytes the delete authority will refuse to release. The
336
+ * authority's verdict (containment, identity, artifact tree) is deliberately
337
+ * not predicted here — it is re-derived against live state at delete time.
338
+ */
339
+ export async function probeSessionRetirement(
340
+ storage: FileSessionStorage,
341
+ sessionsRoot: string,
342
+ transcriptPath: string,
343
+ continuation?: SessionRetirementContinuation,
344
+ ): Promise<SessionRetirementProbeOutcome> {
345
+ const plan = await planSessionRetirement(storage, sessionsRoot, transcriptPath, continuation);
346
+ if (plan.kind === "kept") return plan;
347
+ if (plan.kind === "owner_shared" || plan.kind === "cleanup_pending") {
348
+ const receipt = plan.gcContinuationReceipt;
349
+ if (!receipt?.artifactsRemoved) return { kind: "kept", reason: plan.reason };
350
+ return cleanupPending(
351
+ plan.reason,
352
+ receipt.taskArtifactOwnerDeletionEvidence,
353
+ receipt.taskArtifactOwnerRetirementOutcome,
354
+ receipt.state === "owner_retired",
355
+ );
356
+ }
357
+ return planRetirementOutcome(plan);
358
+ }
359
+
360
+ /**
361
+ * A transcript that survives after its artifacts are already gone is not a
362
+ * benign keep: the session is half-destroyed and the caller has to surface it.
363
+ */
364
+ function retirementIncomplete(artifactsRemoved: boolean, reason: string): SessionRetirementOutcome {
365
+ return artifactsRemoved ? { kind: "cleanup_pending", reason } : { kind: "kept", reason };
366
+ }
367
+
368
+ function cleanupPending(
369
+ reason: string,
370
+ evidence: TaskArtifactOwnerDeletionEvidence,
371
+ outcome: TaskArtifactOwnerRetirementOutcome | undefined,
372
+ ownerRetired: boolean,
373
+ phase: "artifacts" | "transcript" = "artifacts",
374
+ ): Extract<SessionRetirementOutcome, { kind: "cleanup_pending" }> {
375
+ return { kind: "cleanup_pending", reason, phase, ...ownerOutcomeFields(evidence, outcome, ownerRetired) };
376
+ }
377
+
378
+ function errorMessage(error: unknown): string {
379
+ if (!(error instanceof AggregateError)) return toError(error).message;
380
+ const details = [...error.errors].map(cause => toError(cause).message).join("; ");
381
+ return details ? `${error.message}: ${details}` : error.message;
382
+ }
383
+
384
+ type ManagedGcOwnerProgress =
385
+ | { kind: "owner_retired"; receipt: ManagedGcSessionRetirementReceipt; outcome: TaskArtifactOwnerRetirementOutcome }
386
+ | { kind: "owner_pending"; receipt: ManagedGcSessionRetirementReceipt; outcome: TaskArtifactOwnerRetirementOutcome };
387
+
388
+ async function advanceManagedGcOwnerRetirement(
389
+ scope: ManagedScope,
390
+ receipt: ManagedGcSessionRetirementReceipt,
391
+ context: TaskArtifactOwnerStorageContext,
392
+ evidence: TaskArtifactOwnerDeletionEvidence,
393
+ ): Promise<ManagedGcOwnerProgress> {
394
+ if (receipt.state === "owner_retired") {
395
+ const outcome = receipt.taskArtifactOwnerRetirementOutcome;
396
+ if (outcome?.kind !== "completed") throw new Error("task_artifact_owner_continuation_outcome_mismatch");
397
+ verifyTaskArtifactOwnerPhysicalRetirement(context, evidence, outcome);
398
+ return { kind: "owner_retired", receipt, outcome };
399
+ }
400
+ if (receipt.state === "prepared") throw new Error("task_artifact_owner_continuation_state_order_invalid");
401
+ // `artifacts_removed` is the durable authorization for the first native
402
+ // scrub. Persist owner_pending only after that scrub returns its real proof;
403
+ // a replay from owner_pending revalidates the prior continuation first.
404
+ let previousContinuation: TaskArtifactOwnerRetirementContinuation | undefined;
405
+ if (receipt.state === "owner_pending") {
406
+ const previous = receipt.taskArtifactOwnerRetirementOutcome;
407
+ if (!previous || previous.kind === "completed")
408
+ throw new Error("task_artifact_owner_continuation_outcome_mismatch");
409
+ previousContinuation = verifyTaskArtifactOwnerRetirementContinuation(context, evidence, previous.continuation);
410
+ }
411
+ const outcome = retireTaskArtifactOwner(context, evidence, previousContinuation);
412
+ if (outcome.kind === "completed") {
413
+ const retired = await publishManagedGcSessionRetirementReceipt(scope, {
414
+ ...targetFromReceipt(receipt),
415
+ state: "owner_retired",
416
+ taskArtifactOwnerDeletionEvidence: evidence,
417
+ artifactsRemoved: true,
418
+ taskArtifactOwnerRetirementOutcome: outcome,
419
+ taskArtifactOwnerRetired: true,
420
+ });
421
+ return { kind: "owner_retired", receipt: retired, outcome: retired.taskArtifactOwnerRetirementOutcome! };
422
+ }
423
+ const pending = await publishManagedGcSessionRetirementReceipt(scope, {
424
+ ...targetFromReceipt(receipt),
425
+ state: "owner_pending",
426
+ taskArtifactOwnerDeletionEvidence: evidence,
427
+ artifactsRemoved: true,
428
+ taskArtifactOwnerRetirementOutcome: outcome,
429
+ taskArtifactOwnerRetirementContinuation: outcome.continuation,
430
+ ...(outcome.kind === "payload_retired"
431
+ ? { taskArtifactOwnerPayloadRetired: true as const, taskArtifactOwnerNamespaceRetained: true as const }
432
+ : {}),
433
+ });
434
+ return { kind: "owner_pending", receipt: pending, outcome: pending.taskArtifactOwnerRetirementOutcome! };
435
+ }
436
+
437
+ /** Resume journaled owner cleanup without broadening the captured tree authority. */
438
+ async function retireGcOwnerTranscript(
439
+ storage: FileSessionStorage,
440
+ plan: Extract<SessionRetirementPlan, { kind: "retirable" }>,
441
+ continuation: SessionRetirementContinuation,
442
+ ): Promise<SessionRetirementOutcome> {
443
+ const target = plan.target;
444
+ const ownerContext = target.taskArtifactOwnerStorageContext;
445
+ const evidence = target.taskArtifactOwnerDeletionEvidence;
446
+ const locator = plan.taskArtifactOwnerLocator;
447
+ if (!ownerContext || !evidence || !locator)
448
+ return { kind: "kept", reason: "task_artifact_owner_continuation_evidence_missing" };
449
+ const receiptTarget: ManagedGcSessionRetirementTarget = {
450
+ transcriptPath: target.transcriptPath,
451
+ sessionId: target.sessionId,
452
+ cwd: target.cwd,
453
+ transcriptIdentity: target.transcriptIdentity,
454
+ taskArtifactOwnerLocator: locator,
455
+ };
456
+ let receipt = plan.gcContinuationReceipt;
457
+ let artifactsRemoved = receipt?.artifactsRemoved === true;
458
+ let ownerOutcome = receipt?.taskArtifactOwnerRetirementOutcome;
459
+ let ownerRetired = receipt?.state === "owner_retired";
460
+ try {
461
+ if (
462
+ await hasSiblingTaskArtifactOwnerTranscript(
463
+ storage,
464
+ target.transcriptPath,
465
+ locator,
466
+ ownerContext,
467
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
468
+ )
469
+ )
470
+ return artifactsRemoved
471
+ ? cleanupPending("task_artifact_owner_shared_with_sibling_transcript", evidence, ownerOutcome, ownerRetired)
472
+ : { kind: "kept", reason: "task_artifact_owner_shared_with_sibling_transcript" };
473
+
474
+ if (!receipt) {
475
+ receipt = await publishManagedGcSessionRetirementReceipt(continuation.managedScope, {
476
+ ...receiptTarget,
477
+ state: "prepared",
478
+ taskArtifactOwnerDeletionEvidence: evidence,
479
+ });
480
+ }
481
+ if (receipt.state === "prepared") {
482
+ const artifactPhase = await storage.deleteSessionVerified(
483
+ {
484
+ ...target,
485
+ taskArtifactOwnerStorageContext: ownerContext,
486
+ taskArtifactOwnerDeletionEvidence: evidence,
487
+ deferTaskArtifactOwnerRetirement: true,
488
+ },
489
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
490
+ );
491
+ if (artifactPhase.kind === "cleanup_pending") {
492
+ if (artifactPhase.phase === "task_artifact_owner" && artifactPhase.artifactsRemoved === false)
493
+ return { kind: "kept", reason: artifactPhase.error.message };
494
+ return cleanupPending(
495
+ `cleanup_pending_${artifactPhase.phase}: ${artifactPhase.error.message}`,
496
+ evidence,
497
+ ownerOutcome,
498
+ ownerRetired,
499
+ "artifacts",
500
+ );
501
+ }
502
+ if (artifactPhase.kind !== "artifacts_removed") {
503
+ return { kind: "kept", reason: "task_artifact_owner_continuation_artifact_phase_incomplete" };
504
+ }
505
+ artifactsRemoved = true;
506
+ receipt = await publishManagedGcSessionRetirementReceipt(continuation.managedScope, {
507
+ ...receiptTarget,
508
+ state: "artifacts_removed",
509
+ taskArtifactOwnerDeletionEvidence: evidence,
510
+ artifactsRemoved: true,
511
+ });
512
+ }
513
+ if (
514
+ await hasSiblingTaskArtifactOwnerTranscript(
515
+ storage,
516
+ target.transcriptPath,
517
+ locator,
518
+ ownerContext,
519
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
520
+ )
521
+ )
522
+ return cleanupPending(
523
+ "task_artifact_owner_shared_with_sibling_transcript",
524
+ evidence,
525
+ ownerOutcome,
526
+ ownerRetired,
527
+ );
528
+
529
+ const progress = await advanceManagedGcOwnerRetirement(
530
+ continuation.managedScope,
531
+ receipt,
532
+ ownerContext,
533
+ evidence,
534
+ );
535
+ receipt = progress.receipt;
536
+ ownerOutcome = progress.outcome;
537
+ ownerRetired = progress.kind === "owner_retired";
538
+ if (progress.kind === "owner_pending")
539
+ return cleanupPending(
540
+ progress.outcome.kind === "uncertain"
541
+ ? progress.outcome.reason
542
+ : "task_artifact_owner_namespace_cleanup_pending",
543
+ evidence,
544
+ progress.outcome,
545
+ false,
546
+ );
547
+ if (
548
+ await hasSiblingTaskArtifactOwnerTranscript(
549
+ storage,
550
+ target.transcriptPath,
551
+ locator,
552
+ ownerContext,
553
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
554
+ )
555
+ )
556
+ return cleanupPending("task_artifact_owner_shared_with_sibling_transcript", evidence, ownerOutcome, true);
557
+
558
+ const deletion = await storage.deleteSessionVerified(
559
+ {
560
+ ...target,
561
+ taskArtifactOwnerStorageContext: ownerContext,
562
+ taskArtifactOwnerDeletionEvidence: evidence,
563
+ taskArtifactOwnerRetirementOutcome: ownerOutcome,
564
+ taskArtifactOwnerRetired: true,
565
+ deferTaskArtifactOwnerRetirement: true,
566
+ artifactsRemoved: true,
567
+ },
568
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
569
+ );
570
+ if (deletion.kind === "deleted") return { kind: "retired", taskArtifactOwnerRetired: true };
571
+ if (deletion.kind === "cleanup_pending")
572
+ return cleanupPending(
573
+ `cleanup_pending_${deletion.phase}: ${deletion.error.message}`,
574
+ evidence,
575
+ ownerOutcome,
576
+ true,
577
+ deletion.phase === "transcript" ? "transcript" : "artifacts",
578
+ );
579
+ return cleanupPending("transcript_not_deleted", evidence, ownerOutcome, true, "transcript");
580
+ } catch (error) {
581
+ const reason = `gc_task_artifact_owner_continuation_failed: ${errorMessage(error)}`;
582
+ return artifactsRemoved ? cleanupPending(reason, evidence, ownerOutcome, ownerRetired) : { kind: "kept", reason };
583
+ }
584
+ }
585
+
586
+ export async function retireOrphanSessionTranscript(
587
+ storage: FileSessionStorage,
588
+ sessionsRoot: string,
589
+ continuation: SessionRetirementContinuation,
590
+ receipt: ManagedGcSessionRetirementReceipt,
591
+ ): Promise<SessionRetirementOutcome> {
592
+ const evidence = receipt.taskArtifactOwnerDeletionEvidence;
593
+ let ownerOutcome = receipt.taskArtifactOwnerRetirementOutcome;
594
+ let ownerRetired = receipt.state === "owner_retired";
595
+ const artifactsRemoved = receipt.artifactsRemoved === true;
596
+ try {
597
+ const ownerContext = taskArtifactOwnerStorageContextForScope(continuation.managedScope);
598
+ if (
599
+ path.resolve(ownerContext.sessionsRoot) !== path.resolve(sessionsRoot) ||
600
+ !path.isAbsolute(receipt.transcriptPath) ||
601
+ path.resolve(receipt.transcriptPath) !== receipt.transcriptPath ||
602
+ !isWithinPath(sessionsRoot, receipt.transcriptPath) ||
603
+ path.dirname(receipt.transcriptPath) !== continuation.managedScope.directoryPath
604
+ )
605
+ return { kind: "kept", reason: "task_artifact_owner_continuation_authority_mismatch" };
606
+ const currentReceipt = await readManagedGcSessionRetirementReceiptReadOnly(
607
+ continuation.managedScope,
608
+ receipt.transcriptPath,
609
+ );
610
+ if (!currentReceipt || !util.isDeepStrictEqual(currentReceipt, receipt))
611
+ return { kind: "kept", reason: "task_artifact_owner_continuation_state_changed" };
612
+ if (storage.existsSync(receipt.transcriptPath))
613
+ return { kind: "kept", reason: "task_artifact_owner_orphan_transcript_reappeared" };
614
+ if (receipt.state === "prepared")
615
+ return cleanupPending("transcript_missing_before_artifact_phase", evidence, ownerOutcome, false, "transcript");
616
+ if (
617
+ await hasSiblingTaskArtifactOwnerTranscript(
618
+ storage,
619
+ receipt.transcriptPath,
620
+ evidence.locator,
621
+ ownerContext,
622
+ managedGcProtocolScopeInspectorForScope(continuation.managedScope),
623
+ )
624
+ )
625
+ return cleanupPending(
626
+ "task_artifact_owner_shared_with_sibling_transcript",
627
+ evidence,
628
+ ownerOutcome,
629
+ ownerRetired,
630
+ );
631
+
632
+ const progress = await advanceManagedGcOwnerRetirement(
633
+ continuation.managedScope,
634
+ receipt,
635
+ ownerContext,
636
+ evidence,
637
+ );
638
+ ownerOutcome = progress.outcome;
639
+ ownerRetired = progress.kind === "owner_retired";
640
+ if (progress.kind === "owner_pending")
641
+ return cleanupPending(
642
+ progress.outcome.kind === "uncertain"
643
+ ? progress.outcome.reason
644
+ : "task_artifact_owner_namespace_cleanup_pending",
645
+ evidence,
646
+ progress.outcome,
647
+ false,
648
+ );
649
+ return cleanupPending(
650
+ "transcript_missing_without_verified_retirement",
651
+ evidence,
652
+ ownerOutcome,
653
+ ownerRetired,
654
+ "transcript",
655
+ );
656
+ } catch (error) {
657
+ const reason = `gc_task_artifact_owner_continuation_failed: ${errorMessage(error)}`;
658
+ return artifactsRemoved ? cleanupPending(reason, evidence, ownerOutcome, ownerRetired) : { kind: "kept", reason };
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Retire one managed session transcript through the verified hard-delete
664
+ * authority.
665
+ *
666
+ * This is the only supported way for a retention pass to remove a transcript:
667
+ * identity, containment, header id/cwd, artifact tree and parent directory are
668
+ * all re-verified inside {@link FileSessionStorage.deleteSessionVerified}, and
669
+ * anything ambiguous fails closed as `kept` rather than deleting bytes. The
670
+ * caller owns the retention policy; this function owns nothing but the delete
671
+ * authority.
672
+ */
673
+ export async function retireSessionTranscript(
674
+ storage: FileSessionStorage,
675
+ sessionsRoot: string,
676
+ transcriptPath: string,
677
+ continuation?: SessionRetirementContinuation,
678
+ ): Promise<SessionRetirementOutcome> {
679
+ const plan = await planSessionRetirement(storage, sessionsRoot, transcriptPath, continuation);
680
+ if (plan.kind === "kept") return plan;
681
+ if (plan.kind === "owner_shared" || plan.kind === "cleanup_pending") {
682
+ const receipt = plan.gcContinuationReceipt;
683
+ if (!receipt?.artifactsRemoved) return { kind: "kept", reason: plan.reason };
684
+ return cleanupPending(
685
+ plan.reason,
686
+ receipt.taskArtifactOwnerDeletionEvidence,
687
+ receipt.taskArtifactOwnerRetirementOutcome,
688
+ receipt.state === "owner_retired",
689
+ );
690
+ }
691
+ if (plan.taskArtifactOwnerLocator && continuation) return retireGcOwnerTranscript(storage, plan, continuation);
692
+
693
+ let artifactsRemoved = false;
694
+ try {
695
+ // Phase 1 removes the sibling artifact directory (or proves it absent);
696
+ // phase 2 unlinks the transcript only against the same bound identity.
697
+ const artifacts = await storage.deleteSessionVerified(plan.target);
698
+ if (artifacts.kind === "deleted") return { kind: "retired" };
699
+ if (artifacts.kind === "cleanup_pending") {
700
+ return { kind: "cleanup_pending", reason: `cleanup_pending_${artifacts.phase}: ${artifacts.error.message}` };
701
+ }
702
+ artifactsRemoved = plan.artifactsPresent;
703
+ const deletion = await storage.deleteSessionVerified({ ...plan.target, artifactsRemoved: true });
704
+ if (deletion.kind === "deleted") return { kind: "retired" };
705
+ if (deletion.kind === "cleanup_pending") {
706
+ return { kind: "cleanup_pending", reason: `cleanup_pending_${deletion.phase}: ${deletion.error.message}` };
707
+ }
708
+ return retirementIncomplete(artifactsRemoved, "transcript_not_deleted");
709
+ } catch (err) {
710
+ const error = toError(err);
711
+ const kind = err instanceof SessionDeleteVerificationError ? err.kind : "error";
712
+ return retirementIncomplete(artifactsRemoved, `verified_delete_rejected(${kind}): ${error.message}`);
713
+ }
714
+ }