@moretea-labs/forge 1.8.0 → 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 (528) hide show
  1. package/AGENTS.md +70 -7
  2. package/CHANGELOG.md +14 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/THIRD_PARTY_NOTICES.md +1 -1
  6. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
  8. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  9. package/adapters/chatgpt/controller-round-host.ts +15 -25
  10. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  11. package/adapters/chatgpt/provider-delivery.ts +228 -1
  12. package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
  13. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  14. package/adapters/computer/chatgpt-target-router.ts +103 -0
  15. package/adapters/computer/chrome-extension/background.js +385 -0
  16. package/adapters/computer/chrome-extension/content.js +378 -0
  17. package/adapters/computer/chrome-extension/core.js +24 -0
  18. package/adapters/computer/chrome-extension/manifest.json +30 -0
  19. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  20. package/adapters/computer/interaction-target-authority.ts +142 -18
  21. package/adapters/mcp/auth.ts +0 -1
  22. package/adapters/mcp/instructions.ts +5 -4
  23. package/adapters/mcp/multi-repository.ts +3 -19
  24. package/adapters/mcp/oauth.ts +73 -0
  25. package/adapters/mcp/paths.ts +4 -1
  26. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  27. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  28. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  29. package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
  30. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  31. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  32. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  33. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  34. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  35. package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
  36. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  37. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
  38. package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
  39. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  40. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  41. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  42. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  43. package/adapters/mcp/runtime-gateway/router.ts +18 -504
  44. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  45. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
  46. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  47. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
  48. package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
  49. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
  50. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  51. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
  52. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  53. package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
  54. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  55. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  56. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  57. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  58. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  59. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  60. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  61. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  62. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  63. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  64. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  65. package/adapters/mcp/server.ts +67 -23
  66. package/adapters/mcp/setup.ts +1 -3
  67. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
  68. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
  69. package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
  70. package/adapters/mcp/toolset-names.ts +5 -0
  71. package/adapters/mcp/toolset.ts +6 -5
  72. package/adapters/mcp/transports/http-observation.ts +365 -0
  73. package/adapters/mcp/transports/http.ts +154 -677
  74. package/adapters/mcp/transports/oauth-http.ts +322 -0
  75. package/adapters/mcp/transports/session-registry.ts +6 -0
  76. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  77. package/assets/plugin-registry.v1.json +6 -4
  78. package/assets/reference-configs/agentic-development-flow.md +25 -0
  79. package/assets/reference-configs/global-working-rules.md +19 -0
  80. package/assets/skill-version.json +2 -2
  81. package/docs/architecture/CURRENT.md +111 -41
  82. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  83. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  84. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  85. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  86. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  87. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  88. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  89. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  90. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  91. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  92. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  93. package/docs/architecture/domains/verification.md +1 -0
  94. package/docs/architecture/domains/workflow-engine.md +6 -0
  95. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  96. package/docs/forge-plugin-management.md +2 -2
  97. package/docs/operations/controller-performance-and-502.md +17 -0
  98. package/docs/operations/controller-reliability-runbook.md +2 -2
  99. package/docs/operations/features.md +1 -1
  100. package/docs/operations/features.zh-CN.md +1 -1
  101. package/docs/operations/homebrew.md +2 -2
  102. package/docs/operations/mcp-tool-exposure.md +2 -0
  103. package/docs/operations/releasing.md +2 -0
  104. package/docs/operations/standalone-disaster-recovery.md +14 -6
  105. package/docs/wiki/Implementation.md +1 -1
  106. package/package.json +25 -14
  107. package/packages/kernel/cognition/api/index.ts +6 -0
  108. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  109. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  110. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  111. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  112. package/packages/kernel/cognition/domain/learning.ts +106 -0
  113. package/packages/kernel/cognition/domain/memory.ts +254 -0
  114. package/packages/kernel/controller/api/index.ts +1 -0
  115. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  116. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  117. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  118. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  119. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  120. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  121. package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
  122. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  123. package/packages/kernel/identity/api/index.ts +10 -0
  124. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  125. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  126. package/packages/kernel/identity/domain/grant.ts +72 -0
  127. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  128. package/packages/kernel/identity/domain/user-request.ts +93 -0
  129. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  130. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  131. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  132. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  133. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  134. package/packages/kernel/scheduler/api/index.ts +0 -2
  135. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  136. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  137. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  138. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
  139. package/packages/kernel/work/api/index.ts +3 -2
  140. package/packages/kernel/work/application/work-service.ts +17 -42
  141. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  142. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  143. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  144. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  145. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  146. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  147. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  148. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  149. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  150. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  151. package/packages/kernel/work/domain/state-machine.ts +25 -63
  152. package/packages/kernel/work/domain/types.ts +57 -51
  153. package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
  154. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  155. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  156. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  157. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  158. package/packages/plugin-runtime/computer/index.ts +2 -0
  159. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  160. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  161. package/packages/protocols/computer/contract.ts +27 -1
  162. package/scripts/activate-source-baseline.ts +7 -9
  163. package/scripts/canary-controller-semantic-wait.ts +12 -24
  164. package/scripts/check-mcp-compatibility.ts +48 -136
  165. package/scripts/check-runtime-architecture.mjs +622 -217
  166. package/scripts/forge-local-recovery-helper.mjs +93 -15
  167. package/scripts/lib/project-init-lib.sh +3 -0
  168. package/scripts/open-source-audit-allowlist.txt +1 -2
  169. package/scripts/run-bounded-command.mjs +5 -0
  170. package/scripts/run-governed-gate.ts +0 -1
  171. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  172. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  173. package/scripts/smoke-mcp-http-runtime.ts +5 -0
  174. package/scripts/smoke-runtime-recovery.ts +12 -43
  175. package/scripts/smoke-schedule-engine.ts +23 -15
  176. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  177. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  178. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  179. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  180. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  181. package/scripts/smoke-workflow-supervisor.ts +103 -0
  182. package/scripts/verify-forge-runtime.sh +2 -1
  183. package/src/cli/agent-jobs/executor-health.ts +11 -34
  184. package/src/cli/agent-jobs/integration.ts +18 -49
  185. package/src/cli/agent-jobs/job-manager.ts +7 -3
  186. package/src/cli/commands/brain-assistant.ts +27 -13
  187. package/src/cli/commands/brain.ts +13 -15
  188. package/src/cli/commands/chatgpt.ts +1 -163
  189. package/src/cli/commands/recovery.ts +135 -121
  190. package/src/cli/commands/runtime.ts +3 -3
  191. package/src/cli/commands/setup-profile.ts +8 -0
  192. package/src/cli/controller/check-runner.ts +13 -2
  193. package/src/cli/controller/completion-backlog.ts +3 -3
  194. package/src/cli/controller/completion-orchestrator.ts +4 -43
  195. package/src/cli/controller/completion-target.ts +3 -72
  196. package/src/cli/controller/context/source-materializer.ts +81 -100
  197. package/src/cli/controller/context-pack.ts +10 -8
  198. package/src/cli/controller/execution-completion.ts +4 -7
  199. package/src/cli/controller/execution-policy.ts +36 -153
  200. package/src/cli/controller/governance.ts +2 -3
  201. package/src/cli/controller/issue-store.ts +4 -99
  202. package/src/cli/controller/launch-agents.ts +59 -18
  203. package/src/cli/controller/operational-plan.ts +24 -51
  204. package/src/cli/controller/runtime-config.ts +5 -1
  205. package/src/cli/controller/stuck-state-migration.ts +2 -3
  206. package/src/cli/controller/systemd-user.ts +39 -1
  207. package/src/cli/editing/edit-session.ts +1 -7
  208. package/src/cli/local-bridge/console-automations.ts +18 -13
  209. package/src/cli/local-bridge/console-view-models.ts +3 -12
  210. package/src/cli/local-bridge/facade-api.ts +64 -191
  211. package/src/cli/local-bridge/server.ts +24 -147
  212. package/src/cli/local-bridge/types.ts +0 -1
  213. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  214. package/src/cli/repositories/command-executor.ts +29 -23
  215. package/src/cli/repositories/command-scope.ts +35 -16
  216. package/src/cli/repositories/controller-home.ts +48 -0
  217. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  218. package/src/cli/repositories/git-command-executor.ts +2 -2
  219. package/src/cli/repositories/registry.ts +77 -11
  220. package/src/cli/repositories/structured-git.ts +62 -2
  221. package/src/cli/repository/session-cache.ts +92 -6
  222. package/src/runtime/context/assistant-context.ts +20 -7
  223. package/src/runtime/context/assistant-work-context.ts +89 -27
  224. package/src/runtime/context/automatic-learning.ts +565 -0
  225. package/src/runtime/context/cognitive-attention.ts +173 -0
  226. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  227. package/src/runtime/context/context-closure.ts +46 -9
  228. package/src/runtime/context/context-pack-process.ts +103 -0
  229. package/src/runtime/context/context-plane.ts +1 -9
  230. package/src/runtime/context/direct-controller-learning.ts +298 -0
  231. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  232. package/src/runtime/context/semantic-navigation.ts +6 -34
  233. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  234. package/src/runtime/context/typescript-navigation.ts +89 -3
  235. package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
  236. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  237. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  238. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  239. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  240. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  241. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  242. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  243. package/src/runtime/control-plane/execution/validation.ts +6 -8
  244. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  245. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  246. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  247. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  248. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  249. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  250. package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
  251. package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
  252. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  253. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  254. package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
  255. package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
  256. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  257. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  258. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
  259. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
  260. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  261. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  262. package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
  263. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  264. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  265. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  266. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  267. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  268. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  269. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  270. package/src/runtime/control-plane/facade/index.ts +0 -1
  271. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  272. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  273. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  274. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  275. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  276. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  277. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  278. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  279. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  280. package/src/runtime/control-plane/facade/types.ts +49 -108
  281. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  282. package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
  283. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  284. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  285. package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
  286. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
  287. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  288. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  289. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  290. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  291. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  292. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
  293. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  294. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  295. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  296. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  297. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  298. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  299. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  300. package/src/runtime/control-plane/release-retention.ts +135 -47
  301. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  302. package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
  303. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  304. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  305. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  306. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  307. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  308. package/src/runtime/evidence/artifact-store.ts +158 -40
  309. package/src/runtime/evidence/evidence-store.ts +112 -8
  310. package/src/runtime/evidence/output-handle-index.ts +225 -0
  311. package/src/runtime/evidence/result-store.ts +158 -44
  312. package/src/runtime/execution/jobs/store.ts +3 -4
  313. package/src/runtime/execution/managed-workspace.ts +57 -0
  314. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  315. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  316. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  317. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  318. package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
  319. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  320. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  321. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  322. package/src/runtime/execution/process-runtime/index.ts +3 -0
  323. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  324. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  325. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  326. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  327. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  328. package/src/runtime/execution/process-runtime/runtime.ts +35 -2
  329. package/src/runtime/execution/process-runtime/store.ts +30 -2
  330. package/src/runtime/execution/process-runtime/types.ts +6 -1
  331. package/src/runtime/execution/workers/executor.ts +2 -6
  332. package/src/runtime/platform/service-inventory.ts +130 -0
  333. package/src/runtime/platform/service-manager.ts +8 -0
  334. package/src/runtime/plugins/action-application.ts +85 -28
  335. package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
  336. package/src/runtime/plugins/browser-adapter.ts +757 -34
  337. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  338. package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
  339. package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
  340. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  341. package/src/runtime/plugins/browser-registration.ts +16 -0
  342. package/src/runtime/plugins/browser-runtime.ts +14 -1
  343. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  344. package/src/runtime/plugins/browser-session-store.ts +46 -76
  345. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  346. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  347. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  348. package/src/runtime/plugins/computer-registration.ts +222 -11
  349. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  350. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  351. package/src/runtime/plugins/external-adapter.ts +42 -6
  352. package/src/runtime/plugins/external-registration.ts +21 -1
  353. package/src/runtime/plugins/gmail-adapter.ts +23 -2
  354. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  355. package/src/runtime/plugins/google-shared.ts +14 -7
  356. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  357. package/src/runtime/plugins/ios-adapter.ts +1 -1
  358. package/src/runtime/plugins/lightweight-action.ts +208 -2
  359. package/src/runtime/plugins/local-system-adapter.ts +12 -2
  360. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  361. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  362. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  363. package/src/runtime/plugins/store.ts +602 -428
  364. package/src/runtime/plugins/types.ts +20 -0
  365. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  366. package/src/runtime/projections/invalidation.ts +7 -3
  367. package/src/runtime/projections/materialized-view.ts +80 -36
  368. package/src/runtime/recovery/actions.ts +3 -3
  369. package/src/runtime/recovery/capability-action-application.ts +46 -45
  370. package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
  371. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  372. package/src/runtime/recovery/store.ts +29 -11
  373. package/src/runtime/release/release-coordinator.ts +296 -0
  374. package/src/runtime/release/release-session.ts +716 -0
  375. package/src/runtime/release/release-state-migration.ts +25 -0
  376. package/src/runtime/resources/leases/store.ts +56 -25
  377. package/src/runtime/root/computer-composition.ts +191 -6
  378. package/src/runtime/root/controller-progression-composition.ts +192 -0
  379. package/src/runtime/root/controller-round-composition.ts +20 -1
  380. package/src/runtime/root/deployment-topology.ts +106 -0
  381. package/src/runtime/root/entry.ts +4 -3
  382. package/src/runtime/root/known-good-recovery.ts +184 -0
  383. package/src/runtime/root/local-bridge.ts +0 -1
  384. package/src/runtime/root/mcp-transport.ts +111 -18
  385. package/src/runtime/root/ownership.ts +261 -9
  386. package/src/runtime/root/package-connector-service.ts +105 -19
  387. package/src/runtime/root/package-runtime-release.ts +117 -18
  388. package/src/runtime/root/package-runtime-service.ts +17 -11
  389. package/src/runtime/root/release-execution-canary.ts +44 -12
  390. package/src/runtime/root/release-loader.ts +90 -8
  391. package/src/runtime/root/release-manifest.ts +80 -6
  392. package/src/runtime/root/release-materialize.ts +372 -28
  393. package/src/runtime/root/release-store.ts +395 -36
  394. package/src/runtime/root/runtime-lane.ts +191 -0
  395. package/src/runtime/root/runtime.ts +159 -27
  396. package/src/runtime/root/scheduled-controller-composition.ts +28 -3
  397. package/src/runtime/root/service-runner.ts +2 -2
  398. package/src/runtime/root/service.ts +36 -33
  399. package/src/runtime/root/status.ts +48 -0
  400. package/src/runtime/root/types.ts +32 -3
  401. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  402. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  403. package/src/runtime/root/write-fence.ts +50 -2
  404. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  405. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  406. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  407. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  408. package/src/runtime/shared/process-environment.ts +20 -9
  409. package/src/runtime/shared/response-budget.ts +1 -3
  410. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  411. package/src/runtime/standalone-recovery/core.ts +3702 -342
  412. package/src/runtime/standalone-recovery/entry.ts +469 -39
  413. package/src/runtime/standalone-recovery/installer.ts +262 -148
  414. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  415. package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
  416. package/src/runtime/standalone-recovery/performance.ts +54 -16
  417. package/src/runtime/standalone-recovery/release.ts +53 -5
  418. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  419. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  420. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  421. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
  422. package/src/runtime/workflow/schedules/engine.ts +138 -172
  423. package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
  424. package/src/runtime/workflows/runtime.ts +15 -19
  425. package/src/testing/test-governance.ts +5 -1
  426. package/supervisor/chatgpt-conversation.ts +23 -0
  427. package/supervisor/client.ts +201 -0
  428. package/supervisor/control-plane.ts +868 -0
  429. package/supervisor/entry.ts +15 -0
  430. package/supervisor/forge-validators.ts +80 -0
  431. package/supervisor/index.ts +5 -0
  432. package/supervisor/native-browser-adapter.ts +786 -0
  433. package/supervisor/native-messaging/host.ts +39 -0
  434. package/supervisor/native-messaging/manifest.ts +26 -0
  435. package/supervisor/native-messaging/protocol.ts +27 -0
  436. package/supervisor/paths.ts +18 -0
  437. package/supervisor/protocol.ts +202 -0
  438. package/supervisor/server.ts +404 -0
  439. package/supervisor/store.ts +1451 -0
  440. package/supervisor/tsconfig.json +8 -0
  441. package/supervisor/types.ts +255 -0
  442. package/adapters/chatgpt/controller-host.ts +0 -119
  443. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  444. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  445. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  446. package/packages/kernel/progression/api/index.ts +0 -2
  447. package/packages/kernel/progression/application/projector.ts +0 -211
  448. package/packages/kernel/progression/domain/types.ts +0 -116
  449. package/packages/kernel/progression/index.ts +0 -1
  450. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  451. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  452. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  453. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  454. package/scripts/benchmark-background-check-overlap.ts +0 -228
  455. package/scripts/benchmark-computer-hotpath.ts +0 -255
  456. package/scripts/benchmark-controller-hotpath.ts +0 -212
  457. package/scripts/benchmark-controller-round2.ts +0 -191
  458. package/scripts/benchmark-ios-agent-device.ts +0 -511
  459. package/scripts/benchmark-mcp-host.sh +0 -117
  460. package/scripts/benchmark-mcp-transport.ts +0 -169
  461. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  462. package/scripts/benchmark-operational-shadow.ts +0 -28
  463. package/scripts/benchmark-quality-harness.ts +0 -146
  464. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  465. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  466. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  467. package/scripts/benchmark-thin-harness.ts +0 -251
  468. package/scripts/benchmark-typescript-navigation.ts +0 -186
  469. package/scripts/benchmark-work-efficiency.ts +0 -334
  470. package/scripts/build-controller-ui.ts +0 -28
  471. package/scripts/check-npm-release.sh +0 -32
  472. package/scripts/check-public-release-surface.sh +0 -19
  473. package/scripts/check-release-published.sh +0 -57
  474. package/scripts/check-release-readiness.sh +0 -37
  475. package/scripts/check-release-version.mjs +0 -74
  476. package/scripts/check-tarball-install-smoke.sh +0 -150
  477. package/scripts/package-source-archive.sh +0 -147
  478. package/scripts/public-release-files.txt +0 -39
  479. package/scripts/public-release.ts +0 -174
  480. package/scripts/publish-release-tarball.sh +0 -27
  481. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  482. package/scripts/stage-runtime-release.ts +0 -44
  483. package/src/cli/controller/progress.ts +0 -656
  484. package/src/cli/controller/work-mode.ts +0 -181
  485. package/src/cli/local-bridge/console-assets.ts +0 -17
  486. package/src/cli/local-bridge/dashboard.ts +0 -17
  487. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  488. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  489. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  490. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  491. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  492. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  493. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  494. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  495. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  496. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  497. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  498. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  499. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  500. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  501. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  502. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  503. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  504. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  505. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  506. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  507. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  508. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  509. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  510. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  511. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  512. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  513. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  514. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  515. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  516. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  517. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  518. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  519. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  520. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  521. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  522. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  523. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  524. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  525. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  526. package/src/runtime/control-plane/routing/index.ts +0 -1
  527. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  528. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,11 +1,9 @@
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';
@@ -19,15 +17,10 @@ import {
19
17
  import { managedPathInside, managedWorktreeStorageRoot } from '../../../cli/repositories/worktree-storage';
20
18
  import { markRepositoryProjectionDirty } from '../../projections/invalidation';
21
19
  import { listControlPlaneRecords } from '../persistence/sqlite-store';
22
- import { getWorkContract } from '../../../../packages/kernel/work/api/index';
23
- import {
24
- controllerTerminalizationAuthorityFromSession,
25
- getControllerSession,
26
- releaseControllerSessionWithAuthority,
27
- releaseObservedControllerSession,
28
- type ControllerTerminalizationAuthority,
29
- } from '../../../../packages/kernel/controller/api/index';
30
- 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';
31
24
  import {
32
25
  cancelProcess,
33
26
  getProcessHandle,
@@ -49,8 +42,6 @@ import {
49
42
  } from './work-handle-store';
50
43
  import { proveWorkPreservationContained } from '../cleanup-artifact-retention';
51
44
 
52
- const CHECKPOINT_MESSAGE = 'chore(checkpoint): preserve terminal work before cleanup';
53
-
54
45
  export interface TerminalWorkCleanupInput {
55
46
  controllerHome: string;
56
47
  handle: WorkHandleState;
@@ -118,7 +109,7 @@ function newReceipt(handle: WorkHandleState, targetBranch: string, terminalOutco
118
109
  updatedAt: timestamp,
119
110
  verification: { mode: 'cleanup_only', checksRun: [] },
120
111
  processes: { examined: [], terminated: [], blocking: [], allTerminal: false },
121
- ownership: { controllerLease: 'pending', processLeases: 'pending' },
112
+ ownership: { controllerLease: 'already_released', processLeases: 'pending' },
122
113
  preservation: { status: 'not_needed' },
123
114
  worktree: { path: handle.worktreePath, status: 'pending' },
124
115
  branchCleanup: { branch: handle.branch, status: 'pending' },
@@ -168,62 +159,6 @@ function branchUsedByAnotherWorktree(root: string, branch: string, currentPath:
168
159
  return false;
169
160
  }
170
161
 
171
- function copyUntrackedFiles(worktreePath: string, archiveRoot: string): Array<{ path: string; sha256: string }> {
172
- const listed = git(worktreePath, ['ls-files', '--others', '--exclude-standard', '-z']);
173
- if (!listed.ok || !listed.stdout) return [];
174
- const manifest: Array<{ path: string; sha256: string }> = [];
175
- for (const relativePath of listed.stdout.split('\0').filter(Boolean)) {
176
- const source = join(worktreePath, relativePath);
177
- const destination = join(archiveRoot, 'untracked', relativePath);
178
- mkdirSync(dirname(destination), { recursive: true });
179
- if (!existsSync(destination)) {
180
- cpSync(source, destination, { recursive: true, force: false, errorOnExist: true });
181
- }
182
- if (existsSync(source)) {
183
- try {
184
- manifest.push({ path: relativePath, sha256: hashText(readFileSync(source)) });
185
- } catch {
186
- manifest.push({ path: relativePath, sha256: 'directory-or-unreadable' });
187
- }
188
- }
189
- }
190
- return manifest;
191
- }
192
-
193
- function createPatchArchive(controllerHome: string, handle: WorkHandleState): {
194
- path: string;
195
- sha256: string;
196
- recoveryInstructions: string;
197
- } {
198
- const root = artifactRoot(controllerHome, handle);
199
- const path = join(root, 'worktree.patch.json');
200
- if (!existsSync(path)) {
201
- const unstaged = git(handle.worktreePath, ['diff', '--binary', 'HEAD']);
202
- const staged = git(handle.worktreePath, ['diff', '--binary', '--cached', 'HEAD']);
203
- const untracked = copyUntrackedFiles(handle.worktreePath, root);
204
- const archive = {
205
- schemaVersion: 1,
206
- repoId: handle.repositoryId,
207
- checkoutId: handle.checkoutId,
208
- workId: handle.workId,
209
- branch: handle.branch,
210
- baseCommit: handle.baseCommit,
211
- expectedHead: handle.expectedHead,
212
- createdAt: nowIso(),
213
- unstagedPatch: unstaged.stdout,
214
- stagedPatch: staged.stdout,
215
- untracked,
216
- };
217
- writeFileSync(path, `${JSON.stringify(archive, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
218
- }
219
- const sha256 = hashText(readFileSync(path));
220
- return {
221
- path,
222
- sha256,
223
- recoveryInstructions: `Apply tracked changes from ${path}; restore copied untracked files from ${join(root, 'untracked')}. Verify SHA-256 ${sha256}.`,
224
- };
225
- }
226
-
227
162
  function createVerifiedBundle(controllerHome: string, handle: WorkHandleState, targetRoot: string): {
228
163
  path: string;
229
164
  sha256: string;
@@ -305,7 +240,7 @@ function workHandleOwnsCheckout(controllerHome: string, handle: WorkHandleState)
305
240
  // WorkContract completion is the authoritative ownership boundary. Older
306
241
  // runtimes could persist a completed contract while leaving the handle in a
307
242
  // non-terminal state; that historical handle must not remain a live owner.
308
- return contract === undefined || !isTerminalWorkContractStatus(contract.status);
243
+ return contract === undefined || semanticWorkState(contract) === 'open';
309
244
  }
310
245
 
311
246
  function assertNoOtherLiveWork(input: TerminalWorkCleanupInput, receipt: WorkCleanupReceipt): void {
@@ -330,59 +265,22 @@ function preserveDirtyWorktree(
330
265
  return persist(input.controllerHome, current, receipt);
331
266
  }
332
267
  if (!status.stdout) {
333
- receipt.preservation.status = receipt.preservation.checkpointCommit || receipt.preservation.patchArchivePath
334
- ? receipt.preservation.status
335
- : 'not_needed';
268
+ receipt.preservation.status = 'not_needed';
336
269
  return persist(input.controllerHome, current, receipt);
337
270
  }
338
271
 
339
- if (!receipt.preservation.checkpointCommit && !receipt.preservation.patchArchivePath) {
340
- const staged = git(current.worktreePath, ['add', '-A']);
341
- const committed = staged.ok
342
- ? git(current.worktreePath, [
343
- '-c', 'user.name=forge',
344
- '-c', 'user.email=forge@local.invalid',
345
- 'commit', '-m', CHECKPOINT_MESSAGE,
346
- ], 60_000)
347
- : { ok: false, stdout: '', stderr: staged.stderr };
348
- if (committed.ok) {
349
- const head = git(current.worktreePath, ['rev-parse', 'HEAD']);
350
- if (!head.ok || !head.stdout) {
351
- receipt.preservation.status = 'failed';
352
- addBlocker(receipt, 'CHECKPOINT_HEAD_UNAVAILABLE');
353
- } else {
354
- receipt.preservation.status = 'checkpointed';
355
- receipt.preservation.checkpointCommit = head.stdout;
356
- current = writeWorkHandle(input.controllerHome, {
357
- ...current,
358
- expectedHead: head.stdout,
359
- cleanupReceipt: receipt,
360
- });
361
- }
362
- } else {
363
- try {
364
- const archive = createPatchArchive(input.controllerHome, current);
365
- receipt.preservation.status = 'patch_archived';
366
- receipt.preservation.patchArchivePath = archive.path;
367
- receipt.preservation.patchArchiveSha256 = archive.sha256;
368
- receipt.preservation.recoveryInstructions = archive.recoveryInstructions;
369
- } catch (error) {
370
- receipt.preservation.status = 'failed';
371
- addBlocker(receipt, `PRESERVATION_FAILED: ${error instanceof Error ? error.message : String(error)}`);
372
- }
373
- }
374
- } else if (receipt.preservation.checkpointCommit && status.stdout) {
375
- try {
376
- const archive = createPatchArchive(input.controllerHome, current);
377
- receipt.preservation.status = 'patch_archived';
378
- receipt.preservation.patchArchivePath = archive.path;
379
- receipt.preservation.patchArchiveSha256 = archive.sha256;
380
- receipt.preservation.recoveryInstructions = archive.recoveryInstructions;
381
- } catch (error) {
382
- receipt.preservation.status = 'failed';
383
- addBlocker(receipt, `POST_CHECKPOINT_PRESERVATION_FAILED: ${error instanceof Error ? error.message : String(error)}`);
384
- }
385
- }
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));
386
284
  return persist(input.controllerHome, current, receipt);
387
285
  }
388
286
 
@@ -460,9 +358,15 @@ export interface TerminalWorkCleanupReconcileReport {
460
358
  truncated: boolean;
461
359
  }
462
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
+
463
367
  function terminalOutcomeForContract(contract: WorkContract): WorkTerminalOutcome {
464
- if (contract.status === 'cancelled') return 'cancelled';
465
- if (contract.status === 'completed') return 'completed_cleanup';
368
+ if (semanticWorkState(contract) === 'cancelled') return 'cancelled';
369
+ if (semanticWorkState(contract) === 'completed') return 'completed_cleanup';
466
370
  return 'failed';
467
371
  }
468
372
 
@@ -474,7 +378,7 @@ export function recoverTerminalWorkHandle(
474
378
  const existing = readWorkHandle(controllerHome, repositoryId, workId);
475
379
  if (existing) return existing;
476
380
  const contract = getWorkContract({ controllerHome, repoId: repositoryId }, workId);
477
- if (!contract || !isTerminalWorkContractStatus(contract.status)) return undefined;
381
+ if (!contract || semanticWorkState(contract) === 'open') return undefined;
478
382
  const worktreePath = contract.worktreeRef?.trim();
479
383
  const checkoutId = contract.checkoutId?.trim();
480
384
  if (!worktreePath || !checkoutId || !existsSync(worktreePath)) return undefined;
@@ -500,15 +404,15 @@ export function recoverTerminalWorkHandle(
500
404
  const contained = targetExists
501
405
  ? git(repository.canonicalRoot, ['merge-base', '--is-ancestor', head.stdout, `refs/heads/${targetBranch}`]).ok
502
406
  : false;
503
- const delivered = contract.status === 'completed' && contained;
504
- const session = getControllerSession({ controllerHome, repoId: repositoryId }, workId);
407
+ const completedNoChange = contractCompletedNoChange(contract);
408
+ const delivered = semanticWorkState(contract) === 'completed' && contained && !completedNoChange;
505
409
  const recordedAt = nowIso();
506
410
  return writeWorkHandle(controllerHome, {
507
411
  schemaVersion: 1,
508
412
  workId,
509
413
  workContractId: workId,
510
- sessionId: session?.sessionId ?? `terminal-recovery:${workId}`,
511
- principalId: contract.principalId?.trim() || session?.principalId || 'terminal-recovery',
414
+ sessionId: `terminal-recovery:${workId}`,
415
+ principalId: contract.principalId?.trim() || 'terminal-recovery',
512
416
  repositoryId,
513
417
  checkoutId,
514
418
  sourceCheckoutId: repository.activeCheckoutId,
@@ -520,17 +424,18 @@ export function recoverTerminalWorkHandle(
520
424
  expectedHead: head.stdout,
521
425
  permissionSnapshotVersion: 1,
522
426
  state: delivered ? 'merged' : 'failed_terminal_cleanup',
523
- 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.',
524
430
  createdAt: contract.createdAt || recordedAt,
525
431
  updatedAt: recordedAt,
526
432
  cleanupResponsibility: { owner: 'work_finalizer', registeredAt: recordedAt },
527
433
  finalization: {
528
- validation: contract.status === 'failed' ? 'failed' : 'done',
434
+ validation: 'done',
529
435
  commit: 'skipped',
530
436
  merge: delivered ? 'done' : 'skipped',
531
437
  branchCleanup: 'pending',
532
438
  worktreeCleanup: 'pending',
533
- ...(contract.status === 'failed' ? { lastError: 'Recovered failed terminal Work for cleanup.' } : {}),
534
439
  },
535
440
  });
536
441
  }
@@ -646,6 +551,7 @@ function prepareManagedBranchPreservation(
646
551
  }
647
552
 
648
553
  function applyManagedBranchCleanup(
554
+ controllerHome: string,
649
555
  target: ReturnType<typeof getRepository>,
650
556
  current: WorkHandleState,
651
557
  targetBranch: string,
@@ -673,6 +579,21 @@ function applyManagedBranchCleanup(
673
579
  receipt.branchCleanup.reason = 'Unique commits are not archived.';
674
580
  addBlocker(receipt, `BRANCH_UNPRESERVED: ${current.branch}`);
675
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
+ }
676
597
  // We already proved the exact branch relation against targetBranch above.
677
598
  // `git branch -d` instead consults the checkout's current HEAD, which may be
678
599
  // an older/stale source checkout and can falsely reject a branch that is
@@ -699,6 +620,24 @@ function cleanupReceiptComplete(receipt: WorkCleanupReceipt, deleteBranch: boole
699
620
  : receipt.branchCleanup.status === 'retained');
700
621
  }
701
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
+
702
641
  function reconcileCleanedManagedBranchRetirement(
703
642
  input: TerminalWorkCleanupInput,
704
643
  repository: ReturnType<typeof getRepository>,
@@ -709,7 +648,7 @@ function reconcileCleanedManagedBranchRetirement(
709
648
  ): TerminalWorkCleanupResult | undefined {
710
649
  const workId = current.workContractId ?? current.workId;
711
650
  const contract = getWorkContract({ controllerHome: input.controllerHome, repoId: current.repositoryId }, workId);
712
- if (!contract || !isTerminalWorkContractStatus(contract.status)
651
+ if (!contract || semanticWorkState(contract) === 'open'
713
652
  || !cleanedManagedBranchRetirementCandidate(repository, contract, current, targetBranch, deleteBranch)) return undefined;
714
653
 
715
654
  const target = selectTerminalCleanupTarget(repository, current);
@@ -724,6 +663,7 @@ function reconcileCleanedManagedBranchRetirement(
724
663
  current = persist(input.controllerHome, current, receipt);
725
664
  if (receipt.blockers.length === 0) {
726
665
  applyManagedBranchCleanup(
666
+ input.controllerHome,
727
667
  target,
728
668
  current,
729
669
  targetBranch,
@@ -818,8 +758,19 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
818
758
  const targetBranch = resolveWorkDeliveryTargetBranch(input.handle, repository.defaultBranch, input.targetBranch);
819
759
  const deleteBranch = input.deleteBranch !== false;
820
760
  let current = input.handle;
821
- const landed = current.state === 'merged' || current.finalization.merge === 'done';
822
- 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);
823
774
  const receipt = current.cleanupReceipt ?? newReceipt(current, targetBranch, input.terminalOutcome);
824
775
 
825
776
  if (
@@ -827,6 +778,12 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
827
778
  || receipt.checkoutId !== current.checkoutId
828
779
  || receipt.workId !== current.workId
829
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
+ }
830
787
 
831
788
  if (current.state === 'cleaned') {
832
789
  const branchRetirement = reconcileCleanedManagedBranchRetirement(
@@ -844,7 +801,6 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
844
801
  receipt.completedAt = receipt.completedAt ?? current.updatedAt ?? nowIso();
845
802
  receipt.blockers = [];
846
803
  receipt.processes.allTerminal = true;
847
- if (receipt.ownership.controllerLease === 'pending') receipt.ownership.controllerLease = 'already_released';
848
804
  if (receipt.ownership.processLeases === 'pending') receipt.ownership.processLeases = 'released';
849
805
  if (receipt.worktree.status === 'pending' || receipt.worktree.status === 'failed') {
850
806
  receipt.worktree.status = 'already_removed';
@@ -890,7 +846,11 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
890
846
  ? 'failed' as const
891
847
  : current.finalization.validation,
892
848
  commit: current.finalization.commit === 'pending' ? 'skipped' as const : current.finalization.commit,
893
- 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,
894
854
  lastError: preservedFailure,
895
855
  };
896
856
  if (landed) {
@@ -987,7 +947,19 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
987
947
  current = persist(input.controllerHome, current, receipt);
988
948
  return { handle: current, receipt };
989
949
  }
990
- 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)) {
991
963
  addBlocker(receipt, 'WORKTREE_IDENTITY_INVALID');
992
964
  current = persist(input.controllerHome, current, receipt);
993
965
  return { handle: current, receipt };
@@ -1020,6 +992,19 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
1020
992
  }
1021
993
 
1022
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
+ }
1023
1008
  const removed = git(target.canonicalRoot, ['worktree', 'remove', '--force', current.worktreePath], 60_000);
1024
1009
  if (!removed.ok && existsSync(current.worktreePath)) {
1025
1010
  receipt.worktree.status = 'failed';
@@ -1056,6 +1041,7 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
1056
1041
  }
1057
1042
 
1058
1043
  applyManagedBranchCleanup(
1044
+ input.controllerHome,
1059
1045
  target,
1060
1046
  current,
1061
1047
  targetBranch,
@@ -1075,6 +1061,7 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
1075
1061
  receipt.complete = cleanupReceiptComplete(receipt, deleteBranch);
1076
1062
  receipt.partial = !receipt.complete;
1077
1063
  if (receipt.complete) receipt.completedAt = receipt.completedAt ?? nowIso();
1064
+ projectOwnedResourceCleanup(input.controllerHome, current, receipt);
1078
1065
 
1079
1066
  const finalization = {
1080
1067
  ...current.finalization,
@@ -1120,6 +1107,30 @@ export interface SingleTerminalWorkCleanupResult {
1120
1107
  * Controller ownership or unsafe branch drift. A caller proving the exact
1121
1108
  * leftover owner epoch may release it before physical cleanup.
1122
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
+
1123
1134
  export async function reconcileSingleTerminalWorkCleanup(
1124
1135
  controllerHome: string,
1125
1136
  repositoryId: string,
@@ -1127,38 +1138,13 @@ export async function reconcileSingleTerminalWorkCleanup(
1127
1138
  options: {
1128
1139
  targetBranch?: string;
1129
1140
  deleteBranch?: boolean;
1130
- /** Recovery authority for the narrow crash window after semantic terminalization but before owner release. */
1131
- controllerAuthority?: ControllerTerminalizationAuthority;
1132
1141
  } = {},
1133
1142
  ): Promise<SingleTerminalWorkCleanupResult> {
1134
1143
  const repository = getRepository(repositoryId, controllerHome, { includeRemoved: true });
1135
1144
  const contract = getWorkContract({ controllerHome, repoId: repositoryId }, workId);
1136
- if (!contract || !isTerminalWorkContractStatus(contract.status)) {
1145
+ if (!contract || semanticWorkState(contract) === 'open') {
1137
1146
  return { status: 'not_terminal', workId, reason: 'Work is not terminal.' };
1138
1147
  }
1139
- let controllerLease: WorkCleanupReceipt['ownership']['controllerLease'] = 'already_released';
1140
- const owner = getControllerSession({ controllerHome, repoId: repositoryId }, workId);
1141
- if (owner) {
1142
- const authority = options.controllerAuthority;
1143
- if (!authority) return { status: 'blocked', workId, reason: 'Active Controller ownership still exists.' };
1144
- const ownerAuthority = controllerTerminalizationAuthorityFromSession(owner);
1145
- if (!ownerAuthority
1146
- || ownerAuthority.controllerId !== authority.controllerId
1147
- || ownerAuthority.controllerType !== authority.controllerType
1148
- || ownerAuthority.principalId !== authority.principalId
1149
- || ownerAuthority.controllerInstanceId !== authority.controllerInstanceId
1150
- || ownerAuthority.claimGeneration !== authority.claimGeneration) {
1151
- return { status: 'blocked', workId, reason: 'Active Controller ownership does not match the cleanup authority.' };
1152
- }
1153
- const released = releaseControllerSessionWithAuthority(
1154
- { controllerHome, repoId: repositoryId },
1155
- { workId, actor: `terminal-cleanup:${workId}`, authority },
1156
- );
1157
- if (!released.allowed) {
1158
- return { status: 'blocked', workId, reason: `Active Controller ownership release fenced: ${released.reason}.` };
1159
- }
1160
- controllerLease = 'released';
1161
- }
1162
1148
  const originalHandle = readWorkHandle(controllerHome, repositoryId, workId)
1163
1149
  ?? recoverTerminalWorkHandle(controllerHome, repositoryId, workId);
1164
1150
  if (!originalHandle) return { status: 'no_handle', workId };
@@ -1176,8 +1162,10 @@ export async function reconcileSingleTerminalWorkCleanup(
1176
1162
  terminalOutcome: terminalOutcomeForContract(contract),
1177
1163
  failureReason: drift.handle.failureReason ?? drift.handle.finalization.lastError,
1178
1164
  });
1179
- cleaned.receipt.ownership.controllerLease = controllerLease;
1180
1165
  const persisted = writeWorkHandle(controllerHome, { ...cleaned.handle, cleanupReceipt: cleaned.receipt });
1166
+ if (cleaned.receipt.complete) {
1167
+ reconcileCancelledCleanupProjection(controllerHome, repositoryId, contract, cleaned.receipt);
1168
+ }
1181
1169
  if (!cleaned.receipt.complete) {
1182
1170
  return {
1183
1171
  status: 'blocked',
@@ -1211,7 +1199,14 @@ export async function reconcileTerminalWorkCleanups(
1211
1199
  truncated: false,
1212
1200
  };
1213
1201
 
1214
- 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
+ });
1215
1210
  outer: for (const repository of repositories) {
1216
1211
  const handles = listWorkHandles(controllerHome, repository.repoId, 10_000)
1217
1212
  .filter((handle) => handle.managedWorktree);
@@ -1223,7 +1218,7 @@ export async function reconcileTerminalWorkCleanups(
1223
1218
  limit: 10_000,
1224
1219
  })) {
1225
1220
  const contract = record.value;
1226
- if (knownWorkIds.has(contract.workId) || !isTerminalWorkContractStatus(contract.status)) continue;
1221
+ if (knownWorkIds.has(contract.workId) || semanticWorkState(contract) === 'open') continue;
1227
1222
  if (!contract.worktreeRef?.trim() || !existsSync(contract.worktreeRef)) continue;
1228
1223
  let recovered: WorkHandleState | undefined;
1229
1224
  try {
@@ -1247,15 +1242,22 @@ export async function reconcileTerminalWorkCleanups(
1247
1242
  report.errors.push({ workId: originalHandle.workId, error: error instanceof Error ? error.message : String(error) });
1248
1243
  continue;
1249
1244
  }
1250
- if (!contract || !isTerminalWorkContractStatus(contract.status)) {
1245
+ if (!contract || semanticWorkState(contract) === 'open') {
1251
1246
  report.skippedNonTerminal.push(originalHandle.workId);
1252
1247
  continue;
1253
1248
  }
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.
1254
1255
  if (cleanupRetainedByRequest(contract, originalHandle)) {
1255
1256
  report.skippedRetained.push(originalHandle.workId);
1256
1257
  continue;
1257
1258
  }
1258
1259
  if (originalHandle.state === 'cleaned' && originalHandle.cleanupReceipt?.complete === true) {
1260
+ reconcileCancelledCleanupProjection(controllerHome, repository.repoId, contract, originalHandle.cleanupReceipt);
1259
1261
  const targetBranch = resolveWorkDeliveryTargetBranch(originalHandle, repository.defaultBranch);
1260
1262
  if (!cleanedManagedBranchRetirementCandidate(
1261
1263
  repository,
@@ -1296,26 +1298,11 @@ export async function reconcileTerminalWorkCleanups(
1296
1298
  terminalOutcome: terminalOutcomeForContract(contract),
1297
1299
  failureReason: drift.handle.failureReason ?? drift.handle.finalization.lastError,
1298
1300
  });
1299
- const workId = cleaned.handle.workContractId ?? cleaned.handle.workId;
1300
- const controllerSession = getControllerSession({ controllerHome, repoId: repository.repoId }, workId);
1301
- if (controllerSession) {
1302
- const released = releaseObservedControllerSession(
1303
- { controllerHome, repoId: repository.repoId },
1304
- { workId, actor: `terminal-cleanup:${originalHandle.workId}`, owner: controllerSession },
1305
- );
1306
- if (released.allowed) {
1307
- cleaned.receipt.ownership.controllerLease = 'released';
1308
- } else {
1309
- cleaned.receipt.complete = false;
1310
- cleaned.receipt.partial = true;
1311
- cleaned.receipt.completedAt = undefined;
1312
- cleaned.receipt.blockers = appendUnique(cleaned.receipt.blockers, `controller lease release fenced: ${released.reason}`);
1313
- }
1314
- } else {
1315
- cleaned.receipt.ownership.controllerLease = 'already_released';
1316
- }
1317
1301
  writeWorkHandle(controllerHome, { ...cleaned.handle, cleanupReceipt: cleaned.receipt });
1318
- 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
+ }
1319
1306
  else report.blocked.push({
1320
1307
  workId: originalHandle.workId,
1321
1308
  reason: cleaned.receipt.blockers.join('; ') || cleaned.receipt.worktree.reason || cleaned.receipt.branchCleanup.reason || 'terminal cleanup incomplete',