@gajae-code/coding-agent 0.18.7 → 0.18.9

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 (344) hide show
  1. package/CHANGELOG.md +377 -1
  2. package/dist/types/async/endpoint-id.d.ts +16 -0
  3. package/dist/types/async/job-manager.d.ts +1 -1
  4. package/dist/types/async/support.d.ts +0 -14
  5. package/dist/types/capability/mcp.d.ts +2 -0
  6. package/dist/types/capability/skill.d.ts +2 -0
  7. package/dist/types/capability/types.d.ts +2 -0
  8. package/dist/types/cli/public-command-errors.d.ts +3 -1
  9. package/dist/types/cli/setup-cli.d.ts +1 -1
  10. package/dist/types/commands/deep-interview.d.ts +3 -0
  11. package/dist/types/commands/mcp-serve.d.ts +4 -0
  12. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  13. package/dist/types/config/file-lock.d.ts +1 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  15. package/dist/types/config/model-profile-activation.d.ts +11 -3
  16. package/dist/types/config/model-registry.d.ts +4 -9
  17. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  18. package/dist/types/cursor-delete-path.d.ts +1 -0
  19. package/dist/types/cursor-mcp-name.d.ts +8 -0
  20. package/dist/types/customization/inventory.d.ts +2 -0
  21. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  24. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  25. package/dist/types/discovery/helpers.d.ts +6 -1
  26. package/dist/types/discovery/mcp-json.d.ts +1 -0
  27. package/dist/types/eval/py/kernel.d.ts +18 -0
  28. package/dist/types/export/html/css-color.d.ts +2 -0
  29. package/dist/types/export/html/html-export-destination.d.ts +2 -0
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
  32. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  33. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  34. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  35. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  36. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  37. package/dist/types/extensibility/skill-management.d.ts +4 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  40. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  41. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  42. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  44. package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
  45. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  46. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  47. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  48. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  49. package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
  50. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  51. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  52. package/dist/types/lsp/config.d.ts +8 -5
  53. package/dist/types/lsp/workspace-path.d.ts +1 -0
  54. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  56. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  57. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  58. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  59. package/dist/types/modes/interactive-mode.d.ts +1 -0
  60. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  61. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  62. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  63. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  64. package/dist/types/progress/progress-contract.d.ts +154 -0
  65. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  66. package/dist/types/progress/project-progress.d.ts +141 -0
  67. package/dist/types/progress/render-progress.d.ts +28 -0
  68. package/dist/types/runtime-mcp/manager.d.ts +2 -1
  69. package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
  70. package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
  71. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  72. package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
  73. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  74. package/dist/types/runtime-mcp/types.d.ts +6 -0
  75. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  76. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  77. package/dist/types/sdk/broker/broker.d.ts +24 -2
  78. package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
  79. package/dist/types/sdk/broker/ensure.d.ts +14 -1
  80. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  81. package/dist/types/sdk/bus/index.d.ts +12 -1
  82. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  83. package/dist/types/sdk/client/client.d.ts +2 -0
  84. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  85. package/dist/types/sdk/index.d.ts +1 -0
  86. package/dist/types/sdk/router/session-router.d.ts +2 -0
  87. package/dist/types/sdk/session-list.d.ts +1 -1
  88. package/dist/types/sdk/session.d.ts +8 -0
  89. package/dist/types/session/agent-session.d.ts +38 -9
  90. package/dist/types/session/artifacts.d.ts +2 -2
  91. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  92. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  93. package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
  94. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  95. package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
  96. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
  97. package/dist/types/session/session-manager.d.ts +31 -5
  98. package/dist/types/session/session-retirement.d.ts +31 -7
  99. package/dist/types/session/session-storage.d.ts +69 -2
  100. package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
  101. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  102. package/dist/types/setup/credential-import.d.ts +6 -4
  103. package/dist/types/skill-state/active-state.d.ts +38 -0
  104. package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
  105. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  106. package/dist/types/task/executor.d.ts +3 -0
  107. package/dist/types/task/isolation-dir.d.ts +26 -0
  108. package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
  109. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  110. package/dist/types/tools/browser/cdp-url.d.ts +2 -0
  111. package/dist/types/tools/github-side-effect.d.ts +22 -0
  112. package/dist/types/tools/index.d.ts +3 -0
  113. package/dist/types/tools/monitor-mutation.d.ts +9 -0
  114. package/dist/types/tools/python.d.ts +9 -1
  115. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  116. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  117. package/dist/types/tools/todo-write.d.ts +6 -0
  118. package/dist/types/utils/tools-checksum.d.ts +7 -0
  119. package/package.json +7 -7
  120. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  121. package/scripts/verify-managed-empty-stop.ts +28 -0
  122. package/src/async/endpoint-id.ts +26 -0
  123. package/src/async/job-manager.ts +247 -132
  124. package/src/async/support.ts +0 -25
  125. package/src/capability/index.ts +15 -0
  126. package/src/capability/mcp.ts +2 -0
  127. package/src/capability/skill.ts +2 -0
  128. package/src/capability/types.ts +2 -0
  129. package/src/cli/auth-broker-cli.ts +1 -1
  130. package/src/cli/customize-doctor.ts +15 -2
  131. package/src/cli/doctor/repairs.ts +4 -1
  132. package/src/cli/doctor-supervisor.ts +3 -0
  133. package/src/cli/public-command-errors.ts +21 -1
  134. package/src/cli/public-command-registry.ts +28 -6
  135. package/src/cli/setup-cli.ts +4 -4
  136. package/src/cli/skills-cli.ts +5 -0
  137. package/src/commands/deep-interview.ts +47 -8
  138. package/src/commands/mcp-serve.ts +7 -0
  139. package/src/commands/sdk.ts +455 -397
  140. package/src/config/autorouting-tier-map.ts +117 -7
  141. package/src/config/file-lock.ts +316 -79
  142. package/src/config/mcp-schema.json +4 -0
  143. package/src/config/model-bindings-applier.ts +20 -0
  144. package/src/config/model-profile-activation.ts +240 -46
  145. package/src/config/model-registry.ts +47 -153
  146. package/src/config/skill-settings-defaults.ts +7 -0
  147. package/src/coordinator-mcp/policy.ts +3 -2
  148. package/src/coordinator-mcp/server.ts +2 -0
  149. package/src/cursor-delete-path.ts +10 -0
  150. package/src/cursor-mcp-name.ts +15 -0
  151. package/src/cursor.ts +13 -2
  152. package/src/customization/inventory.ts +4 -13
  153. package/src/dap/config.ts +2 -2
  154. package/src/debug/report-bundle-parent.ts +22 -0
  155. package/src/debug/report-bundle.ts +3 -0
  156. package/src/debug/system-info.ts +13 -1
  157. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
  158. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  159. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  160. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  161. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  162. package/src/defaults/gjc-defaults.test.ts +18 -1
  163. package/src/defaults/gjc-defaults.ts +3 -0
  164. package/src/defaults/gjc-skills.generated.ts +58 -36
  165. package/src/discovery/agents.ts +6 -1
  166. package/src/discovery/builtin.ts +13 -1
  167. package/src/discovery/helpers.ts +171 -29
  168. package/src/discovery/mcp-json.ts +26 -12
  169. package/src/discovery/ssh.ts +1 -1
  170. package/src/discovery/substitute-plugin-root.ts +3 -1
  171. package/src/edit/file-read-cache.ts +1 -1
  172. package/src/eval/py/executor.ts +796 -154
  173. package/src/eval/py/kernel.ts +79 -13
  174. package/src/eval/py/tool-bridge.ts +4 -0
  175. package/src/export/html/css-color.ts +9 -0
  176. package/src/export/html/html-export-destination.ts +16 -0
  177. package/src/export/html/index.ts +7 -10
  178. package/src/export/html/template.generated.ts +1 -1
  179. package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
  180. package/src/extensibility/extensions/runner.ts +15 -1
  181. package/src/extensibility/extensions/types.ts +7 -0
  182. package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
  183. package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
  184. package/src/extensibility/plugins/manager.ts +3 -2
  185. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  186. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  187. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  188. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  189. package/src/extensibility/skill-management.ts +9 -2
  190. package/src/extensibility/skills.ts +6 -1
  191. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  192. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  193. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  194. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  195. package/src/gjc-runtime/gc-runtime.ts +206 -9
  196. package/src/gjc-runtime/launch-worktree.ts +70 -9
  197. package/src/gjc-runtime/psmux-detect.ts +28 -4
  198. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  199. package/src/gjc-runtime/session-state-sidecar.ts +3 -2
  200. package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
  201. package/src/gjc-runtime/state-argv.ts +2 -0
  202. package/src/gjc-runtime/state-migrations.ts +91 -29
  203. package/src/gjc-runtime/state-runtime.ts +3716 -135
  204. package/src/gjc-runtime/state-writer.ts +105 -16
  205. package/src/gjc-runtime/tmux-common.ts +8 -5
  206. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  207. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  208. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  209. package/src/harness-control-plane/operate.ts +12 -4
  210. package/src/harness-control-plane/owner.ts +5 -1
  211. package/src/hashline/parser.ts +48 -9
  212. package/src/hashline/recovery.ts +13 -4
  213. package/src/internal-urls/docs-index.generated.ts +20 -19
  214. package/src/internal-urls/skill-path.ts +19 -0
  215. package/src/internal-urls/skill-protocol.ts +2 -0
  216. package/src/lsp/config.ts +12 -63
  217. package/src/lsp/edits.ts +6 -0
  218. package/src/lsp/workspace-path.ts +51 -0
  219. package/src/main.ts +195 -31
  220. package/src/memories/index.ts +88 -7
  221. package/src/modes/acp/acp-agent.ts +88 -4
  222. package/src/modes/components/custom-editor.ts +14 -0
  223. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  224. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  225. package/src/modes/components/extensions/state-manager.ts +15 -5
  226. package/src/modes/components/hook-editor.ts +1 -1
  227. package/src/modes/components/hook-selector.ts +1 -1
  228. package/src/modes/components/model-selector.ts +1 -4
  229. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  230. package/src/modes/components/status-line/segments.ts +4 -0
  231. package/src/modes/controllers/event-controller.ts +11 -4
  232. package/src/modes/controllers/input-controller.ts +76 -31
  233. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
  234. package/src/modes/controllers/selector-controller.ts +24 -15
  235. package/src/modes/controllers/todo-command-controller.ts +1 -1
  236. package/src/modes/interactive-mode.ts +5 -1
  237. package/src/modes/runtime-init.ts +4 -0
  238. package/src/modes/utils/abort-message.ts +3 -1
  239. package/src/modes/utils/interrupt-ownership.ts +12 -0
  240. package/src/modes/utils/keybinding-matchers.ts +4 -0
  241. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  242. package/src/progress/collect-project-progress.ts +153 -0
  243. package/src/progress/progress-contract.ts +172 -0
  244. package/src/progress/progress-snapshot.ts +140 -0
  245. package/src/progress/project-progress.ts +446 -0
  246. package/src/progress/render-progress.ts +209 -0
  247. package/src/prompts/system/master-mode.md +1 -0
  248. package/src/prompts/tools/skill-discovery.md +1 -1
  249. package/src/runtime-mcp/client.ts +4 -2
  250. package/src/runtime-mcp/config-writer.ts +10 -6
  251. package/src/runtime-mcp/config.ts +1 -0
  252. package/src/runtime-mcp/json-rpc.ts +2 -1
  253. package/src/runtime-mcp/manager.ts +29 -11
  254. package/src/runtime-mcp/mcp-redirect.ts +73 -0
  255. package/src/runtime-mcp/oauth-flow.ts +14 -2
  256. package/src/runtime-mcp/oauth-public-url.ts +14 -0
  257. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  258. package/src/runtime-mcp/pool.ts +6 -0
  259. package/src/runtime-mcp/project-host-values.ts +46 -0
  260. package/src/runtime-mcp/smithery-registry.ts +22 -3
  261. package/src/runtime-mcp/transports/http.ts +4 -3
  262. package/src/runtime-mcp/types.ts +6 -0
  263. package/src/sdk/acp/adapter.ts +95 -8
  264. package/src/sdk/broker/broker-exit.ts +52 -23
  265. package/src/sdk/broker/broker.ts +81 -6
  266. package/src/sdk/broker/doctor-restart.ts +4 -0
  267. package/src/sdk/broker/ensure.ts +31 -2
  268. package/src/sdk/broker/lifecycle.ts +711 -66
  269. package/src/sdk/broker/session-index.ts +191 -81
  270. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  271. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  272. package/src/sdk/bus/index.ts +238 -43
  273. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  274. package/src/sdk/cli/session-cli.ts +17 -3
  275. package/src/sdk/client/client.ts +28 -8
  276. package/src/sdk/host/query/handlers.ts +9 -1
  277. package/src/sdk/host/session-runtime.test.ts +2705 -303
  278. package/src/sdk/host/session-runtime.ts +490 -143
  279. package/src/sdk/host/surface-policy.ts +1 -0
  280. package/src/sdk/index.ts +8 -0
  281. package/src/sdk/lifecycle-session.ts +3 -1
  282. package/src/sdk/prompt-deadline-manager.ts +15 -0
  283. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  284. package/src/sdk/protocol/operation-registry.ts +9 -2
  285. package/src/sdk/router/session-router.ts +82 -18
  286. package/src/sdk/session-list.ts +1 -0
  287. package/src/sdk/session.ts +69 -45
  288. package/src/session/agent-session.ts +679 -238
  289. package/src/session/artifacts.ts +176 -71
  290. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  291. package/src/session/internal/managed-session-scope.ts +3779 -462
  292. package/src/session/internal/managed-session-storage.ts +501 -78
  293. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  294. package/src/session/internal/task-artifact-owner-access.ts +19 -3
  295. package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
  296. package/src/session/session-manager.ts +951 -133
  297. package/src/session/session-retirement.ts +573 -14
  298. package/src/session/session-storage.ts +447 -15
  299. package/src/session/task-artifact-owner-codec.ts +71 -8
  300. package/src/session/task-artifact-owner-retirement.ts +21 -19
  301. package/src/setup/credential-auto-import.ts +12 -3
  302. package/src/setup/credential-import.ts +176 -5
  303. package/src/setup/paseo/announce.ts +6 -1
  304. package/src/skill-state/active-state.ts +145 -2
  305. package/src/skill-state/workflow-mutation-guard.ts +102 -10
  306. package/src/slash-commands/builtin-registry.ts +39 -9
  307. package/src/slash-commands/helpers/mcp.ts +8 -4
  308. package/src/slash-commands/helpers/progress-report.ts +23 -0
  309. package/src/stt/setup.ts +3 -5
  310. package/src/task/commands.ts +3 -1
  311. package/src/task/executor.ts +4 -0
  312. package/src/task/index.ts +6 -0
  313. package/src/task/isolation-dir.ts +143 -0
  314. package/src/task/worktree.ts +16 -7
  315. package/src/tools/ask.ts +205 -6
  316. package/src/tools/ast-edit-write-path.ts +68 -0
  317. package/src/tools/ast-edit.ts +10 -1
  318. package/src/tools/bash-allowed-prefixes.ts +15 -0
  319. package/src/tools/bash-skill-urls.ts +56 -3
  320. package/src/tools/bash.ts +48 -79
  321. package/src/tools/browser/cdp-url.ts +18 -0
  322. package/src/tools/browser.ts +8 -1
  323. package/src/tools/descriptors.ts +5 -1
  324. package/src/tools/eval.ts +56 -36
  325. package/src/tools/gh.ts +10 -0
  326. package/src/tools/github-side-effect.ts +112 -0
  327. package/src/tools/image-gen.ts +1 -1
  328. package/src/tools/index.ts +3 -0
  329. package/src/tools/monitor-mutation.ts +27 -0
  330. package/src/tools/monitor.ts +7 -0
  331. package/src/tools/python.ts +188 -66
  332. package/src/tools/recipe/index.ts +9 -0
  333. package/src/tools/recipe/recipe-guard.ts +19 -0
  334. package/src/tools/restricted-cwd.ts +10 -0
  335. package/src/tools/skill-discovery.ts +5 -0
  336. package/src/tools/skill.ts +28 -1
  337. package/src/tools/todo-write.ts +10 -1
  338. package/src/tools/tool-catalog.generated.ts +1 -1
  339. package/src/tools/write.ts +1 -0
  340. package/src/utils/external-editor.ts +3 -2
  341. package/src/utils/tools-checksum.ts +34 -0
  342. package/src/utils/tools-manager.ts +24 -6
  343. package/src/web/insane/url-guard.ts +2 -1
  344. package/src/web/search/providers/codex.ts +3 -2
@@ -13,7 +13,7 @@ function nativeLifecycle(): typeof import("@gajae-code/natives") {
13
13
  return nativeLifecycleBindings;
14
14
  }
15
15
 
16
- import { $credentialEnv, logger, resolveEquivalentPath } from "@gajae-code/utils";
16
+ import { $credentialEnv, getSessionsDir, logger, resolveEquivalentPath } from "@gajae-code/utils";
17
17
  import {
18
18
  loadAcceptedModelPresetRegistry,
19
19
  loadAcceptedModelPresetRegistryAsync,
@@ -43,7 +43,19 @@ import {
43
43
  GJC_COORDINATOR_SIDECAR_SIGNATURE_REQUIRED_ENV,
44
44
  GJC_COORDINATOR_SIDECAR_SIGNING_KEY_ENV,
45
45
  } from "../../gjc-runtime/session-state-sidecar";
46
+ import {
47
+ type ManagedGcProtocolScopeInspector,
48
+ type ManagedScope,
49
+ managedGcProtocolScopeInspectorForScope,
50
+ resolveManagedGcScopeForRead,
51
+ taskArtifactOwnerStorageContextForScope,
52
+ } from "../../session/internal/managed-session-scope";
46
53
  import { validateManagedArtifactTree } from "../../session/internal/managed-session-storage";
54
+ import { captureTaskArtifactOwnerDeletionEvidence } from "../../session/internal/task-artifact-owner-access";
55
+ import {
56
+ hasSiblingTaskArtifactOwnerTranscript,
57
+ taskArtifactOwnerLocatorFromTranscriptBytes,
58
+ } from "../../session/internal/task-artifact-owner-transcript";
47
59
  import {
48
60
  FileSessionStorage,
49
61
  SessionDeleteVerificationError,
@@ -52,7 +64,18 @@ import {
52
64
  type VerifiedSessionDeleteResult,
53
65
  type VerifiedSessionDeleteTarget,
54
66
  } from "../../session/session-storage";
67
+ import { parseFirstJsonlLine } from "../../session/session-transcript-header";
55
68
  import type { SessionWorkLease } from "../../session/session-work-lease";
69
+ import type {
70
+ TaskArtifactOwnerDeletionEvidence,
71
+ TaskArtifactOwnerLocator,
72
+ TaskArtifactOwnerRetirementOutcome,
73
+ TaskArtifactOwnerStorageContext,
74
+ } from "../../session/task-artifact-owner-codec";
75
+ import {
76
+ retireTaskArtifactOwner,
77
+ verifyTaskArtifactOwnerPhysicalRetirement,
78
+ } from "../../session/task-artifact-owner-retirement";
56
79
  import type { SessionLifecycleMcpServer } from "../acp/mcp";
57
80
  import { SdkClient, SdkClientError } from "../client/client";
58
81
  import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "../host/control/runtime-gate";
@@ -106,6 +129,12 @@ import {
106
129
  readPreparationTimeouts,
107
130
  startupQueueWaitMs,
108
131
  } from "./startup-budget";
132
+ import {
133
+ type BrokerTaskArtifactOwnerCleanupValidation,
134
+ type BrokerTaskArtifactOwnerRetirementDisposition,
135
+ decodeBrokerTaskArtifactOwnerCleanupFields,
136
+ serializeBrokerTaskArtifactOwnerRetirementDisposition,
137
+ } from "./task-artifact-owner-validation";
109
138
  import { worktreeOccupant } from "./worktree-occupancy";
110
139
 
111
140
  export {
@@ -961,6 +990,130 @@ function lifecycleLaunchKnownSecrets(launch: SessionLaunch): string[] {
961
990
  }
962
991
 
963
992
  type CleanupEvidence = BrokerCleanupEvidence;
993
+
994
+ function brokerTaskArtifactOwnerCleanupFields(cleanup: CleanupEvidence): Record<string, unknown> {
995
+ const fields: Record<string, unknown> = {};
996
+ for (const [key, value] of Object.entries(cleanup)) {
997
+ if (key.startsWith("taskArtifactOwner")) fields[key] = value;
998
+ }
999
+ return fields;
1000
+ }
1001
+
1002
+ function managedOwnerScopeFromInventory(scope: ManagedSessionScope): ManagedScope {
1003
+ const resolved = resolveManagedGcScopeForRead({
1004
+ cwd: scope.legacyLexicalCwd,
1005
+ agentDir: scope.agentDir,
1006
+ sessionsRoot: scope.sessionsRoot,
1007
+ });
1008
+ if (
1009
+ resolved.kind !== "resolved" ||
1010
+ resolved.scope.apiVersion !== scope.apiVersion ||
1011
+ resolved.scope.layoutVersion !== scope.layoutVersion ||
1012
+ resolved.scope.identityVersion !== scope.identityVersion ||
1013
+ resolved.scope.agentDir !== scope.agentDir ||
1014
+ resolved.scope.sessionsRoot !== scope.sessionsRoot ||
1015
+ resolved.scope.canonicalCwd !== scope.canonicalCwd ||
1016
+ resolved.scope.legacyLexicalCwd !== scope.legacyLexicalCwd ||
1017
+ resolved.scope.directoryName !== scope.directoryName ||
1018
+ resolved.scope.directoryPath !== scope.directoryPath
1019
+ )
1020
+ throw new Error("managed_task_artifact_owner_scope_changed");
1021
+ return resolved.scope;
1022
+ }
1023
+
1024
+ function taskArtifactOwnerTranscriptMatches(
1025
+ storage: FileSessionStorage,
1026
+ target: VerifiedSessionDeleteTarget,
1027
+ evidence: TaskArtifactOwnerDeletionEvidence,
1028
+ ): boolean {
1029
+ const transcriptPath = target.detachedTranscriptPath ?? target.transcriptPath;
1030
+ try {
1031
+ const parent = fsSync.lstatSync(path.dirname(transcriptPath), { bigint: true });
1032
+ if (
1033
+ !parent.isDirectory() ||
1034
+ parent.isSymbolicLink() ||
1035
+ parent.dev !== target.transcriptParentIdentity?.dev ||
1036
+ parent.ino !== target.transcriptParentIdentity?.ino
1037
+ )
1038
+ return false;
1039
+ const snapshot = storage.readSnapshotSync(transcriptPath);
1040
+ const digest = createHash("sha256").update(snapshot.bytes).digest("hex");
1041
+ if (
1042
+ !snapshot.stat.isFile ||
1043
+ snapshot.stat.dev !== target.transcriptIdentity.dev ||
1044
+ snapshot.stat.ino !== target.transcriptIdentity.ino ||
1045
+ snapshot.stat.nlink !== target.transcriptIdentity.nlink ||
1046
+ snapshot.stat.size !== target.transcriptIdentity.size ||
1047
+ snapshot.stat.mtimeNs !== target.transcriptIdentity.mtimeNs ||
1048
+ digest !== target.transcriptIdentity.sha256
1049
+ )
1050
+ return false;
1051
+ const header = parseFirstJsonlLine(snapshot.bytes);
1052
+ if (
1053
+ !header ||
1054
+ typeof header.cwd !== "string" ||
1055
+ canonicalExistingPath(header.cwd) !== canonicalExistingPath(target.cwd)
1056
+ )
1057
+ return false;
1058
+ const locator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, target.sessionId);
1059
+ return (
1060
+ locator?.schemaVersion === evidence.locator.schemaVersion &&
1061
+ locator.ownerId === evidence.locator.ownerId &&
1062
+ locator.directoryDev === evidence.locator.directoryDev &&
1063
+ locator.directoryIno === evidence.locator.directoryIno
1064
+ );
1065
+ } catch {
1066
+ return false;
1067
+ }
1068
+ }
1069
+
1070
+ function taskArtifactOwnerFailureDiagnostic(error: unknown): string {
1071
+ const message = error instanceof Error ? error.message : String(error);
1072
+ return (
1073
+ message.startsWith("task_artifact_owner_") ? message : `task_artifact_owner_cleanup_failed:${message}`
1074
+ ).slice(0, 4096);
1075
+ }
1076
+
1077
+ function taskArtifactOwnerRetirementDisposition(
1078
+ outcome: TaskArtifactOwnerRetirementOutcome,
1079
+ ): BrokerTaskArtifactOwnerRetirementDisposition {
1080
+ return serializeBrokerTaskArtifactOwnerRetirementDisposition(outcome);
1081
+ }
1082
+
1083
+ function captureTaskArtifactOwnerEvidence(
1084
+ storage: FileSessionStorage,
1085
+ target: VerifiedSessionDeleteTarget,
1086
+ ): { evidence?: TaskArtifactOwnerDeletionEvidence; error?: string } {
1087
+ const transcriptPath = target.detachedTranscriptPath ?? target.transcriptPath;
1088
+ let snapshot: SessionStorageSnapshot;
1089
+ try {
1090
+ snapshot = storage.readSnapshotSync(transcriptPath);
1091
+ } catch {
1092
+ return { error: "task_artifact_owner_transcript_unavailable" };
1093
+ }
1094
+ const digest = createHash("sha256").update(snapshot.bytes).digest("hex");
1095
+ if (
1096
+ !snapshot.stat.isFile ||
1097
+ snapshot.stat.dev !== target.transcriptIdentity.dev ||
1098
+ snapshot.stat.ino !== target.transcriptIdentity.ino ||
1099
+ snapshot.stat.nlink !== target.transcriptIdentity.nlink ||
1100
+ snapshot.stat.size !== target.transcriptIdentity.size ||
1101
+ snapshot.stat.mtimeNs !== target.transcriptIdentity.mtimeNs ||
1102
+ digest !== target.transcriptIdentity.sha256
1103
+ )
1104
+ return { error: "task_artifact_owner_transcript_identity_mismatch" };
1105
+ try {
1106
+ const locator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, target.sessionId);
1107
+ if (!locator) return {};
1108
+ const context = target.taskArtifactOwnerStorageContext;
1109
+ if (!context) return { error: "task_artifact_owner_context_missing" };
1110
+ const evidence = captureTaskArtifactOwnerDeletionEvidence(context, target.sessionId, locator);
1111
+ return evidence ? { evidence } : { error: "task_artifact_owner_evidence_missing" };
1112
+ } catch (error) {
1113
+ return { error: taskArtifactOwnerFailureDiagnostic(error) };
1114
+ }
1115
+ }
1116
+
964
1117
  type CleanupIdentity = {
965
1118
  dev: bigint;
966
1119
  ino: bigint;
@@ -5965,6 +6118,8 @@ type ValidatedDelete = {
5965
6118
  target: VerifiedSessionDeleteTarget;
5966
6119
  metadataRoot: string;
5967
6120
  transcriptParentIdentity: { dev: string; ino: string };
6121
+ taskArtifactOwnerCaptureError?: string;
6122
+ inspectProtocol?: ManagedGcProtocolScopeInspector;
5968
6123
  };
5969
6124
  function cleanupIdentity(
5970
6125
  identity: BrokerCleanupEvidence["transcriptIdentity"],
@@ -6038,8 +6193,12 @@ function replayDeleteTarget(cleanup: CleanupEvidence): ValidatedDelete | BrokerR
6038
6193
  (artifactsIdentity.dev !== artifactTreeIdentity.dev || artifactsIdentity.ino !== artifactTreeIdentity.ino)
6039
6194
  )
6040
6195
  return fail("terminal_uncertain", "Artifact cleanup tree does not match its ledger-bound root identity.");
6041
- if (cleanup.phase === "artifacts" && cleanup.artifactsRemoved === true)
6042
- return fail("terminal_uncertain", "Artifacts-phase cleanup receipt falsely claims artifact completion.");
6196
+ if (
6197
+ cleanup.phase === "artifacts" &&
6198
+ cleanup.artifactsRemoved === true &&
6199
+ cleanup.taskArtifactOwnerDeletionEvidence === undefined
6200
+ )
6201
+ return fail("terminal_uncertain", "Artifacts-phase cleanup receipt lacks immutable owner evidence.");
6043
6202
  if (
6044
6203
  cleanup.artifactsRemoved === true &&
6045
6204
  cleanup.artifactTree &&
@@ -6243,7 +6402,126 @@ async function validateDeletePath(
6243
6402
  canonicalExistingPath(replay.metadataRoot) !== canonicalRequestedRoot
6244
6403
  )
6245
6404
  return fail("invalid_input", "Cleanup receipt does not match the requested saved-session locator.");
6246
- return replay;
6405
+ const ownerFields = brokerTaskArtifactOwnerCleanupFields(cleanup);
6406
+ if (Object.keys(ownerFields).length === 0) {
6407
+ const sessionsRoot = canonicalExistingPath(getSessionsDir(broker.settings.agentDir));
6408
+ const relative = path.relative(sessionsRoot, replay.target.transcriptPath);
6409
+ if (
6410
+ path.resolve(replay.target.sessionsRoot) !== sessionsRoot ||
6411
+ relative === "" ||
6412
+ relative === ".." ||
6413
+ relative.startsWith(`..${path.sep}`) ||
6414
+ path.isAbsolute(relative)
6415
+ )
6416
+ return fail("terminal_uncertain", "Cleanup receipt does not match the configured session root.");
6417
+ return replay;
6418
+ }
6419
+ const inventory = await managedCandidates(broker, cwd, "Saved");
6420
+ if ("ok" in inventory) return inventory;
6421
+ const transcriptRelative = path.relative(inventory.scope.sessionsRoot, replay.target.transcriptPath);
6422
+ if (
6423
+ path.resolve(replay.target.sessionsRoot) !== inventory.scope.sessionsRoot ||
6424
+ transcriptRelative === "" ||
6425
+ transcriptRelative === ".." ||
6426
+ transcriptRelative.startsWith(`..${path.sep}`) ||
6427
+ path.isAbsolute(transcriptRelative)
6428
+ )
6429
+ return fail("terminal_uncertain", "Cleanup receipt does not match the current managed session authority.");
6430
+ replay.target.sessionsRoot = inventory.scope.sessionsRoot;
6431
+ let ownerContext: TaskArtifactOwnerStorageContext;
6432
+ let ownerScope: ManagedScope;
6433
+ try {
6434
+ ownerScope = managedOwnerScopeFromInventory(inventory.scope);
6435
+ ownerContext = taskArtifactOwnerStorageContextForScope(ownerScope);
6436
+ } catch {
6437
+ return fail(
6438
+ "terminal_uncertain",
6439
+ "Managed task-artifact-owner authority could not be restored for cleanup replay.",
6440
+ );
6441
+ }
6442
+ let owner: BrokerTaskArtifactOwnerCleanupValidation;
6443
+ try {
6444
+ owner = decodeBrokerTaskArtifactOwnerCleanupFields(
6445
+ ownerFields,
6446
+ ownerContext,
6447
+ id,
6448
+ cleanup.phase,
6449
+ cleanup.artifactsRemoved,
6450
+ );
6451
+ } catch {
6452
+ return fail("terminal_uncertain", "Cleanup receipt contains invalid task-artifact owner state.");
6453
+ }
6454
+ let captureError = owner.cleanupDiagnostic;
6455
+ if (!owner.deletionEvidence && captureError === "task_artifact_owner_writer_not_quiescent") {
6456
+ const retainedPaths = [
6457
+ replay.target.detachedArtifactsPath,
6458
+ replay.target.detachedTranscriptPath,
6459
+ replay.target.retainedArtifactsSuccessorPath,
6460
+ replay.target.retainedArtifactsPlaceholderPath,
6461
+ replay.target.retainedArtifactsUnknownPath,
6462
+ replay.target.retainedTranscriptSuccessorPath,
6463
+ replay.target.retainedTranscriptPlaceholderPath,
6464
+ replay.target.retainedTranscriptUnknownPath,
6465
+ ];
6466
+ const plannedPaths = [
6467
+ cleanup.plannedArtifactsPath,
6468
+ cleanup.artifactTree?.plannedPath,
6469
+ cleanup.plannedTranscriptPath,
6470
+ ];
6471
+ const occupiedPlan = plannedPaths.some(planned => {
6472
+ if (!planned) return false;
6473
+ return [planned, `${planned}.removing`].some(candidate => {
6474
+ try {
6475
+ fsSync.lstatSync(candidate);
6476
+ return true;
6477
+ } catch (error) {
6478
+ return (error as NodeJS.ErrnoException).code !== "ENOENT";
6479
+ }
6480
+ });
6481
+ });
6482
+ if (
6483
+ cleanup.phase !== "artifacts" ||
6484
+ cleanup.artifactsRemoved === true ||
6485
+ Object.keys(ownerFields).some(key => key !== "taskArtifactOwnerCleanupError") ||
6486
+ retainedPaths.some(candidate => candidate !== undefined) ||
6487
+ occupiedPlan ||
6488
+ transcriptParentStat.dev.toString() !== replay.transcriptParentIdentity.dev ||
6489
+ transcriptParentStat.ino.toString() !== replay.transcriptParentIdentity.ino
6490
+ )
6491
+ return fail(
6492
+ "terminal_uncertain",
6493
+ "Initial task-artifact owner capture cannot be retried after unverified cleanup effects.",
6494
+ );
6495
+ replay.target.taskArtifactOwnerStorageContext = ownerContext;
6496
+ const captured = captureTaskArtifactOwnerEvidence(replay.storage, replay.target);
6497
+ if (!captured.evidence && !captured.error)
6498
+ return fail(
6499
+ "terminal_uncertain",
6500
+ "The original task-artifact owner claim could not be restored for capture retry.",
6501
+ );
6502
+ if (captured.evidence) {
6503
+ replay.target.taskArtifactOwnerDeletionEvidence = captured.evidence;
6504
+ replay.target.deferTaskArtifactOwnerRetirement = true;
6505
+ }
6506
+ captureError = captured.error;
6507
+ }
6508
+ if (owner.deletionEvidence) {
6509
+ replay.target.taskArtifactOwnerStorageContext = ownerContext;
6510
+ replay.target.taskArtifactOwnerDeletionEvidence = owner.deletionEvidence;
6511
+ replay.target.deferTaskArtifactOwnerRetirement = true;
6512
+ if (owner.retirementContinuation)
6513
+ replay.target.taskArtifactOwnerRetirementContinuation = owner.retirementContinuation;
6514
+ if (owner.retirementOutcome) replay.target.taskArtifactOwnerRetirementOutcome = owner.retirementOutcome;
6515
+ if (owner.retired) replay.target.taskArtifactOwnerRetired = true;
6516
+ if (owner.payloadRetired) replay.target.taskArtifactOwnerPayloadRetired = true;
6517
+ if (owner.namespaceRetained) replay.target.taskArtifactOwnerNamespaceRetained = true;
6518
+ if (owner.transcriptDeleted) replay.target.taskArtifactOwnerTranscriptDeleted = true;
6519
+ }
6520
+ return {
6521
+ ...replay,
6522
+ inspectProtocol: managedGcProtocolScopeInspectorForScope(ownerScope),
6523
+ ...(captureError && !owner.deletionEvidence ? { taskArtifactOwnerCaptureError: captureError } : {}),
6524
+ };
6247
6525
  }
6248
6526
  const inventory = await managedCandidates(broker, cwd, "Saved");
6249
6527
  if ("ok" in inventory) return inventory;
@@ -6255,7 +6533,6 @@ async function validateDeletePath(
6255
6533
  const match = matches[0]!;
6256
6534
  if (inventory.migrationPolicy === "disabled" && match.provenance === "legacy")
6257
6535
  return fail("legacy_migration_disabled", "Saved legacy session migration is disabled for this workspace.");
6258
-
6259
6536
  const storage = new FileSessionStorage();
6260
6537
  let snapshot: SessionStorageSnapshot;
6261
6538
  try {
@@ -6284,28 +6561,56 @@ async function validateDeletePath(
6284
6561
  } catch {
6285
6562
  return fail("invalid_input", "session.delete transcript parent changed during authorization.");
6286
6563
  }
6564
+ let ownerContext: TaskArtifactOwnerStorageContext | undefined;
6565
+ let ownerScope: ManagedScope | undefined;
6566
+ let ownerCaptureError: string | undefined;
6567
+ let ownerLocator: TaskArtifactOwnerLocator | undefined;
6568
+ try {
6569
+ ownerLocator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, id);
6570
+ } catch (error) {
6571
+ ownerCaptureError = taskArtifactOwnerFailureDiagnostic(error);
6572
+ }
6573
+ if (ownerLocator) {
6574
+ try {
6575
+ ownerScope = managedOwnerScopeFromInventory(inventory.scope);
6576
+ ownerContext = taskArtifactOwnerStorageContextForScope(ownerScope);
6577
+ } catch {
6578
+ return fail("invalid_input", "Managed task-artifact-owner authority could not be established for deletion.");
6579
+ }
6580
+ }
6581
+ const target: VerifiedSessionDeleteTarget = {
6582
+ sessionsRoot: canonicalExistingPath(inventory.scope.sessionsRoot),
6583
+ transcriptPath: candidatePath,
6584
+ sessionId: id,
6585
+ cwd,
6586
+ transcriptIdentity: {
6587
+ dev: snapshot.stat.dev,
6588
+ ino: snapshot.stat.ino,
6589
+ nlink: snapshot.stat.nlink,
6590
+ size: snapshot.stat.size,
6591
+ mtimeNs: snapshot.stat.mtimeNs,
6592
+ sha256: digest,
6593
+ },
6594
+ transcriptParentIdentity: { dev: transcriptParentStat.dev, ino: transcriptParentStat.ino },
6595
+ ...(ownerContext ? { taskArtifactOwnerStorageContext: ownerContext } : {}),
6596
+ };
6597
+ const capturedOwner: { evidence?: TaskArtifactOwnerDeletionEvidence; error?: string } = ownerCaptureError
6598
+ ? { error: ownerCaptureError }
6599
+ : captureTaskArtifactOwnerEvidence(storage, target);
6600
+ if (capturedOwner.evidence) {
6601
+ target.taskArtifactOwnerDeletionEvidence = capturedOwner.evidence;
6602
+ target.deferTaskArtifactOwnerRetirement = true;
6603
+ }
6287
6604
  return {
6288
6605
  storage,
6289
- target: {
6290
- sessionsRoot: canonicalExistingPath(inventory.scope.sessionsRoot),
6291
- transcriptPath: candidatePath,
6292
- sessionId: id,
6293
- cwd,
6294
- transcriptIdentity: {
6295
- dev: snapshot.stat.dev,
6296
- ino: snapshot.stat.ino,
6297
- nlink: snapshot.stat.nlink,
6298
- size: snapshot.stat.size,
6299
- mtimeNs: snapshot.stat.mtimeNs,
6300
- sha256: digest,
6301
- },
6302
- transcriptParentIdentity: { dev: transcriptParentStat.dev, ino: transcriptParentStat.ino },
6303
- },
6606
+ target,
6304
6607
  metadataRoot: canonicalRequestedRoot,
6608
+ ...(ownerScope ? { inspectProtocol: managedGcProtocolScopeInspectorForScope(ownerScope) } : {}),
6305
6609
  transcriptParentIdentity: {
6306
6610
  dev: transcriptParentStat.dev.toString(),
6307
6611
  ino: transcriptParentStat.ino.toString(),
6308
6612
  },
6613
+ ...(capturedOwner.error ? { taskArtifactOwnerCaptureError: capturedOwner.error } : {}),
6309
6614
  };
6310
6615
  }
6311
6616
  type CloseAuthority = { endpointGeneration: number; endpointIncarnation: string };
@@ -6448,7 +6753,13 @@ async function executeLifecycleResponse(
6448
6753
  if (requestedSourceSessionId !== undefined && !isCanonicalSessionId(requestedSourceSessionId))
6449
6754
  return fail("invalid_input", "sourceSessionId must be a canonical safe identifier.");
6450
6755
  if (operation === "session.create" || operation === "session.fork" || operation === "session.resume") {
6451
- await broker.index.refresh();
6756
+ // Creates without managed-worktree admission have no existing session
6757
+ // authority to reconcile, so the change-stamp fast path avoids replaying
6758
+ // the full index under its lock on every ACP spawn. Managed-worktree creates
6759
+ // must serialize with registrations before checking checkout occupancy.
6760
+ if (operation === "session.create" && lifecycleWorktreeTarget(input) === undefined)
6761
+ await broker.index.refreshIfChanged();
6762
+ else await broker.index.refresh();
6452
6763
  if (operation === "session.create") {
6453
6764
  // Creation has no existing session authority to reconcile. Keep the
6454
6765
  // broker's periodic/startup heartbeat checkpoint as the reaping owner,
@@ -7483,15 +7794,45 @@ async function executeLifecycleResponse(
7483
7794
  const transcriptParentIdentity = cleanup?.transcriptParentIdentity ?? validated.transcriptParentIdentity;
7484
7795
  const durableArtifactsPlan =
7485
7796
  cleanup?.artifactTree?.plannedPath ?? cleanup?.plannedArtifactsPath ?? cleanupTarget.plannedArtifactsPath;
7797
+ let ownerRetirementOutcome = cleanupTarget.taskArtifactOwnerRetirementOutcome
7798
+ ? taskArtifactOwnerRetirementDisposition(cleanupTarget.taskArtifactOwnerRetirementOutcome)
7799
+ : undefined;
7800
+ let ownerTranscriptDeleted = cleanupTarget.taskArtifactOwnerTranscriptDeleted;
7801
+ let freshPayloadRetirementForThisAttempt = false;
7802
+ const ownerNamespacePending =
7803
+ cleanupTarget.taskArtifactOwnerPayloadRetired === true &&
7804
+ cleanupTarget.taskArtifactOwnerNamespaceRetained === true &&
7805
+ cleanupTarget.taskArtifactOwnerRetired !== true;
7486
7806
  const preauthorizedCleanup: CleanupEvidence = {
7487
7807
  cleanupReceiptVersion: 1,
7488
- phase: cleanupTarget.artifactsRemoved ? "transcript" : "artifacts",
7808
+ phase:
7809
+ cleanupTarget.artifactsRemoved &&
7810
+ !ownerNamespacePending &&
7811
+ (cleanupTarget.taskArtifactOwnerRetired === true || !cleanupTarget.taskArtifactOwnerDeletionEvidence)
7812
+ ? "transcript"
7813
+ : "artifacts",
7489
7814
  sessionId: cleanupTarget.sessionId,
7490
7815
  sessionsRoot: cleanupTarget.sessionsRoot,
7491
7816
  transcriptPath: cleanupTarget.transcriptPath,
7492
7817
  cwd: cleanupTarget.cwd,
7493
7818
  ...(cleanupTarget.artifactsRemoved ? { artifactsRemoved: true } : {}),
7494
7819
  ...(cleanupTarget.artifactsAbsentAtAuthorization ? { artifactsAbsentAtAuthorization: true as const } : {}),
7820
+ ...(cleanupTarget.taskArtifactOwnerDeletionEvidence
7821
+ ? { taskArtifactOwnerDeletionEvidence: cleanupTarget.taskArtifactOwnerDeletionEvidence }
7822
+ : {}),
7823
+ ...(cleanupTarget.taskArtifactOwnerRetirementContinuation
7824
+ ? { taskArtifactOwnerRetirementContinuation: cleanupTarget.taskArtifactOwnerRetirementContinuation }
7825
+ : {}),
7826
+ ...(ownerRetirementOutcome ? { taskArtifactOwnerRetirementOutcome: ownerRetirementOutcome } : {}),
7827
+ ...(cleanupTarget.taskArtifactOwnerPayloadRetired ? { taskArtifactOwnerPayloadRetired: true as const } : {}),
7828
+ ...(cleanupTarget.taskArtifactOwnerNamespaceRetained
7829
+ ? { taskArtifactOwnerNamespaceRetained: true as const }
7830
+ : {}),
7831
+ ...(cleanupTarget.taskArtifactOwnerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
7832
+ ...(ownerTranscriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
7833
+ ...(validated.taskArtifactOwnerCaptureError
7834
+ ? { taskArtifactOwnerCleanupError: validated.taskArtifactOwnerCaptureError }
7835
+ : {}),
7495
7836
  metadataRoot: validated.metadataRoot,
7496
7837
  transcriptIdentity: serializeCleanupIdentity(cleanupTarget.transcriptIdentity),
7497
7838
  transcriptParentIdentity,
@@ -7591,6 +7932,145 @@ async function executeLifecycleResponse(
7591
7932
  });
7592
7933
  return pending;
7593
7934
  };
7935
+ const cleanupWithFreshOwnerPayload = (receipt: CleanupEvidence): CleanupEvidence => {
7936
+ const current = { ...receipt };
7937
+ delete current.taskArtifactOwnerDeletionEvidence;
7938
+ delete current.taskArtifactOwnerRetirementContinuation;
7939
+ delete current.taskArtifactOwnerRetirementOutcome;
7940
+ delete current.taskArtifactOwnerPayloadRetired;
7941
+ delete current.taskArtifactOwnerNamespaceRetained;
7942
+ delete current.taskArtifactOwnerRetired;
7943
+ delete current.taskArtifactOwnerTranscriptDeleted;
7944
+ delete current.taskArtifactOwnerCleanupError;
7945
+ return {
7946
+ ...current,
7947
+ ...(cleanupTarget.taskArtifactOwnerDeletionEvidence
7948
+ ? { taskArtifactOwnerDeletionEvidence: cleanupTarget.taskArtifactOwnerDeletionEvidence }
7949
+ : {}),
7950
+ ...(cleanupTarget.taskArtifactOwnerRetirementContinuation
7951
+ ? { taskArtifactOwnerRetirementContinuation: cleanupTarget.taskArtifactOwnerRetirementContinuation }
7952
+ : {}),
7953
+ ...(ownerRetirementOutcome ? { taskArtifactOwnerRetirementOutcome: ownerRetirementOutcome } : {}),
7954
+ ...(cleanupTarget.taskArtifactOwnerPayloadRetired
7955
+ ? { taskArtifactOwnerPayloadRetired: true as const }
7956
+ : {}),
7957
+ ...(cleanupTarget.taskArtifactOwnerNamespaceRetained
7958
+ ? { taskArtifactOwnerNamespaceRetained: true as const }
7959
+ : {}),
7960
+ ...(cleanupTarget.taskArtifactOwnerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
7961
+ ...(ownerTranscriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
7962
+ };
7963
+ };
7964
+ const publishTaskArtifactOwnerPending = async (
7965
+ error: unknown,
7966
+ receipt: CleanupEvidence = preauthorizedCleanup,
7967
+ ): Promise<BrokerResponse> => {
7968
+ const diagnostic = taskArtifactOwnerFailureDiagnostic(error);
7969
+ const evidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
7970
+ const ownerReceipt: CleanupEvidence = {
7971
+ ...cleanupWithFreshOwnerPayload(receipt),
7972
+ phase: "artifacts",
7973
+ artifactsRemoved: receipt.artifactsRemoved === true && evidence ? true : undefined,
7974
+ artifactsAbsentAtAuthorization:
7975
+ receipt.artifactsRemoved === true ? undefined : receipt.artifactsAbsentAtAuthorization,
7976
+ taskArtifactOwnerCleanupError: diagnostic,
7977
+ };
7978
+ const pending = fail(
7979
+ "cleanup_pending",
7980
+ `Saved session cleanup is pending in artifacts: ${diagnostic}`,
7981
+ ownerReceipt,
7982
+ );
7983
+ await broker.ledger.transition(identity, "effect_started", {
7984
+ intendedSessionId: id,
7985
+ response: pending,
7986
+ });
7987
+ return pending;
7988
+ };
7989
+ const persistFreshTaskArtifactOwnerRetirement = async (
7990
+ receipt: CleanupEvidence,
7991
+ ): Promise<BrokerResponse | undefined> => {
7992
+ const evidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
7993
+ if (!evidence) return undefined;
7994
+ const ownerContext = cleanupTarget.taskArtifactOwnerStorageContext;
7995
+ if (!ownerContext)
7996
+ return await publishTaskArtifactOwnerPending("task_artifact_owner_context_missing", receipt);
7997
+ if (
7998
+ ownerTranscriptDeleted === true
7999
+ ? !logicalTranscriptRetirementMatches()
8000
+ : !taskArtifactOwnerTranscriptMatches(validated.storage, cleanupTarget, evidence)
8001
+ )
8002
+ return await publishTaskArtifactOwnerPending("task_artifact_owner_transcript_identity_mismatch", receipt);
8003
+ let outcome: TaskArtifactOwnerRetirementOutcome;
8004
+ try {
8005
+ if (
8006
+ await hasSiblingTaskArtifactOwnerTranscript(
8007
+ validated.storage,
8008
+ cleanupTarget.transcriptPath,
8009
+ evidence.locator,
8010
+ ownerContext,
8011
+ validated.inspectProtocol,
8012
+ )
8013
+ )
8014
+ return await publishTaskArtifactOwnerPending(
8015
+ "task_artifact_owner_shared_with_sibling_transcript",
8016
+ receipt,
8017
+ );
8018
+ outcome = retireTaskArtifactOwner(
8019
+ ownerContext,
8020
+ evidence,
8021
+ cleanupTarget.taskArtifactOwnerRetirementContinuation,
8022
+ );
8023
+ } catch (error) {
8024
+ freshPayloadRetirementForThisAttempt = false;
8025
+ return await publishTaskArtifactOwnerPending(error, receipt);
8026
+ }
8027
+ freshPayloadRetirementForThisAttempt = outcome.kind === "payload_retired";
8028
+ cleanupTarget.taskArtifactOwnerRetirementOutcome = outcome;
8029
+ ownerRetirementOutcome = taskArtifactOwnerRetirementDisposition(outcome);
8030
+ cleanupTarget.taskArtifactOwnerRetired = outcome.kind === "completed" ? true : undefined;
8031
+ cleanupTarget.taskArtifactOwnerRetirementContinuation =
8032
+ outcome.kind === "completed" ? undefined : outcome.continuation;
8033
+ cleanupTarget.taskArtifactOwnerPayloadRetired = outcome.kind === "payload_retired" ? true : undefined;
8034
+ cleanupTarget.taskArtifactOwnerNamespaceRetained = outcome.kind === "payload_retired" ? true : undefined;
8035
+ if (outcome.kind === "completed") ownerTranscriptDeleted = undefined;
8036
+ const ownerError =
8037
+ outcome.kind === "uncertain"
8038
+ ? taskArtifactOwnerFailureDiagnostic(outcome.reason)
8039
+ : outcome.kind === "cleanup_pending"
8040
+ ? taskArtifactOwnerFailureDiagnostic(
8041
+ outcome.nativeOutcome?.code ?? "task_artifact_owner_cleanup_pending",
8042
+ )
8043
+ : undefined;
8044
+ const ownerReceipt: CleanupEvidence = {
8045
+ ...cleanupWithFreshOwnerPayload(receipt),
8046
+ phase: "artifacts",
8047
+ artifactsRemoved: true,
8048
+ artifactsAbsentAtAuthorization: undefined,
8049
+ ...(ownerError ? { taskArtifactOwnerCleanupError: ownerError } : {}),
8050
+ };
8051
+ const stateResponse = fail(
8052
+ "cleanup_pending",
8053
+ outcome.kind === "payload_retired"
8054
+ ? "Task-artifact payload is retired; its native namespace remains pending in artifacts."
8055
+ : outcome.kind === "completed"
8056
+ ? "Task-artifact owner retirement is durably complete; transcript cleanup is preauthorized."
8057
+ : `Task-artifact owner retirement remains ${outcome.kind} in artifacts.`,
8058
+ ownerReceipt,
8059
+ );
8060
+ await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: stateResponse });
8061
+ return outcome.kind === "completed" || outcome.kind === "payload_retired" ? undefined : stateResponse;
8062
+ };
8063
+ const storageOwnerFields = (): Partial<VerifiedSessionDeleteTarget> => {
8064
+ const evidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
8065
+ if (!evidence) return {};
8066
+ if (!cleanupTarget.taskArtifactOwnerStorageContext) throw new Error("task_artifact_owner_context_missing");
8067
+ return {
8068
+ deferTaskArtifactOwnerRetirement: true,
8069
+ ...(cleanupTarget.taskArtifactOwnerRetirementOutcome
8070
+ ? { taskArtifactOwnerRetirementOutcome: cleanupTarget.taskArtifactOwnerRetirementOutcome }
8071
+ : {}),
8072
+ };
8073
+ };
7594
8074
  const canonicalArtifactsPath = cleanupTarget.transcriptPath.slice(0, -6);
7595
8075
  const transcriptCleanupAuthorityIsAbsent = (): boolean =>
7596
8076
  [
@@ -7646,6 +8126,11 @@ async function executeLifecycleResponse(
7646
8126
  return false;
7647
8127
  }
7648
8128
  };
8129
+ const logicalTranscriptRetirementMatches = (): boolean =>
8130
+ transcriptParentMatchesPersistedIdentity() &&
8131
+ pathIsAbsent(cleanupTarget.transcriptPath) &&
8132
+ pathIsAbsent(cleanupTarget.retainedTranscriptSuccessorPath) &&
8133
+ pathIsAbsent(cleanupTarget.retainedTranscriptUnknownPath);
7649
8134
  const retainedTranscriptIdentityIsAbsentFromParent = (): boolean => {
7650
8135
  const transcriptParent = path.dirname(cleanupTarget.transcriptPath);
7651
8136
  const expectedParent = preauthorizedCleanup.transcriptParentIdentity;
@@ -7741,31 +8226,81 @@ async function executeLifecycleResponse(
7741
8226
  preauthorizedCleanup,
7742
8227
  ),
7743
8228
  });
7744
- let deleted: VerifiedSessionDeleteResult;
7745
- try {
7746
- const completedArtifactReplay = cleanup?.phase === "transcript" && cleanup.artifactsRemoved === true;
7747
- if (completedArtifactReplay && !pathIsAbsent(canonicalArtifactsPath))
8229
+ if (validated.taskArtifactOwnerCaptureError)
8230
+ return await publishTaskArtifactOwnerPending(validated.taskArtifactOwnerCaptureError);
8231
+ if (
8232
+ cleanup?.phase === "artifacts" &&
8233
+ cleanupTarget.artifactsRemoved === true &&
8234
+ cleanupTarget.taskArtifactOwnerDeletionEvidence &&
8235
+ cleanupTarget.taskArtifactOwnerRetired !== true
8236
+ ) {
8237
+ if (!pathIsAbsent(canonicalArtifactsPath))
7748
8238
  return await publishCanonicalArtifactReappearance(preauthorizedCleanup);
7749
- if (
7750
- completedArtifactReplay &&
7751
- transcriptCleanupAuthorityIsAbsent() &&
7752
- retainedTranscriptIdentityIsAbsentFromParent()
7753
- )
7754
- return fail(
7755
- "cleanup_pending",
7756
- "Saved session cleanup remains pending because transcript authority disappeared without native deletion proof.",
8239
+ const ownerPending = await persistFreshTaskArtifactOwnerRetirement(preauthorizedCleanup);
8240
+ if (ownerPending) return ownerPending;
8241
+ }
8242
+ let deleted: VerifiedSessionDeleteResult | undefined;
8243
+ if (cleanup?.taskArtifactOwnerTranscriptDeleted === true) {
8244
+ if (!logicalTranscriptRetirementMatches())
8245
+ return await publishTaskArtifactOwnerPending(
8246
+ "task_artifact_owner_transcript_disposition_mismatch",
7757
8247
  preauthorizedCleanup,
7758
8248
  );
7759
- else {
8249
+ if (cleanupTarget.taskArtifactOwnerPayloadRetired === true) {
8250
+ const residual = fail(
8251
+ "cleanup_pending",
8252
+ "Transcript retirement is complete; the task-artifact owner namespace remains pending in artifacts.",
8253
+ cleanupWithFreshOwnerPayload({ ...preauthorizedCleanup, phase: "artifacts", artifactsRemoved: true }),
8254
+ );
8255
+ await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: residual });
8256
+ return residual;
8257
+ }
8258
+ if (cleanupTarget.taskArtifactOwnerRetired === true) {
8259
+ const ownerEvidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
8260
+ const ownerContext = cleanupTarget.taskArtifactOwnerStorageContext;
8261
+ const ownerOutcome = cleanupTarget.taskArtifactOwnerRetirementOutcome;
8262
+ if (!ownerEvidence || !ownerContext || !ownerOutcome)
8263
+ return await publishTaskArtifactOwnerPending(
8264
+ "task_artifact_owner_retired_evidence_missing",
8265
+ preauthorizedCleanup,
8266
+ );
8267
+ try {
8268
+ verifyTaskArtifactOwnerPhysicalRetirement(ownerContext, ownerEvidence, ownerOutcome);
8269
+ } catch (error) {
8270
+ return await publishTaskArtifactOwnerPending(error, preauthorizedCleanup);
8271
+ }
8272
+ deleted = { kind: "deleted" };
8273
+ }
8274
+ }
8275
+ try {
8276
+ if (deleted === undefined) {
8277
+ const completedArtifactReplay = cleanup?.phase === "transcript" && cleanup.artifactsRemoved === true;
8278
+ if (completedArtifactReplay && !pathIsAbsent(canonicalArtifactsPath))
8279
+ return await publishCanonicalArtifactReappearance(preauthorizedCleanup);
8280
+ if (
8281
+ completedArtifactReplay &&
8282
+ transcriptCleanupAuthorityIsAbsent() &&
8283
+ retainedTranscriptIdentityIsAbsentFromParent()
8284
+ )
8285
+ return fail(
8286
+ "cleanup_pending",
8287
+ "Saved session cleanup remains pending because transcript authority disappeared without native deletion proof.",
8288
+ preauthorizedCleanup,
8289
+ );
7760
8290
  if (!transcriptParentMatchesPersistedIdentity())
7761
8291
  return fail(
7762
8292
  "cleanup_pending",
7763
8293
  "Saved session cleanup is pending because transcript parent identity changed before exact mutation.",
7764
8294
  preauthorizedCleanup,
7765
8295
  );
7766
- deleted = await validated.storage.deleteSessionVerified(cleanupTarget);
8296
+ deleted = await validated.storage.deleteSessionVerified(
8297
+ { ...cleanupTarget, ...storageOwnerFields() },
8298
+ validated.inspectProtocol,
8299
+ );
7767
8300
  }
7768
8301
  } catch (error) {
8302
+ if (error instanceof Error && error.message.startsWith("task_artifact_owner_"))
8303
+ return await publishTaskArtifactOwnerPending(error);
7769
8304
  if (error instanceof SessionDeleteVerificationError) {
7770
8305
  if (cleanup?.phase === "artifacts")
7771
8306
  return await publishChangedArtifactRoot(
@@ -7784,6 +8319,7 @@ async function executeLifecycleResponse(
7784
8319
  `Unable to delete saved session artifacts: ${error instanceof Error ? error.message : String(error)}`,
7785
8320
  );
7786
8321
  }
8322
+ if (!deleted) return fail("unavailable", "Saved session deletion did not produce an outcome.");
7787
8323
  if (
7788
8324
  deleted.kind === "deleted" &&
7789
8325
  cleanup?.phase === "transcript" &&
@@ -7807,10 +8343,22 @@ async function executeLifecycleResponse(
7807
8343
  cleanupTarget.detachedArtifactsPath,
7808
8344
  "Saved session cleanup is pending in artifacts: an authorized quarantine alias remains after exact removal.",
7809
8345
  );
7810
- const transcriptPhaseCleanup: CleanupEvidence = {
7811
- ...preauthorizedCleanup,
7812
- phase: "transcript",
8346
+ const returnedOwnerEvidence = deleted.taskArtifactOwnerDeletionEvidence;
8347
+ if (
8348
+ cleanupTarget.taskArtifactOwnerDeletionEvidence &&
8349
+ returnedOwnerEvidence &&
8350
+ JSON.stringify(cleanupTarget.taskArtifactOwnerDeletionEvidence) !== JSON.stringify(returnedOwnerEvidence)
8351
+ )
8352
+ return await publishTaskArtifactOwnerPending("task_artifact_owner_evidence_changed_during_delete");
8353
+ if (returnedOwnerEvidence && !cleanupTarget.taskArtifactOwnerDeletionEvidence)
8354
+ return await publishTaskArtifactOwnerPending("task_artifact_owner_evidence_not_prepared_before_effect");
8355
+ const ownerEvidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
8356
+ const artifactCompletionCleanup: CleanupEvidence = {
8357
+ ...cleanupWithFreshOwnerPayload(preauthorizedCleanup),
8358
+ phase: ownerEvidence ? "artifacts" : "transcript",
7813
8359
  artifactsRemoved: true,
8360
+ artifactsAbsentAtAuthorization: undefined,
8361
+ ...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
7814
8362
  detachedArtifactsPath: undefined,
7815
8363
  retainedArtifactsSuccessorPath: undefined,
7816
8364
  retainedArtifactsPlaceholderPath: undefined,
@@ -7826,12 +8374,41 @@ async function executeLifecycleResponse(
7826
8374
  }
7827
8375
  : {}),
7828
8376
  };
7829
-
7830
8377
  await broker.ledger.transition(identity, "effect_started", {
7831
8378
  intendedSessionId: id,
7832
8379
  response: fail(
7833
8380
  "cleanup_pending",
7834
- "Saved session artifacts were removed; transcript cleanup is preauthorized.",
8381
+ "Saved session artifacts are removed; owner cleanup is durably preauthorized.",
8382
+ artifactCompletionCleanup,
8383
+ ),
8384
+ });
8385
+ if (ownerEvidence && cleanupTarget.taskArtifactOwnerRetired !== true) {
8386
+ const ownerPending = await persistFreshTaskArtifactOwnerRetirement(artifactCompletionCleanup);
8387
+ if (ownerPending) return ownerPending;
8388
+ }
8389
+ const ownerNamespaceRetained =
8390
+ cleanupTarget.taskArtifactOwnerPayloadRetired === true &&
8391
+ cleanupTarget.taskArtifactOwnerNamespaceRetained === true;
8392
+ const transcriptPhaseCleanup: CleanupEvidence = {
8393
+ ...cleanupWithFreshOwnerPayload(artifactCompletionCleanup),
8394
+ phase: ownerNamespaceRetained ? "artifacts" : "transcript",
8395
+ artifactsRemoved: true,
8396
+ artifactsAbsentAtAuthorization: undefined,
8397
+ ...(artifactCompletionCleanup.artifactTree
8398
+ ? {
8399
+ artifactTree: {
8400
+ ...artifactCompletionCleanup.artifactTree,
8401
+ detachedPath: undefined,
8402
+ completed: true as const,
8403
+ },
8404
+ }
8405
+ : {}),
8406
+ };
8407
+ await broker.ledger.transition(identity, "effect_started", {
8408
+ intendedSessionId: id,
8409
+ response: fail(
8410
+ "cleanup_pending",
8411
+ "Saved session artifacts and owner cleanup are durably recorded; transcript cleanup is preauthorized.",
7835
8412
  transcriptPhaseCleanup,
7836
8413
  ),
7837
8414
  });
@@ -7843,33 +8420,39 @@ async function executeLifecycleResponse(
7843
8420
  "Saved session cleanup remains pending because transcript authority disappeared without native deletion proof.",
7844
8421
  transcriptPhaseCleanup,
7845
8422
  );
7846
- else {
7847
- if (!transcriptParentMatchesPersistedIdentity())
7848
- return fail(
7849
- "cleanup_pending",
7850
- "Saved session cleanup is pending because transcript parent identity changed before exact mutation.",
7851
- transcriptPhaseCleanup,
7852
- );
7853
- try {
7854
- deleted = await validated.storage.deleteSessionVerified({
8423
+ if (!transcriptParentMatchesPersistedIdentity())
8424
+ return fail(
8425
+ "cleanup_pending",
8426
+ "Saved session cleanup is pending because transcript parent identity changed before exact mutation.",
8427
+ transcriptPhaseCleanup,
8428
+ );
8429
+ try {
8430
+ deleted = await validated.storage.deleteSessionVerified(
8431
+ {
7855
8432
  ...cleanupTarget,
8433
+ ...storageOwnerFields(),
7856
8434
  expectedArtifactsIdentity: undefined,
7857
8435
  detachedArtifactsPath: undefined,
7858
8436
  artifactsRemoved: true,
7859
- });
7860
- } catch (error) {
7861
- if (error instanceof SessionDeleteVerificationError && error.kind === "artifacts")
7862
- return await publishCanonicalArtifactReappearance(transcriptPhaseCleanup);
7863
- if (error instanceof SessionDeleteVerificationError)
7864
- return fail(
7865
- "invalid_input",
7866
- `Saved session deletion verification failed (${error.kind}): ${error.message}`,
7867
- );
8437
+ },
8438
+ validated.inspectProtocol,
8439
+ );
8440
+ } catch (error) {
8441
+ if (error instanceof Error && error.message.startsWith("task_artifact_owner_"))
8442
+ return await publishTaskArtifactOwnerPending(error, transcriptPhaseCleanup);
8443
+ if (error instanceof SessionDeleteVerificationError && error.kind === "artifacts")
8444
+ return await publishCanonicalArtifactReappearance(transcriptPhaseCleanup);
8445
+ if (error instanceof SessionDeleteVerificationError)
7868
8446
  return fail(
7869
- "unavailable",
7870
- `Unable to delete saved session transcript: ${error instanceof Error ? error.message : String(error)}`,
8447
+ "invalid_input",
8448
+ `Saved session deletion verification failed (${error.kind}): ${error.message}`,
7871
8449
  );
7872
- }
8450
+ if (freshPayloadRetirementForThisAttempt)
8451
+ return await publishTaskArtifactOwnerPending(error, transcriptPhaseCleanup);
8452
+ return fail(
8453
+ "unavailable",
8454
+ `Unable to delete saved session transcript: ${error instanceof Error ? error.message : String(error)}`,
8455
+ );
7873
8456
  }
7874
8457
  if (deleted.kind === "deleted" && !pathIsAbsent(canonicalArtifactsPath))
7875
8458
  return await publishCanonicalArtifactReappearance(transcriptPhaseCleanup);
@@ -7885,13 +8468,47 @@ async function executeLifecycleResponse(
7885
8468
  "Saved session cleanup is pending in artifacts: a planned quarantine alias remains before terminal completion.",
7886
8469
  );
7887
8470
  const retainedRootArtifactsPlan = durableArtifactsPlan;
7888
- if (deleted.kind === "cleanup_pending")
7889
- return fail(
8471
+ if (deleted.kind === "cleanup_pending") {
8472
+ if (deleted.phase === "task_artifact_owner" && !deleted.artifactsRemoved)
8473
+ return await publishTaskArtifactOwnerPending(deleted.error.message);
8474
+ const ownerEvidence =
8475
+ deleted.taskArtifactOwnerDeletionEvidence ?? cleanupTarget.taskArtifactOwnerDeletionEvidence;
8476
+ const ownerRetired = deleted.taskArtifactOwnerRetired ?? cleanupTarget.taskArtifactOwnerRetired;
8477
+ const ownerPayloadRetired =
8478
+ deleted.taskArtifactOwnerPayloadRetired ?? cleanupTarget.taskArtifactOwnerPayloadRetired;
8479
+ const ownerNamespaceRetained =
8480
+ deleted.taskArtifactOwnerNamespaceRetained ?? cleanupTarget.taskArtifactOwnerNamespaceRetained;
8481
+ const retainedOwnerNamespace = ownerPayloadRetired === true && ownerNamespaceRetained === true;
8482
+ const ownerContinuation =
8483
+ deleted.taskArtifactOwnerRetirementContinuation ?? cleanupTarget.taskArtifactOwnerRetirementContinuation;
8484
+ const ownerOutcome = deleted.taskArtifactOwnerRetirementOutcome
8485
+ ? taskArtifactOwnerRetirementDisposition(deleted.taskArtifactOwnerRetirementOutcome)
8486
+ : ownerRetirementOutcome;
8487
+ const transcriptDeleted =
8488
+ deleted.taskArtifactOwnerTranscriptDeleted === true || ownerTranscriptDeleted === true;
8489
+ const ownerError =
8490
+ deleted.phase === "task_artifact_owner" || deleted.error.message.startsWith("task_artifact_owner_")
8491
+ ? taskArtifactOwnerFailureDiagnostic(deleted.error)
8492
+ : undefined;
8493
+ const publicPhase =
8494
+ retainedOwnerNamespace || deleted.phase === "task_artifact_owner" ? "artifacts" : deleted.phase;
8495
+ const pending = fail(
7890
8496
  "cleanup_pending",
7891
- `Saved session cleanup is pending in ${deleted.phase}: ${deleted.error.message}`,
8497
+ `Saved session cleanup is pending in ${publicPhase}: ${deleted.error.message}`,
7892
8498
  {
7893
8499
  cleanupReceiptVersion: 1,
7894
- phase: deleted.phase,
8500
+ phase: publicPhase,
8501
+ ...(ownerEvidence && (deleted.phase === "task_artifact_owner" || retainedOwnerNamespace)
8502
+ ? { artifactsRemoved: true }
8503
+ : {}),
8504
+ ...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
8505
+ ...(ownerContinuation ? { taskArtifactOwnerRetirementContinuation: ownerContinuation } : {}),
8506
+ ...(ownerOutcome ? { taskArtifactOwnerRetirementOutcome: ownerOutcome } : {}),
8507
+ ...(ownerPayloadRetired ? { taskArtifactOwnerPayloadRetired: true as const } : {}),
8508
+ ...(ownerNamespaceRetained ? { taskArtifactOwnerNamespaceRetained: true as const } : {}),
8509
+ ...(ownerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
8510
+ ...(transcriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
8511
+ ...(ownerError ? { taskArtifactOwnerCleanupError: ownerError } : {}),
7895
8512
  sessionId: validated.target.sessionId,
7896
8513
  sessionsRoot: validated.target.sessionsRoot,
7897
8514
  transcriptPath: validated.target.transcriptPath,
@@ -7975,6 +8592,34 @@ async function executeLifecycleResponse(
7975
8592
  : {}),
7976
8593
  },
7977
8594
  );
8595
+ await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: pending });
8596
+ return pending;
8597
+ }
8598
+ if (
8599
+ deleted.kind === "deleted" &&
8600
+ cleanupTarget.taskArtifactOwnerPayloadRetired === true &&
8601
+ cleanupTarget.taskArtifactOwnerNamespaceRetained === true
8602
+ ) {
8603
+ if (!logicalTranscriptRetirementMatches())
8604
+ return await publishTaskArtifactOwnerPending(
8605
+ "task_artifact_owner_transcript_disposition_mismatch",
8606
+ preauthorizedCleanup,
8607
+ );
8608
+ if (record) await appendSessionDeletedEvidence(broker, record);
8609
+ const residual = fail(
8610
+ "cleanup_pending",
8611
+ "Transcript retirement is complete; the task-artifact owner namespace remains pending in artifacts.",
8612
+ {
8613
+ ...cleanupWithFreshOwnerPayload(preauthorizedCleanup),
8614
+ phase: "artifacts",
8615
+ artifactsRemoved: true,
8616
+ artifactsAbsentAtAuthorization: undefined,
8617
+ taskArtifactOwnerTranscriptDeleted: true,
8618
+ },
8619
+ );
8620
+ await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: residual });
8621
+ return residual;
8622
+ }
7978
8623
 
7979
8624
  const completion = { ok: true, result: { sessionId: id } } as const;
7980
8625
  if (metadataCleanup.lifecycleFiles?.length) {