@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
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
 
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
+ import * as util from "node:util";
5
6
 
6
7
  import type * as native from "@gajae-code/natives";
7
8
 
@@ -15,6 +16,7 @@ function nativeSessionStorage(): typeof import("@gajae-code/natives") {
15
16
  }
16
17
 
17
18
  import { isEnoent, pathIsWithin, peekFile, toError } from "@gajae-code/utils";
19
+ import type { ManagedGcProtocolScopeInspector } from "./internal/managed-session-scope";
18
20
  import {
19
21
  assertManagedDirectoryRoot,
20
22
  type ManagedDirectoryRoot,
@@ -28,6 +30,26 @@ import {
28
30
  type NativePublishOutcome,
29
31
  } from "./internal/native-publish-outcome";
30
32
  import { isDerivedSessionMemoryFile } from "./internal/session-memory-sidecar";
33
+ import { captureTaskArtifactOwnerDeletionEvidence } from "./internal/task-artifact-owner-access";
34
+ import {
35
+ hasSiblingTaskArtifactOwnerTranscript,
36
+ taskArtifactOwnerLocatorFromTranscriptBytes,
37
+ } from "./internal/task-artifact-owner-transcript";
38
+ import { parseFirstJsonlLine } from "./session-transcript-header";
39
+ import {
40
+ parseTaskArtifactOwnerDeletionEvidence,
41
+ parseTaskArtifactOwnerRetirementOutcome,
42
+ type TaskArtifactOwnerDeletionEvidence,
43
+ type TaskArtifactOwnerLocator,
44
+ type TaskArtifactOwnerRetirementContinuation,
45
+ type TaskArtifactOwnerRetirementOutcome,
46
+ type TaskArtifactOwnerStorageContext,
47
+ } from "./task-artifact-owner-codec";
48
+ import {
49
+ retireTaskArtifactOwner,
50
+ verifyTaskArtifactOwnerPhysicalRetirement,
51
+ verifyTaskArtifactOwnerRetirementContinuation,
52
+ } from "./task-artifact-owner-retirement";
31
53
 
32
54
  const utf8Decoder = new TextDecoder("utf-8");
33
55
  const newlineBuffer = Buffer.from("\n", "utf8");
@@ -442,8 +464,24 @@ export interface VerifiedSessionDeleteTarget {
442
464
  retainedTranscriptSuccessorPath?: string;
443
465
  /** Native-retained exchange placeholder observed during transcript cleanup. */
444
466
  retainedTranscriptPlaceholderPath?: string;
445
- /** Native-retained transcript entry whose identity could not be verified. */
467
+ /** Native-retained entry whose identity could not be verified. */
446
468
  retainedTranscriptUnknownPath?: string;
469
+ /** Trusted managed authority for the path-free persisted task-artifact locator. */
470
+ taskArtifactOwnerStorageContext?: TaskArtifactOwnerStorageContext;
471
+ /** Exact owner-tree evidence captured before deletion and retained across retries. */
472
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
473
+ /** Owning journal persists the full native disposition across delete effects. */
474
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
475
+ /** Native terminal evidence of logical transcript deletion; pathname absence alone is insufficient. */
476
+ taskArtifactOwnerTranscriptDeleted?: true;
477
+ /** Explicitly leave owner retirement to its owning coordinator. */
478
+ deferTaskArtifactOwnerRetirement?: true;
479
+ /** Durable evidence that exact owner retirement completed. */
480
+ taskArtifactOwnerRetired?: true;
481
+ /** Exact native remnant, separate from immutable original authority. */
482
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
483
+ taskArtifactOwnerPayloadRetired?: true;
484
+ taskArtifactOwnerNamespaceRetained?: true;
447
485
  /** Native-retained publisher successor observed during cleanup. */
448
486
  retainedArtifactsSuccessorPath?: string;
449
487
  /** Native-retained exchange placeholder observed during cleanup. */
@@ -466,8 +504,28 @@ export interface VerifiedSessionDeleteTarget {
466
504
  * `deleted` and never `{}`.
467
505
  */
468
506
  export type VerifiedSessionDeleteResult =
469
- | { kind: "artifacts_removed"; phase: "artifacts"; transcriptIdentity: SessionStorageFileIdentity }
470
- | { kind: "deleted" }
507
+ | {
508
+ kind: "artifacts_removed";
509
+ phase: "artifacts";
510
+ transcriptIdentity: SessionStorageFileIdentity;
511
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
512
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
513
+ taskArtifactOwnerTranscriptDeleted?: true;
514
+ taskArtifactOwnerRetired?: true;
515
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
516
+ taskArtifactOwnerPayloadRetired?: true;
517
+ taskArtifactOwnerNamespaceRetained?: true;
518
+ }
519
+ | {
520
+ kind: "deleted";
521
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
522
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
523
+ taskArtifactOwnerTranscriptDeleted?: true;
524
+ taskArtifactOwnerRetired?: true;
525
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
526
+ taskArtifactOwnerPayloadRetired?: true;
527
+ taskArtifactOwnerNamespaceRetained?: true;
528
+ }
471
529
  | {
472
530
  kind: "cleanup_pending";
473
531
  phase: "artifacts";
@@ -484,6 +542,28 @@ export type VerifiedSessionDeleteResult =
484
542
  retainedSuccessorPath?: string;
485
543
  retainedPlaceholderPath?: string;
486
544
  retainedUnknownPath?: string;
545
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
546
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
547
+ taskArtifactOwnerTranscriptDeleted?: true;
548
+ taskArtifactOwnerRetired?: true;
549
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
550
+ taskArtifactOwnerPayloadRetired?: true;
551
+ taskArtifactOwnerNamespaceRetained?: true;
552
+ }
553
+ | {
554
+ kind: "cleanup_pending";
555
+ phase: "task_artifact_owner";
556
+ /** True only after verified artifact completion, never inferred from the failure phase. */
557
+ artifactsRemoved: boolean;
558
+ error: Error;
559
+ transcriptIdentity: SessionStorageFileIdentity;
560
+ taskArtifactOwnerDeletionEvidence: TaskArtifactOwnerDeletionEvidence;
561
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
562
+ taskArtifactOwnerTranscriptDeleted?: true;
563
+ taskArtifactOwnerRetired?: true;
564
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
565
+ taskArtifactOwnerPayloadRetired?: true;
566
+ taskArtifactOwnerNamespaceRetained?: true;
487
567
  }
488
568
  | {
489
569
  kind: "cleanup_pending";
@@ -497,6 +577,13 @@ export type VerifiedSessionDeleteResult =
497
577
  retainedSuccessorPath?: string;
498
578
  retainedPlaceholderPath?: string;
499
579
  retainedUnknownPath?: string;
580
+ taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
581
+ taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
582
+ taskArtifactOwnerTranscriptDeleted?: true;
583
+ taskArtifactOwnerRetired?: true;
584
+ taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
585
+ taskArtifactOwnerPayloadRetired?: true;
586
+ taskArtifactOwnerNamespaceRetained?: true;
500
587
  };
501
588
 
502
589
  /** Default OS-close dispatcher: a direct `fs.closeSync`. */
@@ -2264,7 +2351,10 @@ export class FileSessionStorage implements SessionStorage {
2264
2351
  * revalidate, transcript last. Partial deletion returns typed cleanup_pending
2265
2352
  * evidence; identity/symlink/containment/header/cwd mismatch throws.
2266
2353
  */
2267
- async deleteSessionVerified(target: VerifiedSessionDeleteTarget): Promise<VerifiedSessionDeleteResult> {
2354
+ async deleteSessionVerified(
2355
+ target: VerifiedSessionDeleteTarget,
2356
+ inspectProtocol?: ManagedGcProtocolScopeInspector,
2357
+ ): Promise<VerifiedSessionDeleteResult> {
2268
2358
  const {
2269
2359
  sessionsRoot,
2270
2360
  transcriptPath,
@@ -2286,6 +2376,15 @@ export class FileSessionStorage implements SessionStorage {
2286
2376
  plannedArtifactsPath,
2287
2377
  plannedTranscriptPath,
2288
2378
  artifactsRemoved,
2379
+ taskArtifactOwnerStorageContext,
2380
+ taskArtifactOwnerDeletionEvidence: ownerEvidenceValue,
2381
+ deferTaskArtifactOwnerRetirement,
2382
+ taskArtifactOwnerRetirementOutcome: ownerOutcomeValue,
2383
+ taskArtifactOwnerTranscriptDeleted: ownerTranscriptDeletedValue,
2384
+ taskArtifactOwnerRetired: ownerRetiredValue,
2385
+ taskArtifactOwnerRetirementContinuation: ownerContinuationValue,
2386
+ taskArtifactOwnerPayloadRetired: ownerPayloadRetiredValue,
2387
+ taskArtifactOwnerNamespaceRetained: ownerNamespaceRetainedValue,
2289
2388
  } = target;
2290
2389
  try {
2291
2390
  assertNoReparsePath(sessionsRoot);
@@ -2301,6 +2400,16 @@ export class FileSessionStorage implements SessionStorage {
2301
2400
  if (!pathIsWithin(sessionsRoot, transcriptPath)) {
2302
2401
  throw new SessionDeleteVerificationError("containment", "Transcript is outside the sessions root");
2303
2402
  }
2403
+ if (
2404
+ taskArtifactOwnerStorageContext &&
2405
+ path.resolve(taskArtifactOwnerStorageContext.sessionsRoot) !== path.resolve(sessionsRoot)
2406
+ )
2407
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_root_mismatch");
2408
+ if (
2409
+ (ownerTranscriptDeletedValue !== undefined && ownerTranscriptDeletedValue !== true) ||
2410
+ (deferTaskArtifactOwnerRetirement !== undefined && deferTaskArtifactOwnerRetirement !== true)
2411
+ )
2412
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_flag_invalid");
2304
2413
  if (
2305
2414
  !plannedArtifactsPath ||
2306
2415
  !plannedTranscriptPath ||
@@ -2354,6 +2463,243 @@ export class FileSessionStorage implements SessionStorage {
2354
2463
  "Transcript parent identity does not match authorization",
2355
2464
  );
2356
2465
  const authorizedTranscriptParentIdentity = transcriptParentIdentity ?? parentIdentity;
2466
+ let initial: { snapshot: SessionStorageSnapshot } | undefined;
2467
+ if (ownerTranscriptDeletedValue === true && fs.existsSync(transcriptPath))
2468
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_transcript_deleted_mismatch");
2469
+ if (!hasDetachedTranscript && ownerTranscriptDeletedValue !== true) {
2470
+ try {
2471
+ initial = this.#verifiedReadAndHeader(transcriptPath, sessionId, cwd);
2472
+ } catch (error) {
2473
+ const absent =
2474
+ error instanceof SessionDeleteVerificationError &&
2475
+ error.kind === "stat" &&
2476
+ (error.cause as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
2477
+ if (!absent || !detachedArtifactsPath || artifactsRemoved) throw error;
2478
+ }
2479
+ }
2480
+ const ownerTranscript =
2481
+ ownerTranscriptDeletedValue === true
2482
+ ? undefined
2483
+ : (initial ??
2484
+ (hasDetachedTranscript && fs.existsSync(cleanupTranscriptPath)
2485
+ ? this.#verifiedReadAndHeader(cleanupTranscriptPath, sessionId, cwd)
2486
+ : undefined));
2487
+ let ownerLocator: TaskArtifactOwnerLocator | undefined;
2488
+ try {
2489
+ ownerLocator = ownerTranscript
2490
+ ? taskArtifactOwnerLocatorFromTranscriptBytes(ownerTranscript.snapshot.bytes, sessionId)
2491
+ : undefined;
2492
+ } catch (error) {
2493
+ const failure = toError(error);
2494
+ if (failure.message.startsWith("task_artifact_owner_")) throw failure;
2495
+ throw new SessionDeleteVerificationError("artifacts", `task_artifact_owner_patch_invalid:${failure.message}`, {
2496
+ cause: failure,
2497
+ });
2498
+ }
2499
+ const withTaskArtifactOwnerFailure = <T>(operation: () => T): T => {
2500
+ try {
2501
+ return operation();
2502
+ } catch (error) {
2503
+ const failure = toError(error);
2504
+ if (failure.message.startsWith("task_artifact_owner_")) throw failure;
2505
+ throw new SessionDeleteVerificationError(
2506
+ "artifacts",
2507
+ `task_artifact_owner_cleanup_failed:${failure.message}`,
2508
+ { cause: failure },
2509
+ );
2510
+ }
2511
+ };
2512
+ let ownerEvidence = ownerEvidenceValue
2513
+ ? withTaskArtifactOwnerFailure(() => parseTaskArtifactOwnerDeletionEvidence(ownerEvidenceValue))
2514
+ : undefined;
2515
+ if (ownerEvidence && ownerEvidence.sessionId !== sessionId)
2516
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_session_mismatch");
2517
+ if (ownerLocator && ownerEvidence && !util.isDeepStrictEqual(ownerLocator, ownerEvidence.locator))
2518
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_locator_mismatch");
2519
+ if (ownerLocator && !ownerEvidence && taskArtifactOwnerStorageContext)
2520
+ ownerEvidence = withTaskArtifactOwnerFailure(() =>
2521
+ captureTaskArtifactOwnerDeletionEvidence(taskArtifactOwnerStorageContext, sessionId, ownerLocator),
2522
+ );
2523
+ if (ownerLocator && !ownerEvidence)
2524
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_locator_missing");
2525
+ if (ownerEvidence && ownerTranscript && !ownerLocator)
2526
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_evidence_not_in_transcript");
2527
+ if (ownerEvidence && !taskArtifactOwnerStorageContext)
2528
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_context_missing");
2529
+ if (ownerLocator && deferTaskArtifactOwnerRetirement && (!ownerEvidence || !taskArtifactOwnerStorageContext))
2530
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_defer_evidence_missing");
2531
+ if (ownerTranscriptDeletedValue === true && !ownerEvidence)
2532
+ throw new SessionDeleteVerificationError(
2533
+ "artifacts",
2534
+ "task_artifact_owner_transcript_deleted_evidence_missing",
2535
+ );
2536
+ let ownerOutcome =
2537
+ ownerOutcomeValue !== undefined && ownerEvidence && taskArtifactOwnerStorageContext
2538
+ ? withTaskArtifactOwnerFailure(() =>
2539
+ parseTaskArtifactOwnerRetirementOutcome(
2540
+ taskArtifactOwnerStorageContext,
2541
+ ownerEvidence!,
2542
+ ownerOutcomeValue,
2543
+ ),
2544
+ )
2545
+ : undefined;
2546
+ if (ownerOutcomeValue !== undefined && !ownerOutcome)
2547
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_outcome_missing");
2548
+ if (
2549
+ ownerTranscriptDeletedValue === true &&
2550
+ ownerOutcome?.kind !== "completed" &&
2551
+ ownerOutcome?.kind !== "payload_retired"
2552
+ )
2553
+ throw new SessionDeleteVerificationError(
2554
+ "artifacts",
2555
+ "task_artifact_owner_transcript_deleted_outcome_missing",
2556
+ );
2557
+ if ((ownerContinuationValue !== undefined) !== (ownerOutcome !== undefined && ownerOutcome.kind !== "completed"))
2558
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_continuation_mismatch");
2559
+ if (
2560
+ ownerOutcome &&
2561
+ ownerOutcome.kind !== "completed" &&
2562
+ !util.isDeepStrictEqual(ownerContinuationValue, ownerOutcome.continuation)
2563
+ )
2564
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_continuation_mismatch");
2565
+ const ownerContinuationRecord =
2566
+ ownerOutcome && ownerOutcome.kind !== "completed" ? ownerOutcome.continuation : undefined;
2567
+ let ownerContinuation = ownerContinuationRecord
2568
+ ? withTaskArtifactOwnerFailure(() =>
2569
+ verifyTaskArtifactOwnerRetirementContinuation(
2570
+ taskArtifactOwnerStorageContext!,
2571
+ ownerEvidence!,
2572
+ ownerContinuationRecord,
2573
+ ),
2574
+ )
2575
+ : undefined;
2576
+ const payloadRetired = ownerOutcome?.kind === "payload_retired";
2577
+ if (
2578
+ (ownerRetiredValue !== undefined && ownerRetiredValue !== true) ||
2579
+ (ownerPayloadRetiredValue !== undefined && ownerPayloadRetiredValue !== true) ||
2580
+ (ownerNamespaceRetainedValue !== undefined && ownerNamespaceRetainedValue !== true) ||
2581
+ (ownerRetiredValue === true) !== (ownerOutcome?.kind === "completed") ||
2582
+ (ownerPayloadRetiredValue === true) !== payloadRetired ||
2583
+ (ownerNamespaceRetainedValue === true) !== payloadRetired
2584
+ )
2585
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_outcome_mismatch");
2586
+ if (ownerRetiredValue === true) {
2587
+ if (!ownerEvidence || !taskArtifactOwnerStorageContext || !ownerOutcome)
2588
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retired_evidence_missing");
2589
+ withTaskArtifactOwnerFailure(() =>
2590
+ verifyTaskArtifactOwnerPhysicalRetirement(taskArtifactOwnerStorageContext, ownerEvidence!, ownerOutcome),
2591
+ );
2592
+ }
2593
+ if (
2594
+ ownerEvidence &&
2595
+ ownerLocator &&
2596
+ taskArtifactOwnerStorageContext &&
2597
+ !ownerContinuation &&
2598
+ !ownerRetiredValue
2599
+ ) {
2600
+ const current = withTaskArtifactOwnerFailure(() =>
2601
+ captureTaskArtifactOwnerDeletionEvidence(taskArtifactOwnerStorageContext, sessionId, ownerLocator),
2602
+ );
2603
+ if (!current || !util.isDeepStrictEqual(current, ownerEvidence))
2604
+ throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_changed_since_capture");
2605
+ }
2606
+ let ownerRetired = ownerRetiredValue === true;
2607
+ let ownerPayloadRetired = ownerPayloadRetiredValue === true;
2608
+ let ownerNamespaceRetained = ownerNamespaceRetainedValue === true;
2609
+ let ownerTranscriptDeleted = ownerTranscriptDeletedValue === true;
2610
+ const ownerSiblingRefusal = async (): Promise<Error | undefined> => {
2611
+ // Deferral changes who retires the owner, not the pre-artifact sibling authority boundary.
2612
+ if (!ownerEvidence) return undefined;
2613
+ if (!taskArtifactOwnerStorageContext)
2614
+ return new SessionDeleteVerificationError("artifacts", "task_artifact_owner_context_missing");
2615
+ try {
2616
+ if (
2617
+ await hasSiblingTaskArtifactOwnerTranscript(
2618
+ this,
2619
+ transcriptPath,
2620
+ ownerEvidence.locator,
2621
+ taskArtifactOwnerStorageContext,
2622
+ inspectProtocol,
2623
+ )
2624
+ )
2625
+ return new SessionDeleteVerificationError(
2626
+ "artifacts",
2627
+ "task_artifact_owner_shared_with_sibling_transcript",
2628
+ );
2629
+ return undefined;
2630
+ } catch (error) {
2631
+ return new SessionDeleteVerificationError(
2632
+ "artifacts",
2633
+ `task_artifact_owner_cleanup_failed:${toError(error).message}`,
2634
+ { cause: toError(error) },
2635
+ );
2636
+ }
2637
+ };
2638
+ const retireOwnerIfRequired = async (): Promise<Error | undefined> => {
2639
+ const refusal = await ownerSiblingRefusal();
2640
+ if (refusal) return refusal;
2641
+ if (deferTaskArtifactOwnerRetirement || ownerRetired || !ownerEvidence || !taskArtifactOwnerStorageContext)
2642
+ return undefined;
2643
+ try {
2644
+ ownerOutcome = withTaskArtifactOwnerFailure(() =>
2645
+ retireTaskArtifactOwner(taskArtifactOwnerStorageContext, ownerEvidence!, ownerContinuation),
2646
+ );
2647
+ if (ownerOutcome.kind === "completed") {
2648
+ ownerRetired = true;
2649
+ ownerContinuation = undefined;
2650
+ ownerPayloadRetired = false;
2651
+ ownerNamespaceRetained = false;
2652
+ return undefined;
2653
+ }
2654
+ ownerContinuation = ownerOutcome.continuation;
2655
+ ownerPayloadRetired = ownerOutcome.kind === "payload_retired";
2656
+ ownerNamespaceRetained = ownerOutcome.kind === "payload_retired";
2657
+ return new SessionDeleteVerificationError(
2658
+ "artifacts",
2659
+ ownerOutcome.kind === "uncertain"
2660
+ ? ownerOutcome.reason
2661
+ : "task_artifact_owner_namespace_cleanup_pending",
2662
+ );
2663
+ } catch (error) {
2664
+ return new SessionDeleteVerificationError(
2665
+ "artifacts",
2666
+ `task_artifact_owner_cleanup_failed:${toError(error).message}`,
2667
+ { cause: toError(error) },
2668
+ );
2669
+ }
2670
+ };
2671
+ const ownerResultFields = () => ({
2672
+ ...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
2673
+ ...(ownerOutcome ? { taskArtifactOwnerRetirementOutcome: ownerOutcome } : {}),
2674
+ ...(ownerTranscriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
2675
+ ...(ownerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
2676
+ ...(ownerContinuation ? { taskArtifactOwnerRetirementContinuation: ownerContinuation } : {}),
2677
+ ...(ownerPayloadRetired ? { taskArtifactOwnerPayloadRetired: true as const } : {}),
2678
+ ...(ownerNamespaceRetained ? { taskArtifactOwnerNamespaceRetained: true as const } : {}),
2679
+ });
2680
+ const ownerCleanupPending = (error: Error, completedArtifacts: boolean): VerifiedSessionDeleteResult => ({
2681
+ kind: "cleanup_pending",
2682
+ phase: "task_artifact_owner",
2683
+ artifactsRemoved: completedArtifacts,
2684
+ error,
2685
+ transcriptIdentity,
2686
+ taskArtifactOwnerDeletionEvidence: ownerEvidence!,
2687
+ ...ownerResultFields(),
2688
+ });
2689
+ const finishArtifactPhase = async (): Promise<VerifiedSessionDeleteResult> => {
2690
+ const ownerError = await retireOwnerIfRequired();
2691
+ if (ownerError && ownerEvidence) return ownerCleanupPending(ownerError, true);
2692
+ if (ownerTranscriptDeleted) return { kind: "deleted", ...ownerResultFields() };
2693
+ return {
2694
+ kind: "artifacts_removed",
2695
+ phase: "artifacts",
2696
+ transcriptIdentity,
2697
+ ...ownerResultFields(),
2698
+ };
2699
+ };
2700
+ const ownerPreflightError = await ownerSiblingRefusal();
2701
+ if (ownerPreflightError && ownerEvidence)
2702
+ return ownerCleanupPending(ownerPreflightError, artifactsRemoved === true);
2357
2703
  if (detachedArtifactsPath && !artifactsRemoved) {
2358
2704
  if (
2359
2705
  !expectedArtifactsIdentity ||
@@ -2426,16 +2772,16 @@ export class FileSessionStorage implements SessionStorage {
2426
2772
  ? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
2427
2773
  : {}),
2428
2774
  transcriptIdentity,
2775
+ ...ownerResultFields(),
2429
2776
  };
2430
2777
  }
2431
- return { kind: "artifacts_removed", phase: "artifacts", transcriptIdentity };
2778
+ return await finishArtifactPhase();
2432
2779
  }
2433
2780
  if (transcriptIdentity.nlink === undefined || transcriptIdentity.nlink !== 1n)
2434
2781
  throw new SessionDeleteVerificationError(
2435
2782
  "identity",
2436
2783
  "Single-link transcript authority is required for exact deletion",
2437
2784
  );
2438
- const initial = hasDetachedTranscript ? undefined : this.#verifiedReadAndHeader(transcriptPath, sessionId, cwd);
2439
2785
  const initialStat = initial?.snapshot.stat;
2440
2786
  const initialDigest = initial ? createHash("sha256").update(initial.snapshot.bytes).digest("hex") : undefined;
2441
2787
  if (
@@ -2490,7 +2836,7 @@ export class FileSessionStorage implements SessionStorage {
2490
2836
  "artifacts",
2491
2837
  "Authorized artifact removal root remains after restart",
2492
2838
  );
2493
- return { kind: "artifacts_removed", phase: "artifacts", transcriptIdentity };
2839
+ return await finishArtifactPhase();
2494
2840
  }
2495
2841
 
2496
2842
  if (artifactsIdentity && !artifactsRemoved) {
@@ -2563,6 +2909,7 @@ export class FileSessionStorage implements SessionStorage {
2563
2909
  detachedArtifactsPath: detach.detachedPath,
2564
2910
  artifactsTree,
2565
2911
  transcriptIdentity,
2912
+ ...ownerResultFields(),
2566
2913
  };
2567
2914
  } finally {
2568
2915
  if (descriptor !== undefined) fs.closeSync(descriptor);
@@ -2580,6 +2927,7 @@ export class FileSessionStorage implements SessionStorage {
2580
2927
  ...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
2581
2928
  ...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
2582
2929
  transcriptIdentity,
2930
+ ...ownerResultFields(),
2583
2931
  };
2584
2932
  }
2585
2933
  const removal = removeDirectoryTreeExact(
@@ -2632,6 +2980,7 @@ export class FileSessionStorage implements SessionStorage {
2632
2980
  ? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
2633
2981
  : {}),
2634
2982
  transcriptIdentity,
2983
+ ...ownerResultFields(),
2635
2984
  };
2636
2985
  }
2637
2986
  }
@@ -2657,8 +3006,19 @@ export class FileSessionStorage implements SessionStorage {
2657
3006
  if (descriptor !== undefined) fs.closeSync(descriptor);
2658
3007
  }
2659
3008
  }
2660
- return { kind: "artifacts_removed", phase: "artifacts", transcriptIdentity };
3009
+ return await finishArtifactPhase();
2661
3010
  }
3011
+ if (deferTaskArtifactOwnerRetirement && ownerEvidence && !ownerRetired && !ownerPayloadRetired) {
3012
+ return {
3013
+ kind: "artifacts_removed",
3014
+ phase: "artifacts",
3015
+ transcriptIdentity,
3016
+ ...ownerResultFields(),
3017
+ };
3018
+ }
3019
+ const ownerError = await retireOwnerIfRequired();
3020
+ if (ownerError && ownerEvidence) return ownerCleanupPending(ownerError, true);
3021
+ if (ownerTranscriptDeleted) return { kind: "deleted", ...ownerResultFields() };
2662
3022
  if (hasDetachedTranscript) {
2663
3023
  const deletion = nativeExactUnlink(cleanupTranscriptPath, {
2664
3024
  dev: transcriptIdentity.dev,
@@ -2672,7 +3032,10 @@ export class FileSessionStorage implements SessionStorage {
2672
3032
  quarantineName: path.basename(plannedTranscriptPath),
2673
3033
  });
2674
3034
  if (!deletion.ok) {
2675
- if (transcriptDeletionTerminal(deletion)) return { kind: "deleted" };
3035
+ if (transcriptDeletionTerminal(deletion)) {
3036
+ if (ownerEvidence) ownerTranscriptDeleted = true;
3037
+ return { kind: "deleted", ...ownerResultFields() };
3038
+ }
2676
3039
  const error = exactUnlinkFailure(deletion);
2677
3040
  const retainedAuthority =
2678
3041
  deletion.detachedPath ||
@@ -2688,6 +3051,7 @@ export class FileSessionStorage implements SessionStorage {
2688
3051
  ? { transcriptPayloadDurable: true as const }
2689
3052
  : {}),
2690
3053
  transcriptIdentity,
3054
+ ...ownerResultFields(),
2691
3055
  detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
2692
3056
  ...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
2693
3057
  ? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
@@ -2700,7 +3064,8 @@ export class FileSessionStorage implements SessionStorage {
2700
3064
  : {}),
2701
3065
  };
2702
3066
  }
2703
- return { kind: "deleted" };
3067
+ if (ownerEvidence) ownerTranscriptDeleted = true;
3068
+ return { kind: "deleted", ...ownerResultFields() };
2704
3069
  }
2705
3070
  if (!initialStat || !initialDigest)
2706
3071
  throw new SessionDeleteVerificationError("stat", "Transcript cleanup state is invalid");
@@ -2741,7 +3106,10 @@ export class FileSessionStorage implements SessionStorage {
2741
3106
  quarantineName: path.basename(plannedTranscriptPath),
2742
3107
  });
2743
3108
  if (!deletion.ok) {
2744
- if (transcriptDeletionTerminal(deletion)) return { kind: "deleted" };
3109
+ if (transcriptDeletionTerminal(deletion)) {
3110
+ if (ownerEvidence) ownerTranscriptDeleted = true;
3111
+ return { kind: "deleted", ...ownerResultFields() };
3112
+ }
2745
3113
  const error = exactUnlinkFailure(deletion);
2746
3114
  const retainedAuthority =
2747
3115
  deletion.detachedPath ||
@@ -2757,6 +3125,7 @@ export class FileSessionStorage implements SessionStorage {
2757
3125
  ? { transcriptPayloadDurable: true as const }
2758
3126
  : {}),
2759
3127
  transcriptIdentity,
3128
+ ...ownerResultFields(),
2760
3129
  detachedTranscriptPath: deletion.detachedPath,
2761
3130
  ...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
2762
3131
  ? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
@@ -2769,7 +3138,8 @@ export class FileSessionStorage implements SessionStorage {
2769
3138
  : {}),
2770
3139
  };
2771
3140
  }
2772
- return { kind: "deleted" };
3141
+ if (ownerEvidence) ownerTranscriptDeleted = true;
3142
+ return { kind: "deleted", ...ownerResultFields() };
2773
3143
  }
2774
3144
 
2775
3145
  #verifiedReadAndHeader(
@@ -2883,22 +3253,6 @@ export class FileSessionStorage implements SessionStorage {
2883
3253
  }
2884
3254
  }
2885
3255
 
2886
- /** Parse the first JSONL line as a generic record; returns undefined on parse failure. */
2887
- function parseFirstJsonlLine(bytes: Uint8Array): Record<string, unknown> | undefined {
2888
- const NL = 0x0a;
2889
- const end = bytes.indexOf(NL);
2890
- const firstLine = end === -1 ? bytes : bytes.subarray(0, end);
2891
- if (firstLine.length === 0) return undefined;
2892
- try {
2893
- const text = utf8Decoder.decode(firstLine).trim();
2894
- if (!text) return undefined;
2895
- const value: unknown = JSON.parse(text);
2896
- return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : undefined;
2897
- } catch {
2898
- return undefined;
2899
- }
2900
- }
2901
-
2902
3256
  function matchesPattern(name: string, pattern: string): boolean {
2903
3257
  if (pattern === "*") return true;
2904
3258
  if (pattern === ".*") return name.startsWith(".");
@@ -3100,9 +3454,11 @@ class MemorySessionStorageWriter implements SessionStorageBufferedWriter {
3100
3454
  this.#length = nextLength;
3101
3455
  }
3102
3456
 
3457
+ // Memory storage retains this view, so expose only visible bytes, never capacity.
3458
+ // Appends write after each published prefix; growth switches backing buffers.
3103
3459
  #writeCurrent(bytesWritten: number): void {
3104
3460
  this.#writeCalls++;
3105
- this.#storage.writeBytesOwnedSync(this.#path, Buffer.from(this.#bytes.subarray(0, this.#length)));
3461
+ this.#storage.writeBytesOwnedSync(this.#path, this.#bytes.subarray(0, this.#length));
3106
3462
  this.#bytesWritten += bytesWritten;
3107
3463
  }
3108
3464
 
@@ -3118,7 +3474,7 @@ class MemorySessionStorageWriter implements SessionStorageBufferedWriter {
3118
3474
  }
3119
3475
  this.#buffer.copy(this.#bytes, this.#length, 0, pendingBytes);
3120
3476
  this.#writeCalls++;
3121
- this.#storage.writeBytesOwnedSync(this.#path, Buffer.from(this.#bytes.subarray(0, nextLength)));
3477
+ this.#storage.writeBytesOwnedSync(this.#path, this.#bytes.subarray(0, nextLength));
3122
3478
  this.#length = nextLength;
3123
3479
  this.#bufferedBytes = 0;
3124
3480
  this.#bytesWritten += pendingBytes;
@@ -3468,6 +3824,23 @@ export class MemorySessionStorage implements SessionStorage {
3468
3824
  new SessionDeleteVerificationError("containment", "Transcript is outside the sessions root"),
3469
3825
  );
3470
3826
  }
3827
+ if (
3828
+ [
3829
+ target.taskArtifactOwnerStorageContext,
3830
+ target.taskArtifactOwnerDeletionEvidence,
3831
+ target.taskArtifactOwnerRetirementOutcome,
3832
+ target.taskArtifactOwnerTranscriptDeleted,
3833
+ target.deferTaskArtifactOwnerRetirement,
3834
+ target.taskArtifactOwnerRetired,
3835
+ target.taskArtifactOwnerRetirementContinuation,
3836
+ target.taskArtifactOwnerPayloadRetired,
3837
+ target.taskArtifactOwnerNamespaceRetained,
3838
+ ].some(value => value !== undefined)
3839
+ ) {
3840
+ return Promise.reject(
3841
+ new SessionDeleteVerificationError("artifacts", "task_artifact_owner_memory_backend_unsupported"),
3842
+ );
3843
+ }
3471
3844
  const entry = this.#files.get(transcriptPath);
3472
3845
  if (!entry) return Promise.resolve({ kind: "deleted" });
3473
3846
  const snapshot = this.readSnapshotSync(transcriptPath);
@@ -3500,6 +3873,21 @@ export class MemorySessionStorage implements SessionStorage {
3500
3873
  if (path.resolve(header.cwd) !== path.resolve(cwd)) {
3501
3874
  return Promise.reject(new SessionDeleteVerificationError("cwd", "Transcript header cwd mismatch"));
3502
3875
  }
3876
+ let ownerLocator: TaskArtifactOwnerLocator | undefined;
3877
+ try {
3878
+ ownerLocator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, sessionId);
3879
+ } catch (error) {
3880
+ return Promise.reject(
3881
+ new SessionDeleteVerificationError("artifacts", "task_artifact_owner_memory_backend_unsupported", {
3882
+ cause: toError(error),
3883
+ }),
3884
+ );
3885
+ }
3886
+ if (ownerLocator) {
3887
+ return Promise.reject(
3888
+ new SessionDeleteVerificationError("artifacts", "task_artifact_owner_memory_backend_unsupported"),
3889
+ );
3890
+ }
3503
3891
  // Compatible artifact semantics: the memory backend models no directories, so
3504
3892
  // a key at the artifact path is a non-directory sibling that must fail closed
3505
3893
  // rather than be silently treated as an absent artifacts directory.
@@ -3531,147 +3919,3 @@ export class MemorySessionStorage implements SessionStorage {
3531
3919
  return new MemoryStagedStreamingWriter(this, path);
3532
3920
  }
3533
3921
  }
3534
-
3535
- /**
3536
- * Outcome of a disk-retention retirement attempt.
3537
- *
3538
- * `kept` means the transcript survived and nothing of the session was
3539
- * destroyed, with the exact reason so `gjc gc --disk` can report it.
3540
- * `cleanup_pending` means the delete authority already detached or removed the
3541
- * session's artifact tree (or quarantined the transcript) before it stopped:
3542
- * the record survives, the session does not, and a caller must NOT report that
3543
- * as an ordinary keep.
3544
- */
3545
- export type SessionRetirementOutcome =
3546
- | { kind: "retired" }
3547
- | { kind: "kept"; reason: string }
3548
- | { kind: "cleanup_pending"; reason: string };
3549
-
3550
- /** A retirement that cleared every precondition the retention pass itself owns. */
3551
- type SessionRetirementPlan =
3552
- | { kind: "retirable"; target: VerifiedSessionDeleteTarget; artifactsPresent: boolean }
3553
- | { kind: "kept"; reason: string };
3554
-
3555
- /**
3556
- * Evaluate the preconditions retirement owns — the transcript must be readable
3557
- * and carry a usable session header — and bind the delete target to the exact
3558
- * bytes just read. Nothing here mutates the store.
3559
- */
3560
- function planSessionRetirement(
3561
- storage: FileSessionStorage,
3562
- sessionsRoot: string,
3563
- transcriptPath: string,
3564
- ): SessionRetirementPlan {
3565
- let snapshot: SessionStorageSnapshot;
3566
- try {
3567
- snapshot = storage.readSnapshotSync(transcriptPath);
3568
- } catch (err) {
3569
- return { kind: "kept", reason: `transcript_unreadable: ${toError(err).message}` };
3570
- }
3571
-
3572
- const header = parseFirstJsonlLine(snapshot.bytes);
3573
- if (header?.type !== "session" || typeof header.id !== "string" || typeof header.cwd !== "string") {
3574
- return { kind: "kept", reason: "transcript_header_unusable" };
3575
- }
3576
-
3577
- // The verified delete authority only unlinks a single-link transcript, so a
3578
- // hard-linked one can never be retired. Refusing here keeps the dry run from
3579
- // promising bytes prune cannot release, and — because this runs before the
3580
- // artifact phase — keeps a doomed retirement from destroying the artifact
3581
- // tree of a session whose transcript will survive anyway.
3582
- if (snapshot.stat.nlink === undefined || snapshot.stat.nlink !== 1n) {
3583
- return { kind: "kept", reason: "transcript_not_single_link" };
3584
- }
3585
-
3586
- const directory = path.dirname(transcriptPath);
3587
- return {
3588
- kind: "retirable",
3589
- artifactsPresent: storage.existsSync(transcriptPath.slice(0, -".jsonl".length)),
3590
- target: {
3591
- sessionsRoot,
3592
- transcriptPath,
3593
- sessionId: header.id,
3594
- cwd: header.cwd,
3595
- transcriptIdentity: {
3596
- dev: snapshot.stat.dev,
3597
- ino: snapshot.stat.ino,
3598
- nlink: snapshot.stat.nlink,
3599
- size: snapshot.stat.size,
3600
- mtimeNs: snapshot.stat.mtimeNs,
3601
- sha256: createHash("sha256").update(snapshot.bytes).digest("hex"),
3602
- },
3603
- plannedArtifactsPath: path.join(directory, `.gjc-delete-gc-${randomUUID()}-artifacts`),
3604
- plannedTranscriptPath: path.join(directory, `.gjc-delete-gc-${randomUUID()}-transcript`),
3605
- },
3606
- };
3607
- }
3608
-
3609
- /**
3610
- * Non-mutating projection of {@link retireSessionTranscript}: would the
3611
- * retention pass's own preconditions let this transcript be retired at all?
3612
- *
3613
- * `gjc gc --disk` runs it so a dry run reports the verdict a prune would reach
3614
- * instead of promising bytes the delete authority will refuse to release. The
3615
- * authority's verdict (containment, identity, artifact tree) is deliberately
3616
- * not predicted here — it is re-derived against live state at delete time.
3617
- */
3618
- export function probeSessionRetirement(
3619
- storage: FileSessionStorage,
3620
- sessionsRoot: string,
3621
- transcriptPath: string,
3622
- ): { kind: "retirable" } | { kind: "kept"; reason: string } {
3623
- const plan = planSessionRetirement(storage, sessionsRoot, transcriptPath);
3624
- return plan.kind === "retirable" ? { kind: "retirable" } : plan;
3625
- }
3626
-
3627
- /**
3628
- * A transcript that survives after its artifacts are already gone is not a
3629
- * benign keep: the session is half-destroyed and the caller has to surface it.
3630
- */
3631
- function retirementIncomplete(artifactsRemoved: boolean, reason: string): SessionRetirementOutcome {
3632
- return artifactsRemoved ? { kind: "cleanup_pending", reason } : { kind: "kept", reason };
3633
- }
3634
-
3635
- /**
3636
- * Retire one managed session transcript through the verified hard-delete
3637
- * authority.
3638
- *
3639
- * This is the only supported way for a retention pass to remove a transcript:
3640
- * identity, containment, header id/cwd, artifact tree and parent directory are
3641
- * all re-verified inside {@link FileSessionStorage.deleteSessionVerified}, and
3642
- * anything ambiguous fails closed as `kept` rather than deleting bytes. The
3643
- * caller owns the retention policy; this function owns nothing but the delete
3644
- * authority.
3645
- */
3646
- export async function retireSessionTranscript(
3647
- storage: FileSessionStorage,
3648
- sessionsRoot: string,
3649
- transcriptPath: string,
3650
- ): Promise<SessionRetirementOutcome> {
3651
- const plan = planSessionRetirement(storage, sessionsRoot, transcriptPath);
3652
- if (plan.kind === "kept") return plan;
3653
-
3654
- let artifactsRemoved = false;
3655
- try {
3656
- // Phase 1 removes the sibling artifact directory (or proves it absent);
3657
- // phase 2 unlinks the transcript only against the same bound identity.
3658
- const artifacts = await storage.deleteSessionVerified(plan.target);
3659
- if (artifacts.kind === "deleted") return { kind: "retired" };
3660
- if (artifacts.kind === "cleanup_pending") {
3661
- return { kind: "cleanup_pending", reason: `cleanup_pending_${artifacts.phase}: ${artifacts.error.message}` };
3662
- }
3663
- // Phase 1 is durable: whatever artifact tree the session had is gone, so
3664
- // every later refusal leaves a record without its artifacts.
3665
- artifactsRemoved = plan.artifactsPresent;
3666
- const deletion = await storage.deleteSessionVerified({ ...plan.target, artifactsRemoved: true });
3667
- if (deletion.kind === "deleted") return { kind: "retired" };
3668
- if (deletion.kind === "cleanup_pending") {
3669
- return { kind: "cleanup_pending", reason: `cleanup_pending_${deletion.phase}: ${deletion.error.message}` };
3670
- }
3671
- return retirementIncomplete(artifactsRemoved, "transcript_not_deleted");
3672
- } catch (err) {
3673
- const error = toError(err);
3674
- const kind = err instanceof SessionDeleteVerificationError ? err.kind : "error";
3675
- return retirementIncomplete(artifactsRemoved, `verified_delete_rejected(${kind}): ${error.message}`);
3676
- }
3677
- }