@moretea-labs/forge 1.7.2 → 1.8.1

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 (900) hide show
  1. package/AGENTS.md +121 -62
  2. package/CHANGELOG.md +40 -0
  3. package/CLAUDE.md +57 -62
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.md +2 -2
  6. package/SKILL.md +0 -1
  7. package/THIRD_PARTY_NOTICES.md +5 -2
  8. package/adapters/browser/index.ts +2 -0
  9. package/adapters/chatgpt/browser-delivery-host.ts +77 -0
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
  11. package/adapters/chatgpt/controller-binding-store.ts +134 -0
  12. package/adapters/chatgpt/controller-round-host.ts +68 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
  14. package/adapters/chatgpt/provider-delivery.ts +340 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  18. package/adapters/computer/chatgpt-target-router.ts +103 -0
  19. package/adapters/computer/chrome-extension/background.js +385 -0
  20. package/adapters/computer/chrome-extension/content.js +378 -0
  21. package/adapters/computer/chrome-extension/core.js +24 -0
  22. package/adapters/computer/chrome-extension/manifest.json +30 -0
  23. package/adapters/computer/desktop-operator-contract.ts +7 -0
  24. package/adapters/computer/desktop-operator-discovery.ts +181 -0
  25. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  26. package/adapters/computer/desktop-operator-provider.ts +312 -0
  27. package/adapters/computer/index.ts +1 -0
  28. package/adapters/computer/interaction-target-authority.ts +1068 -0
  29. package/adapters/controller-process/binding-store.ts +34 -0
  30. package/adapters/controller-process/controller-host.ts +26 -0
  31. package/adapters/mcp/access-mode.ts +132 -0
  32. package/adapters/mcp/audit.ts +32 -0
  33. package/adapters/mcp/auth.ts +406 -0
  34. package/adapters/mcp/instructions.ts +37 -0
  35. package/adapters/mcp/legacy-context.ts +18 -0
  36. package/adapters/mcp/multi-repository.ts +421 -0
  37. package/adapters/mcp/oauth.ts +464 -0
  38. package/adapters/mcp/paths.ts +143 -0
  39. package/adapters/mcp/policy.ts +232 -0
  40. package/adapters/mcp/proxy-env.ts +62 -0
  41. package/adapters/mcp/readiness-projection.ts +24 -0
  42. package/adapters/mcp/redaction.ts +21 -0
  43. package/adapters/mcp/repo.ts +24 -0
  44. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  45. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  46. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  47. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  48. package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
  49. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  50. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
  51. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  52. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
  53. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  54. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  55. package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
  56. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  57. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
  58. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  59. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  60. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  61. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  62. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
  63. package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
  64. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
  65. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
  66. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  67. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  68. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
  69. package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
  70. package/adapters/mcp/runtime-gateway/router.ts +242 -0
  71. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  72. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
  73. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  74. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
  75. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  76. package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
  77. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
  78. package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
  79. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
  80. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  81. package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
  82. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  83. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  84. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  85. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  86. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  87. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  88. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  89. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  90. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  91. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  92. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  93. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  94. package/adapters/mcp/server.ts +1092 -0
  95. package/adapters/mcp/setup.ts +789 -0
  96. package/adapters/mcp/tool-capability-cache.ts +87 -0
  97. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  98. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
  99. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
  100. package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
  101. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  102. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  103. package/adapters/mcp/toolset-names.ts +79 -0
  104. package/adapters/mcp/toolset-selection.ts +41 -0
  105. package/adapters/mcp/toolset.ts +337 -0
  106. package/adapters/mcp/transports/http-observation.ts +365 -0
  107. package/adapters/mcp/transports/http.ts +933 -0
  108. package/adapters/mcp/transports/oauth-http.ts +322 -0
  109. package/adapters/mcp/transports/session-registry.ts +519 -0
  110. package/adapters/mcp/transports/stdio.ts +8 -0
  111. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
  112. package/adapters/mcp/types.ts +41 -0
  113. package/assets/AGENTS.md +2 -16
  114. package/assets/CLAUDE.md +2 -16
  115. package/assets/hooks/AGENTS.md +3 -14
  116. package/assets/hooks/CLAUDE.md +3 -14
  117. package/assets/hooks/hook-input.sh +9 -2
  118. package/assets/hooks/lib/workflow-state.sh +28 -10
  119. package/assets/hooks/prompt-guard.sh +63 -37
  120. package/assets/hooks/security-sentinel.sh +2 -1
  121. package/assets/hooks/session-start-context.sh +7 -5
  122. package/assets/hooks/stop-orchestrator.sh +13 -3
  123. package/assets/initializer-question-pack.v4.json +11 -7
  124. package/assets/partials/05-workflow.partial.md +18 -73
  125. package/assets/partials/07-footer.partial.md +1 -1
  126. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  127. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  128. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  129. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  130. package/assets/plan-map.json +11 -11
  131. package/assets/plugin-registry.v1.json +11 -3
  132. package/assets/plugin-trust-policy.v1.json +6 -0
  133. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  134. package/assets/recovery/forge-wsl-rescue +271 -0
  135. package/assets/reference-configs/agentic-development-flow.md +25 -0
  136. package/assets/reference-configs/development-protocol.md +11 -15
  137. package/assets/reference-configs/document-generation.md +1 -1
  138. package/assets/reference-configs/global-working-rules.md +28 -1
  139. package/assets/reference-configs/harness-overview.md +22 -112
  140. package/assets/reference-configs/hook-operations.md +1 -1
  141. package/assets/reference-configs/sprint-contracts.md +2 -2
  142. package/assets/skill-version.json +2 -2
  143. package/assets/templates/helpers/architecture-event.ts +3 -34
  144. package/assets/templates/helpers/architecture-queue.sh +61 -10
  145. package/assets/templates/helpers/archive-workflow.sh +90 -180
  146. package/assets/templates/helpers/capability-config.ts +8 -11
  147. package/assets/templates/helpers/capability-resolver.ts +37 -19
  148. package/assets/templates/helpers/capture-plan.sh +2 -2
  149. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  150. package/assets/templates/helpers/check-task-sync.sh +82 -11
  151. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  152. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  153. package/assets/templates/helpers/contract-worktree.sh +26 -24
  154. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  155. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  156. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  157. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  158. package/assets/templates/helpers/new-plan.sh +2 -2
  159. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  160. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  161. package/assets/templates/helpers/switch-plan.sh +1 -1
  162. package/assets/templates/helpers/workflow-contract.ts +1 -0
  163. package/assets/workflow-contract.v1.json +63 -171
  164. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  165. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  166. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  167. package/docs/architecture/CURRENT.md +233 -28
  168. package/docs/architecture/EVOLUTION.md +13 -0
  169. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  170. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  171. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  172. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  173. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  174. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  175. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
  176. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
  177. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  178. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  179. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  180. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  181. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  182. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  183. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  184. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  185. package/docs/architecture/domains/verification.md +8 -0
  186. package/docs/architecture/domains/workflow-engine.md +7 -1
  187. package/docs/architecture/index.md +3 -3
  188. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  189. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  190. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  191. package/docs/forge-plugin-management.md +20 -4
  192. package/docs/forge-runtime-self-healing-loop.md +2 -0
  193. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  194. package/docs/operations/controller-browser-plugin.md +3 -1
  195. package/docs/operations/controller-performance-and-502.md +17 -0
  196. package/docs/operations/controller-reliability-runbook.md +2 -2
  197. package/docs/operations/features.md +1 -1
  198. package/docs/operations/features.zh-CN.md +1 -1
  199. package/docs/operations/homebrew.md +2 -2
  200. package/docs/operations/mcp-tool-exposure.md +2 -0
  201. package/docs/operations/platform-support.md +16 -8
  202. package/docs/operations/platform-support.zh-CN.md +2 -2
  203. package/docs/operations/releasing.md +2 -0
  204. package/docs/operations/standalone-disaster-recovery.md +38 -5
  205. package/docs/operations/uu-remote-rescue.md +41 -0
  206. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  207. package/docs/public-usage-guide.md +1 -1
  208. package/docs/public-usage-guide.zh-CN.md +1 -1
  209. package/docs/tutorials/01-install-and-start.md +1 -1
  210. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  211. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  212. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  213. package/docs/wiki/Implementation.md +1 -1
  214. package/package.json +46 -26
  215. package/packages/kernel/cognition/api/index.ts +6 -0
  216. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  217. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  218. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  219. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  220. package/packages/kernel/cognition/domain/learning.ts +106 -0
  221. package/packages/kernel/cognition/domain/memory.ts +254 -0
  222. package/packages/kernel/controller/api/index.ts +3 -0
  223. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  224. package/packages/kernel/controller/application/controller-round-service.ts +41 -0
  225. package/packages/kernel/controller/application/controller-service.ts +30 -0
  226. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
  227. package/packages/kernel/controller/domain/controller-round.ts +110 -0
  228. package/packages/kernel/controller/domain/execution-quality.ts +135 -0
  229. package/packages/kernel/controller/domain/types.ts +56 -0
  230. package/packages/kernel/controller/index.ts +1 -0
  231. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  232. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
  233. package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
  234. package/packages/kernel/controller/ports/controller-host.ts +18 -0
  235. package/packages/kernel/goal/api/index.ts +1 -0
  236. package/packages/kernel/goal/domain/types.ts +40 -0
  237. package/packages/kernel/goal/index.ts +1 -0
  238. package/packages/kernel/identity/api/index.ts +15 -0
  239. package/packages/kernel/identity/application/identity-service.ts +67 -0
  240. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  241. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  242. package/packages/kernel/identity/domain/grant.ts +72 -0
  243. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  244. package/packages/kernel/identity/domain/scope.ts +63 -0
  245. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  246. package/packages/kernel/identity/domain/types.ts +44 -0
  247. package/packages/kernel/identity/domain/user-request.ts +93 -0
  248. package/packages/kernel/identity/domain/workspace.ts +56 -0
  249. package/packages/kernel/identity/index.ts +1 -0
  250. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  251. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  252. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  253. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  254. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  255. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  256. package/packages/kernel/memory/api/index.ts +5 -0
  257. package/packages/kernel/memory/application/experience-service.ts +118 -0
  258. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  259. package/packages/kernel/memory/domain/experience.ts +103 -0
  260. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  261. package/packages/kernel/memory/index.ts +1 -0
  262. package/packages/kernel/scheduler/api/index.ts +4 -0
  263. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  264. package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
  265. package/packages/kernel/scheduler/application/settlement.ts +182 -0
  266. package/packages/kernel/scheduler/domain/schedule.ts +236 -0
  267. package/packages/kernel/scheduler/index.ts +1 -0
  268. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
  269. package/packages/kernel/work/api/index.ts +16 -0
  270. package/packages/kernel/work/application/work-service.ts +123 -0
  271. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  272. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  273. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  274. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  275. package/packages/kernel/work/domain/engineering-design.ts +286 -0
  276. package/packages/kernel/work/domain/engineering-profile.ts +238 -0
  277. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  278. package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
  279. package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
  280. package/packages/kernel/work/domain/implementation-review.ts +193 -0
  281. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  282. package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
  283. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  284. package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
  285. package/packages/kernel/work/domain/types.ts +534 -0
  286. package/packages/kernel/work/index.ts +1 -0
  287. package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
  288. package/packages/kernel/work/ports/work-contract-store.ts +21 -0
  289. package/packages/plugin-runtime/browser/index.ts +3 -0
  290. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  291. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  292. package/packages/plugin-runtime/browser/session-authority.ts +62 -0
  293. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  294. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  295. package/packages/plugin-runtime/computer/index.ts +8 -0
  296. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  297. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  298. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  299. package/packages/plugin-runtime/computer/provider.ts +19 -0
  300. package/packages/plugin-runtime/computer/target-authority.ts +215 -0
  301. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  302. package/packages/plugin-runtime/external/index.ts +1 -0
  303. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
  304. package/packages/plugin-runtime/index.ts +3 -0
  305. package/packages/protocols/browser/index.ts +1 -0
  306. package/packages/protocols/browser/session.ts +98 -0
  307. package/packages/protocols/computer/contract.ts +225 -0
  308. package/packages/protocols/computer/index.ts +1 -0
  309. package/packages/protocols/failure/contract.ts +158 -0
  310. package/packages/protocols/failure/index.ts +1 -0
  311. package/packages/protocols/handoff/index.ts +1 -0
  312. package/packages/protocols/handoff/status.ts +22 -0
  313. package/packages/protocols/mcp/execution-context.ts +11 -0
  314. package/packages/protocols/mcp/index.ts +2 -0
  315. package/packages/protocols/mcp/tool-contract.ts +42 -0
  316. package/packages/workflow-runtime/api/index.ts +2 -0
  317. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  318. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  319. package/packages/workflow-runtime/index.ts +1 -0
  320. package/scripts/activate-source-baseline.ts +7 -9
  321. package/scripts/assemble-template.ts +1 -1
  322. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  323. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  324. package/scripts/canary-controller-semantic-wait.ts +183 -0
  325. package/scripts/canary-global-hook.sh +2 -2
  326. package/scripts/check-mcp-compatibility.ts +85 -7
  327. package/scripts/check-platform-support.mjs +28 -2
  328. package/scripts/check-public-docs.sh +1 -1
  329. package/scripts/check-repository-hygiene.mjs +92 -0
  330. package/scripts/check-runtime-architecture.mjs +1643 -127
  331. package/scripts/check-source-duplication.mjs +9 -1
  332. package/scripts/controller-ngrok-rotation.sh +5 -4
  333. package/scripts/create-project-dirs.sh +10 -74
  334. package/scripts/forge-local-recovery-helper.mjs +282 -0
  335. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  336. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  337. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  338. package/scripts/hook-shim.sh +2 -2
  339. package/scripts/init-project.sh +12 -70
  340. package/scripts/install-desktop-operator-registration.ts +4 -2
  341. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  342. package/scripts/install-standalone-recovery.ts +122 -10
  343. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  344. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  345. package/scripts/lib/controller-home-env.sh +6 -3
  346. package/scripts/lib/project-init-lib.sh +42 -582
  347. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  348. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  349. package/scripts/open-source-audit-allowlist.txt +11 -1
  350. package/scripts/portability-bindings.mjs +70 -0
  351. package/scripts/run-bounded-command.mjs +60 -0
  352. package/scripts/run-bun-test-file.ts +22 -6
  353. package/scripts/run-governed-gate.ts +4 -2
  354. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  355. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  356. package/scripts/smoke-mcp-http-runtime.ts +6 -1
  357. package/scripts/smoke-runtime-recovery.ts +13 -11
  358. package/scripts/smoke-schedule-engine.ts +24 -16
  359. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  360. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  361. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  362. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  363. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  364. package/scripts/smoke-workflow-supervisor.ts +103 -0
  365. package/scripts/test-governance.ts +51 -2
  366. package/scripts/verify-forge-runtime.sh +2 -1
  367. package/src/cli/agent-jobs/executor-health.ts +11 -34
  368. package/src/cli/agent-jobs/integration.ts +18 -49
  369. package/src/cli/agent-jobs/job-manager.ts +17 -4
  370. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  371. package/src/cli/chatgpt-browser/binding.ts +20 -10
  372. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  373. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  374. package/src/cli/chatgpt-browser/engine.ts +10 -11
  375. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  376. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  377. package/src/cli/chatgpt-browser/types.ts +1 -0
  378. package/src/cli/commands/bootstrap-control.ts +334 -0
  379. package/src/cli/commands/brain-assistant.ts +52 -0
  380. package/src/cli/commands/brain.ts +191 -40
  381. package/src/cli/commands/chatgpt.ts +32 -67
  382. package/src/cli/commands/computer.ts +437 -0
  383. package/src/cli/commands/doctor.ts +18 -1
  384. package/src/cli/commands/init-hook.ts +71 -61
  385. package/src/cli/commands/init.ts +3 -1
  386. package/src/cli/commands/mcp.ts +16 -10
  387. package/src/cli/commands/plugin.ts +38 -12
  388. package/src/cli/commands/recovery.ts +507 -80
  389. package/src/cli/commands/runtime.ts +12 -7
  390. package/src/cli/commands/setup-profile.ts +97 -39
  391. package/src/cli/commands/status.ts +2 -1
  392. package/src/cli/controller/check-runner.ts +500 -100
  393. package/src/cli/controller/completion-backlog.ts +3 -3
  394. package/src/cli/controller/completion-orchestrator.ts +4 -43
  395. package/src/cli/controller/completion-target.ts +3 -72
  396. package/src/cli/controller/context/source-materializer.ts +81 -100
  397. package/src/cli/controller/context/types.ts +7 -1
  398. package/src/cli/controller/context-pack.ts +28 -6
  399. package/src/cli/controller/execution-completion.ts +4 -7
  400. package/src/cli/controller/execution-policy.ts +36 -153
  401. package/src/cli/controller/governance.ts +2 -3
  402. package/src/cli/controller/issue-store.ts +4 -99
  403. package/src/cli/controller/launch-agents.ts +59 -18
  404. package/src/cli/controller/operational-plan.ts +24 -51
  405. package/src/cli/controller/runtime-config.ts +5 -1
  406. package/src/cli/controller/stuck-state-migration.ts +2 -3
  407. package/src/cli/controller/systemd-user.ts +148 -0
  408. package/src/cli/controller/types.ts +10 -70
  409. package/src/cli/controller/worklog.ts +67 -13
  410. package/src/cli/editing/edit-operation-contract.ts +92 -0
  411. package/src/cli/editing/edit-session.ts +290 -20
  412. package/src/cli/github/plugin.ts +17 -12
  413. package/src/cli/hook/route-registry.ts +3 -5
  414. package/src/cli/hook/runtime.ts +49 -3
  415. package/src/cli/index.ts +3 -0
  416. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  417. package/src/cli/local-bridge/console-automations.ts +20 -15
  418. package/src/cli/local-bridge/console-view-models.ts +3 -12
  419. package/src/cli/local-bridge/facade-api.ts +105 -205
  420. package/src/cli/local-bridge/job-store.ts +1 -1
  421. package/src/cli/local-bridge/server.ts +48 -174
  422. package/src/cli/local-bridge/types.ts +0 -1
  423. package/src/cli/mcp/access-mode.ts +2 -132
  424. package/src/cli/mcp/access-tools.ts +2 -387
  425. package/src/cli/mcp/audit.ts +2 -32
  426. package/src/cli/mcp/auth.ts +2 -404
  427. package/src/cli/mcp/instructions.ts +2 -34
  428. package/src/cli/mcp/legacy-context.ts +2 -16
  429. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  430. package/src/cli/mcp/multi-repository.ts +2 -436
  431. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  432. package/src/cli/mcp/oauth.ts +2 -375
  433. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  434. package/src/cli/mcp/paths.ts +2 -140
  435. package/src/cli/mcp/policy.ts +2 -232
  436. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  437. package/src/cli/mcp/proxy-env.ts +2 -62
  438. package/src/cli/mcp/readiness-projection.ts +2 -24
  439. package/src/cli/mcp/redaction.ts +2 -21
  440. package/src/cli/mcp/repo.ts +2 -24
  441. package/src/cli/mcp/repository-tools.ts +2 -1613
  442. package/src/cli/mcp/server.ts +2 -951
  443. package/src/cli/mcp/setup.ts +2 -737
  444. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  445. package/src/cli/mcp/tool-contract.ts +2 -14
  446. package/src/cli/mcp/tools.ts +2 -9
  447. package/src/cli/mcp/toolset-names.ts +2 -70
  448. package/src/cli/mcp/toolset-selection.ts +2 -41
  449. package/src/cli/mcp/toolset.ts +2 -336
  450. package/src/cli/mcp/transports/AGENTS.md +1 -10
  451. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  452. package/src/cli/mcp/transports/http.ts +2 -1414
  453. package/src/cli/mcp/transports/session-registry.ts +2 -490
  454. package/src/cli/mcp/transports/stdio.ts +2 -8
  455. package/src/cli/mcp/types.ts +2 -41
  456. package/src/cli/mcp/umbrella-tools.ts +2 -117
  457. package/src/cli/repositories/command-executor.ts +91 -39
  458. package/src/cli/repositories/command-scope.ts +139 -4
  459. package/src/cli/repositories/controller-home.ts +167 -1
  460. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  461. package/src/cli/repositories/git-command-executor.ts +2 -2
  462. package/src/cli/repositories/identity.ts +13 -0
  463. package/src/cli/repositories/registry.ts +145 -60
  464. package/src/cli/repositories/repository-snapshot.ts +53 -7
  465. package/src/cli/repositories/runtime-storage.ts +27 -2
  466. package/src/cli/repositories/safe-patch.ts +34 -38
  467. package/src/cli/repositories/selected-path-actions.ts +22 -2
  468. package/src/cli/repositories/structured-git.ts +167 -27
  469. package/src/cli/repository/session-cache.ts +92 -6
  470. package/src/cli/runtime/helper-runner.ts +7 -0
  471. package/src/cli/runtime-invocation.ts +8 -1
  472. package/src/cli/tools/codegraph.ts +44 -25
  473. package/src/core/adoption/manifest-templates.ts +1 -1
  474. package/src/core/adoption/plan.ts +3 -26
  475. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  476. package/src/effects/process-runner.ts +8 -14
  477. package/src/runtime/context/assistant-context.ts +167 -0
  478. package/src/runtime/context/assistant-work-context.ts +209 -0
  479. package/src/runtime/context/automatic-learning.ts +565 -0
  480. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  481. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  482. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  483. package/src/runtime/context/cognitive-attention.ts +173 -0
  484. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  485. package/src/runtime/context/context-closure.ts +305 -0
  486. package/src/runtime/context/context-pack-process.ts +103 -0
  487. package/src/runtime/context/context-plane.ts +203 -0
  488. package/src/runtime/context/direct-controller-learning.ts +298 -0
  489. package/src/runtime/context/project-engineering-contract.ts +60 -0
  490. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  491. package/src/runtime/context/semantic-navigation.ts +6 -34
  492. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  493. package/src/runtime/context/typescript-navigation.ts +89 -3
  494. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  495. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  496. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  497. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  498. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  499. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  500. package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
  501. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  502. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
  503. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
  504. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
  505. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  506. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  507. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  508. package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
  509. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  510. package/src/runtime/control-plane/execution/validation.ts +34 -11
  511. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
  512. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  513. package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
  514. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  515. package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
  516. package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
  517. package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
  518. package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
  519. package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
  520. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
  521. package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
  522. package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
  523. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  524. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  525. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
  526. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
  527. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  528. package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
  529. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
  530. package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
  531. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  532. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  533. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  534. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  535. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
  536. package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
  537. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
  538. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
  539. package/src/runtime/control-plane/facade/index.ts +4 -3
  540. package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
  541. package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
  542. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  543. package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
  544. package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
  545. package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
  546. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
  547. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  548. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  549. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
  550. package/src/runtime/control-plane/facade/types.ts +218 -620
  551. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  552. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  553. package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
  554. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  555. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
  556. package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
  557. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  558. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  559. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
  560. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  561. package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
  562. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  563. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  564. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  565. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  566. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
  567. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  568. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
  569. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  570. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
  571. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
  572. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  573. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  574. package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
  575. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  576. package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
  577. package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
  578. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  579. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  580. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  581. package/src/runtime/control-plane/release-retention.ts +154 -26
  582. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  583. package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
  584. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  585. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  586. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
  587. package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
  588. package/src/runtime/diagnostics/incident-repair.ts +573 -0
  589. package/src/runtime/diagnostics/mcp-timing.ts +239 -6
  590. package/src/runtime/diagnostics/performance.ts +6 -14
  591. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  592. package/src/runtime/evidence/artifact-store.ts +216 -35
  593. package/src/runtime/evidence/evidence-store.ts +112 -8
  594. package/src/runtime/evidence/operational-shadow.ts +38 -0
  595. package/src/runtime/evidence/output-handle-index.ts +225 -0
  596. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  597. package/src/runtime/evidence/result-store.ts +158 -44
  598. package/src/runtime/evidence/sensitive-output.ts +4 -4
  599. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  600. package/src/runtime/execution/jobs/store.ts +124 -6
  601. package/src/runtime/execution/managed-workspace.ts +112 -5
  602. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  603. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  604. package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
  605. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  606. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  607. package/src/runtime/execution/process-runtime/check-result.ts +177 -2
  608. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
  609. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  610. package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
  611. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  612. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  613. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  614. package/src/runtime/execution/process-runtime/index.ts +4 -0
  615. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  616. package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
  617. package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
  618. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  619. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  620. package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
  621. package/src/runtime/execution/process-runtime/runtime.ts +167 -17
  622. package/src/runtime/execution/process-runtime/store.ts +30 -2
  623. package/src/runtime/execution/process-runtime/types.ts +27 -0
  624. package/src/runtime/execution/workers/executor.ts +18 -19
  625. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  626. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  627. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  628. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  629. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  630. package/src/runtime/gateway/mcp/router.ts +2 -1125
  631. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  632. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  633. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  634. package/src/runtime/maintenance/cleanup.ts +38 -24
  635. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  636. package/src/runtime/platform/computer-platform.ts +14 -0
  637. package/src/runtime/platform/executable-discovery.ts +54 -0
  638. package/src/runtime/platform/fetch-http-server.ts +90 -0
  639. package/src/runtime/platform/service-inventory.ts +130 -0
  640. package/src/runtime/platform/service-manager.ts +146 -0
  641. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  642. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  643. package/src/runtime/plugins/action-application.ts +192 -0
  644. package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
  645. package/src/runtime/plugins/browser-adapter.ts +898 -135
  646. package/src/runtime/plugins/browser-automation-service.ts +33 -34
  647. package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
  648. package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
  649. package/src/runtime/plugins/browser-native-session-service.ts +64 -14
  650. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  651. package/src/runtime/plugins/browser-registration.ts +32 -0
  652. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  653. package/src/runtime/plugins/browser-runtime.ts +55 -10
  654. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  655. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  656. package/src/runtime/plugins/browser-session-store.ts +243 -80
  657. package/src/runtime/plugins/browser-session-types.ts +2 -98
  658. package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
  659. package/src/runtime/plugins/catalog-trust.ts +82 -0
  660. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  661. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  662. package/src/runtime/plugins/computer-registration.ts +1050 -0
  663. package/src/runtime/plugins/config-store.ts +119 -0
  664. package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
  665. package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
  666. package/src/runtime/plugins/errors.ts +8 -1
  667. package/src/runtime/plugins/external-adapter.ts +82 -323
  668. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  669. package/src/runtime/plugins/external-registration.ts +30 -2
  670. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  671. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  672. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  673. package/src/runtime/plugins/first-party-registry.ts +3 -1
  674. package/src/runtime/plugins/github-adapter.ts +1 -2
  675. package/src/runtime/plugins/gmail-adapter.ts +29 -8
  676. package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
  677. package/src/runtime/plugins/google-shared.ts +45 -53
  678. package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
  679. package/src/runtime/plugins/ios-adapter.ts +2 -3
  680. package/src/runtime/plugins/lightweight-action.ts +208 -2
  681. package/src/runtime/plugins/local-system-adapter.ts +192 -2
  682. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  683. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  684. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  685. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  686. package/src/runtime/plugins/resend-adapter.ts +15 -24
  687. package/src/runtime/plugins/store.ts +1003 -387
  688. package/src/runtime/plugins/types.ts +28 -1
  689. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  690. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  691. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  692. package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
  693. package/src/runtime/projections/controller-context.ts +12 -6
  694. package/src/runtime/projections/git-status-sampler.ts +73 -22
  695. package/src/runtime/projections/invalidation.ts +7 -3
  696. package/src/runtime/projections/materialized-view.ts +93 -43
  697. package/src/runtime/recovery/actions.ts +3 -3
  698. package/src/runtime/recovery/capability-action-application.ts +229 -0
  699. package/src/runtime/recovery/index.ts +3 -0
  700. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  701. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  702. package/src/runtime/recovery/maintenance-executor.ts +701 -168
  703. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  704. package/src/runtime/recovery/store.ts +29 -11
  705. package/src/runtime/release/identity.ts +6 -0
  706. package/src/runtime/release/release-coordinator.ts +296 -0
  707. package/src/runtime/release/release-session.ts +716 -0
  708. package/src/runtime/release/release-state-migration.ts +25 -0
  709. package/src/runtime/resources/leases/store.ts +56 -25
  710. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  711. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  712. package/src/runtime/root/browser-session-composition.ts +59 -0
  713. package/src/runtime/root/computer-composition.ts +376 -0
  714. package/src/runtime/root/computer-target-composition.ts +16 -0
  715. package/src/runtime/root/computer-target-persistence.ts +39 -0
  716. package/src/runtime/root/controller-progression-composition.ts +192 -0
  717. package/src/runtime/root/controller-round-composition.ts +126 -0
  718. package/src/runtime/root/controller-services.ts +2 -0
  719. package/src/runtime/root/deployment-topology.ts +106 -0
  720. package/src/runtime/root/entry.ts +8 -6
  721. package/src/runtime/root/gateway-adapter.ts +3 -3
  722. package/src/runtime/root/known-good-recovery.ts +184 -0
  723. package/src/runtime/root/local-bridge.ts +5 -3
  724. package/src/runtime/root/mcp-transport.ts +116 -23
  725. package/src/runtime/root/ownership.ts +261 -9
  726. package/src/runtime/root/package-connector-service.ts +212 -74
  727. package/src/runtime/root/package-runtime-release.ts +199 -33
  728. package/src/runtime/root/package-runtime-service.ts +77 -108
  729. package/src/runtime/root/release-execution-canary.ts +103 -0
  730. package/src/runtime/root/release-loader.ts +150 -0
  731. package/src/runtime/root/release-manifest.ts +185 -7
  732. package/src/runtime/root/release-materialize.ts +474 -31
  733. package/src/runtime/root/release-store.ts +425 -37
  734. package/src/runtime/root/runtime-lane.ts +191 -0
  735. package/src/runtime/root/runtime.ts +379 -32
  736. package/src/runtime/root/scheduled-controller-composition.ts +76 -0
  737. package/src/runtime/root/scheduler.ts +12 -4
  738. package/src/runtime/root/service-runner.ts +3 -3
  739. package/src/runtime/root/service.ts +73 -45
  740. package/src/runtime/root/status.ts +49 -0
  741. package/src/runtime/root/types.ts +55 -3
  742. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  743. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  744. package/src/runtime/root/write-fence.ts +50 -2
  745. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  746. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  747. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  748. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  749. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  750. package/src/runtime/shared/process-environment.ts +36 -10
  751. package/src/runtime/shared/response-budget.ts +1 -3
  752. package/src/runtime/shared/storage-capacity.ts +68 -0
  753. package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
  754. package/src/runtime/standalone-recovery/core.ts +4609 -495
  755. package/src/runtime/standalone-recovery/entry.ts +642 -81
  756. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  757. package/src/runtime/standalone-recovery/installer.ts +399 -177
  758. package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
  759. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  760. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  761. package/src/runtime/standalone-recovery/performance.ts +173 -0
  762. package/src/runtime/standalone-recovery/release.ts +58 -5
  763. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  764. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  765. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  766. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  767. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
  768. package/src/runtime/workflow/schedules/engine.ts +298 -484
  769. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  770. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  771. package/src/runtime/workflow/schedules/store.ts +2 -607
  772. package/src/runtime/workflow/schedules/types.ts +2 -127
  773. package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
  774. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  775. package/src/runtime/workflows/runtime.ts +482 -0
  776. package/src/testing/test-governance.ts +95 -30
  777. package/supervisor/chatgpt-conversation.ts +23 -0
  778. package/supervisor/client.ts +201 -0
  779. package/supervisor/control-plane.ts +868 -0
  780. package/supervisor/entry.ts +15 -0
  781. package/supervisor/forge-validators.ts +80 -0
  782. package/supervisor/index.ts +5 -0
  783. package/supervisor/native-browser-adapter.ts +786 -0
  784. package/supervisor/native-messaging/host.ts +39 -0
  785. package/supervisor/native-messaging/manifest.ts +26 -0
  786. package/supervisor/native-messaging/protocol.ts +27 -0
  787. package/supervisor/paths.ts +18 -0
  788. package/supervisor/protocol.ts +202 -0
  789. package/supervisor/server.ts +404 -0
  790. package/supervisor/store.ts +1451 -0
  791. package/supervisor/tsconfig.json +8 -0
  792. package/supervisor/types.ts +255 -0
  793. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  794. package/docs/architecture/history/README.md +0 -7
  795. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  796. package/docs/architecture/history/global-hook-runtime.md +0 -166
  797. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  798. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  799. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  800. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  801. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  802. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  803. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  804. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  805. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  806. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  807. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  808. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  809. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  810. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  811. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  812. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  813. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  814. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  815. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  816. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  817. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  818. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  819. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  820. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  821. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  822. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  823. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  824. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  825. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  826. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  827. package/docs/architecture/requests/archive/2026/root.md +0 -68
  828. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  829. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  830. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  831. package/scripts/archive-architecture-request.sh +0 -4
  832. package/scripts/benchmark-background-check-overlap.ts +0 -228
  833. package/scripts/benchmark-controller-hotpath.ts +0 -212
  834. package/scripts/benchmark-controller-round2.ts +0 -139
  835. package/scripts/benchmark-ios-agent-device.ts +0 -514
  836. package/scripts/benchmark-mcp-host.sh +0 -117
  837. package/scripts/benchmark-mcp-transport.ts +0 -170
  838. package/scripts/benchmark-quality-harness.ts +0 -146
  839. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  840. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  841. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  842. package/scripts/benchmark-thin-harness.ts +0 -251
  843. package/scripts/benchmark-typescript-navigation.ts +0 -186
  844. package/scripts/benchmark-work-efficiency.ts +0 -334
  845. package/scripts/build-controller-ui.ts +0 -28
  846. package/scripts/check-npm-release.sh +0 -32
  847. package/scripts/check-public-release-surface.sh +0 -19
  848. package/scripts/check-release-published.sh +0 -57
  849. package/scripts/check-release-readiness.sh +0 -37
  850. package/scripts/check-release-version.mjs +0 -74
  851. package/scripts/check-tarball-install-smoke.sh +0 -67
  852. package/scripts/package-source-archive.sh +0 -147
  853. package/scripts/public-release-files.txt +0 -39
  854. package/scripts/public-release.ts +0 -174
  855. package/scripts/publish-release-tarball.sh +0 -27
  856. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  857. package/scripts/run-skill-evals.ts +0 -1022
  858. package/scripts/stage-runtime-release.ts +0 -39
  859. package/src/cli/controller/progress.ts +0 -656
  860. package/src/cli/controller/work-mode.ts +0 -181
  861. package/src/cli/local-bridge/console-assets.ts +0 -17
  862. package/src/cli/local-bridge/dashboard.ts +0 -17
  863. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  864. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  865. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  866. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  867. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  868. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  869. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  870. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  871. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  872. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  873. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  874. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  875. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  876. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  877. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  878. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  879. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  880. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  881. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  882. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  883. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  884. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  885. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  886. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  887. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  888. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  889. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  890. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  891. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
  892. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
  893. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  894. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
  895. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
  896. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
  897. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  898. package/src/runtime/control-plane/routing/index.ts +0 -1
  899. package/src/runtime/control-plane/routing/route-policy.ts +0 -382
  900. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,26 +1,26 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { spawnSync } from 'child_process';
3
3
  import {
4
- cpSync,
5
4
  existsSync,
6
5
  mkdirSync,
7
6
  readFileSync,
8
- writeFileSync,
9
7
  } from 'fs';
10
8
  import { dirname, join, resolve } from 'path';
11
9
  import { repoLocalNoIndexControllerHome, repositoryControllerRoot } from '../../../cli/repositories/controller-home';
12
10
  import {
13
11
  getRepository,
14
12
  listRepositories,
13
+ RepositoryCheckoutSelectionError,
15
14
  selectRepositoryCheckout,
16
15
  setRepositoryCheckoutLifecycle,
17
16
  } from '../../../cli/repositories/registry';
18
17
  import { managedPathInside, managedWorktreeStorageRoot } from '../../../cli/repositories/worktree-storage';
19
18
  import { markRepositoryProjectionDirty } from '../../projections/invalidation';
20
19
  import { listControlPlaneRecords } from '../persistence/sqlite-store';
21
- import { getWorkContract } from '../facade/work-contract-store';
22
- import { getControllerSession, releaseObservedControllerSession } from '../facade/controller-session-store';
23
- import { isRepositoryCompletionReceipt, isTerminalWorkContractStatus, type WorkContract } from '../facade/types';
20
+ import { getWorkContract, recordCancelledWorkCleanupCompleted, semanticWorkState } from '../../../../packages/kernel/work/api/index';
21
+ import { assertOwnedResourceCleanupTarget, markOwnedResourceCleaned, markOwnedResourceRetained } from '../../../../packages/kernel/identity/api/index';
22
+ import { managedBranchOwnedResourceId, managedBranchOwnedResourceLocator, managedWorkspaceOwnedResourceId } from '../../execution/managed-workspace';
23
+ import { isRepositoryCompletionReceipt, type WorkContract } from '../facade/types';
24
24
  import {
25
25
  cancelProcess,
26
26
  getProcessHandle,
@@ -33,14 +33,14 @@ import { getProcessRecord, listProcessRecords } from '../../execution/process-ru
33
33
  import {
34
34
  listWorkHandles,
35
35
  readWorkHandle,
36
+ resolveWorkDeliveryTargetBranch,
36
37
  transitionWorkHandle,
37
38
  writeWorkHandle,
38
39
  type WorkCleanupReceipt,
39
40
  type WorkHandleState,
40
41
  type WorkTerminalOutcome,
41
42
  } from './work-handle-store';
42
-
43
- const CHECKPOINT_MESSAGE = 'chore(checkpoint): preserve terminal work before cleanup';
43
+ import { proveWorkPreservationContained } from '../cleanup-artifact-retention';
44
44
 
45
45
  export interface TerminalWorkCleanupInput {
46
46
  controllerHome: string;
@@ -109,7 +109,7 @@ function newReceipt(handle: WorkHandleState, targetBranch: string, terminalOutco
109
109
  updatedAt: timestamp,
110
110
  verification: { mode: 'cleanup_only', checksRun: [] },
111
111
  processes: { examined: [], terminated: [], blocking: [], allTerminal: false },
112
- ownership: { controllerLease: 'pending', processLeases: 'pending' },
112
+ ownership: { controllerLease: 'already_released', processLeases: 'pending' },
113
113
  preservation: { status: 'not_needed' },
114
114
  worktree: { path: handle.worktreePath, status: 'pending' },
115
115
  branchCleanup: { branch: handle.branch, status: 'pending' },
@@ -159,62 +159,6 @@ function branchUsedByAnotherWorktree(root: string, branch: string, currentPath:
159
159
  return false;
160
160
  }
161
161
 
162
- function copyUntrackedFiles(worktreePath: string, archiveRoot: string): Array<{ path: string; sha256: string }> {
163
- const listed = git(worktreePath, ['ls-files', '--others', '--exclude-standard', '-z']);
164
- if (!listed.ok || !listed.stdout) return [];
165
- const manifest: Array<{ path: string; sha256: string }> = [];
166
- for (const relativePath of listed.stdout.split('\0').filter(Boolean)) {
167
- const source = join(worktreePath, relativePath);
168
- const destination = join(archiveRoot, 'untracked', relativePath);
169
- mkdirSync(dirname(destination), { recursive: true });
170
- if (!existsSync(destination)) {
171
- cpSync(source, destination, { recursive: true, force: false, errorOnExist: true });
172
- }
173
- if (existsSync(source)) {
174
- try {
175
- manifest.push({ path: relativePath, sha256: hashText(readFileSync(source)) });
176
- } catch {
177
- manifest.push({ path: relativePath, sha256: 'directory-or-unreadable' });
178
- }
179
- }
180
- }
181
- return manifest;
182
- }
183
-
184
- function createPatchArchive(controllerHome: string, handle: WorkHandleState): {
185
- path: string;
186
- sha256: string;
187
- recoveryInstructions: string;
188
- } {
189
- const root = artifactRoot(controllerHome, handle);
190
- const path = join(root, 'worktree.patch.json');
191
- if (!existsSync(path)) {
192
- const unstaged = git(handle.worktreePath, ['diff', '--binary', 'HEAD']);
193
- const staged = git(handle.worktreePath, ['diff', '--binary', '--cached', 'HEAD']);
194
- const untracked = copyUntrackedFiles(handle.worktreePath, root);
195
- const archive = {
196
- schemaVersion: 1,
197
- repoId: handle.repositoryId,
198
- checkoutId: handle.checkoutId,
199
- workId: handle.workId,
200
- branch: handle.branch,
201
- baseCommit: handle.baseCommit,
202
- expectedHead: handle.expectedHead,
203
- createdAt: nowIso(),
204
- unstagedPatch: unstaged.stdout,
205
- stagedPatch: staged.stdout,
206
- untracked,
207
- };
208
- writeFileSync(path, `${JSON.stringify(archive, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
209
- }
210
- const sha256 = hashText(readFileSync(path));
211
- return {
212
- path,
213
- sha256,
214
- recoveryInstructions: `Apply tracked changes from ${path}; restore copied untracked files from ${join(root, 'untracked')}. Verify SHA-256 ${sha256}.`,
215
- };
216
- }
217
-
218
162
  function createVerifiedBundle(controllerHome: string, handle: WorkHandleState, targetRoot: string): {
219
163
  path: string;
220
164
  sha256: string;
@@ -296,7 +240,7 @@ function workHandleOwnsCheckout(controllerHome: string, handle: WorkHandleState)
296
240
  // WorkContract completion is the authoritative ownership boundary. Older
297
241
  // runtimes could persist a completed contract while leaving the handle in a
298
242
  // non-terminal state; that historical handle must not remain a live owner.
299
- return contract === undefined || !isTerminalWorkContractStatus(contract.status);
243
+ return contract === undefined || semanticWorkState(contract) === 'open';
300
244
  }
301
245
 
302
246
  function assertNoOtherLiveWork(input: TerminalWorkCleanupInput, receipt: WorkCleanupReceipt): void {
@@ -321,59 +265,22 @@ function preserveDirtyWorktree(
321
265
  return persist(input.controllerHome, current, receipt);
322
266
  }
323
267
  if (!status.stdout) {
324
- receipt.preservation.status = receipt.preservation.checkpointCommit || receipt.preservation.patchArchivePath
325
- ? receipt.preservation.status
326
- : 'not_needed';
268
+ receipt.preservation.status = 'not_needed';
327
269
  return persist(input.controllerHome, current, receipt);
328
270
  }
329
271
 
330
- if (!receipt.preservation.checkpointCommit && !receipt.preservation.patchArchivePath) {
331
- const staged = git(current.worktreePath, ['add', '-A']);
332
- const committed = staged.ok
333
- ? git(current.worktreePath, [
334
- '-c', 'user.name=forge',
335
- '-c', 'user.email=forge@local.invalid',
336
- 'commit', '-m', CHECKPOINT_MESSAGE,
337
- ], 60_000)
338
- : { ok: false, stdout: '', stderr: staged.stderr };
339
- if (committed.ok) {
340
- const head = git(current.worktreePath, ['rev-parse', 'HEAD']);
341
- if (!head.ok || !head.stdout) {
342
- receipt.preservation.status = 'failed';
343
- addBlocker(receipt, 'CHECKPOINT_HEAD_UNAVAILABLE');
344
- } else {
345
- receipt.preservation.status = 'checkpointed';
346
- receipt.preservation.checkpointCommit = head.stdout;
347
- current = writeWorkHandle(input.controllerHome, {
348
- ...current,
349
- expectedHead: head.stdout,
350
- cleanupReceipt: receipt,
351
- });
352
- }
353
- } else {
354
- try {
355
- const archive = createPatchArchive(input.controllerHome, current);
356
- receipt.preservation.status = 'patch_archived';
357
- receipt.preservation.patchArchivePath = archive.path;
358
- receipt.preservation.patchArchiveSha256 = archive.sha256;
359
- receipt.preservation.recoveryInstructions = archive.recoveryInstructions;
360
- } catch (error) {
361
- receipt.preservation.status = 'failed';
362
- addBlocker(receipt, `PRESERVATION_FAILED: ${error instanceof Error ? error.message : String(error)}`);
363
- }
364
- }
365
- } else if (receipt.preservation.checkpointCommit && status.stdout) {
366
- try {
367
- const archive = createPatchArchive(input.controllerHome, current);
368
- receipt.preservation.status = 'patch_archived';
369
- receipt.preservation.patchArchivePath = archive.path;
370
- receipt.preservation.patchArchiveSha256 = archive.sha256;
371
- receipt.preservation.recoveryInstructions = archive.recoveryInstructions;
372
- } catch (error) {
373
- receipt.preservation.status = 'failed';
374
- addBlocker(receipt, `POST_CHECKPOINT_PRESERVATION_FAILED: ${error instanceof Error ? error.message : String(error)}`);
375
- }
376
- }
272
+ // Semantic Work terminal state is not authority to mutate, commit, archive,
273
+ // or delete uncommitted workspace content. Dirty bytes stay exactly where the
274
+ // model/user left them until an explicit delivery/discard action resolves them.
275
+ receipt.preservation.status = 'not_needed';
276
+ receipt.preservation.recoveryInstructions = `Dirty managed worktree retained in place at ${current.worktreePath}; commit, deliver, or explicitly discard the pending changes before cleanup.`;
277
+ receipt.worktree.status = 'retained';
278
+ receipt.worktree.reason = 'Dirty managed worktree retained; terminal semantic state does not authorize destructive cleanup.';
279
+ receipt.branchCleanup.status = 'retained';
280
+ receipt.branchCleanup.reason = 'Branch retained with dirty managed worktree.';
281
+ addBlocker(receipt, 'DIRTY_WORKTREE_RETAINED');
282
+ markOwnedResourceRetained(input.controllerHome, managedWorkspaceOwnedResourceId(current.repositoryId, current.checkoutId));
283
+ markOwnedResourceRetained(input.controllerHome, managedBranchOwnedResourceId(current.repositoryId, current.checkoutId));
377
284
  return persist(input.controllerHome, current, receipt);
378
285
  }
379
286
 
@@ -383,8 +290,9 @@ function selectTerminalCleanupTarget(repository: ReturnType<typeof getRepository
383
290
  try {
384
291
  return selectRepositoryCheckout(repository, sourceCheckoutId, { allowArchived: true });
385
292
  } catch (error) {
386
- const message = error instanceof Error ? error.message : String(error);
387
- if (!message.startsWith('CHECKOUT_NOT_ACTIVE:') && !message.startsWith('checkout not found for ')) throw error;
293
+ const unavailable = error instanceof RepositoryCheckoutSelectionError
294
+ && (error.code === 'CHECKOUT_NOT_FOUND' || (error.code === 'CHECKOUT_NOT_ACTIVE' && error.lifecycle === 'removed'));
295
+ if (!unavailable) throw error;
388
296
  }
389
297
  }
390
298
  // Terminal cleanup only performs repository-common Git administration after
@@ -450,9 +358,15 @@ export interface TerminalWorkCleanupReconcileReport {
450
358
  truncated: boolean;
451
359
  }
452
360
 
361
+ function contractCompletedNoChange(contract: WorkContract): boolean {
362
+ if (contract.workKind === 'completed_no_change' || contract.completionOutcome === 'completed_no_change') return true;
363
+ const receipt = contract.completionReceipt;
364
+ return Boolean(receipt && isRepositoryCompletionReceipt(receipt) && receipt.delivery.kind === 'no_change');
365
+ }
366
+
453
367
  function terminalOutcomeForContract(contract: WorkContract): WorkTerminalOutcome {
454
- if (contract.status === 'cancelled') return 'cancelled';
455
- if (contract.status === 'completed') return 'completed_cleanup';
368
+ if (semanticWorkState(contract) === 'cancelled') return 'cancelled';
369
+ if (semanticWorkState(contract) === 'completed') return 'completed_cleanup';
456
370
  return 'failed';
457
371
  }
458
372
 
@@ -464,7 +378,7 @@ export function recoverTerminalWorkHandle(
464
378
  const existing = readWorkHandle(controllerHome, repositoryId, workId);
465
379
  if (existing) return existing;
466
380
  const contract = getWorkContract({ controllerHome, repoId: repositoryId }, workId);
467
- if (!contract || !isTerminalWorkContractStatus(contract.status)) return undefined;
381
+ if (!contract || semanticWorkState(contract) === 'open') return undefined;
468
382
  const worktreePath = contract.worktreeRef?.trim();
469
383
  const checkoutId = contract.checkoutId?.trim();
470
384
  if (!worktreePath || !checkoutId || !existsSync(worktreePath)) return undefined;
@@ -482,23 +396,27 @@ export function recoverTerminalWorkHandle(
482
396
 
483
397
  const head = git(worktreePath, ['rev-parse', 'HEAD']);
484
398
  if (!head.ok || !head.stdout) return undefined;
485
- const targetBranch = repository.defaultBranch || 'main';
399
+ const receiptTargetBranch = contract.completionReceipt && isRepositoryCompletionReceipt(contract.completionReceipt)
400
+ ? contract.completionReceipt.targetBranch?.trim()
401
+ : undefined;
402
+ const targetBranch = receiptTargetBranch || repository.defaultBranch || 'main';
486
403
  const targetExists = branchExists(repository.canonicalRoot, targetBranch);
487
404
  const contained = targetExists
488
405
  ? git(repository.canonicalRoot, ['merge-base', '--is-ancestor', head.stdout, `refs/heads/${targetBranch}`]).ok
489
406
  : false;
490
- const delivered = contract.status === 'completed' && contained;
491
- const session = getControllerSession({ controllerHome, repoId: repositoryId }, workId);
407
+ const completedNoChange = contractCompletedNoChange(contract);
408
+ const delivered = semanticWorkState(contract) === 'completed' && contained && !completedNoChange;
492
409
  const recordedAt = nowIso();
493
410
  return writeWorkHandle(controllerHome, {
494
411
  schemaVersion: 1,
495
412
  workId,
496
413
  workContractId: workId,
497
- sessionId: session?.sessionId ?? `terminal-recovery:${workId}`,
498
- principalId: contract.principalId?.trim() || session?.principalId || 'terminal-recovery',
414
+ sessionId: `terminal-recovery:${workId}`,
415
+ principalId: contract.principalId?.trim() || 'terminal-recovery',
499
416
  repositoryId,
500
417
  checkoutId,
501
418
  sourceCheckoutId: repository.activeCheckoutId,
419
+ ...(receiptTargetBranch ? { deliveryTargetBranch: receiptTargetBranch } : {}),
502
420
  worktreePath,
503
421
  branch,
504
422
  managedWorktree: true,
@@ -506,17 +424,18 @@ export function recoverTerminalWorkHandle(
506
424
  expectedHead: head.stdout,
507
425
  permissionSnapshotVersion: 1,
508
426
  state: delivered ? 'merged' : 'failed_terminal_cleanup',
509
- failureReason: delivered ? undefined : 'Recovered terminal Work ownership for cleanup; target-branch containment was not proven.',
427
+ failureReason: delivered || completedNoChange
428
+ ? undefined
429
+ : 'Recovered terminal Work ownership for cleanup; target-branch containment was not proven.',
510
430
  createdAt: contract.createdAt || recordedAt,
511
431
  updatedAt: recordedAt,
512
432
  cleanupResponsibility: { owner: 'work_finalizer', registeredAt: recordedAt },
513
433
  finalization: {
514
- validation: contract.status === 'failed' ? 'failed' : 'done',
434
+ validation: 'done',
515
435
  commit: 'skipped',
516
436
  merge: delivered ? 'done' : 'skipped',
517
437
  branchCleanup: 'pending',
518
438
  worktreeCleanup: 'pending',
519
- ...(contract.status === 'failed' ? { lastError: 'Recovered failed terminal Work for cleanup.' } : {}),
520
439
  },
521
440
  });
522
441
  }
@@ -529,13 +448,264 @@ function cleanupRetainedByRequest(contract: WorkContract, handle: WorkHandleStat
529
448
  // completion-receipt warning as the delivery authority.
530
449
  if (
531
450
  handle.terminalResourceDisposition?.mode === 'retained_by_request'
532
- && handle.terminalResourceDisposition.retainWorktree === true
451
+ && (handle.terminalResourceDisposition.retainWorktree === true
452
+ || handle.terminalResourceDisposition.retainBranch === true)
533
453
  ) return true;
534
454
  const receipt = contract.completionReceipt;
535
455
  if (!receipt || !isRepositoryCompletionReceipt(receipt)) return false;
536
456
  return receipt.cleanup.warnings.some((warning) => warning.code === 'cleanup_retained_by_request');
537
457
  }
538
458
 
459
+ function cleanedManagedBranchRetirementCandidate(
460
+ repository: ReturnType<typeof getRepository>,
461
+ contract: WorkContract,
462
+ handle: WorkHandleState,
463
+ targetBranch: string,
464
+ deleteBranch: boolean,
465
+ ): boolean {
466
+ const receipt = handle.cleanupReceipt;
467
+ if (
468
+ handle.state !== 'cleaned'
469
+ || !handle.managedWorktree
470
+ || !deleteBranch
471
+ || !receipt
472
+ || receipt.targetBranch !== targetBranch
473
+ || cleanupRetainedByRequest(contract, handle)
474
+ ) return false;
475
+ if (!['removed', 'already_removed'].includes(receipt.worktree.status)
476
+ || !['removed', 'already_removed'].includes(receipt.checkoutRegistry.status)
477
+ || receipt.prune.status !== 'done'
478
+ || !['retained', 'failed', 'pending'].includes(receipt.branchCleanup.status)) return false;
479
+ // A crash may occur after branch deletion but before the final cleanup receipt
480
+ // is persisted. The unsettled branchCleanup status is sufficient residue to
481
+ // re-enter cleanup; applyManagedBranchCleanup will converge an absent branch
482
+ // to already_deleted without reopening semantic Work state.
483
+ return handle.branch !== targetBranch;
484
+ }
485
+
486
+ function prepareManagedBranchPreservation(
487
+ input: TerminalWorkCleanupInput,
488
+ receipt: WorkCleanupReceipt,
489
+ current: WorkHandleState,
490
+ target: ReturnType<typeof getRepository>,
491
+ targetBranch: string,
492
+ ): { uniqueCommits: number; branchPreserved: boolean } {
493
+ let uniqueCommits = 0;
494
+ if (branchExists(target.canonicalRoot, current.branch)) {
495
+ if (!branchExists(target.canonicalRoot, targetBranch)) {
496
+ addBlocker(receipt, `TARGET_BRANCH_MISSING: ${targetBranch}`);
497
+ } else {
498
+ const unique = git(target.canonicalRoot, ['rev-list', '--count', `refs/heads/${targetBranch}..refs/heads/${current.branch}`]);
499
+ if (!unique.ok || !/^\d+$/.test(unique.stdout)) addBlocker(receipt, `BRANCH_UNIQUENESS_UNKNOWN: ${unique.stderr || unique.stdout}`);
500
+ else uniqueCommits = Number(unique.stdout);
501
+ }
502
+ }
503
+ receipt.branchCleanup.uniqueCommits = uniqueCommits;
504
+
505
+ let branchPreserved = Boolean(
506
+ receipt.preservation.bundleRetirement?.status === 'not_needed'
507
+ || receipt.preservation.bundleRetirement?.status === 'removed',
508
+ );
509
+ if (receipt.preservation.bundlePath && !branchPreserved) {
510
+ try {
511
+ const bundle = createVerifiedBundle(input.controllerHome, current, target.canonicalRoot);
512
+ if (receipt.preservation.bundleSha256 && receipt.preservation.bundleSha256 !== bundle.sha256) {
513
+ throw new Error(`stored bundle digest ${receipt.preservation.bundleSha256} does not match ${bundle.sha256}`);
514
+ }
515
+ receipt.preservation.bundleSha256 = bundle.sha256;
516
+ branchPreserved = true;
517
+ } catch (error) {
518
+ addBlocker(receipt, `BRANCH_BUNDLE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
519
+ }
520
+ }
521
+ if (uniqueCommits > 0 && !branchPreserved && receipt.blockers.length === 0) {
522
+ const proof = proveWorkPreservationContained(target.canonicalRoot, { ...current, cleanupReceipt: receipt }, targetBranch);
523
+ if (proof.contained) {
524
+ receipt.preservation.bundleRetirement = {
525
+ status: 'not_needed',
526
+ reason: proof.reason === 'no_source_delta' ? 'no_source_delta' : 'target_and_remote_content_contained',
527
+ protectedRevision: proof.protectedRevision!,
528
+ targetRevision: proof.targetRevision,
529
+ remoteRevision: proof.remoteRevision,
530
+ comparedPaths: proof.comparedPaths,
531
+ provedAt: nowIso(),
532
+ };
533
+ receipt.preservation.recoveryInstructions = `Branch bundle not created after ${proof.reason}; exact proof is stored in cleanupReceipt.preservation.bundleRetirement.`;
534
+ branchPreserved = true;
535
+ } else {
536
+ try {
537
+ const bundle = createVerifiedBundle(input.controllerHome, current, target.canonicalRoot);
538
+ receipt.preservation.bundlePath = bundle.path;
539
+ receipt.preservation.bundleSha256 = bundle.sha256;
540
+ receipt.preservation.recoveryInstructions = [
541
+ receipt.preservation.recoveryInstructions,
542
+ `Recover branch commits with: git fetch ${bundle.path} refs/heads/${current.branch}:refs/heads/${current.branch}`,
543
+ ].filter(Boolean).join(' ');
544
+ branchPreserved = true;
545
+ } catch (error) {
546
+ addBlocker(receipt, `BRANCH_BUNDLE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
547
+ }
548
+ }
549
+ }
550
+ return { uniqueCommits, branchPreserved };
551
+ }
552
+
553
+ function applyManagedBranchCleanup(
554
+ controllerHome: string,
555
+ target: ReturnType<typeof getRepository>,
556
+ current: WorkHandleState,
557
+ targetBranch: string,
558
+ deleteBranch: boolean,
559
+ receipt: WorkCleanupReceipt,
560
+ uniqueCommits: number,
561
+ branchPreserved: boolean,
562
+ ): void {
563
+ if (!deleteBranch) {
564
+ receipt.branchCleanup.status = 'retained';
565
+ receipt.branchCleanup.reason = 'Branch retention was explicitly requested.';
566
+ } else if (!branchExists(target.canonicalRoot, current.branch)) {
567
+ receipt.branchCleanup.status = 'already_deleted';
568
+ receipt.branchCleanup.reason = undefined;
569
+ } else if (current.branch === targetBranch) {
570
+ receipt.branchCleanup.status = 'retained';
571
+ receipt.branchCleanup.reason = 'Refusing to delete the target branch.';
572
+ addBlocker(receipt, `BRANCH_IS_TARGET: ${targetBranch}`);
573
+ } else if (branchUsedByAnotherWorktree(target.canonicalRoot, current.branch, current.worktreePath)) {
574
+ receipt.branchCleanup.status = 'retained';
575
+ receipt.branchCleanup.reason = 'Branch is checked out by another worktree.';
576
+ addBlocker(receipt, `BRANCH_IN_USE: ${current.branch}`);
577
+ } else if (uniqueCommits > 0 && !branchPreserved) {
578
+ receipt.branchCleanup.status = 'retained';
579
+ receipt.branchCleanup.reason = 'Unique commits are not archived.';
580
+ addBlocker(receipt, `BRANCH_UNPRESERVED: ${current.branch}`);
581
+ } else {
582
+ // Branch deletion requires the canonical OwnedResource witness in addition
583
+ // to Git relation checks. Repository/ref shape alone is not cleanup authority.
584
+ try {
585
+ assertOwnedResourceCleanupTarget(controllerHome, {
586
+ resourceId: managedBranchOwnedResourceId(current.repositoryId, current.checkoutId),
587
+ kind: 'git_branch',
588
+ targetRef: current.branch,
589
+ locator: managedBranchOwnedResourceLocator(target.canonicalRoot, current.checkoutId, current.branch),
590
+ });
591
+ } catch (error) {
592
+ receipt.branchCleanup.status = 'retained';
593
+ receipt.branchCleanup.reason = error instanceof Error ? error.message : String(error);
594
+ addBlocker(receipt, `OWNED_BRANCH_CLEANUP_FENCED: ${receipt.branchCleanup.reason}`);
595
+ return;
596
+ }
597
+ // We already proved the exact branch relation against targetBranch above.
598
+ // `git branch -d` instead consults the checkout's current HEAD, which may be
599
+ // an older/stale source checkout and can falsely reject a branch that is
600
+ // fully contained in the explicit target branch. After that target-branch
601
+ // proof (or a verified archive for unique commits), delete the ref directly.
602
+ const deleted = git(target.canonicalRoot, ['branch', '-D', current.branch]);
603
+ if (!deleted.ok && branchExists(target.canonicalRoot, current.branch)) {
604
+ receipt.branchCleanup.status = 'failed';
605
+ receipt.branchCleanup.reason = deleted.stderr || 'branch delete failed';
606
+ addBlocker(receipt, `BRANCH_DELETE_FAILED: ${receipt.branchCleanup.reason}`);
607
+ } else {
608
+ receipt.branchCleanup.status = uniqueCommits > 0 && receipt.preservation.bundlePath ? 'archived' : 'deleted';
609
+ receipt.branchCleanup.reason = undefined;
610
+ }
611
+ }
612
+ }
613
+
614
+ function cleanupReceiptComplete(receipt: WorkCleanupReceipt, deleteBranch: boolean): boolean {
615
+ return receipt.blockers.length === 0
616
+ && ['removed', 'already_removed'].includes(receipt.worktree.status)
617
+ && ['removed', 'already_removed'].includes(receipt.checkoutRegistry.status)
618
+ && (deleteBranch
619
+ ? ['deleted', 'already_deleted', 'archived'].includes(receipt.branchCleanup.status)
620
+ : receipt.branchCleanup.status === 'retained');
621
+ }
622
+
623
+ function projectOwnedResourceCleanup(
624
+ controllerHome: string,
625
+ handle: WorkHandleState,
626
+ receipt: WorkCleanupReceipt,
627
+ ): void {
628
+ const worktreeResourceId = managedWorkspaceOwnedResourceId(handle.repositoryId, handle.checkoutId);
629
+ if (['removed', 'already_removed'].includes(receipt.worktree.status)) {
630
+ markOwnedResourceCleaned(controllerHome, worktreeResourceId, 'forge:work-terminal-cleanup', receipt.receiptId);
631
+ }
632
+
633
+ const branchResourceId = managedBranchOwnedResourceId(handle.repositoryId, handle.checkoutId);
634
+ if (['deleted', 'already_deleted', 'archived'].includes(receipt.branchCleanup.status)) {
635
+ markOwnedResourceCleaned(controllerHome, branchResourceId, 'forge:work-terminal-cleanup', receipt.receiptId);
636
+ } else if (receipt.branchCleanup.status === 'retained') {
637
+ markOwnedResourceRetained(controllerHome, branchResourceId);
638
+ }
639
+ }
640
+
641
+ function reconcileCleanedManagedBranchRetirement(
642
+ input: TerminalWorkCleanupInput,
643
+ repository: ReturnType<typeof getRepository>,
644
+ targetBranch: string,
645
+ deleteBranch: boolean,
646
+ current: WorkHandleState,
647
+ receipt: WorkCleanupReceipt,
648
+ ): TerminalWorkCleanupResult | undefined {
649
+ const workId = current.workContractId ?? current.workId;
650
+ const contract = getWorkContract({ controllerHome: input.controllerHome, repoId: current.repositoryId }, workId);
651
+ if (!contract || semanticWorkState(contract) === 'open'
652
+ || !cleanedManagedBranchRetirementCandidate(repository, contract, current, targetBranch, deleteBranch)) return undefined;
653
+
654
+ const target = selectTerminalCleanupTarget(repository, current);
655
+ receipt.blockers = [];
656
+ receipt.partial = false;
657
+ receipt.complete = false;
658
+ receipt.completedAt = undefined;
659
+ receipt.branchCleanup.status = 'pending';
660
+ receipt.branchCleanup.reason = undefined;
661
+
662
+ const preservation = prepareManagedBranchPreservation(input, receipt, current, target, targetBranch);
663
+ current = persist(input.controllerHome, current, receipt);
664
+ if (receipt.blockers.length === 0) {
665
+ applyManagedBranchCleanup(
666
+ input.controllerHome,
667
+ target,
668
+ current,
669
+ targetBranch,
670
+ deleteBranch,
671
+ receipt,
672
+ preservation.uniqueCommits,
673
+ preservation.branchPreserved,
674
+ );
675
+ } else if (receipt.branchCleanup.status === 'pending') {
676
+ receipt.branchCleanup.status = 'retained';
677
+ receipt.branchCleanup.reason = 'Branch retirement proof could not be re-established.';
678
+ }
679
+
680
+ const pruned = git(target.canonicalRoot, ['worktree', 'prune']);
681
+ receipt.prune.status = pruned.ok ? 'done' : 'failed';
682
+ if (!pruned.ok) {
683
+ receipt.prune.reason = pruned.stderr || 'git worktree prune failed';
684
+ addBlocker(receipt, `WORKTREE_PRUNE_FAILED: ${receipt.prune.reason}`);
685
+ } else {
686
+ receipt.prune.reason = undefined;
687
+ }
688
+
689
+ receipt.complete = cleanupReceiptComplete(receipt, deleteBranch);
690
+ receipt.partial = !receipt.complete;
691
+ if (receipt.complete) receipt.completedAt = nowIso();
692
+ const finalization = {
693
+ ...current.finalization,
694
+ branchCleanup: receipt.complete ? 'done' as const : 'failed' as const,
695
+ worktreeCleanup: 'done' as const,
696
+ };
697
+ current = writeWorkHandle(input.controllerHome, {
698
+ ...current,
699
+ state: 'cleaned',
700
+ cleanupReceipt: receipt,
701
+ finalization,
702
+ });
703
+ if (receipt.complete) {
704
+ markRepositoryProjectionDirty(input.controllerHome, current.repositoryId, `cleanup:${current.workId}:terminal-branch-retirement`);
705
+ }
706
+ return { handle: current, receipt };
707
+ }
708
+
539
709
  function reconcileLegacyTerminalBranchDrift(
540
710
  controllerHome: string,
541
711
  repository: ReturnType<typeof getRepository>,
@@ -585,11 +755,22 @@ function reconcileLegacyTerminalBranchDrift(
585
755
 
586
756
  export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Promise<TerminalWorkCleanupResult> {
587
757
  const repository = getRepository(input.handle.repositoryId, input.controllerHome, { includeRemoved: true });
588
- const targetBranch = input.targetBranch?.trim() || repository.defaultBranch || 'main';
758
+ const targetBranch = resolveWorkDeliveryTargetBranch(input.handle, repository.defaultBranch, input.targetBranch);
589
759
  const deleteBranch = input.deleteBranch !== false;
590
760
  let current = input.handle;
591
- const landed = current.state === 'merged' || current.finalization.merge === 'done';
592
- const preservedFailure = (input.failureReason ?? current.failureReason ?? current.finalization.lastError ?? 'terminal work cleanup').slice(0, 1_000);
761
+ const contract = getWorkContract(
762
+ { controllerHome: input.controllerHome, repoId: current.repositoryId },
763
+ current.workContractId ?? current.workId,
764
+ );
765
+ const completedNoChange = Boolean(contract && contractCompletedNoChange(contract));
766
+ // `merged` is delivery truth, not a generic terminal-cleanup waypoint. Historical
767
+ // no-change handles may carry merge=done/state=merged from the retired finalizer;
768
+ // normalize that stale projection before any destructive cleanup attempt.
769
+ const landed = !completedNoChange && (current.state === 'merged' || current.finalization.merge === 'done');
770
+ const successfulCompletion = completedNoChange || input.terminalOutcome === 'completed_cleanup';
771
+ const preservedFailure = successfulCompletion
772
+ ? undefined
773
+ : (input.failureReason ?? current.failureReason ?? current.finalization.lastError ?? 'terminal work cleanup').slice(0, 1_000);
593
774
  const receipt = current.cleanupReceipt ?? newReceipt(current, targetBranch, input.terminalOutcome);
594
775
 
595
776
  if (
@@ -597,8 +778,50 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
597
778
  || receipt.checkoutId !== current.checkoutId
598
779
  || receipt.workId !== current.workId
599
780
  ) throw new Error('WORK_CLEANUP_RECEIPT_IDENTITY_MISMATCH');
781
+ // Legacy receipts may retain the former Controller lease stage. ControllerSession
782
+ // is no longer a cleanup resource; normalize this projection without touching
783
+ // any explicit continuation state.
784
+ if (receipt.ownership.controllerLease === 'pending') {
785
+ receipt.ownership.controllerLease = 'already_released';
786
+ }
600
787
 
601
- if (current.state === 'cleaned' && receipt.complete) return { handle: current, receipt };
788
+ if (current.state === 'cleaned') {
789
+ const branchRetirement = reconcileCleanedManagedBranchRetirement(
790
+ input,
791
+ repository,
792
+ targetBranch,
793
+ deleteBranch,
794
+ current,
795
+ receipt,
796
+ );
797
+ if (branchRetirement) return branchRetirement;
798
+ if (!receipt.complete) {
799
+ receipt.complete = true;
800
+ receipt.partial = false;
801
+ receipt.completedAt = receipt.completedAt ?? current.updatedAt ?? nowIso();
802
+ receipt.blockers = [];
803
+ receipt.processes.allTerminal = true;
804
+ if (receipt.ownership.processLeases === 'pending') receipt.ownership.processLeases = 'released';
805
+ if (receipt.worktree.status === 'pending' || receipt.worktree.status === 'failed') {
806
+ receipt.worktree.status = 'already_removed';
807
+ receipt.worktree.reason = 'Reconciled from durable cleaned WorkHandle state.';
808
+ }
809
+ if (receipt.checkoutRegistry.status === 'pending' || receipt.checkoutRegistry.status === 'failed') {
810
+ receipt.checkoutRegistry.status = 'already_removed';
811
+ receipt.checkoutRegistry.reason = 'Reconciled from durable cleaned WorkHandle state.';
812
+ }
813
+ if (receipt.prune.status === 'pending' || receipt.prune.status === 'failed') {
814
+ receipt.prune.status = 'done';
815
+ receipt.prune.reason = 'Reconciled from durable cleaned WorkHandle state.';
816
+ }
817
+ if (receipt.branchCleanup.status === 'pending' || receipt.branchCleanup.status === 'failed') {
818
+ receipt.branchCleanup.status = 'retained';
819
+ receipt.branchCleanup.reason = 'Historical cleaned state does not prove branch deletion; branch is conservatively retained.';
820
+ }
821
+ current = writeWorkHandle(input.controllerHome, { ...current, cleanupReceipt: receipt });
822
+ }
823
+ return { handle: current, receipt };
824
+ }
602
825
 
603
826
  // Retryable blockers are observations, not permanent vetoes. Recompute them
604
827
  // from durable Work/Process/Git state after a controller restart or retry.
@@ -623,7 +846,11 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
623
846
  ? 'failed' as const
624
847
  : current.finalization.validation,
625
848
  commit: current.finalization.commit === 'pending' ? 'skipped' as const : current.finalization.commit,
626
- merge: landed ? 'done' as const : current.finalization.merge === 'pending' ? 'skipped' as const : current.finalization.merge,
849
+ merge: completedNoChange
850
+ ? 'skipped' as const
851
+ : landed
852
+ ? 'done' as const
853
+ : current.finalization.merge === 'pending' ? 'skipped' as const : current.finalization.merge,
627
854
  lastError: preservedFailure,
628
855
  };
629
856
  if (landed) {
@@ -649,7 +876,11 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
649
876
  }
650
877
 
651
878
  await settleProcesses(input, receipt);
652
- assertNoOtherLiveWork(input, receipt);
879
+ // The selected/current repository checkout is shared infrastructure, not a
880
+ // disposable Work-owned resource. Other live Works may legitimately share it;
881
+ // only a managed Work checkout requires exclusive lifecycle ownership before
882
+ // physical removal.
883
+ if (current.managedWorktree) assertNoOtherLiveWork(input, receipt);
653
884
  current = persist(input.controllerHome, current, receipt);
654
885
  if (receipt.blockers.length > 0) return { handle: current, receipt };
655
886
 
@@ -716,7 +947,19 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
716
947
  current = persist(input.controllerHome, current, receipt);
717
948
  return { handle: current, receipt };
718
949
  }
719
- if (!root.ok || resolve(root.stdout) !== resolve(current.worktreePath) || !branch.ok || branch.stdout !== current.branch) {
950
+ const rootIdentityValid = root.ok && resolve(root.stdout) === resolve(current.worktreePath);
951
+ const branchIdentityValid = branch.ok && branch.stdout === current.branch;
952
+ let detachedContainedIdentity = false;
953
+ if (rootIdentityValid && branch.ok && !branch.stdout) {
954
+ const status = git(current.worktreePath, ['status', '--porcelain=v1', '--untracked-files=all']);
955
+ const head = git(current.worktreePath, ['rev-parse', 'HEAD']);
956
+ detachedContainedIdentity = status.ok
957
+ && !status.stdout
958
+ && head.ok
959
+ && Boolean(head.stdout)
960
+ && git(target.canonicalRoot, ['merge-base', '--is-ancestor', head.stdout, `refs/heads/${targetBranch}`]).ok;
961
+ }
962
+ if (!rootIdentityValid || (!branchIdentityValid && !detachedContainedIdentity)) {
720
963
  addBlocker(receipt, 'WORKTREE_IDENTITY_INVALID');
721
964
  current = persist(input.controllerHome, current, receipt);
722
965
  return { handle: current, receipt };
@@ -732,31 +975,13 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
732
975
  receipt.worktree.status = 'already_removed';
733
976
  }
734
977
 
735
- let uniqueCommits = 0;
736
- if (branchExists(target.canonicalRoot, current.branch)) {
737
- if (!branchExists(target.canonicalRoot, targetBranch)) {
738
- addBlocker(receipt, `TARGET_BRANCH_MISSING: ${targetBranch}`);
739
- } else {
740
- const unique = git(target.canonicalRoot, ['rev-list', '--count', `refs/heads/${targetBranch}..refs/heads/${current.branch}`]);
741
- if (!unique.ok || !/^\d+$/.test(unique.stdout)) addBlocker(receipt, `BRANCH_UNIQUENESS_UNKNOWN: ${unique.stderr || unique.stdout}`);
742
- else uniqueCommits = Number(unique.stdout);
743
- }
744
- }
745
- receipt.branchCleanup.uniqueCommits = uniqueCommits;
746
-
747
- if (uniqueCommits > 0 && !receipt.preservation.bundlePath) {
748
- try {
749
- const bundle = createVerifiedBundle(input.controllerHome, current, target.canonicalRoot);
750
- receipt.preservation.bundlePath = bundle.path;
751
- receipt.preservation.bundleSha256 = bundle.sha256;
752
- receipt.preservation.recoveryInstructions = [
753
- receipt.preservation.recoveryInstructions,
754
- `Recover branch commits with: git fetch ${bundle.path} refs/heads/${current.branch}:refs/heads/${current.branch}`,
755
- ].filter(Boolean).join(' ');
756
- } catch (error) {
757
- addBlocker(receipt, `BRANCH_BUNDLE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
758
- }
759
- }
978
+ const { uniqueCommits, branchPreserved } = prepareManagedBranchPreservation(
979
+ input,
980
+ receipt,
981
+ current,
982
+ target,
983
+ targetBranch,
984
+ );
760
985
  current = persist(input.controllerHome, current, receipt);
761
986
  if (receipt.blockers.length > 0) {
762
987
  receipt.worktree.status = existsSync(current.worktreePath) ? 'retained' : receipt.worktree.status;
@@ -767,6 +992,19 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
767
992
  }
768
993
 
769
994
  if (existsSync(current.worktreePath)) {
995
+ try {
996
+ assertOwnedResourceCleanupTarget(input.controllerHome, {
997
+ resourceId: managedWorkspaceOwnedResourceId(current.repositoryId, current.checkoutId),
998
+ kind: 'worktree',
999
+ targetRef: current.worktreePath,
1000
+ });
1001
+ } catch (error) {
1002
+ receipt.worktree.status = 'retained';
1003
+ receipt.worktree.reason = error instanceof Error ? error.message : String(error);
1004
+ addBlocker(receipt, `OWNED_WORKTREE_CLEANUP_FENCED: ${receipt.worktree.reason}`);
1005
+ current = persist(input.controllerHome, current, receipt);
1006
+ return { handle: current, receipt };
1007
+ }
770
1008
  const removed = git(target.canonicalRoot, ['worktree', 'remove', '--force', current.worktreePath], 60_000);
771
1009
  if (!removed.ok && existsSync(current.worktreePath)) {
772
1010
  receipt.worktree.status = 'failed';
@@ -802,38 +1040,16 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
802
1040
  receipt.checkoutRegistry.reason = 'Checkout metadata was absent after Controller Home migration; canonical managed-worktree and Git identity checks passed before cleanup.';
803
1041
  }
804
1042
 
805
- if (!deleteBranch) {
806
- receipt.branchCleanup.status = 'retained';
807
- receipt.branchCleanup.reason = 'Branch retention was explicitly requested.';
808
- } else if (!branchExists(target.canonicalRoot, current.branch)) {
809
- receipt.branchCleanup.status = 'already_deleted';
810
- } else if (current.branch === targetBranch) {
811
- receipt.branchCleanup.status = 'retained';
812
- receipt.branchCleanup.reason = 'Refusing to delete the target branch.';
813
- addBlocker(receipt, `BRANCH_IS_TARGET: ${targetBranch}`);
814
- } else if (branchUsedByAnotherWorktree(target.canonicalRoot, current.branch, current.worktreePath)) {
815
- receipt.branchCleanup.status = 'retained';
816
- receipt.branchCleanup.reason = 'Branch is checked out by another worktree.';
817
- addBlocker(receipt, `BRANCH_IN_USE: ${current.branch}`);
818
- } else if (uniqueCommits > 0 && !receipt.preservation.bundlePath) {
819
- receipt.branchCleanup.status = 'retained';
820
- receipt.branchCleanup.reason = 'Unique commits are not archived.';
821
- addBlocker(receipt, `BRANCH_UNPRESERVED: ${current.branch}`);
822
- } else {
823
- // We already proved the exact branch relation against targetBranch above.
824
- // `git branch -d` instead consults the checkout's current HEAD, which may be
825
- // an older/stale source checkout and can falsely reject a branch that is
826
- // fully contained in the explicit target branch. After that target-branch
827
- // proof (or a verified archive for unique commits), delete the ref directly.
828
- const deleted = git(target.canonicalRoot, ['branch', '-D', current.branch]);
829
- if (!deleted.ok && branchExists(target.canonicalRoot, current.branch)) {
830
- receipt.branchCleanup.status = 'failed';
831
- receipt.branchCleanup.reason = deleted.stderr || 'branch delete failed';
832
- addBlocker(receipt, `BRANCH_DELETE_FAILED: ${receipt.branchCleanup.reason}`);
833
- } else {
834
- receipt.branchCleanup.status = uniqueCommits > 0 ? 'archived' : 'deleted';
835
- }
836
- }
1043
+ applyManagedBranchCleanup(
1044
+ input.controllerHome,
1045
+ target,
1046
+ current,
1047
+ targetBranch,
1048
+ deleteBranch,
1049
+ receipt,
1050
+ uniqueCommits,
1051
+ branchPreserved,
1052
+ );
837
1053
 
838
1054
  const pruned = git(target.canonicalRoot, ['worktree', 'prune']);
839
1055
  receipt.prune.status = pruned.ok ? 'done' : 'failed';
@@ -842,14 +1058,10 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
842
1058
  addBlocker(receipt, `WORKTREE_PRUNE_FAILED: ${receipt.prune.reason}`);
843
1059
  }
844
1060
 
845
- receipt.complete = receipt.blockers.length === 0
846
- && ['removed', 'already_removed'].includes(receipt.worktree.status)
847
- && ['removed', 'already_removed'].includes(receipt.checkoutRegistry.status)
848
- && (deleteBranch
849
- ? ['deleted', 'already_deleted', 'archived'].includes(receipt.branchCleanup.status)
850
- : receipt.branchCleanup.status === 'retained');
1061
+ receipt.complete = cleanupReceiptComplete(receipt, deleteBranch);
851
1062
  receipt.partial = !receipt.complete;
852
1063
  if (receipt.complete) receipt.completedAt = receipt.completedAt ?? nowIso();
1064
+ projectOwnedResourceCleanup(input.controllerHome, current, receipt);
853
1065
 
854
1066
  const finalization = {
855
1067
  ...current.finalization,
@@ -880,6 +1092,92 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
880
1092
  return { handle: current, receipt };
881
1093
  }
882
1094
 
1095
+ export interface SingleTerminalWorkCleanupResult {
1096
+ status: 'cleaned' | 'retained' | 'blocked' | 'no_handle' | 'not_terminal';
1097
+ workId: string;
1098
+ handle?: WorkHandleState;
1099
+ receipt?: WorkCleanupReceipt;
1100
+ reason?: string;
1101
+ }
1102
+
1103
+ /**
1104
+ * Reconcile physical resources for one already-terminal Work without reviving its
1105
+ * historical execution principal. WorkContract remains the terminal outcome
1106
+ * authority; this path owns only resource cleanup and refuses unproven active
1107
+ * Controller ownership or unsafe branch drift. A caller proving the exact
1108
+ * leftover owner epoch may release it before physical cleanup.
1109
+ */
1110
+ function reconcileCancelledCleanupProjection(
1111
+ controllerHome: string,
1112
+ repoId: string,
1113
+ contract: WorkContract,
1114
+ receipt: WorkCleanupReceipt,
1115
+ ): void {
1116
+ if (contract.semanticState !== 'cancelled' || receipt.complete !== true) return;
1117
+ if (contract.phase === 'cleanup' && contract.phaseEvidence.cleanup.state === 'satisfied'
1118
+ && contract.phaseEvidence.cleanup.receiptId === receipt.receiptId) return;
1119
+ recordCancelledWorkCleanupCompleted(
1120
+ { controllerHome, repoId },
1121
+ contract.workId,
1122
+ {
1123
+ summary: `Verified terminal resource cleanup completed with receipt ${receipt.receiptId}.`,
1124
+ receiptId: receipt.receiptId,
1125
+ evidenceRefs: [{
1126
+ title: 'cancelled Work terminal cleanup completed',
1127
+ summary: `Physical worktree/branch cleanup completed under receipt ${receipt.receiptId}; unexecuted semantic phases remain skipped rather than satisfied.`,
1128
+ detailLevel: 'summary',
1129
+ }, ...contract.evidenceRefs],
1130
+ },
1131
+ );
1132
+ }
1133
+
1134
+ export async function reconcileSingleTerminalWorkCleanup(
1135
+ controllerHome: string,
1136
+ repositoryId: string,
1137
+ workId: string,
1138
+ options: {
1139
+ targetBranch?: string;
1140
+ deleteBranch?: boolean;
1141
+ } = {},
1142
+ ): Promise<SingleTerminalWorkCleanupResult> {
1143
+ const repository = getRepository(repositoryId, controllerHome, { includeRemoved: true });
1144
+ const contract = getWorkContract({ controllerHome, repoId: repositoryId }, workId);
1145
+ if (!contract || semanticWorkState(contract) === 'open') {
1146
+ return { status: 'not_terminal', workId, reason: 'Work is not terminal.' };
1147
+ }
1148
+ const originalHandle = readWorkHandle(controllerHome, repositoryId, workId)
1149
+ ?? recoverTerminalWorkHandle(controllerHome, repositoryId, workId);
1150
+ if (!originalHandle) return { status: 'no_handle', workId };
1151
+ if (cleanupRetainedByRequest(contract, originalHandle)) {
1152
+ return { status: 'retained', workId, handle: originalHandle, receipt: originalHandle.cleanupReceipt };
1153
+ }
1154
+ const targetBranch = resolveWorkDeliveryTargetBranch(originalHandle, repository.defaultBranch, options.targetBranch);
1155
+ const drift = reconcileLegacyTerminalBranchDrift(controllerHome, repository, originalHandle, targetBranch);
1156
+ if (drift.blocker) return { status: 'blocked', workId, handle: drift.handle, reason: drift.blocker };
1157
+ const cleaned = await cleanupTerminalWork({
1158
+ controllerHome,
1159
+ handle: drift.handle,
1160
+ targetBranch,
1161
+ deleteBranch: options.deleteBranch !== false,
1162
+ terminalOutcome: terminalOutcomeForContract(contract),
1163
+ failureReason: drift.handle.failureReason ?? drift.handle.finalization.lastError,
1164
+ });
1165
+ const persisted = writeWorkHandle(controllerHome, { ...cleaned.handle, cleanupReceipt: cleaned.receipt });
1166
+ if (cleaned.receipt.complete) {
1167
+ reconcileCancelledCleanupProjection(controllerHome, repositoryId, contract, cleaned.receipt);
1168
+ }
1169
+ if (!cleaned.receipt.complete) {
1170
+ return {
1171
+ status: 'blocked',
1172
+ workId,
1173
+ handle: persisted,
1174
+ receipt: cleaned.receipt,
1175
+ reason: cleaned.receipt.blockers.join('; ') || cleaned.receipt.worktree.reason || cleaned.receipt.branchCleanup.reason || 'terminal cleanup incomplete',
1176
+ };
1177
+ }
1178
+ return { status: 'cleaned', workId, handle: persisted, receipt: cleaned.receipt };
1179
+ }
1180
+
883
1181
  export async function reconcileTerminalWorkCleanups(
884
1182
  controllerHome: string,
885
1183
  options: TerminalWorkCleanupReconcileOptions = {},
@@ -901,7 +1199,14 @@ export async function reconcileTerminalWorkCleanups(
901
1199
  truncated: false,
902
1200
  };
903
1201
 
904
- const repositories = listRepositories(controllerHome).filter((repository) => repository.enabled && !repository.removedAt);
1202
+ const maintenanceEpoch = Math.floor(nowMs / 60_000);
1203
+ const repositories = listRepositories(controllerHome)
1204
+ .filter((repository) => repository.enabled && !repository.removedAt)
1205
+ .sort((left, right) => {
1206
+ const leftOrder = hashText(`${maintenanceEpoch}\0${left.repoId}`);
1207
+ const rightOrder = hashText(`${maintenanceEpoch}\0${right.repoId}`);
1208
+ return leftOrder.localeCompare(rightOrder) || left.repoId.localeCompare(right.repoId);
1209
+ });
905
1210
  outer: for (const repository of repositories) {
906
1211
  const handles = listWorkHandles(controllerHome, repository.repoId, 10_000)
907
1212
  .filter((handle) => handle.managedWorktree);
@@ -913,9 +1218,15 @@ export async function reconcileTerminalWorkCleanups(
913
1218
  limit: 10_000,
914
1219
  })) {
915
1220
  const contract = record.value;
916
- if (knownWorkIds.has(contract.workId) || !isTerminalWorkContractStatus(contract.status)) continue;
1221
+ if (knownWorkIds.has(contract.workId) || semanticWorkState(contract) === 'open') continue;
917
1222
  if (!contract.worktreeRef?.trim() || !existsSync(contract.worktreeRef)) continue;
918
- const recovered = recoverTerminalWorkHandle(controllerHome, repository.repoId, contract.workId);
1223
+ let recovered: WorkHandleState | undefined;
1224
+ try {
1225
+ recovered = recoverTerminalWorkHandle(controllerHome, repository.repoId, contract.workId);
1226
+ } catch (error) {
1227
+ report.errors.push({ workId: contract.workId, error: error instanceof Error ? error.message : String(error) });
1228
+ continue;
1229
+ }
919
1230
  if (!recovered) continue;
920
1231
  handles.push(recovered);
921
1232
  knownWorkIds.add(recovered.workId);
@@ -924,16 +1235,38 @@ export async function reconcileTerminalWorkCleanups(
924
1235
  handles.sort((left, right) => left.updatedAt.localeCompare(right.updatedAt));
925
1236
  for (const originalHandle of handles) {
926
1237
  report.scanned += 1;
927
- const contract = getWorkContract({ controllerHome, repoId: repository.repoId }, originalHandle.workContractId ?? originalHandle.workId);
928
- if (!contract || !isTerminalWorkContractStatus(contract.status)) {
1238
+ let contract: WorkContract | undefined;
1239
+ try {
1240
+ contract = getWorkContract({ controllerHome, repoId: repository.repoId }, originalHandle.workContractId ?? originalHandle.workId);
1241
+ } catch (error) {
1242
+ report.errors.push({ workId: originalHandle.workId, error: error instanceof Error ? error.message : String(error) });
1243
+ continue;
1244
+ }
1245
+ if (!contract || semanticWorkState(contract) === 'open') {
929
1246
  report.skippedNonTerminal.push(originalHandle.workId);
930
1247
  continue;
931
1248
  }
932
- if (originalHandle.state === 'cleaned' && originalHandle.cleanupReceipt?.complete === true) continue;
1249
+ // Semantic terminality is the lifecycle authority that starts mechanical
1250
+ // cleanup reconciliation. It is not deletion authority: work_finalizer must
1251
+ // still prove exact Forge ownership, preserve dirty bytes/unique commits,
1252
+ // settle live processes, and honor explicit retention before removing any
1253
+ // physical resource. Completion/cleanup receipts are delivery/progress
1254
+ // evidence, not a second permission gate that can strand terminal Work.
933
1255
  if (cleanupRetainedByRequest(contract, originalHandle)) {
934
1256
  report.skippedRetained.push(originalHandle.workId);
935
1257
  continue;
936
1258
  }
1259
+ if (originalHandle.state === 'cleaned' && originalHandle.cleanupReceipt?.complete === true) {
1260
+ reconcileCancelledCleanupProjection(controllerHome, repository.repoId, contract, originalHandle.cleanupReceipt);
1261
+ const targetBranch = resolveWorkDeliveryTargetBranch(originalHandle, repository.defaultBranch);
1262
+ if (!cleanedManagedBranchRetirementCandidate(
1263
+ repository,
1264
+ contract,
1265
+ originalHandle,
1266
+ targetBranch,
1267
+ true,
1268
+ )) continue;
1269
+ }
937
1270
  // Reconstructing a missing handle is metadata recovery, not new Work activity.
938
1271
  // Do not let that write reset the terminal-age grace period indefinitely.
939
1272
  const terminalAt = recoveredWorkIds.has(originalHandle.workId)
@@ -950,7 +1283,7 @@ export async function reconcileTerminalWorkCleanups(
950
1283
  }
951
1284
  report.attempted += 1;
952
1285
  try {
953
- const targetBranch = repository.defaultBranch || 'main';
1286
+ const targetBranch = resolveWorkDeliveryTargetBranch(originalHandle, repository.defaultBranch);
954
1287
  const drift = reconcileLegacyTerminalBranchDrift(controllerHome, repository, originalHandle, targetBranch);
955
1288
  if (drift.blocker) {
956
1289
  report.blocked.push({ workId: originalHandle.workId, reason: drift.blocker });
@@ -965,26 +1298,11 @@ export async function reconcileTerminalWorkCleanups(
965
1298
  terminalOutcome: terminalOutcomeForContract(contract),
966
1299
  failureReason: drift.handle.failureReason ?? drift.handle.finalization.lastError,
967
1300
  });
968
- const workId = cleaned.handle.workContractId ?? cleaned.handle.workId;
969
- const controllerSession = getControllerSession({ controllerHome, repoId: repository.repoId }, workId);
970
- if (controllerSession) {
971
- const released = releaseObservedControllerSession(
972
- { controllerHome, repoId: repository.repoId },
973
- { workId, actor: `terminal-cleanup:${originalHandle.workId}`, owner: controllerSession },
974
- );
975
- if (released.allowed) {
976
- cleaned.receipt.ownership.controllerLease = 'released';
977
- } else {
978
- cleaned.receipt.complete = false;
979
- cleaned.receipt.partial = true;
980
- cleaned.receipt.completedAt = undefined;
981
- cleaned.receipt.blockers = appendUnique(cleaned.receipt.blockers, `controller lease release fenced: ${released.reason}`);
982
- }
983
- } else {
984
- cleaned.receipt.ownership.controllerLease = 'already_released';
985
- }
986
1301
  writeWorkHandle(controllerHome, { ...cleaned.handle, cleanupReceipt: cleaned.receipt });
987
- if (cleaned.receipt.complete) report.cleaned.push(originalHandle.workId);
1302
+ if (cleaned.receipt.complete) {
1303
+ reconcileCancelledCleanupProjection(controllerHome, repository.repoId, contract, cleaned.receipt);
1304
+ report.cleaned.push(originalHandle.workId);
1305
+ }
988
1306
  else report.blocked.push({
989
1307
  workId: originalHandle.workId,
990
1308
  reason: cleaned.receipt.blockers.join('; ') || cleaned.receipt.worktree.reason || cleaned.receipt.branchCleanup.reason || 'terminal cleanup incomplete',