@moretea-labs/forge 1.8.0 → 1.8.2

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 (695) hide show
  1. package/AGENTS.md +73 -7
  2. package/CHANGELOG.md +34 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.en.md +24 -3
  6. package/README.md +10 -6
  7. package/README.zh-CN.md +5 -3
  8. package/THIRD_PARTY_NOTICES.md +1 -2
  9. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +531 -56
  11. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  12. package/adapters/chatgpt/controller-round-host.ts +15 -25
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  14. package/adapters/chatgpt/provider-delivery.ts +229 -2
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +99 -54
  16. package/adapters/computer/chatgpt-extension-target.ts +708 -0
  17. package/adapters/computer/chrome-extension/background.js +541 -0
  18. package/adapters/computer/chrome-extension/content.js +402 -0
  19. package/adapters/computer/chrome-extension/core.js +24 -0
  20. package/adapters/computer/chrome-extension/manifest.json +32 -0
  21. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  22. package/adapters/computer/desktop-operator-provider.ts +18 -6
  23. package/adapters/computer/interaction-target-authority.ts +178 -24
  24. package/adapters/mcp/access-mode.ts +18 -39
  25. package/adapters/mcp/auth.ts +2 -7
  26. package/adapters/mcp/instructions.ts +6 -5
  27. package/adapters/mcp/multi-repository.ts +10 -47
  28. package/adapters/mcp/oauth.ts +73 -0
  29. package/adapters/mcp/paths.ts +4 -1
  30. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  31. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  32. package/adapters/mcp/runtime-gateway/context-adapter.ts +490 -113
  33. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +83 -0
  34. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  35. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  36. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  37. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  38. package/adapters/mcp/runtime-gateway/execution-tools.ts +14 -54
  39. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  40. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +132 -47
  41. package/adapters/mcp/runtime-gateway/process-tools.ts +173 -23
  42. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  43. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  44. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  45. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  46. package/adapters/mcp/runtime-gateway/router.ts +19 -507
  47. package/adapters/mcp/runtime-gateway/routing-policy.ts +506 -0
  48. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +15 -66
  49. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  50. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +123 -178
  51. package/adapters/mcp/runtime-gateway/runtime-tools.ts +11 -5
  52. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +37 -1
  53. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  54. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +416 -270
  55. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +140 -0
  56. package/adapters/mcp/runtime-gateway/work-adapter.ts +262 -3123
  57. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  58. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  59. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +229 -0
  60. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  61. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  62. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  63. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  64. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  65. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +320 -0
  66. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  67. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +180 -0
  68. package/adapters/mcp/server.ts +91 -26
  69. package/adapters/mcp/setup.ts +5 -13
  70. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  71. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +703 -3092
  72. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +5 -10
  73. package/adapters/mcp/tool-mapping/repository-tools.ts +170 -213
  74. package/adapters/mcp/toolset-names.ts +11 -37
  75. package/adapters/mcp/toolset.ts +19 -92
  76. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  77. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  78. package/adapters/mcp/transports/http-observation.ts +377 -0
  79. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  80. package/adapters/mcp/transports/http.ts +200 -683
  81. package/adapters/mcp/transports/oauth-http.ts +322 -0
  82. package/adapters/mcp/transports/session-registry.ts +6 -0
  83. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  84. package/adapters/mcp/types.ts +0 -6
  85. package/assets/hooks/lib/session-state.sh +0 -38
  86. package/assets/hooks/post-bash.sh +1 -1
  87. package/assets/hooks/post-tool-observer.sh +0 -5
  88. package/assets/hooks/prompt-guard.sh +0 -55
  89. package/assets/partials/04-project-structure.partial.md +1 -1
  90. package/assets/partials/07-footer.partial.md +1 -1
  91. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  92. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  93. package/assets/plugin-registry.v1.json +6 -4
  94. package/assets/reference-configs/agentic-development-flow.md +26 -1
  95. package/assets/reference-configs/external-tooling.md +0 -76
  96. package/assets/reference-configs/global-working-rules.md +19 -0
  97. package/assets/reference-configs/release-deploy.md +1 -1
  98. package/assets/templates/helpers/architecture-queue.sh +1 -1
  99. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  100. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  101. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  102. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  103. package/assets/workflow-contract.v1.json +0 -2
  104. package/docs/README.md +5 -2
  105. package/docs/architecture/CURRENT.md +205 -51
  106. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  107. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  108. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  109. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  110. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  111. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  112. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  113. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +234 -0
  114. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  115. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +74 -0
  116. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +342 -0
  117. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  118. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  119. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  120. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  121. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  122. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  123. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  124. package/docs/architecture/domains/public-surface.md +3 -5
  125. package/docs/architecture/domains/verification.md +6 -5
  126. package/docs/architecture/domains/workflow-engine.md +6 -0
  127. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +3 -3
  128. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  129. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  130. package/docs/forge-plugin-management.md +16 -4
  131. package/docs/operations/controller-browser-plugin.md +23 -0
  132. package/docs/operations/controller-performance-and-502.md +17 -0
  133. package/docs/operations/controller-reliability-runbook.md +2 -2
  134. package/docs/operations/features.md +6 -7
  135. package/docs/operations/features.zh-CN.md +1 -1
  136. package/docs/operations/github-repository.md +4 -4
  137. package/docs/operations/github-semantic-release-projection.md +20 -0
  138. package/docs/operations/homebrew.md +2 -2
  139. package/docs/operations/mcp-tool-exposure.md +13 -52
  140. package/docs/operations/releasing.md +2 -0
  141. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  142. package/docs/operations/standalone-disaster-recovery.md +15 -7
  143. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  144. package/docs/tutorials/03-first-repository-task.md +2 -0
  145. package/docs/wiki/Computer-and-Plugins.md +78 -0
  146. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  147. package/docs/wiki/Home.md +16 -2
  148. package/docs/wiki/Implementation.md +25 -13
  149. package/docs/wiki/Installation.md +28 -32
  150. package/docs/wiki/Integrations.md +4 -0
  151. package/docs/wiki/Quick-Start.md +35 -22
  152. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  153. package/docs/wiki/_Sidebar.md +5 -1
  154. package/package.json +25 -25
  155. package/packages/kernel/cognition/api/index.ts +6 -0
  156. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  157. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  158. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  159. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  160. package/packages/kernel/cognition/domain/learning.ts +106 -0
  161. package/packages/kernel/cognition/domain/memory.ts +254 -0
  162. package/packages/kernel/controller/api/index.ts +1 -0
  163. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  164. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  165. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  166. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  167. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  168. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  169. package/packages/kernel/controller/infrastructure/controller-session-store.ts +348 -97
  170. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  171. package/packages/kernel/identity/api/index.ts +10 -0
  172. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  173. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  174. package/packages/kernel/identity/domain/grant.ts +72 -0
  175. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  176. package/packages/kernel/identity/domain/user-request.ts +93 -0
  177. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +87 -0
  178. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  179. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  180. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  181. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  182. package/packages/kernel/scheduler/api/index.ts +0 -2
  183. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  184. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  185. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  186. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  187. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +166 -143
  188. package/packages/kernel/work/api/index.ts +3 -2
  189. package/packages/kernel/work/application/work-service.ts +18 -42
  190. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  191. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  192. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  193. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  194. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  195. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  196. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  197. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  198. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  199. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  200. package/packages/kernel/work/domain/state-machine.ts +34 -65
  201. package/packages/kernel/work/domain/types.ts +57 -51
  202. package/packages/kernel/work/infrastructure/work-contract-store.ts +847 -729
  203. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  204. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  205. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  206. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  207. package/packages/plugin-runtime/computer/conversation-target.ts +153 -0
  208. package/packages/plugin-runtime/computer/index.ts +2 -0
  209. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  210. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  211. package/packages/protocols/computer/contract.ts +27 -1
  212. package/scripts/activate-source-baseline.ts +7 -9
  213. package/scripts/assemble-template.ts +11 -72
  214. package/scripts/canary-controller-semantic-wait.ts +12 -24
  215. package/scripts/check-mcp-compatibility.ts +94 -201
  216. package/scripts/check-package-identity.mjs +0 -5
  217. package/scripts/check-runtime-architecture.mjs +921 -239
  218. package/scripts/forge-local-recovery-helper.mjs +93 -15
  219. package/scripts/init-project.sh +0 -46
  220. package/scripts/install-ios-hid-toolchain.sh +60 -0
  221. package/scripts/install-standalone-recovery.ts +1 -1
  222. package/scripts/lib/project-init-lib.sh +6 -3
  223. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  224. package/scripts/open-source-audit-allowlist.txt +1 -3
  225. package/scripts/run-bounded-command.mjs +5 -0
  226. package/scripts/run-governed-gate.ts +2 -1
  227. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  228. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  229. package/scripts/smoke-ios-physical-device.ts +38 -10
  230. package/scripts/smoke-mcp-http-runtime.ts +12 -8
  231. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  232. package/scripts/smoke-runtime-recovery.ts +12 -43
  233. package/scripts/smoke-schedule-engine.ts +23 -15
  234. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  235. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  236. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +95 -0
  237. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  238. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  239. package/scripts/smoke-workflow-supervisor.ts +103 -0
  240. package/scripts/sync-generated-authority.mjs +0 -1
  241. package/scripts/verify-forge-runtime.sh +2 -1
  242. package/src/cli/agent-jobs/executor-health.ts +12 -35
  243. package/src/cli/agent-jobs/integration.ts +20 -51
  244. package/src/cli/agent-jobs/job-manager.ts +7 -3
  245. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  246. package/src/cli/commands/brain-assistant.ts +27 -13
  247. package/src/cli/commands/brain.ts +13 -15
  248. package/src/cli/commands/chatgpt.ts +1 -163
  249. package/src/cli/commands/doctor.ts +1 -143
  250. package/src/cli/commands/global-runtime.ts +3 -196
  251. package/src/cli/commands/init-hook.ts +6 -23
  252. package/src/cli/commands/init.ts +3 -252
  253. package/src/cli/commands/mcp.ts +0 -5
  254. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  255. package/src/cli/commands/recovery.ts +137 -135
  256. package/src/cli/commands/repository.ts +17 -1
  257. package/src/cli/commands/runtime.ts +3 -3
  258. package/src/cli/commands/setup-execution.ts +41 -0
  259. package/src/cli/commands/setup-profile.ts +8 -0
  260. package/src/cli/controller/check-runner.ts +13 -2
  261. package/src/cli/controller/completion-backlog.ts +3 -3
  262. package/src/cli/controller/completion-orchestrator.ts +5 -44
  263. package/src/cli/controller/completion-target.ts +3 -72
  264. package/src/cli/controller/composite-operations.ts +1 -1
  265. package/src/cli/controller/context/instruction-context.ts +1 -1
  266. package/src/cli/controller/context/known-paths.ts +2 -2
  267. package/src/cli/controller/context/query-planning.ts +11 -4
  268. package/src/cli/controller/context/rule-resolution.ts +2 -2
  269. package/src/cli/controller/context/source-materializer.ts +83 -102
  270. package/src/cli/controller/context/structural-provider.ts +84 -0
  271. package/src/cli/controller/context/structural.ts +6 -6
  272. package/src/cli/controller/context/types.ts +8 -9
  273. package/src/cli/controller/context-pack.ts +107 -84
  274. package/src/cli/controller/execution-completion.ts +4 -7
  275. package/src/cli/controller/execution-policy.ts +36 -153
  276. package/src/cli/controller/governance.ts +2 -3
  277. package/src/cli/controller/issue-store.ts +4 -99
  278. package/src/cli/controller/launch-agents.ts +59 -18
  279. package/src/cli/controller/operational-plan.ts +25 -52
  280. package/src/cli/controller/runtime-config.ts +5 -1
  281. package/src/cli/controller/stuck-state-migration.ts +2 -3
  282. package/src/cli/controller/systemd-user.ts +42 -10
  283. package/src/cli/editing/edit-session.ts +18 -12
  284. package/src/cli/github/github.ts +4 -4
  285. package/src/cli/github/semantic-projection.ts +174 -0
  286. package/src/cli/hook/prompt-intents.ts +0 -9
  287. package/src/cli/index.ts +1 -43
  288. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  289. package/src/cli/local-bridge/console-automations.ts +19 -14
  290. package/src/cli/local-bridge/console-view-models.ts +4 -13
  291. package/src/cli/local-bridge/facade-api.ts +64 -193
  292. package/src/cli/local-bridge/server.ts +24 -147
  293. package/src/cli/local-bridge/types.ts +0 -1
  294. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  295. package/src/cli/repositories/command-classifier.ts +0 -9
  296. package/src/cli/repositories/command-executor.ts +29 -23
  297. package/src/cli/repositories/command-scope.ts +37 -18
  298. package/src/cli/repositories/controller-home.ts +48 -0
  299. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  300. package/src/cli/repositories/git-command-executor.ts +2 -2
  301. package/src/cli/repositories/locks.ts +20 -0
  302. package/src/cli/repositories/registry.ts +79 -12
  303. package/src/cli/repositories/safe-patch.ts +2 -12
  304. package/src/cli/repositories/structured-git.ts +101 -5
  305. package/src/cli/repository/inspector.ts +185 -343
  306. package/src/cli/repository/session-cache.ts +92 -6
  307. package/src/effects/process-runner.ts +17 -1
  308. package/src/runtime/context/assistant-context.ts +20 -7
  309. package/src/runtime/context/assistant-work-context.ts +89 -27
  310. package/src/runtime/context/automatic-learning.ts +565 -0
  311. package/src/runtime/context/cognitive-attention.ts +173 -0
  312. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  313. package/src/runtime/context/context-closure.ts +46 -9
  314. package/src/runtime/context/context-pack-process.ts +103 -0
  315. package/src/runtime/context/context-plane.ts +1 -9
  316. package/src/runtime/context/direct-controller-learning.ts +298 -0
  317. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  318. package/src/runtime/context/semantic-navigation-contract.ts +16 -0
  319. package/src/runtime/context/semantic-navigation.ts +7 -35
  320. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  321. package/src/runtime/context/typescript-navigation.ts +89 -3
  322. package/src/runtime/control-plane/cleanup-artifact-retention.ts +215 -12
  323. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  324. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  325. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  326. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  327. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  328. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  329. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  330. package/src/runtime/control-plane/execution/validation.ts +6 -8
  331. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  332. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  333. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  334. package/src/runtime/control-plane/execution/work-delivery-service.ts +616 -0
  335. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  336. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  337. package/src/runtime/control-plane/execution/work-handle-authority.ts +197 -110
  338. package/src/runtime/control-plane/execution/work-handle-store.ts +72 -44
  339. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  340. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  341. package/src/runtime/control-plane/execution/work-operation-service.ts +60 -34
  342. package/src/runtime/control-plane/execution/work-preparation-service.ts +231 -267
  343. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  344. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  345. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  346. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +201 -199
  347. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +33 -85
  348. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  349. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +6 -2
  350. package/src/runtime/control-plane/facade/capability-registry.ts +364 -37
  351. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  352. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  353. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  354. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  355. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  356. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  357. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  358. package/src/runtime/control-plane/facade/index.ts +0 -1
  359. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  360. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  361. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  362. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  363. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  364. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  365. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  366. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  367. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  368. package/src/runtime/control-plane/facade/types.ts +49 -108
  369. package/src/runtime/control-plane/facade/work-continuation.ts +9 -9
  370. package/src/runtime/control-plane/generated-cache-retention.ts +206 -59
  371. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  372. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  373. package/src/runtime/control-plane/global-scheduler/maintenance.ts +256 -117
  374. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -12
  375. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  376. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  377. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  378. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  379. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  380. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  381. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +29 -300
  382. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  383. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  384. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  385. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  386. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  387. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  388. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  389. package/src/runtime/control-plane/release-retention.ts +135 -47
  390. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  391. package/src/runtime/control-plane/runtime-cleanup.ts +295 -107
  392. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  393. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  394. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  395. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  396. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  397. package/src/runtime/evidence/artifact-store.ts +158 -40
  398. package/src/runtime/evidence/evidence-store.ts +112 -8
  399. package/src/runtime/evidence/output-handle-index.ts +225 -0
  400. package/src/runtime/evidence/result-store.ts +158 -44
  401. package/src/runtime/evidence/sensitive-output.ts +22 -2
  402. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  403. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  404. package/src/runtime/execution/jobs/store.ts +3 -4
  405. package/src/runtime/execution/managed-workspace.ts +57 -0
  406. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  407. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  408. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  409. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  410. package/src/runtime/execution/process-runtime/command-facade.ts +82 -30
  411. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  412. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  413. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  414. package/src/runtime/execution/process-runtime/index.ts +3 -0
  415. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  416. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  417. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  418. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  419. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  420. package/src/runtime/execution/process-runtime/runtime.ts +55 -18
  421. package/src/runtime/execution/process-runtime/store.ts +30 -2
  422. package/src/runtime/execution/process-runtime/types.ts +6 -1
  423. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  424. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  425. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  426. package/src/runtime/execution/workers/executor.ts +3 -8
  427. package/src/runtime/maintenance/cleanup.ts +41 -5
  428. package/src/runtime/platform/service-inventory.ts +130 -0
  429. package/src/runtime/platform/service-manager.ts +8 -0
  430. package/src/runtime/plugins/action-application.ts +97 -29
  431. package/src/runtime/plugins/app-store-connect-adapter.ts +290 -18
  432. package/src/runtime/plugins/browser-adapter.ts +1124 -333
  433. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  434. package/src/runtime/plugins/browser-macos-bridge.ts +431 -137
  435. package/src/runtime/plugins/browser-manifest-surface.ts +47 -12
  436. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  437. package/src/runtime/plugins/browser-registration.ts +18 -0
  438. package/src/runtime/plugins/browser-runtime.ts +40 -36
  439. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  440. package/src/runtime/plugins/browser-session-store.ts +46 -76
  441. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  442. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +228 -0
  443. package/src/runtime/plugins/computer-registration.ts +298 -20
  444. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  445. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  446. package/src/runtime/plugins/external-adapter.ts +42 -6
  447. package/src/runtime/plugins/external-registration.ts +21 -1
  448. package/src/runtime/plugins/github-adapter.ts +30 -3
  449. package/src/runtime/plugins/gmail-adapter.ts +189 -3
  450. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  451. package/src/runtime/plugins/google-shared.ts +24 -12
  452. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  453. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  454. package/src/runtime/plugins/ios-adapter.ts +46 -41
  455. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  456. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  457. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  458. package/src/runtime/plugins/lightweight-action.ts +208 -2
  459. package/src/runtime/plugins/local-system-adapter.ts +12 -100
  460. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  461. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  462. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  463. package/src/runtime/plugins/store.ts +682 -435
  464. package/src/runtime/plugins/types.ts +24 -0
  465. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  466. package/src/runtime/projections/git-status-sampler.ts +2 -19
  467. package/src/runtime/projections/invalidation.ts +7 -3
  468. package/src/runtime/projections/materialized-view.ts +80 -36
  469. package/src/runtime/recovery/actions.ts +4 -4
  470. package/src/runtime/recovery/capability-action-application.ts +46 -45
  471. package/src/runtime/recovery/lifecycle-inventory.ts +30 -40
  472. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  473. package/src/runtime/recovery/probe.ts +2 -2
  474. package/src/runtime/recovery/store.ts +29 -11
  475. package/src/runtime/release/release-coordinator.ts +312 -0
  476. package/src/runtime/release/release-session.ts +716 -0
  477. package/src/runtime/release/release-state-migration.ts +25 -0
  478. package/src/runtime/resources/leases/store.ts +56 -25
  479. package/src/runtime/root/computer-composition.ts +191 -6
  480. package/src/runtime/root/controller-progression-composition.ts +254 -0
  481. package/src/runtime/root/controller-round-composition.ts +20 -1
  482. package/src/runtime/root/deployment-topology.ts +106 -0
  483. package/src/runtime/root/entry.ts +4 -3
  484. package/src/runtime/root/known-good-recovery.ts +184 -0
  485. package/src/runtime/root/local-bridge.ts +0 -1
  486. package/src/runtime/root/mcp-transport.ts +111 -18
  487. package/src/runtime/root/ownership.ts +261 -9
  488. package/src/runtime/root/package-connector-service.ts +227 -29
  489. package/src/runtime/root/package-runtime-release.ts +117 -30
  490. package/src/runtime/root/package-runtime-service.ts +24 -24
  491. package/src/runtime/root/release-execution-canary.ts +57 -12
  492. package/src/runtime/root/release-loader.ts +90 -8
  493. package/src/runtime/root/release-manifest.ts +80 -40
  494. package/src/runtime/root/release-materialize.ts +372 -106
  495. package/src/runtime/root/release-store.ts +395 -36
  496. package/src/runtime/root/runtime-lane.ts +191 -0
  497. package/src/runtime/root/runtime.ts +160 -29
  498. package/src/runtime/root/scheduled-controller-composition.ts +29 -3
  499. package/src/runtime/root/service-runner.ts +2 -2
  500. package/src/runtime/root/service.ts +126 -44
  501. package/src/runtime/root/status.ts +48 -0
  502. package/src/runtime/root/types.ts +32 -9
  503. package/src/runtime/root/workflow-supervisor-composition.ts +681 -0
  504. package/src/runtime/root/workflow-supervisor-runtime.ts +564 -0
  505. package/src/runtime/root/write-fence.ts +50 -2
  506. package/src/runtime/safe-tooling/google-credential-store.ts +67 -22
  507. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  508. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  509. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  510. package/src/runtime/safe-tooling/workspace-auth.ts +5 -2
  511. package/src/runtime/shared/atomic-text-file.ts +21 -0
  512. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  513. package/src/runtime/shared/json-files.ts +8 -3
  514. package/src/runtime/shared/process-environment.ts +20 -9
  515. package/src/runtime/shared/response-budget.ts +1 -3
  516. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  517. package/src/runtime/standalone-recovery/core.ts +3792 -417
  518. package/src/runtime/standalone-recovery/entry.ts +507 -45
  519. package/src/runtime/standalone-recovery/installer.ts +262 -148
  520. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  521. package/src/runtime/standalone-recovery/operation-lock.ts +12 -2
  522. package/src/runtime/standalone-recovery/performance.ts +54 -16
  523. package/src/runtime/standalone-recovery/release.ts +53 -5
  524. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  525. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  526. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  527. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -3
  528. package/src/runtime/workflow/schedules/engine.ts +166 -178
  529. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  530. package/src/runtime/workflow/schedules/work-continuation.ts +95 -11
  531. package/src/runtime/workflows/runtime.ts +15 -19
  532. package/src/testing/test-governance.ts +5 -1
  533. package/supervisor/chatgpt-conversation.ts +23 -0
  534. package/supervisor/client.ts +218 -0
  535. package/supervisor/control-plane.ts +963 -0
  536. package/supervisor/entry.ts +15 -0
  537. package/supervisor/forge-validators.ts +147 -0
  538. package/supervisor/index.ts +5 -0
  539. package/supervisor/native-browser-adapter.ts +1151 -0
  540. package/supervisor/native-messaging/host.ts +58 -0
  541. package/supervisor/native-messaging/manifest.ts +26 -0
  542. package/supervisor/native-messaging/protocol.ts +27 -0
  543. package/supervisor/paths.ts +18 -0
  544. package/supervisor/protocol.ts +250 -0
  545. package/supervisor/server.ts +412 -0
  546. package/supervisor/store.ts +1870 -0
  547. package/supervisor/tsconfig.json +8 -0
  548. package/supervisor/types.ts +261 -0
  549. package/SKILL.md +0 -427
  550. package/adapters/chatgpt/controller-host.ts +0 -119
  551. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  552. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  553. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  554. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  555. package/adapters/mcp/toolset-selection.ts +0 -41
  556. package/agents/openai.yaml +0 -4
  557. package/assets/skill-hooks.json +0 -41
  558. package/assets/skill-version.json +0 -189
  559. package/assets/skills/claude-review/SKILL.md +0 -231
  560. package/assets/skills/codex-review/SKILL.md +0 -103
  561. package/assets/templates/helpers/check-skill-version.ts +0 -193
  562. package/packages/kernel/progression/api/index.ts +0 -2
  563. package/packages/kernel/progression/application/projector.ts +0 -211
  564. package/packages/kernel/progression/domain/types.ts +0 -116
  565. package/packages/kernel/progression/index.ts +0 -1
  566. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  567. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  568. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  569. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  570. package/scripts/benchmark-background-check-overlap.ts +0 -228
  571. package/scripts/benchmark-computer-hotpath.ts +0 -255
  572. package/scripts/benchmark-controller-hotpath.ts +0 -212
  573. package/scripts/benchmark-controller-round2.ts +0 -191
  574. package/scripts/benchmark-ios-agent-device.ts +0 -511
  575. package/scripts/benchmark-mcp-host.sh +0 -117
  576. package/scripts/benchmark-mcp-transport.ts +0 -169
  577. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  578. package/scripts/benchmark-operational-shadow.ts +0 -28
  579. package/scripts/benchmark-quality-harness.ts +0 -146
  580. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  581. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  582. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  583. package/scripts/benchmark-thin-harness.ts +0 -251
  584. package/scripts/benchmark-typescript-navigation.ts +0 -186
  585. package/scripts/benchmark-work-efficiency.ts +0 -334
  586. package/scripts/build-controller-ui.ts +0 -28
  587. package/scripts/check-npm-release.sh +0 -32
  588. package/scripts/check-public-release-surface.sh +0 -19
  589. package/scripts/check-release-published.sh +0 -57
  590. package/scripts/check-release-readiness.sh +0 -37
  591. package/scripts/check-release-version.mjs +0 -74
  592. package/scripts/check-skill-version.ts +0 -5
  593. package/scripts/check-tarball-install-smoke.sh +0 -150
  594. package/scripts/ensure-codegraph.sh +0 -16
  595. package/scripts/package-source-archive.sh +0 -147
  596. package/scripts/public-release-files.txt +0 -39
  597. package/scripts/public-release.ts +0 -174
  598. package/scripts/publish-release-tarball.sh +0 -27
  599. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  600. package/scripts/run-skill-hook.ts +0 -244
  601. package/scripts/stage-runtime-release.ts +0 -44
  602. package/scripts/sync-codex-installed-copies.sh +0 -182
  603. package/src/cli/commands/tools.ts +0 -167
  604. package/src/cli/controller/progress.ts +0 -656
  605. package/src/cli/controller/work-mode.ts +0 -181
  606. package/src/cli/local-bridge/console-assets.ts +0 -17
  607. package/src/cli/local-bridge/dashboard.ts +0 -17
  608. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  609. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  610. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  611. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  612. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  613. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  614. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  615. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  616. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  617. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  618. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  619. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  620. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  621. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  622. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  623. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  624. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  625. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  626. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  627. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  628. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  629. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  630. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  631. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  632. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  633. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  634. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  635. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  636. package/src/cli/mcp/access-mode.ts +0 -2
  637. package/src/cli/mcp/access-tools.ts +0 -2
  638. package/src/cli/mcp/audit.ts +0 -2
  639. package/src/cli/mcp/auth.ts +0 -2
  640. package/src/cli/mcp/instructions.ts +0 -2
  641. package/src/cli/mcp/legacy-context.ts +0 -2
  642. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  643. package/src/cli/mcp/multi-repository.ts +0 -2
  644. package/src/cli/mcp/oauth.ts +0 -2
  645. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  646. package/src/cli/mcp/paths.ts +0 -2
  647. package/src/cli/mcp/policy.ts +0 -2
  648. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  649. package/src/cli/mcp/proxy-env.ts +0 -2
  650. package/src/cli/mcp/readiness-projection.ts +0 -2
  651. package/src/cli/mcp/redaction.ts +0 -2
  652. package/src/cli/mcp/repo.ts +0 -2
  653. package/src/cli/mcp/repository-tools.ts +0 -2
  654. package/src/cli/mcp/server.ts +0 -2
  655. package/src/cli/mcp/setup.ts +0 -2
  656. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  657. package/src/cli/mcp/tool-contract.ts +0 -2
  658. package/src/cli/mcp/tools.ts +0 -2
  659. package/src/cli/mcp/toolset-names.ts +0 -2
  660. package/src/cli/mcp/toolset-selection.ts +0 -2
  661. package/src/cli/mcp/toolset.ts +0 -2
  662. package/src/cli/mcp/transports/http.ts +0 -2
  663. package/src/cli/mcp/transports/session-registry.ts +0 -2
  664. package/src/cli/mcp/transports/stdio.ts +0 -2
  665. package/src/cli/mcp/types.ts +0 -2
  666. package/src/cli/mcp/umbrella-tools.ts +0 -2
  667. package/src/cli/tools/codegraph.ts +0 -695
  668. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  669. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  670. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  671. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  672. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  673. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  674. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  675. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  676. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  677. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  678. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  679. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  680. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  681. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  682. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  683. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  684. package/src/runtime/control-plane/routing/index.ts +0 -1
  685. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  686. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
  687. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  688. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  689. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  690. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  691. package/src/runtime/gateway/mcp/router.ts +0 -2
  692. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  693. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  694. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  695. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
@@ -1,2850 +0,0 @@
1
- import { listControllerChecks } from '../../../cli/controller/check-runner';
2
- import type { ControllerCheck } from '../../../cli/controller/check-runner';
3
- import type { CompletionReceipt } from '../../../cli/controller/types';
4
- import type { McpExecutionContext } from '../../../../packages/protocols/mcp/execution-context';
5
- import { globMatches } from '../../../cli/mcp/paths';
6
- import { withControllerLock } from '../../../cli/repositories/locks';
7
- import { getRepository, selectRepositoryCheckout, setRepositoryCheckoutLifecycle } from '../../../cli/repositories/registry';
8
- import { repositoryGitCommit, repositoryGitDeleteBranch, repositoryGitFinishWorkflow, repositoryGitRebaseOnto, repositoryGitStatus } from '../../../cli/repositories/structured-git';
9
- import type { RepositoryRecord } from '../../../cli/repositories/types';
10
- import { hasCurrentWorkValidationAuthority, markWorkValidationPending, projectWorkValidationOutcome } from './work-validation-reconciler';
11
- import { markRepositoryProjectionDirty } from '../../projections/invalidation';
12
- import { withControllerSessionTerminalizationFence } from '../../../../packages/kernel/controller/api/index';
13
- import type { VerificationRecord } from '../facade/types';
14
- import { appendVerificationRecord, appendWorkEvidence, listWorkContracts, reconcileApprovedWorkImplementationReviewProjection, transitionWorkContractPhase, updateWorkContract } from '../../../../packages/kernel/work/api/index';
15
- import { readRepositoryAccessPolicy } from '../governance/access-policy';
16
- import { assertResolvedAuthorization, decideAuthorization } from '../governance/authorization';
17
- import { updateExecutionSession } from './session-store';
18
- import { validateWorkHandle, WorkHandleValidationError } from './validation';
19
- import { implementationReviewContentFingerprint } from './implementation-review-content';
20
- import { transferReviewedWorkAuthorityAcrossContentEquivalentCommit } from './content-equivalent-commit-authority';
21
- import {
22
- assertImplementationReviewPreDeliveryBoundary,
23
- authoritativeImplementationReviewVerificationEvidence,
24
- latestImplementationReview,
25
- implementationReviewChangedPathDigest,
26
- normalizeImplementationReviewChangedPaths,
27
- workRequiresImplementationReview,
28
- type ImplementationReviewCandidateIdentity,
29
- } from '../../../../packages/kernel/work/api/index';
30
- import { effectiveVerificationEvidence, verificationInputFingerprint, workspaceValidationFingerprint, workValidationInputFingerprint } from './verification-evidence';
31
- import { completeWorkWithReceipt } from './work-completion-authority';
32
- import { adoptWorkHandleSuccessorCandidate, readWorkHandle, resolveWorkDeliveryTargetBranch, transitionWorkHandle, workDeliveryBaseRevision, writeWorkHandle } from './work-handle-store';
33
- import type { WorkFinalizationFailureCode, WorkFinalizationStages, WorkHandleState } from './work-handle-store';
34
- import { findWorkPathScopeViolation } from './work-path-scope';
35
- import type { WorkRemoteDeliveryReceipt } from './work-remote-delivery';
36
- import { pushExactWorkRemoteDelivery } from './work-remote-delivery';
37
- import { spawnSync } from 'child_process';
38
- import { createHash } from 'crypto';
39
- import { existsSync, realpathSync } from 'fs';
40
- import { basename, resolve } from 'path';
41
- import { assertWorkControllerOwnership, compactHandle, contractFor, gitChangedPaths, gitCommit, gitHead, gitMergeBase, gitRevision, identityFor, reconcileTerminalCleanup, releasePreparedWorkOwnership, requireSession, selectWorkFinalizationTarget, terminalCleanupOutcome, workForSession, workReturnCheckoutId } from './work-execution-support';
42
- import { gitIsAncestor } from './direct-canonical-work-reconciliation';
43
-
44
- export interface WorkTargetAdvanceInspection {
45
- relation: 'candidate_contains_target' | 'target_contains_candidate' | 'diverged_clean' | 'diverged_conflict';
46
- candidateHead: string;
47
- targetHead: string;
48
- mergeBase: string;
49
- candidateChangedPaths: string[];
50
- targetChangedPaths: string[];
51
- mergedTree?: string;
52
- detail?: string;
53
- }
54
-
55
- export function inspectWorkTargetAdvance(root: string, candidateRevision: string, targetRevision: string): WorkTargetAdvanceInspection {
56
- const candidateHead = gitCommit(root, candidateRevision, 'TARGET_ADVANCE_CANDIDATE');
57
- const targetHead = gitCommit(root, targetRevision, 'TARGET_ADVANCE_TARGET');
58
- if (gitIsAncestor(root, targetHead, candidateHead)) {
59
- return {
60
- relation: 'candidate_contains_target', candidateHead, targetHead, mergeBase: targetHead,
61
- candidateChangedPaths: gitChangedPaths(root, targetHead, candidateHead), targetChangedPaths: [],
62
- };
63
- }
64
- if (gitIsAncestor(root, candidateHead, targetHead)) {
65
- return {
66
- relation: 'target_contains_candidate', candidateHead, targetHead, mergeBase: candidateHead,
67
- candidateChangedPaths: [], targetChangedPaths: gitChangedPaths(root, candidateHead, targetHead),
68
- };
69
- }
70
- const mergeBase = gitMergeBase(root, candidateHead, targetHead);
71
- const candidateChangedPaths = gitChangedPaths(root, mergeBase, candidateHead);
72
- const targetChangedPaths = gitChangedPaths(root, mergeBase, targetHead);
73
- const preflight = spawnSync('git', ['-C', root, 'merge-tree', '--write-tree', candidateHead, targetHead], {
74
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000, maxBuffer: 512 * 1024,
75
- });
76
- if (preflight.error || (preflight.status !== 0 && preflight.status !== 1)) {
77
- throw new Error(`WORK_TARGET_ADVANCE_PREFLIGHT_UNAVAILABLE: ${preflight.error?.message ?? preflight.stderr ?? 'merge-tree failed'}`);
78
- }
79
- if (preflight.status === 0) {
80
- const mergedTree = typeof preflight.stdout === 'string' ? preflight.stdout.trim().split(/\s+/)[0] : undefined;
81
- return {
82
- relation: 'diverged_clean', candidateHead, targetHead, mergeBase, candidateChangedPaths, targetChangedPaths,
83
- ...(mergedTree ? { mergedTree } : {}),
84
- };
85
- }
86
- const detail = `${preflight.stdout ?? ''}\n${preflight.stderr ?? ''}`.trim().slice(0, 1_000);
87
- return { relation: 'diverged_conflict', candidateHead, targetHead, mergeBase, candidateChangedPaths, targetChangedPaths, ...(detail ? { detail } : {}) };
88
- }
89
-
90
- export function targetAdvanceLinearMergeCommits(root: string, targetRevision: string, candidateRevision: string): string[] {
91
- const targetHead = gitCommit(root, targetRevision, 'TARGET_ADVANCE_LINEAR_TARGET');
92
- const candidateHead = gitCommit(root, candidateRevision, 'TARGET_ADVANCE_LINEAR_CANDIDATE');
93
- const output = spawnSync('git', ['-C', root, 'rev-list', '--merges', `${targetHead}..${candidateHead}`], {
94
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000, maxBuffer: 512 * 1024,
95
- });
96
- if (output.status !== 0 || output.error || typeof output.stdout !== 'string') {
97
- throw new Error('WORK_TARGET_ADVANCE_LINEAR_HISTORY_UNAVAILABLE: could not inspect candidate merge history');
98
- }
99
- return output.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
100
- }
101
-
102
- export interface FailedNonLinearTargetAdvanceRepairInspection {
103
- eligible: boolean;
104
- reason:
105
- | 'equivalent_linear_repair'
106
- | 'same_revision'
107
- | 'target_not_ancestor_of_failed'
108
- | 'failed_candidate_is_linear'
109
- | 'target_not_ancestor_of_repair'
110
- | 'repair_contains_merge'
111
- | 'tree_mismatch';
112
- failedHead: string;
113
- repairedHead: string;
114
- targetHead: string;
115
- failedTree: string;
116
- repairedTree: string;
117
- failedMergeCommits: string[];
118
- repairedMergeCommits: string[];
119
- }
120
-
121
- function gitTree(root: string, revision: string, label: string): string {
122
- const result = spawnSync('git', ['-C', root, 'rev-parse', '--verify', `${revision}^{tree}`], {
123
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
124
- });
125
- if (result.status !== 0 || result.error || typeof result.stdout !== 'string' || !result.stdout.trim()) {
126
- throw new Error(`WORK_TARGET_ADVANCE_${label}_TREE_UNAVAILABLE: ${revision}`);
127
- }
128
- return result.stdout.trim();
129
- }
130
-
131
- /**
132
- * Inspect the only safe history-repair case after target-advance finalization
133
- * rejected a controller-created merge commit. The repaired candidate may move
134
- * backwards in commit ancestry only when it is a clean linear representation
135
- * of the exact same Git tree above the same reconciled target. This proves a
136
- * history-shape repair, not a source-content rewrite.
137
- */
138
- export function inspectFailedNonLinearTargetAdvanceRepair(
139
- root: string,
140
- failedRevision: string,
141
- repairedRevision: string,
142
- targetRevision: string,
143
- ): FailedNonLinearTargetAdvanceRepairInspection {
144
- const failedHead = gitCommit(root, failedRevision, 'FAILED_NON_LINEAR_CANDIDATE');
145
- const repairedHead = gitCommit(root, repairedRevision, 'REPAIRED_LINEAR_CANDIDATE');
146
- const targetHead = gitCommit(root, targetRevision, 'REPAIRED_LINEAR_TARGET');
147
- const failedTree = gitTree(root, failedHead, 'FAILED_NON_LINEAR');
148
- const repairedTree = gitTree(root, repairedHead, 'REPAIRED_LINEAR');
149
- const failedMergeCommits = gitIsAncestor(root, targetHead, failedHead)
150
- ? targetAdvanceLinearMergeCommits(root, targetHead, failedHead)
151
- : [];
152
- const repairedMergeCommits = gitIsAncestor(root, targetHead, repairedHead)
153
- ? targetAdvanceLinearMergeCommits(root, targetHead, repairedHead)
154
- : [];
155
- const base = { failedHead, repairedHead, targetHead, failedTree, repairedTree, failedMergeCommits, repairedMergeCommits };
156
- if (failedHead === repairedHead) return { ...base, eligible: false, reason: 'same_revision' };
157
- if (!gitIsAncestor(root, targetHead, failedHead)) return { ...base, eligible: false, reason: 'target_not_ancestor_of_failed' };
158
- if (failedMergeCommits.length === 0) return { ...base, eligible: false, reason: 'failed_candidate_is_linear' };
159
- if (!gitIsAncestor(root, targetHead, repairedHead)) return { ...base, eligible: false, reason: 'target_not_ancestor_of_repair' };
160
- if (repairedMergeCommits.length > 0) return { ...base, eligible: false, reason: 'repair_contains_merge' };
161
- if (failedTree !== repairedTree) return { ...base, eligible: false, reason: 'tree_mismatch' };
162
- return { ...base, eligible: true, reason: 'equivalent_linear_repair' };
163
- }
164
-
165
- function normalizedReadScope(scope: string): string {
166
- return scope.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '') || '.';
167
- }
168
-
169
- function pathIsWithinReadScope(path: string, scope: string): boolean {
170
- const normalized = normalizedReadScope(scope);
171
- return normalized === '.' || path === normalized || path.startsWith(`${normalized}/`);
172
- }
173
-
174
- function stableCheckDefinition(check: ControllerCheck | undefined): string | undefined {
175
- if (!check) return undefined;
176
- return JSON.stringify({
177
- id: check.id,
178
- command: check.command,
179
- cwd: check.cwd,
180
- timeoutMs: check.timeoutMs,
181
- source: check.source,
182
- effects: check.effects ?? null,
183
- });
184
- }
185
-
186
- export interface TargetAdvanceValidationTransferPlan {
187
- transferredRecords: VerificationRecord[];
188
- reusableCheckIds: string[];
189
- invalidatedCheckIds: string[];
190
- }
191
-
192
- export function planTargetAdvanceValidationAuthority(input: {
193
- checkIds: string[];
194
- checkRefs: VerificationRecord[];
195
- checksBefore: ControllerCheck[];
196
- checksAfter: ControllerCheck[];
197
- candidateHead: string;
198
- candidateWorkspaceFingerprint: string;
199
- integratedHead: string;
200
- integratedWorkspaceFingerprint: string;
201
- targetChangedPaths: string[];
202
- recordedAt?: string;
203
- }): TargetAdvanceValidationTransferPlan {
204
- const beforeById = new Map(input.checksBefore.map((check) => [check.id, check]));
205
- const afterById = new Map(input.checksAfter.map((check) => [check.id, check]));
206
- const transferredRecords: VerificationRecord[] = [];
207
- const reusableCheckIds: string[] = [];
208
- const invalidatedCheckIds: string[] = [];
209
- const recordedAt = input.recordedAt ?? new Date().toISOString();
210
- for (const checkId of input.checkIds) {
211
- const before = beforeById.get(checkId);
212
- const after = afterById.get(checkId);
213
- const reads = before?.effects?.reads;
214
- const definitionUnchanged = stableCheckDefinition(before) !== undefined
215
- && stableCheckDefinition(before) === stableCheckDefinition(after);
216
- const readsGitHistory = before?.effects?.git !== undefined;
217
- const targetTouchesReadScope = !reads || reads.length === 0
218
- || input.targetChangedPaths.some((path) => reads.some((scope) => pathIsWithinReadScope(path, scope)));
219
- const sourcePass = effectiveVerificationEvidence(input.checkRefs, {
220
- sourceRevision: input.candidateHead,
221
- workspaceFingerprint: input.candidateWorkspaceFingerprint,
222
- checkId,
223
- requestedChecks: input.checkIds,
224
- }).find((entry) => entry.current && entry.record.outcome === 'valid_pass' && Boolean(entry.record.receipt))?.record;
225
- if (!definitionUnchanged || readsGitHistory || targetTouchesReadScope || !sourcePass) {
226
- invalidatedCheckIds.push(checkId);
227
- continue;
228
- }
229
- const transferred: VerificationRecord = {
230
- ...sourcePass,
231
- summary: `Validation authority transferred across target advancement because target-only changes did not intersect check ${checkId} read inputs.`,
232
- recordedAt,
233
- sourceRevision: input.integratedHead,
234
- workspaceFingerprint: input.integratedWorkspaceFingerprint,
235
- verificationInputFingerprint: verificationInputFingerprint({
236
- sourceRevision: input.integratedHead,
237
- workspaceFingerprint: input.integratedWorkspaceFingerprint,
238
- checkId,
239
- requestedChecks: input.checkIds,
240
- }),
241
- evidenceRef: {
242
- title: checkId,
243
- summary: `Reused prior valid receipt after proving target-only path changes do not affect this check's declared inputs.`,
244
- detailLevel: 'summary',
245
- },
246
- };
247
- transferredRecords.push(transferred);
248
- reusableCheckIds.push(checkId);
249
- }
250
- return { transferredRecords, reusableCheckIds, invalidatedCheckIds };
251
- }
252
-
253
- export function targetAdvanceWorkScopeViolation(
254
- scope: { allowedPaths: string[]; forbiddenPaths: string[] } | undefined,
255
- changedPaths: string[],
256
- ): { kind: 'forbidden' | 'out_of_scope'; path: string } | undefined {
257
- if (!scope) return undefined;
258
- const violation = findWorkPathScopeViolation(scope, changedPaths);
259
- return violation ? { kind: violation.kind, path: violation.path } : undefined;
260
- }
261
-
262
- function replaceTargetAdvanceScopeEvidence(
263
- ctx: McpExecutionContext,
264
- contract: NonNullable<ReturnType<typeof contractFor>>,
265
- actualChangedPaths: string[],
266
- ): void {
267
- updateWorkContract({ controllerHome: ctx.controllerHome, repoId: contract.repoId }, contract.workId, {
268
- scopeEvidence: {
269
- initialLikelyPaths: contract.scopeEvidence?.initialLikelyPaths ?? contract.allowedPaths,
270
- inspectedPaths: contract.scopeEvidence?.inspectedPaths ?? [],
271
- actualChangedPaths: [...new Set(actualChangedPaths)].sort((left, right) => left.localeCompare(right)).slice(0, 500),
272
- recordedAt: new Date().toISOString(),
273
- },
274
- });
275
- }
276
-
277
- export interface DirectTargetDeliveryInspection {
278
- integrated: boolean;
279
- reason: 'not_direct_target' | 'path_mismatch' | 'branch_mismatch' | 'dirty' | 'revision_unavailable' | 'not_reachable' | 'integrated';
280
- expectedHead?: string;
281
- targetHead?: string;
282
- }
283
-
284
- /**
285
- * Direct-edit Work may already be executing on the delivery branch. In that
286
- * case Git merge is not a delivery operation at all: the Work commit is
287
- * integrated once its exact expected revision remains reachable from the
288
- * target branch. Keep this proof deliberately strict so a feature Work or a
289
- * dirty/mismatched checkout still follows the normal fail-closed merge path.
290
- */
291
- export function inspectDirectTargetDelivery(
292
- root: string,
293
- worktreePath: string,
294
- managedWorktree: boolean,
295
- workBranch: string,
296
- targetBranch: string,
297
- expectedRevision: string | undefined,
298
- ): DirectTargetDeliveryInspection {
299
- if (managedWorktree || workBranch !== targetBranch) return { integrated: false, reason: 'not_direct_target' };
300
- try {
301
- if (realpathSync(root) !== realpathSync(worktreePath)) return { integrated: false, reason: 'path_mismatch' };
302
- } catch {
303
- return { integrated: false, reason: 'path_mismatch' };
304
- }
305
- const branch = spawnSync('git', ['-C', root, 'branch', '--show-current'], {
306
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
307
- });
308
- if (branch.status !== 0 || typeof branch.stdout !== 'string' || branch.stdout.trim() !== targetBranch) {
309
- return { integrated: false, reason: 'branch_mismatch' };
310
- }
311
- const status = spawnSync('git', ['-C', root, 'status', '--porcelain'], {
312
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
313
- });
314
- if (status.status !== 0 || typeof status.stdout !== 'string' || status.stdout.trim()) {
315
- return { integrated: false, reason: 'dirty' };
316
- }
317
- const expectedHead = expectedRevision ? gitRevision(root, expectedRevision) : undefined;
318
- const targetHead = gitRevision(root, targetBranch);
319
- if (!expectedHead || !targetHead) return { integrated: false, reason: 'revision_unavailable' };
320
- if (!gitIsAncestor(root, expectedHead, targetHead)) {
321
- return { integrated: false, reason: 'not_reachable', expectedHead, targetHead };
322
- }
323
- return { integrated: true, reason: 'integrated', expectedHead, targetHead };
324
- }
325
-
326
- function exactCommitChangedPaths(repoRoot: string, revision: string): string[] {
327
- const result = spawnSync('git', ['-C', repoRoot, 'diff-tree', '--root', '--no-commit-id', '--name-only', '-r', revision], {
328
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
329
- });
330
- if (result.status !== 0 || result.error || typeof result.stdout !== 'string') {
331
- throw new Error(`WORK_COMPLETION_RECEIPT_COMMIT_PATHS_UNAVAILABLE: ${revision}`);
332
- }
333
- return Array.from(new Set(result.stdout.split('\n').map((entry) => entry.trim()).filter(Boolean)))
334
- .sort((left, right) => left.localeCompare(right));
335
- }
336
-
337
- function workOwnedDirtyPaths(
338
- contract: { allowedPaths: string[]; forbiddenPaths: string[] } | undefined,
339
- status: ReturnType<typeof repositoryGitStatus>,
340
- ): { dirtyPaths: string[]; ownedPaths: string[] } {
341
- const dirtyPaths = [...new Set([...status.staged, ...status.unstaged, ...status.untracked])]
342
- .sort((left, right) => left.localeCompare(right));
343
- if (!contract) return { dirtyPaths, ownedPaths: dirtyPaths };
344
- const ownedPaths = dirtyPaths.filter((path) => {
345
- if (contract.forbiddenPaths.some((pattern) => globMatches(pattern, path))) return false;
346
- return contract.allowedPaths.length === 0 || contract.allowedPaths.some((pattern) => globMatches(pattern, path));
347
- });
348
- return { dirtyPaths, ownedPaths };
349
- }
350
-
351
-
352
- export function implementationReviewCommittedBaseRevision(
353
- repository: Pick<RepositoryRecord, 'canonicalRoot' | 'defaultBranch'>,
354
- handle: Pick<WorkHandleState, 'workId' | 'managedWorktree' | 'deliveryTargetBranch' | 'baseCommit' | 'deliveryBaseCommit'> & Partial<Pick<WorkHandleState, 'state'>>,
355
- fallbackBaseRevision: string | undefined,
356
- head: string | undefined,
357
- explicitTargetBranch?: string,
358
- ): string | undefined {
359
- const base = workDeliveryBaseRevision(handle) ?? fallbackBaseRevision;
360
- if (!base || !head || !handle.managedWorktree) return base;
361
- const targetBranch = resolveWorkDeliveryTargetBranch(handle, repository.defaultBranch, explicitTargetBranch);
362
- const targetHead = gitRevision(repository.canonicalRoot, targetBranch);
363
- if (!targetHead || targetHead === base) return base;
364
- // Once the delivery target has caught up to the exact candidate HEAD, using
365
- // that target as the review baseline would erase the Work delta and make an
366
- // unchanged approved review appear stale. Preserve only the already-durable
367
- // delivery base in this exact catch-up state; without that durable fence, keep
368
- // the previous fail-closed behavior below.
369
- if (
370
- targetHead === head
371
- && (
372
- handle.state === 'merged'
373
- || (
374
- handle.deliveryBaseCommit?.trim()
375
- && handle.deliveryBaseCommit.trim() !== (handle.baseCommit?.trim() || fallbackBaseRevision?.trim())
376
- )
377
- )
378
- ) return base;
379
- // Review may exclude canonical target-only history only after proving that the
380
- // recorded delivery base advances linearly to the exact target and that this
381
- // target is already contained by the managed candidate. This is a read-only
382
- // review baseline; deliveryBaseCommit remains persisted later by the existing
383
- // target-advance delivery transition.
384
- if (!gitIsAncestor(repository.canonicalRoot, base, targetHead)) return base;
385
- if (!gitIsAncestor(repository.canonicalRoot, targetHead, head)) return base;
386
- return targetHead;
387
- }
388
-
389
- function implementationReviewCommittedChangedPaths(input: {
390
- repository: Pick<RepositoryRecord, 'canonicalRoot' | 'defaultBranch'>;
391
- handle: WorkHandleState;
392
- contract: NonNullable<ReturnType<typeof contractFor>>;
393
- head: string | undefined;
394
- explicitTargetBranch?: string;
395
- review?: NonNullable<ReturnType<typeof latestImplementationReview>>;
396
- }): string[] {
397
- const committedBase = implementationReviewCommittedBaseRevision(
398
- input.repository,
399
- input.handle,
400
- input.contract.baseRevision,
401
- input.head,
402
- input.explicitTargetBranch,
403
- );
404
- if (!input.head || !committedBase) return [];
405
- const pathsFrom = (base: string) => normalizeImplementationReviewChangedPaths(
406
- input.head === base ? [] : gitChangedPaths(input.repository.canonicalRoot, base, input.head!),
407
- );
408
- const committedPaths = pathsFrom(committedBase);
409
- if (!input.review) return committedPaths;
410
-
411
- // Review preparation, pre-delivery finalization, branch-cleanup retry, and
412
- // cleaned-completion catch-up must share one committed review-baseline
413
- // authority. The canonical target may legitimately move after review because
414
- // this candidate was merged (making the current helper base equal HEAD), or
415
- // it may already have contained the candidate before review. Only select
416
- // between the current proven helper base and the durable delivery base when
417
- // one exactly reproduces the immutable reviewed changed-path identity. A real
418
- // source/path drift therefore still fails closed in the caller.
419
- const reviewedDigest = implementationReviewChangedPathDigest(
420
- normalizeImplementationReviewChangedPaths(input.review.changedPaths),
421
- );
422
- if (implementationReviewChangedPathDigest(committedPaths) === reviewedDigest) return committedPaths;
423
-
424
- const recordedBase = workDeliveryBaseRevision(input.handle) ?? input.contract.baseRevision;
425
- if (recordedBase && recordedBase !== committedBase) {
426
- const recordedPaths = pathsFrom(recordedBase);
427
- if (implementationReviewChangedPathDigest(recordedPaths) === reviewedDigest) return recordedPaths;
428
- }
429
-
430
- // New reviews intentionally preserve the durable delivery base once target has
431
- // caught the exact candidate (implementationReviewCommittedBaseRevision). A
432
- // review already persisted by an older Runtime may, however, legitimately
433
- // carry the former exact-target-relative empty identity. Recovery may honor
434
- // that historical identity only when the current canonical target is exactly
435
- // the immutable reviewed source HEAD and its path digest matches. This branch
436
- // is review-only compatibility; it can never create a new empty review.
437
- const targetBranch = resolveWorkDeliveryTargetBranch(
438
- input.handle,
439
- input.repository.defaultBranch,
440
- input.explicitTargetBranch,
441
- );
442
- const targetHead = gitRevision(input.repository.canonicalRoot, targetBranch);
443
- if (targetHead === input.head && input.review.sourceRevision === input.head) {
444
- const exactTargetPaths = pathsFrom(targetHead);
445
- if (implementationReviewChangedPathDigest(exactTargetPaths) === reviewedDigest) return exactTargetPaths;
446
- }
447
- return committedPaths;
448
- }
449
-
450
- function currentWorkReviewChangedPaths(
451
- repository: RepositoryRecord,
452
- handle: WorkHandleState,
453
- contract: NonNullable<ReturnType<typeof contractFor>>,
454
- explicitTargetBranch?: string,
455
- ): string[] {
456
- const status = repositoryGitStatus(repository);
457
- const { dirtyPaths, ownedPaths } = workOwnedDirtyPaths(contract, status);
458
- if (dirtyPaths.length !== ownedPaths.length) {
459
- const unowned = dirtyPaths.filter((path) => !ownedPaths.includes(path));
460
- throw new Error(`WORK_IMPLEMENTATION_REVIEW_UNOWNED_DIRTY_PATH: ${unowned.join(', ')}`);
461
- }
462
- const head = status.head?.trim();
463
- const committedPaths = implementationReviewCommittedChangedPaths({
464
- repository, handle, contract, head, explicitTargetBranch,
465
- });
466
- const observed = normalizeImplementationReviewChangedPaths([...committedPaths, ...ownedPaths]);
467
- const scopeViolation = findWorkPathScopeViolation(contract, observed);
468
- if (scopeViolation) {
469
- throw new Error(`WORK_IMPLEMENTATION_REVIEW_SCOPE_VIOLATION: ${scopeViolation.kind}:${scopeViolation.path}`);
470
- }
471
- return observed;
472
- }
473
-
474
- function assertPhysicalImplementationReviewGate(input: {
475
- ctx: McpExecutionContext;
476
- repository: RepositoryRecord;
477
- handle: WorkHandleState;
478
- contract: NonNullable<ReturnType<typeof contractFor>>;
479
- /** Use the exact strict verification identity observed before repository staging. */
480
- verificationWorkspaceFingerprint?: string;
481
- targetBranch?: string;
482
- }): ImplementationReviewCandidateIdentity {
483
- const status = repositoryGitStatus(input.repository);
484
- const sourceRevision = status.head?.trim();
485
- if (!sourceRevision) throw new Error('WORK_IMPLEMENTATION_REVIEW_SOURCE_IDENTITY_REQUIRED');
486
- const changedPaths = currentWorkReviewChangedPaths(input.repository, input.handle, input.contract, input.targetBranch);
487
- const verificationWorkspaceFingerprint = input.verificationWorkspaceFingerprint?.trim()
488
- || workspaceValidationFingerprint(input.repository.canonicalRoot, status);
489
- const workspaceFingerprint = implementationReviewContentFingerprint(input.repository.canonicalRoot, changedPaths);
490
- const verification = authoritativeImplementationReviewVerificationEvidence({
491
- repoId: input.contract.repoId,
492
- workId: input.contract.workId,
493
- requiredCheckIds: input.contract.checks,
494
- records: input.contract.checkRefs,
495
- sourceRevision,
496
- workspaceFingerprint: verificationWorkspaceFingerprint,
497
- });
498
- if (verification.missingCheckIds.length > 0) {
499
- throw new Error(`WORK_IMPLEMENTATION_REVIEW_VERIFICATION_REQUIRED: ${verification.missingCheckIds.join(', ')}`);
500
- }
501
- const candidate: ImplementationReviewCandidateIdentity = {
502
- sourceRevision,
503
- workspaceFingerprint,
504
- verificationWorkspaceFingerprint,
505
- changedPaths,
506
- verificationEvidence: verification.evidence,
507
- architectureEvidence: [],
508
- };
509
- assertImplementationReviewPreDeliveryBoundary({
510
- repoId: input.contract.repoId,
511
- workId: input.contract.workId,
512
- workKind: input.contract.workKind,
513
- reviews: input.contract.implementationReviews,
514
- candidate,
515
- requiredCheckIds: input.contract.checks,
516
- verificationRecords: input.contract.checkRefs,
517
- });
518
- return candidate;
519
- }
520
-
521
- /**
522
- * A successful branch-cleanup retry can run after the reviewed worktree has
523
- * already been removed. In that state filesystem content cannot be re-hashed,
524
- * but the Work branch commit is immutable. Re-bind the shared review boundary
525
- * to that exact branch HEAD and recompute current Work-bound verification
526
- * evidence before deleting the ref. Cancelled/failed cleanup uses separate
527
- * recovery authority and never calls this successful-delivery gate.
528
- */
529
- function assertPhysicalBranchCleanupImplementationReviewGate(input: {
530
- target: RepositoryRecord;
531
- handle: WorkHandleState;
532
- contract: NonNullable<ReturnType<typeof contractFor>>;
533
- targetBranch?: string;
534
- }): void {
535
- const branchHead = gitRevision(input.target.canonicalRoot, input.handle.branch);
536
- if (!branchHead) throw new Error('WORK_IMPLEMENTATION_REVIEW_BRANCH_SOURCE_REQUIRED');
537
- if (input.handle.expectedHead && branchHead !== input.handle.expectedHead) {
538
- throw new Error('WORK_IMPLEMENTATION_REVIEW_BRANCH_SOURCE_CHANGED');
539
- }
540
- const review = latestImplementationReview(input.contract.implementationReviews);
541
- const changedPaths = implementationReviewCommittedChangedPaths({
542
- repository: input.target,
543
- handle: input.handle,
544
- contract: input.contract,
545
- head: branchHead,
546
- explicitTargetBranch: input.targetBranch,
547
- review,
548
- });
549
- if (!workRequiresImplementationReview(input.contract.workKind, changedPaths)) return;
550
- if (!review) throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRED');
551
- if (review.sourceRevision !== branchHead) throw new Error('WORK_IMPLEMENTATION_REVIEW_STALE: branch source revision changed');
552
- const verification = authoritativeImplementationReviewVerificationEvidence({
553
- repoId: input.contract.repoId,
554
- workId: input.contract.workId,
555
- requiredCheckIds: input.contract.checks,
556
- records: input.contract.checkRefs,
557
- sourceRevision: branchHead,
558
- workspaceFingerprint: review.verificationWorkspaceFingerprint,
559
- });
560
- if (verification.missingCheckIds.length > 0) {
561
- throw new Error(`WORK_IMPLEMENTATION_REVIEW_VERIFICATION_REQUIRED: ${verification.missingCheckIds.join(', ')}`);
562
- }
563
- assertImplementationReviewPreDeliveryBoundary({
564
- repoId: input.contract.repoId,
565
- workId: input.contract.workId,
566
- workKind: input.contract.workKind,
567
- reviews: input.contract.implementationReviews,
568
- candidate: {
569
- sourceRevision: branchHead,
570
- workspaceFingerprint: review.workspaceFingerprint,
571
- verificationWorkspaceFingerprint: review.verificationWorkspaceFingerprint,
572
- changedPaths,
573
- verificationEvidence: verification.evidence,
574
- architectureEvidence: review.architectureEvidence,
575
- },
576
- requiredCheckIds: input.contract.checks,
577
- verificationRecords: input.contract.checkRefs,
578
- });
579
- }
580
-
581
- /**
582
- * Re-prove review authority from immutable Git + durable review/check identity
583
- * after successful managed-worktree cleanup. This path never recreates a
584
- * checkout and never authorizes commit/merge; it only allows a missing semantic
585
- * completion receipt to catch up with already-durable physical finalization.
586
- */
587
- function assertCleanedCompletionImplementationReviewGate(input: {
588
- target: RepositoryRecord;
589
- handle: WorkHandleState;
590
- contract: NonNullable<ReturnType<typeof contractFor>>;
591
- }): NonNullable<ReturnType<typeof latestImplementationReview>> {
592
- const deliveryRevision = input.handle.expectedHead ?? input.handle.baseCommit;
593
- if (!deliveryRevision) throw new Error('WORK_IMPLEMENTATION_REVIEW_SOURCE_IDENTITY_REQUIRED');
594
- const review = latestImplementationReview(input.contract.implementationReviews);
595
- if (!review) throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRED');
596
- if (review.sourceRevision !== deliveryRevision) {
597
- throw new Error('WORK_IMPLEMENTATION_REVIEW_STALE: cleaned delivery source revision changed');
598
- }
599
- const changedPaths = implementationReviewCommittedChangedPaths({
600
- repository: input.target, handle: input.handle, contract: input.contract, head: deliveryRevision, review,
601
- });
602
- const reviewedPaths = normalizeImplementationReviewChangedPaths(review.changedPaths);
603
- if (implementationReviewChangedPathDigest(reviewedPaths) !== implementationReviewChangedPathDigest(changedPaths)) {
604
- throw new Error('WORK_IMPLEMENTATION_REVIEW_STALE: cleaned delivery changed-path identity changed');
605
- }
606
- const verification = authoritativeImplementationReviewVerificationEvidence({
607
- repoId: input.contract.repoId,
608
- workId: input.contract.workId,
609
- requiredCheckIds: input.contract.checks,
610
- records: input.contract.checkRefs,
611
- sourceRevision: deliveryRevision,
612
- workspaceFingerprint: review.verificationWorkspaceFingerprint,
613
- });
614
- if (verification.missingCheckIds.length > 0) {
615
- throw new Error(`WORK_IMPLEMENTATION_REVIEW_VERIFICATION_REQUIRED: ${verification.missingCheckIds.join(', ')}`);
616
- }
617
- assertImplementationReviewPreDeliveryBoundary({
618
- repoId: input.contract.repoId,
619
- workId: input.contract.workId,
620
- workKind: input.contract.workKind,
621
- reviews: input.contract.implementationReviews,
622
- candidate: {
623
- sourceRevision: deliveryRevision,
624
- workspaceFingerprint: review.workspaceFingerprint,
625
- verificationWorkspaceFingerprint: review.verificationWorkspaceFingerprint,
626
- changedPaths,
627
- verificationEvidence: verification.evidence,
628
- architectureEvidence: review.architectureEvidence,
629
- },
630
- requiredCheckIds: input.contract.checks,
631
- verificationRecords: input.contract.checkRefs,
632
- });
633
- return review;
634
- }
635
-
636
-
637
- export interface TargetDirtyWorkOwnershipInspection {
638
- owned: boolean;
639
- dirtyPaths: string[];
640
- owners: Record<string, string>;
641
- unownedPaths: string[];
642
- ambiguousPaths: string[];
643
- }
644
-
645
- /**
646
- * Positive ownership proof for dirty content preserved in a shared canonical
647
- * target checkout. Exact observed Work scope wins; a unique explicit allow-list
648
- * is the fallback. Repository-wide/legacy scope is never enough to move another
649
- * Work's dirty content through target integration.
650
- */
651
- export function inspectTargetDirtyWorkOwnership(input: {
652
- dirtyPaths: string[];
653
- targetCheckoutId: string;
654
- currentWorkId: string;
655
- activeWorks: Array<{
656
- workId: string;
657
- checkoutId?: string;
658
- allowedPaths: string[];
659
- forbiddenPaths: string[];
660
- scopeEvidence?: { actualChangedPaths: string[] };
661
- }>;
662
- }): TargetDirtyWorkOwnershipInspection {
663
- const dirtyPaths = [...new Set(input.dirtyPaths.map((path) => path.trim()).filter(Boolean))].sort();
664
- const candidates = input.activeWorks.filter((work) =>
665
- work.workId !== input.currentWorkId
666
- && work.checkoutId === input.targetCheckoutId,
667
- );
668
- const owners: Record<string, string> = {};
669
- const unownedPaths: string[] = [];
670
- const ambiguousPaths: string[] = [];
671
- for (const path of dirtyPaths) {
672
- const exactOwners = candidates.filter((work) => work.scopeEvidence?.actualChangedPaths?.includes(path));
673
- const scopedOwners = exactOwners.length > 0 ? exactOwners : candidates.filter((work) =>
674
- work.allowedPaths.length > 0
675
- && !work.forbiddenPaths.some((pattern) => globMatches(pattern, path))
676
- && work.allowedPaths.some((pattern) => globMatches(pattern, path)),
677
- );
678
- const ownerIds = [...new Set(scopedOwners.map((work) => work.workId))].sort();
679
- if (ownerIds.length === 1) owners[path] = ownerIds[0];
680
- else if (ownerIds.length === 0) unownedPaths.push(path);
681
- else ambiguousPaths.push(path);
682
- }
683
- return {
684
- owned: dirtyPaths.length > 0 && unownedPaths.length === 0 && ambiguousPaths.length === 0,
685
- dirtyPaths,
686
- owners,
687
- unownedPaths,
688
- ambiguousPaths,
689
- };
690
- }
691
-
692
- export interface TargetDirtyPreservationPlan {
693
- preserveDirtyTargetPaths: string[];
694
- ownership: TargetDirtyWorkOwnershipInspection;
695
- }
696
-
697
- /**
698
- * Dirty target content is a Git preservation concern first. Semantic Work
699
- * ownership remains useful provenance, but it is not required when the
700
- * integration candidate is disjoint. repositoryGitFinishWorkflow owns the
701
- * exact overlap fence and fails before target mutation when a preserved path
702
- * conflicts with the candidate.
703
- */
704
- export function planTargetDirtyPreservation(input: Parameters<typeof inspectTargetDirtyWorkOwnership>[0]): TargetDirtyPreservationPlan {
705
- const ownership = inspectTargetDirtyWorkOwnership(input);
706
- return {
707
- preserveDirtyTargetPaths: ownership.dirtyPaths,
708
- ownership,
709
- };
710
- }
711
-
712
- export interface DirectCanonicalTargetAdvanceInspection {
713
- reconcilable: boolean;
714
- reason:
715
- | 'not_direct_target'
716
- | 'path_mismatch'
717
- | 'branch_mismatch'
718
- | 'revision_unavailable'
719
- | 'target_not_current'
720
- | 'not_descendant'
721
- | 'explicit_scope_required'
722
- | 'owned_dirty_required'
723
- | 'unrelated_dirty_paths'
724
- | 'target_touches_work_path'
725
- | 'fresh_verification_required'
726
- | 'fresh_verification_missing'
727
- | 'reconcilable';
728
- previousHead?: string;
729
- targetHead?: string;
730
- dirtyPaths: string[];
731
- targetChangedPaths: string[];
732
- workspaceFingerprint?: string;
733
- freshCheckIds: string[];
734
- }
735
-
736
- /**
737
- * Direct canonical Work is intentionally stricter than managed-worktree delivery:
738
- * the shared checkout may contain preserved dirty content owned by another Work.
739
- * Reconcile a stale expectedHead only when the target advanced linearly, every
740
- * current dirty path is positively owned by this Work, target-only commits do not
741
- * touch those dirty paths, and exact current source/workspace verification exists.
742
- * The caller may then move only delivery identity; this function never attributes
743
- * target-only commits or unrelated dirty paths to the Work.
744
- */
745
- export function inspectDirectCanonicalTargetAdvanceReconciliation(input: {
746
- root: string;
747
- worktreePath: string;
748
- managedWorktree: boolean;
749
- workBranch: string;
750
- targetBranch: string;
751
- expectedRevision?: string;
752
- status: ReturnType<typeof repositoryGitStatus>;
753
- scope?: { allowedPaths: string[]; forbiddenPaths: string[] };
754
- checkIds: string[];
755
- checkRefs: VerificationRecord[];
756
- }): DirectCanonicalTargetAdvanceInspection {
757
- const empty = (reason: DirectCanonicalTargetAdvanceInspection['reason']): DirectCanonicalTargetAdvanceInspection => ({
758
- reconcilable: false,
759
- reason,
760
- dirtyPaths: [],
761
- targetChangedPaths: [],
762
- freshCheckIds: [],
763
- });
764
- if (input.managedWorktree || input.workBranch !== input.targetBranch) return empty('not_direct_target');
765
- try {
766
- if (realpathSync(input.root) !== realpathSync(input.worktreePath)) return empty('path_mismatch');
767
- } catch {
768
- return empty('path_mismatch');
769
- }
770
- const branch = spawnSync('git', ['-C', input.root, 'branch', '--show-current'], {
771
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
772
- });
773
- if (branch.status !== 0 || typeof branch.stdout !== 'string' || branch.stdout.trim() !== input.targetBranch) {
774
- return empty('branch_mismatch');
775
- }
776
- const previousHead = input.expectedRevision ? gitRevision(input.root, input.expectedRevision) : undefined;
777
- const targetHead = gitRevision(input.root, input.targetBranch);
778
- const currentHead = input.status.head ? gitRevision(input.root, input.status.head) : undefined;
779
- if (!previousHead || !targetHead || !currentHead) return empty('revision_unavailable');
780
- const base = {
781
- previousHead,
782
- targetHead,
783
- dirtyPaths: [] as string[],
784
- targetChangedPaths: [] as string[],
785
- freshCheckIds: [] as string[],
786
- };
787
- if (targetHead !== currentHead) return { ...base, reconcilable: false, reason: 'target_not_current' };
788
- if (targetHead === previousHead || !gitIsAncestor(input.root, previousHead, targetHead)) {
789
- return { ...base, reconcilable: false, reason: 'not_descendant' };
790
- }
791
- // Positive allow-list ownership is required here. An empty allow-list means
792
- // repository-wide scope and cannot distinguish another Work's preserved dirty
793
- // content in a shared canonical checkout.
794
- if (!input.scope || input.scope.allowedPaths.length === 0) {
795
- return { ...base, reconcilable: false, reason: 'explicit_scope_required' };
796
- }
797
- const { dirtyPaths, ownedPaths } = workOwnedDirtyPaths(input.scope, input.status);
798
- const dirtyBase = { ...base, dirtyPaths };
799
- if (dirtyPaths.length === 0 || ownedPaths.length === 0) {
800
- return { ...dirtyBase, reconcilable: false, reason: 'owned_dirty_required' };
801
- }
802
- if (ownedPaths.length !== dirtyPaths.length) {
803
- return { ...dirtyBase, reconcilable: false, reason: 'unrelated_dirty_paths' };
804
- }
805
- const targetChangedPaths = gitChangedPaths(input.root, previousHead, targetHead);
806
- const targetBase = { ...dirtyBase, targetChangedPaths };
807
- const dirtySet = new Set(dirtyPaths);
808
- if (targetChangedPaths.some((path) => dirtySet.has(path))) {
809
- return { ...targetBase, reconcilable: false, reason: 'target_touches_work_path' };
810
- }
811
- const workspaceFingerprint = workspaceValidationFingerprint(input.root, input.status);
812
- const declaredCheckIds = [...new Set(input.checkIds)].sort((left, right) => left.localeCompare(right));
813
- const candidateCheckIds = declaredCheckIds.length > 0
814
- ? declaredCheckIds
815
- : [...new Set(input.checkRefs.map((record) => record.checkId).filter(Boolean))].sort((left, right) => left.localeCompare(right));
816
- if (candidateCheckIds.length === 0) {
817
- return { ...targetBase, workspaceFingerprint, reconcilable: false, reason: 'fresh_verification_required' };
818
- }
819
- const freshCheckIds = candidateCheckIds.filter((checkId) => effectiveVerificationEvidence(input.checkRefs, {
820
- sourceRevision: targetHead,
821
- workspaceFingerprint,
822
- checkId,
823
- // Contract-declared checks share one validation-input identity. When the
824
- // contract has no declared checks, rh_work verify can still append an
825
- // explicit focused check receipt; such evidence is bound to its one-check
826
- // request and must be evaluated with that exact requested-check set.
827
- requestedChecks: declaredCheckIds.length > 0 ? declaredCheckIds : [checkId],
828
- }).some((entry) => entry.current
829
- && entry.record.outcome === 'valid_pass'
830
- && entry.record.receipt?.ok === true
831
- && entry.record.receipt.status === 'passed'
832
- && entry.record.receipt.runtimeStatus === 'succeeded'));
833
- const freshVerificationSatisfied = declaredCheckIds.length > 0
834
- ? freshCheckIds.length === declaredCheckIds.length
835
- : freshCheckIds.length > 0;
836
- if (!freshVerificationSatisfied) {
837
- return { ...targetBase, workspaceFingerprint, freshCheckIds, reconcilable: false, reason: 'fresh_verification_missing' };
838
- }
839
- return {
840
- ...targetBase,
841
- workspaceFingerprint,
842
- freshCheckIds,
843
- reconcilable: true,
844
- reason: 'reconcilable',
845
- };
846
- }
847
-
848
- export interface DirectCanonicalTargetAdvanceReconciliationCommandResult {
849
- reconciled: boolean;
850
- handle: WorkHandleState;
851
- inspection: DirectCanonicalTargetAdvanceInspection;
852
- }
853
-
854
- /**
855
- * Application command for advancing Direct canonical Work delivery identity.
856
- * The pure inspector remains the admission authority; this command is the only
857
- * place shared review/finalization callers persist the approved identity move
858
- * and its evidence. Gateway callers consume the returned handle and never write
859
- * lifecycle/evidence records themselves.
860
- */
861
- export function reconcileDirectCanonicalTargetAdvanceCommand(input: {
862
- controllerHome: string;
863
- repoId: string;
864
- workId: string;
865
- handle: WorkHandleState;
866
- root: string;
867
- targetBranch: string;
868
- status: ReturnType<typeof repositoryGitStatus>;
869
- scope?: { allowedPaths: string[]; forbiddenPaths: string[] };
870
- checkIds: string[];
871
- checkRefs: VerificationRecord[];
872
- persistHandle?: (patch: { deliveryBaseCommit: string; expectedHead: string; failureReason: undefined }) => WorkHandleState;
873
- evidenceTitle?: string;
874
- }): DirectCanonicalTargetAdvanceReconciliationCommandResult {
875
- if (input.handle.repositoryId !== input.repoId) {
876
- throw new Error(`WORK_DIRECT_TARGET_ADVANCE_REPOSITORY_MISMATCH: expected ${input.handle.repositoryId}, found ${input.repoId}`);
877
- }
878
- const boundWorkId = input.handle.workContractId ?? input.handle.workId;
879
- if (boundWorkId !== input.workId) {
880
- throw new Error(`WORK_DIRECT_TARGET_ADVANCE_IDENTITY_MISMATCH: expected ${boundWorkId}, found ${input.workId}`);
881
- }
882
- const inspection = inspectDirectCanonicalTargetAdvanceReconciliation({
883
- root: input.root,
884
- worktreePath: input.handle.worktreePath,
885
- managedWorktree: input.handle.managedWorktree,
886
- workBranch: input.handle.branch,
887
- targetBranch: input.targetBranch,
888
- expectedRevision: input.handle.expectedHead,
889
- status: input.status,
890
- scope: input.scope,
891
- checkIds: input.checkIds,
892
- checkRefs: input.checkRefs,
893
- });
894
- if (!inspection.reconcilable || !inspection.targetHead) {
895
- return { reconciled: false, handle: input.handle, inspection };
896
- }
897
-
898
- const previousHead = input.handle.expectedHead;
899
- const patch = {
900
- deliveryBaseCommit: inspection.targetHead,
901
- expectedHead: inspection.targetHead,
902
- failureReason: undefined,
903
- } as const;
904
- const handle = input.persistHandle
905
- ? input.persistHandle(patch)
906
- : transitionWorkHandle(input.controllerHome, input.handle, input.handle.state, patch);
907
- appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
908
- title: input.evidenceTitle ?? 'direct canonical target advancement reconciled',
909
- summary: `Canonical ${input.targetBranch} advanced linearly ${previousHead} -> ${inspection.targetHead}. Delivery identity adopted only the target revision after proving all ${inspection.dirtyPaths.length} dirty path(s) are Work-owned, ${inspection.targetChangedPaths.length} target-only path(s) are disjoint, and fresh exact verification bound [${inspection.freshCheckIds.join(', ')}] to source ${inspection.targetHead} plus workspace ${inspection.workspaceFingerprint?.slice(0, 16) ?? 'unknown'}.`,
910
- detailLevel: 'summary',
911
- });
912
- return { reconciled: true, handle, inspection };
913
- }
914
-
915
- export interface ManagedWorkSuccessorAdoptionInspection {
916
- adoptable: boolean;
917
- reason:
918
- | 'not_managed_worktree'
919
- | 'path_mismatch'
920
- | 'branch_mismatch'
921
- | 'workspace_dirty'
922
- | 'revision_unavailable'
923
- | 'no_head_change'
924
- | 'descendant_progress'
925
- | 'target_not_advanced'
926
- | 'target_history_rewritten'
927
- | 'target_not_candidate_ancestor'
928
- | 'non_linear_candidate'
929
- | 'scope_violation'
930
- | 'adoptable';
931
- previousHead?: string;
932
- previousDeliveryBase?: string;
933
- candidateHead?: string;
934
- targetHead?: string;
935
- candidateChangedPaths: string[];
936
- detail?: string;
937
- }
938
-
939
- /**
940
- * Inspect a manually conflict-repaired managed Work candidate before changing
941
- * WorkHandle authority. This is intentionally narrower than ordinary
942
- * descendant progress: canonical target must have advanced linearly from the
943
- * recorded delivery base, and the rewritten clean candidate must contain that
944
- * exact target with only Work-scoped target-relative changes.
945
- */
946
- export function inspectManagedWorkSuccessorAdoption(input: {
947
- root: string;
948
- worktreePath: string;
949
- managedWorktree: boolean;
950
- workBranch: string;
951
- targetBranch: string;
952
- expectedRevision?: string;
953
- deliveryBaseRevision?: string;
954
- status: ReturnType<typeof repositoryGitStatus>;
955
- scope?: { allowedPaths: string[]; forbiddenPaths: string[] };
956
- }): ManagedWorkSuccessorAdoptionInspection {
957
- const empty = (reason: ManagedWorkSuccessorAdoptionInspection['reason'], detail?: string): ManagedWorkSuccessorAdoptionInspection => ({
958
- adoptable: false,
959
- reason,
960
- candidateChangedPaths: [],
961
- ...(detail ? { detail } : {}),
962
- });
963
- if (!input.managedWorktree) return empty('not_managed_worktree');
964
- try {
965
- if (realpathSync(input.root) !== realpathSync(input.worktreePath)) return empty('path_mismatch');
966
- } catch {
967
- return empty('path_mismatch');
968
- }
969
- const branch = spawnSync('git', ['-C', input.root, 'branch', '--show-current'], {
970
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
971
- });
972
- if (branch.status !== 0 || String(branch.stdout ?? '').trim() !== input.workBranch) return empty('branch_mismatch');
973
- if (!input.status.clean) return empty('workspace_dirty');
974
-
975
- const previousHead = input.expectedRevision ? gitRevision(input.root, input.expectedRevision) : undefined;
976
- const previousDeliveryBase = input.deliveryBaseRevision ? gitRevision(input.root, input.deliveryBaseRevision) : undefined;
977
- const candidateHead = input.status.head ? gitRevision(input.root, input.status.head) : undefined;
978
- const targetHead = gitRevision(input.root, input.targetBranch);
979
- if (!previousHead || !previousDeliveryBase || !candidateHead || !targetHead) return empty('revision_unavailable');
980
- const base = { previousHead, previousDeliveryBase, candidateHead, targetHead, candidateChangedPaths: [] as string[] };
981
- if (candidateHead === previousHead) return { ...base, adoptable: false, reason: 'no_head_change' };
982
- if (gitIsAncestor(input.root, previousHead, candidateHead)) return { ...base, adoptable: false, reason: 'descendant_progress' };
983
- if (targetHead === previousDeliveryBase) return { ...base, adoptable: false, reason: 'target_not_advanced' };
984
- if (!gitIsAncestor(input.root, previousDeliveryBase, targetHead)) {
985
- return { ...base, adoptable: false, reason: 'target_history_rewritten' };
986
- }
987
- if (!gitIsAncestor(input.root, targetHead, candidateHead)) {
988
- return { ...base, adoptable: false, reason: 'target_not_candidate_ancestor' };
989
- }
990
- const mergeCommits = targetAdvanceLinearMergeCommits(input.root, targetHead, candidateHead);
991
- if (mergeCommits.length > 0) {
992
- return { ...base, adoptable: false, reason: 'non_linear_candidate', detail: mergeCommits.slice(0, 8).join(', ') };
993
- }
994
- const candidateChangedPaths = gitChangedPaths(input.root, targetHead, candidateHead);
995
- if (!input.scope) {
996
- return { ...base, candidateChangedPaths, adoptable: false, reason: 'scope_violation', detail: 'WorkContract scope unavailable' };
997
- }
998
- const scopeViolation = findWorkPathScopeViolation(input.scope, candidateChangedPaths);
999
- if (scopeViolation) {
1000
- return {
1001
- ...base,
1002
- candidateChangedPaths,
1003
- adoptable: false,
1004
- reason: 'scope_violation',
1005
- detail: `${scopeViolation.kind}:${scopeViolation.path}`,
1006
- };
1007
- }
1008
- return { ...base, candidateChangedPaths, adoptable: true, reason: 'adoptable' };
1009
- }
1010
-
1011
- export function completionReceiptChangedPaths(repoRoot: string, baseRevision: string, deliveryRevision: string): string[] {
1012
- const result = spawnSync('git', ['-C', repoRoot, 'diff', '--name-only', `${baseRevision}..${deliveryRevision}`], {
1013
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1014
- });
1015
- if (result.status !== 0 || result.error || typeof result.stdout !== 'string') {
1016
- throw new Error(`WORK_COMPLETION_RECEIPT_CHANGED_PATHS_UNAVAILABLE: ${baseRevision}..${deliveryRevision}`);
1017
- }
1018
- return Array.from(new Set(result.stdout.split('\n').map((entry) => entry.trim()).filter(Boolean)))
1019
- .sort((left, right) => left.localeCompare(right));
1020
- }
1021
-
1022
- function completionReceiptForFinalizedWork(
1023
- ctx: McpExecutionContext,
1024
- handle: WorkHandleState,
1025
- contract: ReturnType<typeof contractFor>,
1026
- args: Record<string, unknown>,
1027
- ): CompletionReceipt {
1028
- const repository = getRepository(handle.repositoryId, ctx.controllerHome, { includeRemoved: true });
1029
- const target = selectWorkFinalizationTarget(repository, handle);
1030
- const targetBranch = resolveWorkDeliveryTargetBranch(
1031
- handle,
1032
- repository.defaultBranch,
1033
- typeof args.target_branch === 'string' ? args.target_branch : undefined,
1034
- );
1035
- const targetRevision = gitRevision(target.canonicalRoot, targetBranch);
1036
- if (!targetRevision) throw new Error(`WORK_COMPLETION_RECEIPT_TARGET_REQUIRED: target branch ${targetBranch} is unavailable`);
1037
- const noChange = args.completion_outcome === 'completed_no_change';
1038
- // Delivery authority is always the Work's own revision, never the target
1039
- // branch's newest HEAD. The target may have advanced because of unrelated
1040
- // concurrent Work; targetRevision records integration identity, while
1041
- // deliveryRevision owns path attribution and reachability proof.
1042
- const deliveryRevision = handle.expectedHead ?? handle.baseCommit;
1043
- if (!deliveryRevision) throw new Error('WORK_COMPLETION_RECEIPT_SOURCE_REQUIRED: Work delivery revision is unavailable');
1044
- const reachable = deliveryRevision === targetRevision || spawnSync('git', ['-C', target.canonicalRoot, 'merge-base', '--is-ancestor', deliveryRevision, targetBranch], {
1045
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1046
- }).status === 0;
1047
- if (!reachable) throw new Error(`WORK_COMPLETION_RECEIPT_DELIVERY_NOT_PROVEN: ${deliveryRevision} is not reachable from ${targetBranch}`);
1048
- const deliveryBaseRevision = workDeliveryBaseRevision(handle);
1049
- const changedPaths = noChange
1050
- ? []
1051
- : !handle.managedWorktree && handle.finalization.commit === 'done'
1052
- ? exactCommitChangedPaths(target.canonicalRoot, deliveryRevision)
1053
- : deliveryBaseRevision
1054
- ? completionReceiptChangedPaths(target.canonicalRoot, deliveryBaseRevision, deliveryRevision)
1055
- : (() => { throw new Error('WORK_COMPLETION_RECEIPT_BASE_REQUIRED: Work delivery provenance base is unavailable'); })();
1056
- const recordedAt = new Date().toISOString();
1057
- const retainsExplicitDeliveryBranch = args.merge !== true && targetBranch === handle.branch;
1058
- const warnings = [
1059
- ...(handle.finalization.branchCleanup === 'skipped' && !retainsExplicitDeliveryBranch ? [{ code: 'cleanup_retained_by_request' as const, message: 'Branch cleanup was skipped by the finalization request.', resourceKind: 'branch' as const, resourceId: handle.branch, recordedAt }] : []),
1060
- ...(handle.finalization.worktreeCleanup === 'skipped' && handle.managedWorktree ? [{ code: 'cleanup_retained_by_request' as const, message: 'Managed worktree cleanup was skipped by the finalization request.', resourceKind: 'worktree' as const, resourceId: handle.worktreePath, recordedAt }] : []),
1061
- ];
1062
- return {
1063
- schemaVersion: 1,
1064
- receiptId: `REC-controller_work-${createHash('sha256').update(`${handle.workId}\0${targetRevision}`).digest('hex').slice(0, 16)}`,
1065
- source: 'controller_work',
1066
- issueId: contract?.issueId ?? 'work',
1067
- taskId: contract?.taskId ?? handle.workId,
1068
- workId: handle.workId,
1069
- targetBranch,
1070
- targetRevision,
1071
- sourceRevision: deliveryRevision,
1072
- baseRevision: handle.baseCommit,
1073
- changedPaths,
1074
- delivery: {
1075
- kind: noChange ? 'no_change' : 'commit',
1076
- status: 'integrated',
1077
- strategy: noChange ? 'no_change' : handle.finalization.commit === 'done' ? 'edit_session_commit' : 'already_integrated',
1078
- reachable: true,
1079
- recordedAt,
1080
- },
1081
- cleanup: {
1082
- status: warnings.length > 0 ? 'maintenance_warning' : 'complete',
1083
- warnings,
1084
- blockers: [],
1085
- recordedAt,
1086
- },
1087
- verifiedAt: recordedAt,
1088
- recordedAt,
1089
- };
1090
- }
1091
-
1092
- function runCleanup(targetRoot: string, worktreePath: string): { ok: boolean; message?: string } {
1093
- if (targetRoot === worktreePath) return { ok: true };
1094
- if (!existsSync(worktreePath)) return { ok: true, message: 'managed worktree already removed' };
1095
- const status = repositoryGitStatus({ repoId: 'cleanup', activeCheckoutId: 'cleanup', canonicalRoot: worktreePath, localRoot: worktreePath, checkouts: [], schemaVersion: 1, displayName: basename(worktreePath), repositoryType: 'git', enabled: true, createdAt: '', updatedAt: '', lastSeenAt: '', configurationPath: '', stateStorageStrategy: 'controller-home' });
1096
- if (!status.clean) return { ok: false, message: 'managed worktree is dirty; cleanup preserved it' };
1097
- const process = spawnSync('git', ['-C', targetRoot, 'worktree', 'remove', worktreePath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 30_000 });
1098
- return process.status === 0 ? { ok: true } : { ok: false, message: String(process.stderr ?? 'git worktree remove failed').trim() };
1099
- }
1100
-
1101
- /**
1102
- * Reconcile a stale Work Handle only for a cleanup-only request whose exact
1103
- * branch HEAD is already reachable from the requested target branch.
1104
- *
1105
- * Two bounded recovery cases are supported:
1106
- * 1. an older controller committed/merged successfully but failed before
1107
- * recording the finalization stages; and
1108
- * 2. a terminal cancelled WorkContract owns an unchanged, clean duplicate
1109
- * worktree created by an idempotency defect.
1110
- *
1111
- * Missing worktrees are accepted only when this handle already recorded a
1112
- * successful worktree cleanup. This lets a retry finish branch cleanup after a
1113
- * crash without turning a missing checkout into proof of safety.
1114
- */
1115
- export function inspectCleanupOnlyMergedHead(
1116
- ctx: McpExecutionContext,
1117
- current: WorkHandleState,
1118
- args: Record<string, unknown>,
1119
- ): { currentHead: string; cancelledContract: boolean; worktreeMissing: boolean } | undefined {
1120
- if (args.cleanup !== true || args.commit === true || args.merge === true || !current.managedWorktree) return undefined;
1121
- const contract = contractFor(ctx, current);
1122
- const cancelledContract = contract?.status === 'cancelled';
1123
-
1124
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1125
- const target = selectWorkFinalizationTarget(repository, current);
1126
- const targetBranch = resolveWorkDeliveryTargetBranch(
1127
- current,
1128
- repository.defaultBranch,
1129
- typeof args.target_branch === 'string' ? args.target_branch : undefined,
1130
- );
1131
- const branchHeadResult = spawnSync('git', ['-C', target.canonicalRoot, 'rev-parse', '--verify', `refs/heads/${current.branch}`], {
1132
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1133
- });
1134
- const branchHead = branchHeadResult.status === 0 ? String(branchHeadResult.stdout ?? '').trim() : '';
1135
- // A previous cleanup attempt may already have deleted the Work branch before
1136
- // completion-receipt persistence failed. Only the exact durable expectedHead
1137
- // may replace that missing ref, and only after branchCleanup itself is durably
1138
- // recorded done.
1139
- const currentHead = branchHead
1140
- || (current.finalization.branchCleanup === 'done' ? current.expectedHead ?? '' : '');
1141
- if (!currentHead) return undefined;
1142
- const merged = spawnSync('git', ['-C', target.canonicalRoot, 'merge-base', '--is-ancestor', currentHead, targetBranch], {
1143
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1144
- });
1145
- if (merged.status !== 0) return undefined;
1146
-
1147
- const worktreeMissing = !existsSync(current.worktreePath);
1148
- if (worktreeMissing) {
1149
- const integratedDeliveryRecorded = current.state === 'merged'
1150
- || current.state === 'cleaned'
1151
- || current.finalization.merge === 'done';
1152
- if (current.finalization.worktreeCleanup !== 'done') return undefined;
1153
- if (!cancelledContract && !integratedDeliveryRecorded) return undefined;
1154
- return { currentHead, cancelledContract, worktreeMissing: true };
1155
- }
1156
-
1157
- const worktree = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
1158
- if (!repositoryGitStatus(worktree).clean) return undefined;
1159
- const worktreeHead = gitHead(worktree.canonicalRoot);
1160
- if (!worktreeHead || worktreeHead !== currentHead) return undefined;
1161
- const currentBranch = spawnSync('git', ['-C', worktree.canonicalRoot, 'branch', '--show-current'], {
1162
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1163
- });
1164
- if (currentBranch.status !== 0 || String(currentBranch.stdout ?? '').trim() !== current.branch) return undefined;
1165
- // Cleanup reconciliation proves the exact Work-owned revision, regardless of
1166
- // terminal contract status. A same-named branch may be advanced later by a
1167
- // different controller or manual Git operation; reachability from the target
1168
- // is not authority to adopt that newer HEAD as this Work's delivery.
1169
- if (current.expectedHead && currentHead !== current.expectedHead) return undefined;
1170
- return { currentHead, cancelledContract, worktreeMissing: false };
1171
- }
1172
-
1173
- interface FailedCleanupProof {
1174
- currentHead: string;
1175
- worktreeMissing: boolean;
1176
- targetBranch: string;
1177
- }
1178
-
1179
- /**
1180
- * Prove that a failed Work owns an unchanged, clean managed worktree whose
1181
- * branch is already contained in the target branch. This authorizes resource
1182
- * cleanup only; it never proves successful verification or delivery.
1183
- */
1184
- function failedCleanupOnlyHead(
1185
- ctx: McpExecutionContext,
1186
- current: WorkHandleState,
1187
- args: Record<string, unknown>,
1188
- ): FailedCleanupProof | undefined {
1189
- if (
1190
- current.state !== 'failed'
1191
- || args.cleanup !== true
1192
- || args.commit === true
1193
- || args.merge === true
1194
- || !current.managedWorktree
1195
- ) return undefined;
1196
- const contract = contractFor(ctx, current);
1197
- if (contract?.status !== 'failed') return undefined;
1198
-
1199
- const expectedHead = current.expectedHead ?? current.baseCommit;
1200
- if (!expectedHead) return undefined;
1201
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1202
- const target = selectWorkFinalizationTarget(repository, current);
1203
- const targetBranch = resolveWorkDeliveryTargetBranch(
1204
- current,
1205
- repository.defaultBranch,
1206
- typeof args.target_branch === 'string' ? args.target_branch : undefined,
1207
- );
1208
- const merged = spawnSync('git', ['-C', target.canonicalRoot, 'merge-base', '--is-ancestor', expectedHead, targetBranch], {
1209
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1210
- });
1211
- if (merged.status !== 0) return undefined;
1212
-
1213
- const worktreeMissing = !existsSync(current.worktreePath);
1214
- if (worktreeMissing) {
1215
- return current.finalization.worktreeCleanup === 'done'
1216
- ? { currentHead: expectedHead, worktreeMissing: true, targetBranch }
1217
- : undefined;
1218
- }
1219
-
1220
- const worktree = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
1221
- if (resolve(worktree.canonicalRoot) !== resolve(current.worktreePath)) return undefined;
1222
- if (!repositoryGitStatus(worktree).clean) return undefined;
1223
- if (gitHead(worktree.canonicalRoot) !== expectedHead) return undefined;
1224
- const currentBranch = spawnSync('git', ['-C', worktree.canonicalRoot, 'branch', '--show-current'], {
1225
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1226
- });
1227
- if (currentBranch.status !== 0 || String(currentBranch.stdout ?? '').trim() !== current.branch) return undefined;
1228
- return { currentHead: expectedHead, worktreeMissing: false, targetBranch };
1229
- }
1230
-
1231
- type RetryableFinalizationStage = 'commit' | 'merge' | 'branchCleanup' | 'worktreeCleanup';
1232
-
1233
- function requestedFailedFinalizationRetry(
1234
- stages: WorkFinalizationStages,
1235
- wants: { commit: boolean; merge: boolean; cleanup: boolean },
1236
- ): RetryableFinalizationStage | undefined {
1237
- if (stages.validation === 'failed') return undefined;
1238
- if (wants.commit && stages.commit === 'failed') return 'commit';
1239
- if (wants.merge && stages.merge === 'failed') return 'merge';
1240
- if (wants.cleanup && stages.worktreeCleanup === 'failed') return 'worktreeCleanup';
1241
- if (wants.cleanup && stages.branchCleanup === 'failed') return 'branchCleanup';
1242
- return undefined;
1243
- }
1244
-
1245
- function retryPhaseForFinalizationStage(stage: RetryableFinalizationStage): 'delivery' | 'cleanup' {
1246
- return stage === 'commit' || stage === 'merge' ? 'delivery' : 'cleanup';
1247
- }
1248
-
1249
- function retryHandleStateForFinalization(stages: WorkFinalizationStages): WorkHandleState['state'] {
1250
- if (stages.merge === 'done') return 'merged';
1251
- if (stages.commit === 'done') return 'committed';
1252
- return 'validating';
1253
- }
1254
-
1255
- function reconcileFailedNonLinearTargetAdvanceRepair(
1256
- ctx: McpExecutionContext,
1257
- current: WorkHandleState,
1258
- args: Record<string, unknown>,
1259
- ): WorkHandleState {
1260
- if (
1261
- current.state !== 'failed'
1262
- || current.finalization.merge !== 'failed'
1263
- || current.finalization.failureCode !== 'WORK_TARGET_ADVANCE_LINEAR_HISTORY_VIOLATION'
1264
- || args.merge !== true
1265
- || !current.managedWorktree
1266
- || !current.expectedHead
1267
- ) return current;
1268
-
1269
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1270
- const worktree = selectRepositoryCheckout(repository, current.checkoutId);
1271
- const status = repositoryGitStatus(worktree);
1272
- const repairedHead = status.head;
1273
- if (!status.clean || !repairedHead || repairedHead === current.expectedHead) return current;
1274
- const branch = spawnSync('git', ['-C', worktree.canonicalRoot, 'branch', '--show-current'], {
1275
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1276
- });
1277
- if (branch.status !== 0 || String(branch.stdout ?? '').trim() !== current.branch) {
1278
- throw new Error('WORK_TARGET_ADVANCE_REPAIR_BRANCH_MISMATCH');
1279
- }
1280
-
1281
- const targetRevision = workDeliveryBaseRevision(current);
1282
- if (!targetRevision) throw new Error('WORK_TARGET_ADVANCE_REPAIR_TARGET_MISSING');
1283
- const inspection = inspectFailedNonLinearTargetAdvanceRepair(
1284
- worktree.canonicalRoot,
1285
- current.expectedHead,
1286
- repairedHead,
1287
- targetRevision,
1288
- );
1289
- if (!inspection.eligible) {
1290
- throw new Error(`WORK_TARGET_ADVANCE_REPAIR_UNSAFE: ${inspection.reason}`);
1291
- }
1292
-
1293
- const contract = contractFor(ctx, current);
1294
- if (!contract) throw new Error(`WORK_TARGET_ADVANCE_REPAIR_CONTRACT_MISSING: ${current.workContractId ?? current.workId}`);
1295
- const changedPaths = gitChangedPaths(worktree.canonicalRoot, inspection.targetHead, inspection.repairedHead);
1296
- const scopeViolation = targetAdvanceWorkScopeViolation(contract, changedPaths);
1297
- if (scopeViolation) {
1298
- throw new Error(`WORK_TARGET_ADVANCE_REPAIR_SCOPE_VIOLATION: Work-owned ${scopeViolation.kind} path ${scopeViolation.path}`);
1299
- }
1300
-
1301
- const repaired = withControllerLock(
1302
- ctx.controllerHome,
1303
- { scope: 'worktree', repoId: current.repositoryId, worktreeId: current.checkoutId },
1304
- `work-finalize:${current.workId}:repair-non-linear-target-advance`,
1305
- () => {
1306
- const fresh = readWorkHandle(ctx.controllerHome, current.repositoryId, current.workId) ?? current;
1307
- if (fresh.state !== 'failed' || fresh.expectedHead !== inspection.failedHead || fresh.finalization.merge !== 'failed') {
1308
- throw new Error('WORK_TARGET_ADVANCE_REPAIR_STALE_HANDLE');
1309
- }
1310
- return transitionWorkHandle(ctx.controllerHome, fresh, 'validating', {
1311
- expectedHead: inspection.repairedHead,
1312
- deliveryBaseCommit: inspection.targetHead,
1313
- failureReason: undefined,
1314
- validationRun: undefined,
1315
- validatedInputFingerprint: undefined,
1316
- finalization: {
1317
- ...fresh.finalization,
1318
- validation: 'pending',
1319
- commit: fresh.finalization.commit === 'failed' ? 'pending' : fresh.finalization.commit,
1320
- merge: 'pending',
1321
- failureCode: undefined, lastError: undefined,
1322
- },
1323
- });
1324
- },
1325
- 10_000,
1326
- );
1327
- replaceTargetAdvanceScopeEvidence(ctx, contract, changedPaths);
1328
- transitionWorkContractPhase(
1329
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1330
- contract.workId,
1331
- {
1332
- phase: 'delivery',
1333
- status: 'running',
1334
- state: 'active',
1335
- summary: `Repaired non-linear target-advance history ${inspection.failedHead} -> ${inspection.repairedHead} with identical Git tree; exact validation is required again before delivery.`,
1336
- },
1337
- );
1338
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, contract.workId, {
1339
- title: 'non-linear target-advance candidate repaired',
1340
- summary: `Replaced failed merge candidate ${inspection.failedHead} with linear candidate ${inspection.repairedHead} above target ${inspection.targetHead} only after exact Git-tree equality, zero repaired merge commits, clean exact Work checkout/branch ownership, and Work path-scope verification. Prior validation identity was invalidated and must be re-established.`,
1341
- detailLevel: 'summary',
1342
- });
1343
- markRepositoryProjectionDirty(ctx.controllerHome, current.repositoryId, `finalize:${current.workId}:non-linear-target-repair`);
1344
- return repaired;
1345
- }
1346
-
1347
- export function resetFinalizationStagesForRequest(
1348
- stages: WorkFinalizationStages,
1349
- wants: { commit: boolean; merge: boolean; cleanup: boolean },
1350
- options: { managedWorktree?: boolean; deleteBranchRequested?: boolean; retainedByRequest?: boolean; workspaceDirty?: boolean } = {},
1351
- ): WorkFinalizationStages {
1352
- const next = { ...stages };
1353
- let reset = false;
1354
- if (next.validation === 'failed') {
1355
- next.validation = 'pending';
1356
- reset = true;
1357
- }
1358
- if (wants.commit && next.commit === 'failed') {
1359
- next.commit = 'pending';
1360
- reset = true;
1361
- }
1362
- if (wants.merge && next.merge === 'failed') {
1363
- next.merge = 'pending';
1364
- reset = true;
1365
- }
1366
- if (wants.cleanup && next.worktreeCleanup === 'failed') {
1367
- next.worktreeCleanup = 'pending';
1368
- reset = true;
1369
- }
1370
- if (wants.cleanup && next.branchCleanup === 'failed') {
1371
- next.branchCleanup = 'pending';
1372
- reset = true;
1373
- }
1374
- // A Work may be semantically repaired and revalidated after an earlier delivery
1375
- // attempt already marked commit/merge done. Exact validation of a dirty current
1376
- // workspace proves there is new Work-owned content still requiring delivery;
1377
- // re-arm only the requested Git stages instead of letting cleanup discard it.
1378
- if (options.workspaceDirty === true && wants.commit && next.commit === 'done') {
1379
- next.commit = 'pending';
1380
- reset = true;
1381
- if (wants.merge && next.merge === 'done') next.merge = 'pending';
1382
- }
1383
- // A prior finalize(cleanup=false) intentionally records managed resources as
1384
- // skipped/retained. A later explicit cleanup=true is a new resource-disposal
1385
- // request, not an idempotent replay of the earlier retention decision. Re-arm
1386
- // only cleanup stages that are applicable to this managed Work; commit/merge
1387
- // skipped states keep their original delivery semantics.
1388
- if (wants.cleanup && options.retainedByRequest === true && options.managedWorktree === true && next.worktreeCleanup === 'skipped') {
1389
- next.worktreeCleanup = 'pending';
1390
- reset = true;
1391
- }
1392
- if (wants.cleanup && options.retainedByRequest === true && options.managedWorktree === true && options.deleteBranchRequested === true && next.branchCleanup === 'skipped') {
1393
- next.branchCleanup = 'pending';
1394
- reset = true;
1395
- }
1396
- if (reset) delete next.lastError;
1397
- return next;
1398
- }
1399
-
1400
- function completeFinalizedWorkContract(input: {
1401
- ctx: McpExecutionContext;
1402
- handle: WorkHandleState;
1403
- contract: NonNullable<ReturnType<typeof contractFor>>;
1404
- args: Record<string, unknown>;
1405
- terminalizationOwner: ReturnType<typeof assertWorkControllerOwnership>;
1406
- outcome: 'completed_changed' | 'completed_no_change';
1407
- }): CompletionReceipt {
1408
- const receipt = completionReceiptForFinalizedWork(input.ctx, input.handle, input.contract, input.args);
1409
- const ownerPrincipal = input.terminalizationOwner.principalId?.trim() || input.terminalizationOwner.controllerId;
1410
- const ownerInstanceId = input.terminalizationOwner.controllerInstanceId?.trim() || '';
1411
- const ownerClaimGeneration = input.terminalizationOwner.claimGeneration;
1412
- if (!ownerInstanceId || typeof ownerClaimGeneration !== 'number' || ownerClaimGeneration < 1) {
1413
- throw new Error(`WORK_CONTROLLER_TERMINALIZATION_AUTHORITY_INVALID: ${input.contract.workId}`);
1414
- }
1415
- const fencedCompletion = withControllerSessionTerminalizationFence(
1416
- { controllerHome: input.ctx.controllerHome, repoId: input.handle.repositoryId },
1417
- {
1418
- workId: input.contract.workId,
1419
- actor: `work-finalize-completion:${input.terminalizationOwner.controllerId}:${ownerInstanceId}`,
1420
- authority: {
1421
- controllerId: input.terminalizationOwner.controllerId,
1422
- controllerType: input.terminalizationOwner.controllerType,
1423
- principalId: ownerPrincipal,
1424
- controllerInstanceId: ownerInstanceId,
1425
- claimGeneration: ownerClaimGeneration,
1426
- },
1427
- },
1428
- () => completeWorkWithReceipt(
1429
- { controllerHome: input.ctx.controllerHome, repoId: input.handle.repositoryId },
1430
- input.contract.workId,
1431
- receipt,
1432
- input.outcome,
1433
- input.outcome === 'completed_changed' ? 'repository_change' : input.contract.workKind,
1434
- ),
1435
- );
1436
- if (!fencedCompletion.allowed) {
1437
- throw new Error(`WORK_TERMINALIZATION_AUTHORITY_FENCED: ${input.contract.workId}:${fencedCompletion.reason}`);
1438
- }
1439
- return receipt;
1440
- }
1441
-
1442
- function finalizationComplete(stages: WorkFinalizationStages): boolean {
1443
- return stages.validation === 'done'
1444
- && stages.commit !== 'pending'
1445
- && stages.merge !== 'pending'
1446
- && stages.branchCleanup !== 'pending'
1447
- && stages.worktreeCleanup !== 'pending'
1448
- && stages.commit !== 'failed'
1449
- && stages.merge !== 'failed'
1450
- && stages.branchCleanup !== 'failed'
1451
- && stages.worktreeCleanup !== 'failed'
1452
- && !stages.lastError;
1453
- }
1454
-
1455
- function finalStateForStages(stages: WorkFinalizationStages, fallback: WorkHandleState['state']): WorkHandleState['state'] {
1456
- if (stages.worktreeCleanup === 'done') return 'cleaned';
1457
- if (stages.merge === 'done') return 'merged';
1458
- if (stages.commit === 'done') return 'committed';
1459
- return fallback === 'failed' ? 'editing' : fallback;
1460
- }
1461
-
1462
- function currentWorkValidationInput(
1463
- repository: RepositoryRecord,
1464
- handle: WorkHandleState,
1465
- requestedChecks: string[],
1466
- ): { head: string; workspaceFingerprint: string; fingerprint: string; clean: boolean } {
1467
- const status = repositoryGitStatus(repository);
1468
- const head = status.head ?? handle.expectedHead ?? handle.baseCommit ?? 'unknown';
1469
- const workspaceFingerprint = workspaceValidationFingerprint(repository.canonicalRoot, status);
1470
- return {
1471
- head,
1472
- workspaceFingerprint,
1473
- fingerprint: workValidationInputFingerprint(head, workspaceFingerprint, requestedChecks),
1474
- clean: status.clean,
1475
- };
1476
- }
1477
-
1478
- export async function finalizeWork(ctx: McpExecutionContext, args: Record<string, unknown>): Promise<Record<string, unknown>> {
1479
- const session = requireSession(ctx, args);
1480
- let current = workForSession(ctx, session, args, { allowClaimedTerminalCleanup: args.cleanup !== false });
1481
- const requestedWants = { commit: args.commit === true, merge: args.merge === true, cleanup: args.cleanup === true };
1482
- let retryStage = requestedFailedFinalizationRetry(current.finalization, requestedWants);
1483
- const retryContract = retryStage ? contractFor(ctx, current) : undefined;
1484
- if (
1485
- retryStage
1486
- && (retryContract?.status === 'failed' || retryContract?.status === 'blocked')
1487
- && !retryContract.completionOutcome
1488
- && !retryContract.completionReceipt
1489
- ) {
1490
- transitionWorkContractPhase(
1491
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1492
- retryContract.workId,
1493
- {
1494
- phase: retryPhaseForFinalizationStage(retryStage),
1495
- status: 'running',
1496
- state: 'active',
1497
- summary: `Retrying previously failed Work finalization stage ${retryStage}.`,
1498
- },
1499
- );
1500
- }
1501
- const terminalOutcome = terminalCleanupOutcome(ctx, current);
1502
- if (terminalOutcome && args.cleanup !== false) {
1503
- return await reconcileTerminalCleanup(ctx, session, current, args, terminalOutcome);
1504
- }
1505
- const terminalizationOwner = assertWorkControllerOwnership(ctx, session, current, args);
1506
- current = reconcileFailedNonLinearTargetAdvanceRepair(ctx, current, args);
1507
- if (retryStage && current.finalization[retryStage] !== 'failed') retryStage = undefined;
1508
- if (terminalOutcome && args.cleanup === false) {
1509
- const recordedAt = new Date().toISOString();
1510
- current = withControllerLock(
1511
- ctx.controllerHome,
1512
- { scope: 'worktree', repoId: current.repositoryId, worktreeId: current.checkoutId },
1513
- `work-finalize:${current.workId}:retain-terminal-resources`,
1514
- () => {
1515
- const fresh = readWorkHandle(ctx.controllerHome, current.repositoryId, current.workId) ?? current;
1516
- return writeWorkHandle(ctx.controllerHome, {
1517
- ...fresh,
1518
- finalization: {
1519
- ...fresh.finalization,
1520
- validation: fresh.finalization.validation === 'failed' ? 'failed' : 'done',
1521
- commit: fresh.finalization.commit === 'pending' ? 'skipped' : fresh.finalization.commit,
1522
- merge: fresh.finalization.merge === 'pending' ? 'skipped' : fresh.finalization.merge,
1523
- branchCleanup: 'skipped',
1524
- worktreeCleanup: 'skipped',
1525
- failureCode: fresh.finalization.validation === 'failed' ? fresh.finalization.failureCode : undefined,
1526
- lastError: fresh.finalization.validation === 'failed' ? fresh.finalization.lastError : undefined,
1527
- },
1528
- terminalResourceDisposition: {
1529
- mode: 'retained_by_request',
1530
- retainWorktree: fresh.managedWorktree,
1531
- retainBranch: true,
1532
- recordedAt,
1533
- },
1534
- });
1535
- },
1536
- 10_000,
1537
- );
1538
- appendWorkEvidence(
1539
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1540
- current.workContractId ?? current.workId,
1541
- {
1542
- title: 'terminal resources retained by request',
1543
- summary: `Controller explicitly retained ${current.managedWorktree ? 'the managed worktree and ' : ''}local branch after terminal Work; automatic terminal cleanup must not reclaim them.`,
1544
- detailLevel: 'summary',
1545
- },
1546
- );
1547
- releasePreparedWorkOwnership(ctx, current);
1548
- updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
1549
- activeWorkId: undefined,
1550
- activeCheckoutId: current.sourceCheckoutId ?? session.activeCheckoutId,
1551
- });
1552
- return {
1553
- work: compactHandle(current),
1554
- stages: current.finalization,
1555
- completed: true,
1556
- cleanupCompleted: false,
1557
- cleanupRetained: true,
1558
- cleanupPending: false,
1559
- idempotent: true,
1560
- };
1561
- }
1562
- if (current.state === 'cleaned') {
1563
- const terminalContract = contractFor(ctx, current);
1564
- if (
1565
- terminalContract?.status === 'failed'
1566
- && current.finalization.validation === 'failed'
1567
- && current.finalization.worktreeCleanup === 'done'
1568
- ) {
1569
- releasePreparedWorkOwnership(ctx, current);
1570
- updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
1571
- activeWorkId: undefined,
1572
- activeCheckoutId: workReturnCheckoutId(ctx, current, session.activeCheckoutId),
1573
- });
1574
- return {
1575
- idempotent: true,
1576
- work: compactHandle(current),
1577
- stages: current.finalization,
1578
- completed: false,
1579
- cleanupCompleted: true,
1580
- failurePreserved: true,
1581
- };
1582
- }
1583
- if (terminalContract?.status === 'completed' && terminalContract.completionReceipt) {
1584
- updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
1585
- activeWorkId: undefined,
1586
- activeCheckoutId: workReturnCheckoutId(ctx, current, session.activeCheckoutId),
1587
- });
1588
- return { idempotent: true, work: compactHandle(current), stages: current.finalization, completed: true };
1589
- }
1590
- const cleanedCompletionRecovery = finalizationComplete(current.finalization)
1591
- && args.cleanup === true
1592
- && args.commit !== true
1593
- && args.merge !== true
1594
- && args.completion_outcome === 'completed_changed'
1595
- && terminalContract
1596
- && terminalContract.status !== 'cancelled'
1597
- && terminalContract.status !== 'failed'
1598
- && !terminalContract.completionReceipt;
1599
- if (cleanedCompletionRecovery && terminalContract) {
1600
- const terminalizationOwner = assertWorkControllerOwnership(ctx, session, current, args);
1601
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1602
- const target = selectWorkFinalizationTarget(repository, current);
1603
- const durableReview = assertCleanedCompletionImplementationReviewGate({ target, handle: current, contract: terminalContract });
1604
- const reconciledContract = reconcileApprovedWorkImplementationReviewProjection(
1605
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1606
- terminalContract.workId,
1607
- {
1608
- reviewId: durableReview.reviewId,
1609
- sourceRevision: durableReview.sourceRevision,
1610
- changedPathDigest: durableReview.changedPathDigest,
1611
- },
1612
- );
1613
- completeFinalizedWorkContract({
1614
- ctx, handle: current, contract: reconciledContract, args, terminalizationOwner, outcome: 'completed_changed',
1615
- });
1616
- releasePreparedWorkOwnership(ctx, current);
1617
- updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
1618
- activeWorkId: undefined,
1619
- activeCheckoutId: workReturnCheckoutId(ctx, current, session.activeCheckoutId),
1620
- });
1621
- return {
1622
- idempotent: true,
1623
- recoveredCompletionReceipt: true,
1624
- work: compactHandle(current),
1625
- stages: current.finalization,
1626
- completed: true,
1627
- cleanupCompleted: true,
1628
- };
1629
- }
1630
- validateWorkHandle(ctx.controllerHome, current, identityFor(ctx, args), 'none', 'finalize');
1631
- return { idempotent: true, work: compactHandle(current) };
1632
- }
1633
- const identity = identityFor(ctx, args);
1634
- const requestedOutcome = args.completion_outcome === 'completed_no_change' || args.completion_outcome === 'completed_changed'
1635
- ? args.completion_outcome
1636
- : undefined;
1637
- const noChangeProof = typeof args.no_change_evidence === 'string' ? args.no_change_evidence.trim().slice(0, 1_000) : '';
1638
- if (requestedOutcome === 'completed_no_change' && (!noChangeProof || args.commit === true || args.merge === true)) {
1639
- throw new Error('WORK_NO_CHANGE_PROOF_REQUIRED: completed_no_change requires objective-specific evidence and forbids commit/merge');
1640
- }
1641
- const wants = requestedWants;
1642
- const explicitTargetBranch = typeof args.target_branch === 'string' && args.target_branch.trim()
1643
- ? args.target_branch.trim()
1644
- : undefined;
1645
- // With merge=false an explicitly selected Work branch is the delivery target,
1646
- // not a disposable feature branch. Keep the ref reachable for the receipt.
1647
- const retainExplicitWorkTargetBranch = !wants.merge && explicitTargetBranch === current.branch;
1648
- const deleteBranchRequested = args.delete_branch !== false && !retainExplicitWorkTargetBranch;
1649
- const noChangeFastPath = requestedOutcome === 'completed_no_change'
1650
- && !wants.commit
1651
- && !wants.merge
1652
- && current.state !== 'failed';
1653
-
1654
- const transact = (label: string, update: (fresh: WorkHandleState) => WorkHandleState): WorkHandleState =>
1655
- withControllerLock(ctx.controllerHome, { scope: 'worktree', repoId: current.repositoryId, worktreeId: current.checkoutId }, `work-finalize:${current.workId}:${label}`, () => {
1656
- const fresh = readWorkHandle(ctx.controllerHome, current.repositoryId, current.workId) ?? current;
1657
- return update(fresh);
1658
- }, 10_000);
1659
-
1660
- const failStage = (
1661
- stage: keyof WorkFinalizationStages,
1662
- reason: string,
1663
- failureCode?: WorkFinalizationFailureCode,
1664
- ): Record<string, unknown> => {
1665
- current = transact(`fail:${String(stage)}`, (fresh) => {
1666
- const finalization = { ...fresh.finalization, [stage]: 'failed', failureCode, lastError: reason } as WorkFinalizationStages;
1667
- const retryableState = stage === 'validation'
1668
- ? 'validating'
1669
- : retryHandleStateForFinalization(finalization);
1670
- return transitionWorkHandle(ctx.controllerHome, fresh, retryableState, {
1671
- failureReason: reason,
1672
- finalization,
1673
- ...(stage === 'validation' ? { validationRun: undefined, validatedInputFingerprint: undefined } : {}),
1674
- });
1675
- });
1676
- if (current.workContractId) {
1677
- if (stage === 'validation') {
1678
- transitionWorkContractPhase(
1679
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1680
- current.workContractId,
1681
- {
1682
- phase: 'verification',
1683
- status: 'blocked',
1684
- state: 'blocked',
1685
- dispatchState: 'blocked',
1686
- summary: `Retryable Work finalization validation failed; exact verification/review authority must be re-established: ${reason}`,
1687
- },
1688
- );
1689
- markWorkValidationPending(ctx.controllerHome, current);
1690
- } else if (stage === 'commit' || stage === 'merge' || stage === 'branchCleanup' || stage === 'worktreeCleanup') {
1691
- transitionWorkContractPhase(
1692
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1693
- current.workContractId,
1694
- {
1695
- phase: retryPhaseForFinalizationStage(stage),
1696
- status: 'blocked',
1697
- state: 'blocked',
1698
- dispatchState: 'blocked',
1699
- summary: `Retryable Work finalization stage ${stage} failed: ${reason}`,
1700
- },
1701
- );
1702
- }
1703
- }
1704
- markRepositoryProjectionDirty(ctx.controllerHome, current.repositoryId, `finalize:${current.workId}:${String(stage)}-failed`);
1705
- return { work: compactHandle(current), stages: current.finalization, completed: false };
1706
- };
1707
-
1708
- const failedCleanupRequested = current.state === 'failed'
1709
- && wants.cleanup
1710
- && !wants.commit
1711
- && !wants.merge
1712
- && retryStage === undefined;
1713
- const failedCleanupProof = failedCleanupRequested
1714
- ? failedCleanupOnlyHead(ctx, current, args)
1715
- : undefined;
1716
- if (failedCleanupRequested && !failedCleanupProof) {
1717
- throw new Error('WORK_FAILED_CLEANUP_UNSAFE: failed Work cleanup requires exact checkout/branch ownership and an unchanged clean controller-owned managed worktree whose exact HEAD is already contained in the target branch');
1718
- }
1719
-
1720
- if (!failedCleanupRequested && !noChangeFastPath) {
1721
- current = transact('begin', (fresh) => writeWorkHandle(ctx.controllerHome, {
1722
- ...fresh,
1723
- state: fresh.state === 'failed' ? retryHandleStateForFinalization(fresh.finalization) : fresh.state,
1724
- failureReason: undefined,
1725
- finalization: resetFinalizationStagesForRequest(fresh.finalization, wants, {
1726
- managedWorktree: fresh.managedWorktree,
1727
- deleteBranchRequested,
1728
- retainedByRequest: fresh.terminalResourceDisposition?.mode === 'retained_by_request',
1729
- }),
1730
- ...(wants.cleanup && fresh.terminalResourceDisposition?.mode === 'retained_by_request' ? { terminalResourceDisposition: undefined } : {}),
1731
- }));
1732
- }
1733
-
1734
- const approvalRequestId = typeof args.approval_request_id === 'string' ? args.approval_request_id.trim() : '';
1735
- const resolvedAuthorization = approvalRequestId
1736
- ? assertResolvedAuthorization({ controllerHome: ctx.controllerHome, repositoryId: current.repositoryId, approvalRequestId, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, permissionSnapshotVersion: current.permissionSnapshotVersion, command: 'work_finalize' })
1737
- : undefined;
1738
- const gitAuthorization = resolvedAuthorization
1739
- ? { decision: 'allow', source: 'user_confirmation', reason: 'The user resolved the exact finalization approval request.' } as const
1740
- : decideAuthorization({
1741
- controllerHome: ctx.controllerHome,
1742
- accessMode: readRepositoryAccessPolicy(ctx.controllerHome, current.repositoryId).mode,
1743
- risk: 'local_git',
1744
- repositoryId: current.repositoryId,
1745
- currentRepositoryId: current.repositoryId,
1746
- workId: current.workId,
1747
- boundWorkId: current.workId,
1748
- goalId: current.goalId,
1749
- boundGoalId: current.goalId,
1750
- sessionId: session.sessionId,
1751
- principalId: session.principalId,
1752
- permissionSnapshotVersion: current.permissionSnapshotVersion,
1753
- delegation: session.goalDelegation,
1754
- command: 'work_finalize',
1755
- });
1756
- if (gitAuthorization.decision !== 'allow') return { authorization: gitAuthorization, work: compactHandle(current), stages: current.finalization };
1757
-
1758
- // A conflict-repaired managed candidate rewrites commit identity, so the
1759
- // normal descendant-HEAD fence cannot authorize it. Re-adopt only an exact,
1760
- // clean, scope-contained candidate that has reconciled the linearly advanced
1761
- // target. The transition returns before delivery, so the successor must gain
1762
- // current-source verification/review authority before merge can resume.
1763
- if (wants.merge && current.managedWorktree && current.expectedHead) {
1764
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1765
- const worktree = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
1766
- const target = selectWorkFinalizationTarget(repository, current);
1767
- const targetBranch = resolveWorkDeliveryTargetBranch(current, target.defaultBranch, explicitTargetBranch);
1768
- const contract = contractFor(ctx, current);
1769
- const adoption = inspectManagedWorkSuccessorAdoption({
1770
- root: worktree.canonicalRoot,
1771
- worktreePath: current.worktreePath,
1772
- managedWorktree: current.managedWorktree,
1773
- workBranch: current.branch,
1774
- targetBranch,
1775
- expectedRevision: current.expectedHead,
1776
- deliveryBaseRevision: workDeliveryBaseRevision(current),
1777
- status: repositoryGitStatus(worktree),
1778
- scope: contract ? { allowedPaths: contract.allowedPaths, forbiddenPaths: contract.forbiddenPaths } : undefined,
1779
- });
1780
- if (adoption.adoptable && adoption.candidateHead && adoption.targetHead) {
1781
- const previousHead = current.expectedHead;
1782
- current = transact('managed-successor-adopted', (fresh) => adoptWorkHandleSuccessorCandidate(
1783
- ctx.controllerHome,
1784
- fresh,
1785
- { candidateHead: adoption.candidateHead!, targetHead: adoption.targetHead! },
1786
- ));
1787
- if (contract) {
1788
- transitionWorkContractPhase(
1789
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1790
- contract.workId,
1791
- {
1792
- phase: 'verification',
1793
- status: 'running',
1794
- state: 'active',
1795
- summary: `Conflict-repaired successor ${adoption.candidateHead} adopted after canonical ${targetBranch} advanced to ${adoption.targetHead}; current-source verification is required before delivery resumes.`,
1796
- },
1797
- );
1798
- }
1799
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
1800
- title: 'managed Work successor candidate adopted',
1801
- summary: `WorkHandle authority moved ${previousHead} -> ${adoption.candidateHead} only after proving clean exact checkout ownership, canonical target ancestry at ${adoption.targetHead}, linear candidate history, and ${adoption.candidateChangedPaths.length} scope-contained target-relative path(s). Validation/delivery authority was re-armed; no canonical target mutation occurred.`,
1802
- detailLevel: 'summary',
1803
- });
1804
- markWorkValidationPending(ctx.controllerHome, current);
1805
- return {
1806
- work: compactHandle(current),
1807
- stages: current.finalization,
1808
- completed: false,
1809
- successorAdopted: true,
1810
- continuation: `WORK_SUCCESSOR_REVALIDATION_REQUIRED: successor ${adoption.candidateHead} is now the exact Work candidate after target ${adoption.targetHead}; verify/review this source revision before retrying delivery`,
1811
- };
1812
- }
1813
- }
1814
-
1815
- // A direct Work can share canonical main with another Work's preserved dirty
1816
- // delta. If main advanced after this Work was prepared, keep the global
1817
- // execution-identity fence strict and reconcile only here, where delivery
1818
- // ownership plus exact current verification can be proven together.
1819
- if (wants.commit && !current.managedWorktree && current.expectedHead) {
1820
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1821
- const worktree = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
1822
- const target = selectWorkFinalizationTarget(repository, current);
1823
- const targetBranch = resolveWorkDeliveryTargetBranch(current, repository.defaultBranch, explicitTargetBranch);
1824
- const contract = contractFor(ctx, current);
1825
- const reconciliation = reconcileDirectCanonicalTargetAdvanceCommand({
1826
- controllerHome: ctx.controllerHome,
1827
- repoId: current.repositoryId,
1828
- workId: current.workContractId ?? current.workId,
1829
- handle: current,
1830
- root: target.canonicalRoot,
1831
- targetBranch,
1832
- status: repositoryGitStatus(worktree),
1833
- scope: contract ? { allowedPaths: contract.allowedPaths, forbiddenPaths: contract.forbiddenPaths } : undefined,
1834
- checkIds: contract?.checks ?? [],
1835
- checkRefs: contract?.checkRefs ?? [],
1836
- persistHandle: (patch) => transact('direct-canonical-target-advance-reconciled', (fresh) => writeWorkHandle(ctx.controllerHome, {
1837
- ...fresh,
1838
- ...patch,
1839
- })),
1840
- });
1841
- if (reconciliation.reconciled) current = reconciliation.handle;
1842
- }
1843
-
1844
- if (failedCleanupProof) {
1845
- const preservedFailure = current.failureReason ?? current.finalization.lastError ?? 'Work validation failed.';
1846
- current = transact('failed-cleanup-begin', (fresh) => writeWorkHandle(ctx.controllerHome, {
1847
- ...fresh,
1848
- state: 'failed',
1849
- failureReason: preservedFailure,
1850
- finalization: {
1851
- ...fresh.finalization,
1852
- validation: 'failed',
1853
- commit: fresh.finalization.commit === 'pending' ? 'skipped' : fresh.finalization.commit,
1854
- merge: fresh.finalization.merge === 'pending' ? 'skipped' : fresh.finalization.merge,
1855
- branchCleanup: deleteBranchRequested ? fresh.finalization.branchCleanup : 'skipped',
1856
- lastError: preservedFailure,
1857
- },
1858
- }));
1859
-
1860
- if (!failedCleanupProof.worktreeMissing && current.finalization.worktreeCleanup !== 'done') {
1861
- const target = selectWorkFinalizationTarget(
1862
- getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true }),
1863
- current,
1864
- );
1865
- const cleanup = runCleanup(target.canonicalRoot, current.worktreePath);
1866
- if (!cleanup.ok) {
1867
- throw new Error(`WORK_FAILED_CLEANUP_UNSAFE: ${cleanup.message ?? 'managed worktree cleanup failed'}`);
1868
- }
1869
- current = transact('failed-worktree-cleanup-done', (fresh) => {
1870
- setRepositoryCheckoutLifecycle({
1871
- controllerHome: ctx.controllerHome,
1872
- repoId: fresh.repositoryId,
1873
- checkoutId: fresh.checkoutId,
1874
- lifecycle: 'removed',
1875
- reason: `Failed Work ${fresh.workId} cleanup completed without changing delivery state.`,
1876
- });
1877
- markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:failed-worktree`);
1878
- return writeWorkHandle(ctx.controllerHome, {
1879
- ...fresh,
1880
- state: 'failed',
1881
- failureReason: preservedFailure,
1882
- finalization: { ...fresh.finalization, worktreeCleanup: 'done', lastError: preservedFailure },
1883
- });
1884
- });
1885
- }
1886
-
1887
- if (deleteBranchRequested && current.finalization.branchCleanup !== 'done') {
1888
- const target = selectWorkFinalizationTarget(
1889
- getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true }),
1890
- current,
1891
- );
1892
- const deleted = repositoryGitDeleteBranch(ctx.controllerHome, target, {
1893
- branch: current.branch,
1894
- force: false,
1895
- authorizationDecision: gitAuthorization,
1896
- sessionId: session.sessionId,
1897
- principalId: session.principalId,
1898
- workId: current.workId,
1899
- goalId: current.goalId,
1900
- });
1901
- if (deleted.execution.authorizationDecision?.decision === 'user_confirmation_required') {
1902
- return { authorization: deleted.execution.authorizationDecision, work: compactHandle(current), stages: current.finalization };
1903
- }
1904
- if (deleted.execution.status !== 'executed' || deleted.execution.ok !== true) {
1905
- current = transact('failed-branch-cleanup-failed', (fresh) => writeWorkHandle(ctx.controllerHome, {
1906
- ...fresh,
1907
- state: 'failed',
1908
- failureReason: preservedFailure,
1909
- finalization: {
1910
- ...fresh.finalization,
1911
- branchCleanup: 'failed',
1912
- lastError: String(deleted.execution.stderr || 'feature branch cleanup failed').slice(0, 1_000),
1913
- },
1914
- }));
1915
- return {
1916
- work: compactHandle(current),
1917
- stages: current.finalization,
1918
- completed: false,
1919
- cleanupCompleted: false,
1920
- failurePreserved: true,
1921
- };
1922
- }
1923
- current = transact('failed-branch-cleanup-done', (fresh) => {
1924
- markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:failed-branch`);
1925
- return writeWorkHandle(ctx.controllerHome, {
1926
- ...fresh,
1927
- state: 'failed',
1928
- failureReason: preservedFailure,
1929
- finalization: { ...fresh.finalization, branchCleanup: 'done', lastError: preservedFailure },
1930
- });
1931
- });
1932
- }
1933
-
1934
- current = transact('failed-cleanup-complete', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'cleaned', {
1935
- failureReason: preservedFailure,
1936
- finalization: {
1937
- ...fresh.finalization,
1938
- validation: 'failed',
1939
- commit: fresh.finalization.commit === 'pending' ? 'skipped' : fresh.finalization.commit,
1940
- merge: fresh.finalization.merge === 'pending' ? 'skipped' : fresh.finalization.merge,
1941
- branchCleanup: deleteBranchRequested ? fresh.finalization.branchCleanup : 'skipped',
1942
- worktreeCleanup: 'done',
1943
- lastError: preservedFailure,
1944
- },
1945
- }));
1946
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
1947
- title: 'failed work cleanup completed',
1948
- summary: `Controller preserved the failed Work outcome while removing its unchanged clean managed worktree and ${deleteBranchRequested ? 'removing' : 'retaining'} the local branch after proving ${failedCleanupProof.currentHead} is contained in ${failedCleanupProof.targetBranch}.`,
1949
- detailLevel: 'summary',
1950
- });
1951
- releasePreparedWorkOwnership(ctx, current);
1952
- updateExecutionSession(ctx.controllerHome, identity, {
1953
- activeWorkId: undefined,
1954
- activeCheckoutId: current.sourceCheckoutId ?? session.activeCheckoutId,
1955
- });
1956
- return {
1957
- work: compactHandle(current),
1958
- stages: current.finalization,
1959
- completed: false,
1960
- cleanupCompleted: true,
1961
- failurePreserved: true,
1962
- idempotent: false,
1963
- };
1964
- }
1965
-
1966
- const contractAtStart = contractFor(ctx, current);
1967
- const cancelledCleanupRequested = contractAtStart?.status === 'cancelled'
1968
- && wants.cleanup
1969
- && !wants.commit
1970
- && !wants.merge;
1971
- const noChangeCleanupReconciliation = requestedOutcome === 'completed_no_change'
1972
- ? inspectCleanupOnlyMergedHead(ctx, current, args)
1973
- : undefined;
1974
- const noChangeRemovedWorktreeRecovery = Boolean(
1975
- noChangeCleanupReconciliation?.worktreeMissing
1976
- && current.finalization.worktreeCleanup === 'done'
1977
- && current.finalization.validation === 'done'
1978
- && current.validatedInputFingerprint
1979
- && (current.state === 'merged' || current.state === 'cleaned'),
1980
- );
1981
- const cleanupReconciliation = requestedOutcome === 'completed_no_change'
1982
- ? undefined
1983
- : inspectCleanupOnlyMergedHead(ctx, current, args);
1984
- if (cancelledCleanupRequested && !cleanupReconciliation) {
1985
- throw new Error('WORK_CANCELLED_CLEANUP_UNSAFE: cancelled Work cleanup requires an unchanged clean managed worktree (or a previously recorded cleanup) and a branch HEAD already contained in the target branch');
1986
- }
1987
- if (cleanupReconciliation) {
1988
- current = transact('cleanup-reconcile-merged-head', (fresh) => writeWorkHandle(ctx.controllerHome, {
1989
- ...fresh,
1990
- expectedHead: cleanupReconciliation.currentHead,
1991
- state: 'merged',
1992
- failureReason: undefined,
1993
- finalization: {
1994
- ...fresh.finalization,
1995
- validation: cleanupReconciliation.worktreeMissing ? 'done' : fresh.finalization.validation,
1996
- // Exact target containment proves an already-committed candidate. A
1997
- // previous GIT_NOTHING_STAGED failure is superseded rather than kept as
1998
- // a permanent failed stage after successful delivery.
1999
- commit: fresh.finalization.commit === 'done' ? 'done' : 'skipped',
2000
- merge: 'done',
2001
- branchCleanup: deleteBranchRequested
2002
- ? (fresh.finalization.branchCleanup === 'done' ? 'done' : 'pending')
2003
- : 'skipped',
2004
- failureCode: undefined, lastError: undefined,
2005
- },
2006
- }));
2007
- }
2008
-
2009
- const changedCleanupOnly = wants.cleanup
2010
- && !wants.commit
2011
- && !wants.merge
2012
- && requestedOutcome !== 'completed_no_change'
2013
- && current.state !== 'merged'
2014
- && current.state !== 'cleaned';
2015
- if (changedCleanupOnly && !cleanupReconciliation) {
2016
- throw new Error('WORK_CLEANUP_DELIVERY_NOT_PROVEN: cleanup-only completion requires the exact Work branch HEAD to already be contained in the target branch before any managed worktree is removed');
2017
- }
2018
-
2019
- const terminalCleanupOnly = Boolean(cleanupReconciliation && wants.cleanup && !wants.commit && !wants.merge);
2020
- let exactValidationInput: ReturnType<typeof currentWorkValidationInput> | undefined;
2021
- if (terminalCleanupOnly) {
2022
- current = transact('terminal-cleanup-validation-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, {
2023
- ...fresh,
2024
- finalization: { ...fresh.finalization, validation: 'done', failureCode: undefined, lastError: undefined },
2025
- }));
2026
- } else if (noChangeRemovedWorktreeRecovery) {
2027
- // A prior no-change finalize may have durably recorded validation and physical
2028
- // worktree cleanup before terminal Work completion. Target containment above
2029
- // re-proves the exact expected HEAD; do not recreate mutable workspace state
2030
- // merely to repeat a validation whose fingerprint is already durable.
2031
- } else {
2032
- let validatedRepository: RepositoryRecord;
2033
- try {
2034
- validatedRepository = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize').worktreeRepository;
2035
- } catch (error) {
2036
- const failureCode = error instanceof WorkHandleValidationError && error.code === 'WORK_HANDLE_HEAD_CHANGED'
2037
- ? error.code
2038
- : undefined;
2039
- return failStage('validation', error instanceof Error ? error.message : String(error), failureCode);
2040
- }
2041
- const validationContract = contractFor(ctx, current);
2042
- if (!validationContract) throw new Error(`WORK_VALIDATION_CONTRACT_MISSING: ${current.workContractId ?? current.workId}`);
2043
- const validationInput = currentWorkValidationInput(validatedRepository, current, validationContract.checks);
2044
- exactValidationInput = validationInput;
2045
- if (validationContract.checks.length === 0) {
2046
- if (!noChangeFastPath) {
2047
- current = transact('validation-no-checks', (fresh) => writeWorkHandle(ctx.controllerHome, {
2048
- ...fresh,
2049
- validatedInputFingerprint: validationInput.fingerprint,
2050
- finalization: { ...fresh.finalization, validation: 'done', failureCode: undefined, lastError: undefined },
2051
- }));
2052
- }
2053
- projectWorkValidationOutcome(ctx.controllerHome, current, 'passed', 'No validation checks were required.');
2054
- } else if (!hasCurrentWorkValidationAuthority({
2055
- finalizationValidation: current.finalization.validation,
2056
- validatedInputFingerprint: current.validatedInputFingerprint,
2057
- evidenceState: validationContract.evidenceState,
2058
- expectedFingerprint: validationInput.fingerprint,
2059
- })) {
2060
- current = transact('validation-required', (fresh) => writeWorkHandle(ctx.controllerHome, {
2061
- ...fresh,
2062
- validatedInputFingerprint: undefined,
2063
- finalization: { ...fresh.finalization, validation: 'pending', failureCode: undefined, lastError: undefined },
2064
- }));
2065
- markWorkValidationPending(ctx.controllerHome, current);
2066
- throw new Error('WORK_VALIDATION_REQUIRED: run work_validate against the exact current workspace before finalization');
2067
- }
2068
- if (!validationInput.clean && wants.commit && current.finalization.commit === 'done') {
2069
- current = transact('rearm-delivery-after-validated-repair', (fresh) => writeWorkHandle(ctx.controllerHome, {
2070
- ...fresh,
2071
- state: 'validating',
2072
- failureReason: undefined,
2073
- finalization: resetFinalizationStagesForRequest(fresh.finalization, wants, {
2074
- managedWorktree: fresh.managedWorktree,
2075
- deleteBranchRequested,
2076
- retainedByRequest: fresh.terminalResourceDisposition?.mode === 'retained_by_request',
2077
- workspaceDirty: true,
2078
- }),
2079
- }));
2080
- }
2081
- if (wants.merge && !wants.commit && !validationInput.clean) {
2082
- throw new Error('WORK_MERGE_UNCOMMITTED_CHANGES: commit the validated workspace before merging');
2083
- }
2084
- }
2085
-
2086
- if (noChangeFastPath) {
2087
- if (!exactValidationInput?.clean && !noChangeRemovedWorktreeRecovery) {
2088
- throw new Error('WORK_NO_CHANGE_DIRTY: completed_no_change cannot retain an owned dirty worktree');
2089
- }
2090
- const noChangeValidationFingerprint = exactValidationInput?.fingerprint ?? current.validatedInputFingerprint;
2091
- if (!noChangeValidationFingerprint) {
2092
- throw new Error('WORK_NO_CHANGE_VALIDATION_AUTHORITY_REQUIRED: completed_no_change requires retained exact validation authority');
2093
- }
2094
- const noChangeFinalization: WorkFinalizationStages = {
2095
- ...current.finalization,
2096
- validation: 'done',
2097
- commit: 'skipped',
2098
- merge: 'skipped',
2099
- branchCleanup: wants.cleanup && deleteBranchRequested ? 'pending' : 'skipped',
2100
- failureCode: undefined, lastError: undefined,
2101
- };
2102
- // Collapse the no-op Git stages into the same durable lifecycle write used
2103
- // to enter/establish the delivery boundary. Physical cleanup remains
2104
- // separately persisted because it has real side effects and crash semantics.
2105
- if (current.state === 'prepared') {
2106
- current = transact('no-change-validated', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'validating', {
2107
- finalization: noChangeFinalization,
2108
- validatedInputFingerprint: noChangeValidationFingerprint,
2109
- failureReason: undefined,
2110
- }));
2111
- } else if (current.state === 'editing' || current.state === 'validating' || current.state === 'committed') {
2112
- current = transact('no-change-delivery-integrated', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'merged', {
2113
- finalization: noChangeFinalization,
2114
- validatedInputFingerprint: noChangeValidationFingerprint,
2115
- failureReason: undefined,
2116
- }));
2117
- }
2118
- }
2119
-
2120
- if (wants.commit && current.finalization.commit === 'pending') {
2121
- const validated = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize');
2122
- const contract = contractFor(ctx, current);
2123
- if (contract?.constraints.allowCommit === false) throw new Error('WORK_COMMIT_NOT_ALLOWED: WorkContract disallows commit');
2124
- const status = repositoryGitStatus(validated.worktreeRepository);
2125
- const { dirtyPaths, ownedPaths: commitPaths } = workOwnedDirtyPaths(contract, status);
2126
- if (dirtyPaths.length > 0 && commitPaths.length === 0) {
2127
- throw new Error(`WORK_COMMIT_NO_OWNED_DIRTY_PATHS: ${current.workId} has ${dirtyPaths.length} dirty path(s), but none are owned by its WorkContract path scope`);
2128
- }
2129
- if (!contract) throw new Error(`WORK_IMPLEMENTATION_REVIEW_CONTRACT_REQUIRED: ${current.workId}`);
2130
- const preCommitReviewCandidate = assertPhysicalImplementationReviewGate({
2131
- ctx,
2132
- repository: validated.worktreeRepository,
2133
- handle: current,
2134
- contract,
2135
- verificationWorkspaceFingerprint: exactValidationInput?.workspaceFingerprint,
2136
- targetBranch: resolveWorkDeliveryTargetBranch(current, validated.worktreeRepository.defaultBranch, explicitTargetBranch),
2137
- });
2138
- const reviewedCommitPaths = normalizeImplementationReviewChangedPaths(preCommitReviewCandidate.changedPaths);
2139
- const exactCommitPaths = normalizeImplementationReviewChangedPaths(commitPaths);
2140
- if (JSON.stringify(reviewedCommitPaths) !== JSON.stringify(exactCommitPaths)) {
2141
- throw new Error('WORK_IMPLEMENTATION_REVIEW_TRANSFER_COMMIT_SCOPE_MISMATCH: commit must materialize the complete reviewed Work path set');
2142
- }
2143
- const committed = repositoryGitCommit(ctx.controllerHome, validated.worktreeRepository, { message: String(args.message ?? `Complete ${current.workId}`), paths: commitPaths, allowEmpty: false, authorizationDecision: gitAuthorization, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, goalId: current.goalId });
2144
- const pendingAuthorization = [committed.stage, committed.commit].find((execution) => execution?.authorizationDecision?.decision === 'user_confirmation_required')?.authorizationDecision;
2145
- if (pendingAuthorization) return { authorization: pendingAuthorization, work: compactHandle(current), stages: current.finalization };
2146
- if (!committed.committed) return { ...failStage('commit', committed.error?.message ?? 'commit failed'), commit: committed };
2147
- const checks = contract?.checks ?? [];
2148
- const committedHead = gitHead(validated.worktreeRepository.canonicalRoot);
2149
- if (!committedHead) throw new Error('WORK_COMMIT_HEAD_REQUIRED: committed revision is unavailable');
2150
- const postCommitInput = currentWorkValidationInput(validated.worktreeRepository, { ...current, expectedHead: committedHead }, checks);
2151
- const postCommitContentFingerprint = implementationReviewContentFingerprint(
2152
- validated.worktreeRepository.canonicalRoot,
2153
- preCommitReviewCandidate.changedPaths,
2154
- );
2155
- const committedPaths = exactCommitChangedPaths(validated.worktreeRepository.canonicalRoot, committedHead);
2156
- const reviewedCommitPathsAfterCommit = normalizeImplementationReviewChangedPaths(preCommitReviewCandidate.changedPaths);
2157
- const exactCommittedPaths = normalizeImplementationReviewChangedPaths(committedPaths);
2158
- const reviewedContentPreservedAcrossCommit = JSON.stringify(reviewedCommitPathsAfterCommit) === JSON.stringify(exactCommittedPaths)
2159
- && postCommitContentFingerprint === preCommitReviewCandidate.workspaceFingerprint;
2160
- // Git staging/commit changes workspace-status shape even when the reviewed bytes
2161
- // are identical. Preserve validation from exact reviewed content equivalence;
2162
- // transferWorkVerificationAcrossContentEquivalentCommit remains responsible
2163
- // for invalidating checks whose semantics are genuinely Git-sensitive.
2164
- const validationPreservedAcrossCommit = Boolean(
2165
- checks.length > 0
2166
- && exactValidationInput
2167
- && reviewedContentPreservedAcrossCommit
2168
- );
2169
- current = transact('commit-done', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'committed', {
2170
- expectedHead: committedHead,
2171
- finalization: {
2172
- ...fresh.finalization,
2173
- validation: checks.length ? (validationPreservedAcrossCommit ? 'done' : 'pending') : 'done',
2174
- commit: 'done',
2175
- failureCode: undefined, lastError: undefined,
2176
- },
2177
- validatedInputFingerprint: checks.length
2178
- ? (validationPreservedAcrossCommit ? postCommitInput.fingerprint : undefined)
2179
- : postCommitInput.fingerprint,
2180
- failureReason: undefined,
2181
- }));
2182
- if (checks.length && !validationPreservedAcrossCommit) {
2183
- markWorkValidationPending(ctx.controllerHome, current);
2184
- return {
2185
- work: compactHandle(current),
2186
- stages: current.finalization,
2187
- completed: false,
2188
- continuation: 'WORK_COMMITTED_REVALIDATION_REQUIRED: committed content changed from the exact validated workspace; run work_validate on the committed HEAD before merge or completion',
2189
- };
2190
- }
2191
- const authorityTransfer = transferReviewedWorkAuthorityAcrossContentEquivalentCommit({
2192
- controllerHome: ctx.controllerHome,
2193
- repository: validated.worktreeRepository,
2194
- workId: current.workContractId ?? current.workId,
2195
- preCommitCandidate: preCommitReviewCandidate,
2196
- preCommitDirtyPaths: commitPaths,
2197
- committedPaths,
2198
- preCommitContentDigest: preCommitReviewCandidate.workspaceFingerprint,
2199
- postCommitSourceRevision: postCommitInput.head,
2200
- postCommitContentDigest: postCommitContentFingerprint,
2201
- postCommitVerificationWorkspaceFingerprint: postCommitInput.workspaceFingerprint,
2202
- postCommitChangedPaths: preCommitReviewCandidate.changedPaths,
2203
- });
2204
- if (authorityTransfer.invalidatedCheckIds.length > 0) {
2205
- current = transact('commit-review-revalidation-required', (fresh) => writeWorkHandle(ctx.controllerHome, {
2206
- ...fresh,
2207
- finalization: { ...fresh.finalization, validation: 'pending', failureCode: undefined, lastError: undefined },
2208
- validatedInputFingerprint: undefined,
2209
- }));
2210
- markWorkValidationPending(ctx.controllerHome, current);
2211
- return {
2212
- work: compactHandle(current),
2213
- stages: current.finalization,
2214
- completed: false,
2215
- continuation: `WORK_COMMITTED_REVALIDATION_REQUIRED: content-equivalent verification transfer invalidated [${authorityTransfer.invalidatedCheckIds.join(', ')}]`,
2216
- };
2217
- }
2218
- if (!authorityTransfer.transferred || !authorityTransfer.derivedReview) {
2219
- throw new Error('WORK_IMPLEMENTATION_REVIEW_TRANSFER_REQUIRED');
2220
- }
2221
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
2222
- title: 'verification and implementation-review authority preserved across content-equivalent commit',
2223
- summary: `The exact reviewed Work content was committed as ${postCommitInput.head}; ${authorityTransfer.reusableCheckIds.length}/${checks.length} non-Git-sensitive Process receipt(s) and review ${authorityTransfer.derivedReview.reviewId} were transferred atomically.`,
2224
- detailLevel: 'summary',
2225
- });
2226
- } else if (!wants.commit && current.finalization.commit === 'pending') {
2227
- const committedRepository = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize').worktreeRepository;
2228
- const committedStatus = repositoryGitStatus(committedRepository);
2229
- const alreadyMaterializedCommit = Boolean(
2230
- committedStatus.clean
2231
- && committedStatus.head
2232
- && current.expectedHead
2233
- && current.baseCommit
2234
- && committedStatus.head === current.expectedHead
2235
- && committedStatus.head !== current.baseCommit,
2236
- );
2237
- current = alreadyMaterializedCommit
2238
- ? transact('commit-already-materialized', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'committed', {
2239
- finalization: { ...fresh.finalization, commit: 'done', failureCode: undefined, lastError: undefined },
2240
- failureReason: undefined,
2241
- }))
2242
- : transact('commit-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, commit: 'skipped' } }));
2243
- }
2244
-
2245
- if (wants.merge && current.finalization.merge === 'pending' && current.finalization.commit === 'done') {
2246
- const repository = getRepository(current.repositoryId, ctx.controllerHome);
2247
- const target = selectWorkFinalizationTarget(repository, current);
2248
- const targetBranch = resolveWorkDeliveryTargetBranch(current, repository.defaultBranch, explicitTargetBranch);
2249
- const directTarget = inspectDirectTargetDelivery(
2250
- target.canonicalRoot,
2251
- current.worktreePath,
2252
- current.managedWorktree,
2253
- current.branch,
2254
- targetBranch,
2255
- current.expectedHead,
2256
- );
2257
- if (directTarget.integrated) {
2258
- current = transact('direct-target-delivery-integrated', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'merged', {
2259
- finalization: { ...fresh.finalization, merge: 'done', branchCleanup: 'skipped', failureCode: undefined, lastError: undefined },
2260
- failureReason: undefined,
2261
- }));
2262
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
2263
- title: 'direct target delivery already integrated',
2264
- summary: `Work ${current.workId} runs directly on ${targetBranch}; exact delivery revision ${directTarget.expectedHead} is reachable from current target ${directTarget.targetHead}, so no self-merge or target-branch deletion is required.`,
2265
- detailLevel: 'summary',
2266
- });
2267
- }
2268
- }
2269
-
2270
- if (wants.merge && current.finalization.merge === 'pending') {
2271
- const mergeValidated = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize');
2272
- if (mergeValidated.currentHead && mergeValidated.currentHead !== current.expectedHead) {
2273
- current = transact('merge-adopt-work-delivery-head', (fresh) => writeWorkHandle(ctx.controllerHome, {
2274
- ...fresh,
2275
- expectedHead: mergeValidated.currentHead,
2276
- failureReason: undefined,
2277
- }));
2278
- }
2279
- const contract = contractFor(ctx, current);
2280
- if (contract?.constraints.allowMerge === false) throw new Error('WORK_MERGE_NOT_ALLOWED: WorkContract disallows merge');
2281
- if (!contract) throw new Error(`WORK_IMPLEMENTATION_REVIEW_CONTRACT_REQUIRED: ${current.workId}`);
2282
- const target = selectWorkFinalizationTarget(getRepository(current.repositoryId, ctx.controllerHome), current);
2283
- const deleteAfterWorktreeCleanup = current.managedWorktree && deleteBranchRequested;
2284
- const targetBranch = resolveWorkDeliveryTargetBranch(current, target.defaultBranch, explicitTargetBranch);
2285
- // Re-read the exact Work candidate immediately before any merge-path mutation
2286
- // (including target-advance rebase). The finalizer is an independent physical
2287
- // gate; semantic finalize authority is not trusted as a cached boolean.
2288
- assertPhysicalImplementationReviewGate({
2289
- ctx, repository: mergeValidated.worktreeRepository, handle: current, contract, targetBranch,
2290
- });
2291
- const activeMergeHead = retryStage === 'merge' ? gitRevision(target.canonicalRoot, 'MERGE_HEAD') : undefined;
2292
- if (!activeMergeHead && current.managedWorktree && current.expectedHead) {
2293
- const targetHead = gitRevision(target.canonicalRoot, targetBranch);
2294
- if (targetHead) {
2295
- const advance = inspectWorkTargetAdvance(mergeValidated.worktreeRepository.canonicalRoot, current.expectedHead, targetHead);
2296
- if (advance.relation === 'diverged_conflict') {
2297
- if (contract) {
2298
- transitionWorkContractPhase(
2299
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
2300
- contract.workId,
2301
- {
2302
- phase: 'delivery',
2303
- status: 'blocked',
2304
- state: 'blocked',
2305
- dispatchState: 'blocked',
2306
- summary: `Target branch ${targetBranch} advanced to ${advance.targetHead} and conflicts with candidate ${advance.candidateHead}; canonical target was not mutated.`,
2307
- },
2308
- );
2309
- }
2310
- return {
2311
- work: compactHandle(current),
2312
- stages: current.finalization,
2313
- completed: false,
2314
- blocked: true,
2315
- recoverable: true,
2316
- error: {
2317
- code: 'WORK_TARGET_ADVANCE_CONFLICT',
2318
- message: `Target branch ${targetBranch} advanced and cannot be integrated cleanly into the isolated Work candidate. Canonical target was left unchanged.${advance.detail ? ` ${advance.detail}` : ''}`,
2319
- },
2320
- continuation: 'WORK_TARGET_ADVANCE_REVIEW_REQUIRED: inspect the target/candidate conflict, repair only inside the managed Work checkout, then revalidate before retrying finalize.',
2321
- };
2322
- }
2323
- if (advance.relation === 'candidate_contains_target') {
2324
- const nonLinearCommits = targetAdvanceLinearMergeCommits(
2325
- mergeValidated.worktreeRepository.canonicalRoot,
2326
- advance.targetHead,
2327
- advance.candidateHead,
2328
- );
2329
- if (nonLinearCommits.length > 0) {
2330
- return failStage(
2331
- 'merge',
2332
- `WORK_TARGET_ADVANCE_LINEAR_HISTORY_VIOLATION: candidate already contains merge commit(s) above reconciled target ${advance.targetHead}: ${nonLinearCommits.slice(0, 8).join(', ')}`,
2333
- 'WORK_TARGET_ADVANCE_LINEAR_HISTORY_VIOLATION',
2334
- );
2335
- }
2336
- const scopeViolation = targetAdvanceWorkScopeViolation(contract, advance.candidateChangedPaths);
2337
- if (scopeViolation) {
2338
- return failStage('merge', `WORK_TARGET_ADVANCE_SCOPE_VIOLATION: Work-owned ${scopeViolation.kind} path ${scopeViolation.path}`);
2339
- }
2340
- if (contract) replaceTargetAdvanceScopeEvidence(ctx, contract, advance.candidateChangedPaths);
2341
- current = transact('target-advance-provenance-confirmed', (fresh) => writeWorkHandle(ctx.controllerHome, {
2342
- ...fresh,
2343
- deliveryBaseCommit: advance.targetHead,
2344
- }));
2345
- }
2346
- if (advance.relation === 'diverged_clean') {
2347
- const preIntegrationScopeViolation = targetAdvanceWorkScopeViolation(contract, advance.candidateChangedPaths);
2348
- if (preIntegrationScopeViolation) {
2349
- return failStage('merge', `WORK_TARGET_ADVANCE_SCOPE_VIOLATION: Work-owned ${preIntegrationScopeViolation.kind} path ${preIntegrationScopeViolation.path}`);
2350
- }
2351
- const checks = contract?.checks ?? [];
2352
- const candidateInput = currentWorkValidationInput(mergeValidated.worktreeRepository, current, checks);
2353
- const checksBefore = checks.length > 0 ? listControllerChecks(mergeValidated.worktreeRepository.canonicalRoot) : [];
2354
- const integrated = repositoryGitRebaseOnto(ctx.controllerHome, mergeValidated.worktreeRepository, {
2355
- onto: advance.targetHead,
2356
- upstream: advance.mergeBase,
2357
- abortOnFailure: true,
2358
- authorizationDecision: gitAuthorization,
2359
- sessionId: session.sessionId,
2360
- principalId: session.principalId,
2361
- workId: current.workId,
2362
- goalId: current.goalId,
2363
- });
2364
- if (!integrated.rebased) {
2365
- if (contract) {
2366
- transitionWorkContractPhase(
2367
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
2368
- contract.workId,
2369
- {
2370
- phase: 'delivery',
2371
- status: 'blocked',
2372
- state: 'blocked',
2373
- dispatchState: 'blocked',
2374
- summary: `Linear target-advance integration failed inside the isolated Work checkout; canonical ${targetBranch} was not mutated.`,
2375
- },
2376
- );
2377
- }
2378
- return {
2379
- work: compactHandle(current),
2380
- stages: current.finalization,
2381
- completed: false,
2382
- blocked: true,
2383
- recoverable: integrated.restored,
2384
- error: {
2385
- code: 'WORK_TARGET_ADVANCE_LINEAR_INTEGRATION_FAILED',
2386
- message: integrated.restored
2387
- ? 'Target advancement could not be rebased safely; the isolated Work checkout was restored to its original candidate HEAD.'
2388
- : 'Target advancement rebase failed and exact checkout restoration could not be proven; inspect the isolated Work checkout before retrying.',
2389
- },
2390
- continuation: 'WORK_TARGET_ADVANCE_REVIEW_REQUIRED: inspect the isolated Work checkout and retry only after its exact branch/HEAD/cleanliness are proven.',
2391
- };
2392
- }
2393
- const integratedHead = gitHead(mergeValidated.worktreeRepository.canonicalRoot);
2394
- if (!integratedHead || !gitIsAncestor(mergeValidated.worktreeRepository.canonicalRoot, advance.targetHead, integratedHead)) {
2395
- return failStage('merge', `WORK_TARGET_ADVANCE_LINEAR_ANCESTRY_MISMATCH: target ${advance.targetHead} is not an ancestor of ${integratedHead ?? 'unavailable'}`);
2396
- }
2397
- const mergeCommits = targetAdvanceLinearMergeCommits(
2398
- mergeValidated.worktreeRepository.canonicalRoot,
2399
- advance.targetHead,
2400
- integratedHead,
2401
- );
2402
- const integratedTree = spawnSync('git', ['-C', mergeValidated.worktreeRepository.canonicalRoot, 'rev-parse', '--verify', `${integratedHead}^{tree}`], {
2403
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
2404
- });
2405
- if (mergeCommits.length > 0) {
2406
- return failStage(
2407
- 'merge',
2408
- `WORK_TARGET_ADVANCE_LINEAR_HISTORY_VIOLATION: integrated candidate contains merge commit(s): ${mergeCommits.slice(0, 8).join(', ')}`,
2409
- 'WORK_TARGET_ADVANCE_LINEAR_HISTORY_VIOLATION',
2410
- );
2411
- }
2412
- if (
2413
- !advance.mergedTree
2414
- || integratedTree.status !== 0
2415
- || integratedTree.error
2416
- || typeof integratedTree.stdout !== 'string'
2417
- || integratedTree.stdout.trim() !== advance.mergedTree
2418
- ) {
2419
- return failStage('merge', `WORK_TARGET_ADVANCE_LINEAR_TREE_MISMATCH: rebased candidate tree does not equal the preflight clean integration tree ${advance.mergedTree ?? 'unavailable'}`);
2420
- }
2421
- const workOwnedChangedPaths = gitChangedPaths(mergeValidated.worktreeRepository.canonicalRoot, advance.targetHead, integratedHead);
2422
- const postIntegrationScopeViolation = targetAdvanceWorkScopeViolation(contract, workOwnedChangedPaths);
2423
- if (postIntegrationScopeViolation) {
2424
- return failStage('merge', `WORK_TARGET_ADVANCE_SCOPE_VIOLATION: integrated Work-owned ${postIntegrationScopeViolation.kind} path ${postIntegrationScopeViolation.path}`);
2425
- }
2426
- const integratedInput = currentWorkValidationInput(
2427
- mergeValidated.worktreeRepository,
2428
- { ...current, expectedHead: integratedHead },
2429
- checks,
2430
- );
2431
- const checksAfter = checks.length > 0 ? listControllerChecks(mergeValidated.worktreeRepository.canonicalRoot) : [];
2432
- const transferPlan = contract
2433
- ? planTargetAdvanceValidationAuthority({
2434
- checkIds: checks,
2435
- checkRefs: contract.checkRefs,
2436
- checksBefore,
2437
- checksAfter,
2438
- candidateHead: advance.candidateHead,
2439
- candidateWorkspaceFingerprint: candidateInput.workspaceFingerprint,
2440
- integratedHead,
2441
- integratedWorkspaceFingerprint: integratedInput.workspaceFingerprint,
2442
- targetChangedPaths: advance.targetChangedPaths,
2443
- })
2444
- : { transferredRecords: [], reusableCheckIds: [], invalidatedCheckIds: checks };
2445
- for (const record of transferPlan.transferredRecords) {
2446
- appendVerificationRecord({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, record);
2447
- }
2448
- if (contract) replaceTargetAdvanceScopeEvidence(ctx, contract, workOwnedChangedPaths);
2449
- const validationPreserved = transferPlan.invalidatedCheckIds.length === 0;
2450
- current = transact('target-advance-integrated', (fresh) => transitionWorkHandle(
2451
- ctx.controllerHome,
2452
- fresh,
2453
- validationPreserved ? 'committed' : 'validating',
2454
- {
2455
- deliveryBaseCommit: advance.targetHead,
2456
- expectedHead: integratedHead,
2457
- failureReason: undefined,
2458
- finalization: { ...fresh.finalization, validation: validationPreserved ? 'done' : 'pending', merge: 'pending', failureCode: undefined, lastError: undefined },
2459
- validationRun: undefined,
2460
- validatedInputFingerprint: validationPreserved ? integratedInput.fingerprint : undefined,
2461
- },
2462
- ));
2463
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
2464
- title: 'target advancement linearly integrated into isolated Work candidate',
2465
- summary: `Canonical ${targetBranch} at ${advance.targetHead} advanced independently. Forge rebased Work candidate ${advance.candidateHead} onto it as ${integratedHead} with no merge commits; Work scope is ${workOwnedChangedPaths.length} target-relative path(s). Validation authority transferred for ${transferPlan.reusableCheckIds.length}/${checks.length} check(s); ${transferPlan.invalidatedCheckIds.length} check(s) require fresh evidence.`,
2466
- detailLevel: 'summary',
2467
- });
2468
- if (!validationPreserved) {
2469
- markWorkValidationPending(ctx.controllerHome, current);
2470
- return {
2471
- work: compactHandle(current),
2472
- stages: current.finalization,
2473
- completed: false,
2474
- continuation: `WORK_COMMITTED_REVALIDATION_REQUIRED: target branch ${targetBranch} changed inputs for [${transferPlan.invalidatedCheckIds.join(', ')}]; unaffected check evidence was transferred to ${integratedHead} and may be reused`,
2475
- };
2476
- }
2477
- projectWorkValidationOutcome(ctx.controllerHome, current, 'passed', checks.length === 0
2478
- ? 'Target advancement was linearly integrated and no validation checks were required.'
2479
- : `Target advancement was linearly integrated; all ${checks.length} check result(s) retained authority because target-only changes did not affect their declared inputs.`);
2480
- }
2481
- }
2482
- }
2483
- const deliveryContract = contractFor(ctx, current);
2484
- if (!deliveryContract) throw new Error(`WORK_IMPLEMENTATION_REVIEW_CONTRACT_REQUIRED: ${current.workId}`);
2485
- try {
2486
- const deliveryRepository = selectRepositoryCheckout(
2487
- getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true }),
2488
- current.checkoutId,
2489
- { allowArchived: true },
2490
- );
2491
- assertPhysicalImplementationReviewGate({
2492
- ctx, repository: deliveryRepository, handle: current, contract: deliveryContract, targetBranch,
2493
- });
2494
- } catch (error) {
2495
- return {
2496
- work: compactHandle(current),
2497
- stages: current.finalization,
2498
- completed: false,
2499
- blocked: true,
2500
- recoverable: true,
2501
- error: {
2502
- code: 'WORK_IMPLEMENTATION_REVIEW_REQUIRED',
2503
- message: error instanceof Error ? error.message : String(error),
2504
- },
2505
- continuation: 'WORK_IMPLEMENTATION_REVIEW_REQUIRED: target integration changed the Work delivery candidate; verify/review the exact current candidate before mutating the canonical target.',
2506
- };
2507
- }
2508
- let merged: ReturnType<typeof repositoryGitFinishWorkflow>;
2509
- if (activeMergeHead) {
2510
- const expectedMergeHead = current.expectedHead ? gitRevision(target.canonicalRoot, current.expectedHead) : undefined;
2511
- const featureHead = gitRevision(target.canonicalRoot, current.branch);
2512
- const currentBranchResult = spawnSync('git', ['-C', target.canonicalRoot, 'branch', '--show-current'], {
2513
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
2514
- });
2515
- const currentBranchName = currentBranchResult.status === 0 && typeof currentBranchResult.stdout === 'string'
2516
- ? currentBranchResult.stdout.trim()
2517
- : '';
2518
- if (!expectedMergeHead || activeMergeHead !== expectedMergeHead || featureHead !== expectedMergeHead || currentBranchName !== targetBranch) {
2519
- return failStage(
2520
- 'merge',
2521
- `WORK_MERGE_HEAD_OWNERSHIP_MISMATCH: retry may conclude only Work ${current.workId} merge head ${expectedMergeHead ?? 'unavailable'} from ${current.branch} while on ${targetBranch}; observed MERGE_HEAD ${activeMergeHead}, feature ${featureHead ?? 'unavailable'}, branch ${currentBranchName || 'unavailable'}`,
2522
- );
2523
- }
2524
- const unmerged = spawnSync('git', ['-C', target.canonicalRoot, 'diff', '--name-only', '--diff-filter=U'], {
2525
- encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
2526
- });
2527
- if (unmerged.status !== 0 || unmerged.error || typeof unmerged.stdout !== 'string') {
2528
- return failStage('merge', 'WORK_MERGE_CONFLICT_STATE_UNAVAILABLE: unable to prove that the owned merge has no unmerged paths');
2529
- }
2530
- if (unmerged.stdout.trim()) {
2531
- return failStage('merge', `WORK_MERGE_CONFLICTS_UNRESOLVED: resolve and stage all paths before retrying finalize (${unmerged.stdout.trim().split('\n').filter(Boolean).join(', ')})`);
2532
- }
2533
- const beforeConclude = repositoryGitStatus(target);
2534
- const concluded = repositoryGitCommit(ctx.controllerHome, target, {
2535
- message: `Merge ${current.branch} into ${targetBranch}`,
2536
- allowEmpty: true,
2537
- authorizationDecision: gitAuthorization,
2538
- sessionId: session.sessionId,
2539
- principalId: session.principalId,
2540
- workId: current.workId,
2541
- goalId: current.goalId,
2542
- });
2543
- if (!concluded.committed || !concluded.commit) {
2544
- return failStage('merge', concluded.error?.message ?? 'WORK_MERGE_CONCLUDE_FAILED: git commit did not conclude the owned merge');
2545
- }
2546
- const concludedSecondParent = gitRevision(target.canonicalRoot, 'HEAD^2');
2547
- if (concludedSecondParent !== expectedMergeHead || gitRevision(target.canonicalRoot, 'MERGE_HEAD')) {
2548
- return failStage('merge', `WORK_MERGE_CONCLUSION_OWNERSHIP_MISMATCH: concluded merge must retain ${expectedMergeHead} as its second parent and clear MERGE_HEAD`);
2549
- }
2550
- const steps: ReturnType<typeof repositoryGitFinishWorkflow>['steps'] = [{ name: 'conclude_owned_merge', execution: concluded.commit }];
2551
- if (!deleteAfterWorktreeCleanup && deleteBranchRequested) {
2552
- const deleted = repositoryGitDeleteBranch(ctx.controllerHome, target, { branch: current.branch, authorizationDecision: gitAuthorization, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, goalId: current.goalId });
2553
- steps.push({ name: 'delete_feature_branch', execution: deleted.execution });
2554
- if (deleted.execution.status !== 'executed' || deleted.execution.ok !== true) {
2555
- return failStage('merge', deleted.execution.stderr || 'WORK_MERGE_BRANCH_CLEANUP_FAILED: owned merge concluded but feature branch deletion failed');
2556
- }
2557
- }
2558
- merged = {
2559
- repoId: target.repoId,
2560
- checkoutId: target.activeCheckoutId,
2561
- featureBranch: current.branch,
2562
- targetBranch,
2563
- before: beforeConclude,
2564
- steps,
2565
- after: repositoryGitStatus(target),
2566
- completed: true,
2567
- };
2568
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
2569
- title: 'owned resolved merge concluded',
2570
- summary: `Finalizer retry proved MERGE_HEAD ${activeMergeHead} belongs to the exact Work delivery head, found no unmerged paths, and concluded the existing merge instead of replaying git merge.`,
2571
- detailLevel: 'summary',
2572
- });
2573
- } else {
2574
- const integrationLockKey = { scope: 'task' as const, repoId: current.repositoryId, taskId: `target-integration:${targetBranch}` };
2575
- try {
2576
- merged = withControllerLock(
2577
- ctx.controllerHome,
2578
- integrationLockKey,
2579
- `work-finalize-target:${current.workId}`,
2580
- () => {
2581
- const lockedTargetStatus = repositoryGitStatus(target);
2582
- const lockedDirtyPaths = [...new Set([
2583
- ...lockedTargetStatus.staged,
2584
- ...lockedTargetStatus.unstaged,
2585
- ...lockedTargetStatus.untracked,
2586
- ])].sort();
2587
- let preserveDirtyTargetPaths: string[] | undefined;
2588
- if (lockedDirtyPaths.length > 0) {
2589
- const preservation = planTargetDirtyPreservation({
2590
- dirtyPaths: lockedDirtyPaths,
2591
- targetCheckoutId: target.activeCheckoutId,
2592
- currentWorkId: current.workId,
2593
- activeWorks: listWorkContracts({ controllerHome: ctx.controllerHome, repoId: current.repositoryId, status: 'active', limit: 200 }),
2594
- });
2595
- const ownership = preservation.ownership;
2596
- preserveDirtyTargetPaths = preservation.preserveDirtyTargetPaths;
2597
- const attribution = ownership.owned
2598
- ? 'all preserved paths are attributed uniquely to other active Work'
2599
- : `preserved paths include unattributed or ambiguous content (unowned=${ownership.unownedPaths.length}, ambiguous=${ownership.ambiguousPaths.length})`;
2600
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
2601
- title: 'concurrent target dirty content preserved',
2602
- summary: `Target ${targetBranch} retained ${preserveDirtyTargetPaths.length} dirty path(s) in place; ${attribution}. Git integration remains fail-closed before target mutation if the candidate overlaps any preserved path.`,
2603
- detailLevel: 'summary',
2604
- });
2605
- }
2606
- return repositoryGitFinishWorkflow(ctx.controllerHome, target, {
2607
- featureBranch: current.branch,
2608
- targetBranch,
2609
- deleteBranch: !deleteAfterWorktreeCleanup && deleteBranchRequested,
2610
- noFf: args.no_ff === true,
2611
- preserveDirtyTargetPaths,
2612
- authorizationDecision: gitAuthorization,
2613
- sessionId: session.sessionId,
2614
- principalId: session.principalId,
2615
- workId: current.workId,
2616
- goalId: current.goalId,
2617
- });
2618
- },
2619
- 30_000,
2620
- 5_000,
2621
- );
2622
- } catch (error) {
2623
- return failStage('merge', error instanceof Error ? error.message : String(error));
2624
- }
2625
- }
2626
- const pendingAuthorization = merged.steps.find((step) => step.execution.authorizationDecision?.decision === 'user_confirmation_required')?.execution.authorizationDecision;
2627
- if (pendingAuthorization) return { authorization: pendingAuthorization, work: compactHandle(current), stages: current.finalization };
2628
- if (!merged.completed) return { ...failStage('merge', merged.error?.message ?? 'merge failed'), merge: merged };
2629
- current = transact('merge-done', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'merged', {
2630
- finalization: { ...fresh.finalization, merge: 'done', branchCleanup: !deleteBranchRequested ? 'skipped' : deleteAfterWorktreeCleanup ? 'pending' : 'done', failureCode: undefined, lastError: undefined },
2631
- failureReason: undefined,
2632
- }));
2633
- } else if (!wants.merge && current.finalization.merge === 'pending') {
2634
- current = transact('merge-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, {
2635
- ...fresh,
2636
- finalization: {
2637
- ...fresh.finalization,
2638
- merge: 'skipped',
2639
- branchCleanup: requestedOutcome === 'completed_no_change' && wants.cleanup && deleteBranchRequested ? 'pending' : 'skipped',
2640
- },
2641
- }));
2642
- }
2643
-
2644
- let remoteDelivery: WorkRemoteDeliveryReceipt | undefined;
2645
- const remoteDeliveryRequired = args.remote_write === true
2646
- || contractFor(ctx, current)?.constraints.remoteDeliveryRequired === true;
2647
- if (remoteDeliveryRequired && requestedOutcome !== 'completed_no_change') {
2648
- const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
2649
- const target = selectWorkFinalizationTarget(repository, current);
2650
- const targetBranch = resolveWorkDeliveryTargetBranch(current, repository.defaultBranch, explicitTargetBranch);
2651
- const targetRevision = gitRevision(target.canonicalRoot, targetBranch);
2652
- if (!targetRevision) throw new Error(`WORK_REMOTE_DELIVERY_TARGET_REQUIRED: target branch ${targetBranch} is unavailable`);
2653
- const authorityRepository = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
2654
- const remoteDeliveryContract = contractFor(ctx, current);
2655
- if (!remoteDeliveryContract) throw new Error(`WORK_IMPLEMENTATION_REVIEW_CONTRACT_REQUIRED: ${current.workId}`);
2656
- assertPhysicalImplementationReviewGate({
2657
- ctx, repository: authorityRepository, handle: current, contract: remoteDeliveryContract, targetBranch,
2658
- });
2659
- remoteDelivery = await pushExactWorkRemoteDelivery({
2660
- controllerHome: ctx.controllerHome,
2661
- repository: authorityRepository,
2662
- workId: current.workId,
2663
- targetBranch,
2664
- targetRevision,
2665
- });
2666
- appendWorkEvidence(
2667
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
2668
- current.workContractId ?? current.workId,
2669
- {
2670
- title: 'remote delivery verified before Work terminalization',
2671
- summary: remoteDelivery.pushed
2672
- ? `Exact integrated revision ${remoteDelivery.targetRevision} was pushed to origin/${targetBranch} while Work ${current.workId} still held active delivery authority.`
2673
- : `origin/${targetBranch} already contained exact integrated revision ${remoteDelivery.targetRevision}; remote delivery was verified idempotently before Work terminalization.`,
2674
- detailLevel: 'summary',
2675
- },
2676
- );
2677
- }
2678
-
2679
- if (
2680
- requestedOutcome === 'completed_no_change'
2681
- && current.finalization.validation === 'done'
2682
- && current.finalization.commit === 'skipped'
2683
- && current.finalization.merge === 'skipped'
2684
- && current.state !== 'merged'
2685
- && current.state !== 'cleaned'
2686
- ) {
2687
- // `merged` is the WorkHandle's durable integrated-delivery boundary. A
2688
- // no-change completion has no Git merge, but it must cross that boundary
2689
- // before physical cleanup so an earlier failure preserves retryability.
2690
- if (current.state === 'prepared') {
2691
- current = transact('no-change-validation-state', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'validating', {
2692
- finalization: fresh.finalization,
2693
- failureReason: undefined,
2694
- }));
2695
- }
2696
- if (current.state === 'editing' || current.state === 'validating' || current.state === 'committed') {
2697
- current = transact('no-change-delivery-integrated', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'merged', {
2698
- finalization: fresh.finalization,
2699
- failureReason: undefined,
2700
- }));
2701
- } else if (current.state !== 'merged') {
2702
- throw new Error(`WORK_NO_CHANGE_DELIVERY_STATE_INVALID: cannot establish no-change delivery from ${current.state}`);
2703
- }
2704
- }
2705
-
2706
- let prevalidatedNoChangeReceipt: CompletionReceipt | undefined;
2707
- if (requestedOutcome === 'completed_no_change'
2708
- && current.finalization.validation === 'done'
2709
- && current.finalization.commit === 'skipped'
2710
- && current.finalization.merge === 'skipped'
2711
- && (current.state === 'merged' || current.state === 'cleaned')) {
2712
- const completionContract = contractFor(ctx, current);
2713
- if (!completionContract) throw new Error(`WORK_COMPLETION_CONTRACT_MISSING: ${current.workContractId ?? current.workId}`);
2714
- // Delivery/target identity is semantic completion evidence. Prove it before
2715
- // deleting any managed worktree or branch so a wrong target remains safely
2716
- // retryable with the exact workspace intact.
2717
- prevalidatedNoChangeReceipt = completionReceiptForFinalizedWork(ctx, current, completionContract, args);
2718
- }
2719
-
2720
- if (wants.cleanup && current.finalization.worktreeCleanup === 'pending') {
2721
- const contract = contractFor(ctx, current);
2722
- if (contract?.constraints.allowCleanup === false) throw new Error('WORK_CLEANUP_NOT_ALLOWED: WorkContract disallows cleanup');
2723
- if (!current.managedWorktree) {
2724
- current = transact('worktree-cleanup-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, worktreeCleanup: 'skipped' } }));
2725
- } else {
2726
- const cleanupContract = contractFor(ctx, current);
2727
- if (!cleanupContract) throw new Error(`WORK_IMPLEMENTATION_REVIEW_CONTRACT_REQUIRED: ${current.workId}`);
2728
- if (cleanupContract.status !== 'cancelled' && cleanupContract.status !== 'failed') {
2729
- const cleanupRepository = selectRepositoryCheckout(
2730
- getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true }),
2731
- current.checkoutId,
2732
- { allowArchived: true },
2733
- );
2734
- const cleanupTargetBranch = resolveWorkDeliveryTargetBranch(
2735
- current,
2736
- cleanupRepository.defaultBranch,
2737
- explicitTargetBranch,
2738
- );
2739
- assertPhysicalImplementationReviewGate({
2740
- ctx, repository: cleanupRepository, handle: current, contract: cleanupContract, targetBranch: cleanupTargetBranch,
2741
- });
2742
- }
2743
- const target = selectWorkFinalizationTarget(getRepository(current.repositoryId, ctx.controllerHome), current);
2744
- const cleanup = runCleanup(target.canonicalRoot, current.worktreePath);
2745
- if (!cleanup.ok) return failStage('worktreeCleanup', cleanup.message ?? 'worktree cleanup failed');
2746
- current = transact('worktree-cleanup-done', (fresh) => {
2747
- setRepositoryCheckoutLifecycle({ controllerHome: ctx.controllerHome, repoId: fresh.repositoryId, checkoutId: fresh.checkoutId, lifecycle: 'removed', reason: `Work ${fresh.workId} cleanup completed.` });
2748
- markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:worktree`);
2749
- return writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, worktreeCleanup: 'done', failureCode: undefined, lastError: undefined } });
2750
- });
2751
- }
2752
- } else if (!wants.cleanup && current.finalization.worktreeCleanup === 'pending') {
2753
- current = transact('worktree-cleanup-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, worktreeCleanup: 'skipped' } }));
2754
- }
2755
-
2756
- if (
2757
- wants.cleanup
2758
- && current.finalization.branchCleanup === 'pending'
2759
- && (current.finalization.merge === 'done' || requestedOutcome === 'completed_no_change')
2760
- ) {
2761
- const target = selectWorkFinalizationTarget(getRepository(current.repositoryId, ctx.controllerHome), current);
2762
- const branchCleanupContract = contractFor(ctx, current);
2763
- if (!branchCleanupContract) throw new Error(`WORK_IMPLEMENTATION_REVIEW_CONTRACT_REQUIRED: ${current.workId}`);
2764
- if (branchCleanupContract.status !== 'cancelled' && branchCleanupContract.status !== 'failed') {
2765
- const targetBranch = resolveWorkDeliveryTargetBranch(current, target.defaultBranch, explicitTargetBranch);
2766
- assertPhysicalBranchCleanupImplementationReviewGate({
2767
- target,
2768
- handle: current,
2769
- contract: branchCleanupContract,
2770
- targetBranch,
2771
- });
2772
- }
2773
- const deleted = repositoryGitDeleteBranch(ctx.controllerHome, target, { branch: current.branch, force: false, authorizationDecision: gitAuthorization, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, goalId: current.goalId });
2774
- if (deleted.execution.authorizationDecision?.decision === 'user_confirmation_required') return { authorization: deleted.execution.authorizationDecision, work: compactHandle(current), stages: current.finalization };
2775
- if (deleted.execution.status !== 'executed' || deleted.execution.ok !== true) return failStage('branchCleanup', deleted.execution.stderr || 'feature branch cleanup failed');
2776
- current = transact('branch-cleanup-done', (fresh) => {
2777
- markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:branch`);
2778
- return writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, branchCleanup: 'done', failureCode: undefined, lastError: undefined } });
2779
- });
2780
- }
2781
-
2782
- const complete = finalizationComplete(current.finalization);
2783
- if (complete) {
2784
- const finalState = finalStateForStages(current.finalization, current.state);
2785
- current = transact('complete', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, finalState, { finalization: current.finalization, failureReason: undefined }));
2786
- const completedContract = contractFor(ctx, current);
2787
- if (completedContract?.status === 'cancelled') {
2788
- appendWorkEvidence(
2789
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
2790
- completedContract.workId,
2791
- {
2792
- title: 'cancelled work cleanup completed',
2793
- summary: 'Controller verified terminal ownership, a clean or previously removed managed worktree, and a branch HEAD already contained in the target branch before deleting retained workspace references.',
2794
- detailLevel: 'summary',
2795
- },
2796
- );
2797
- } else {
2798
- const workId = current.workContractId ?? current.workId;
2799
- const completionContract = contractFor(ctx, current);
2800
- if (!completionContract) throw new Error(`WORK_COMPLETION_CONTRACT_MISSING: ${workId}`);
2801
- if (requestedOutcome === 'completed_no_change') {
2802
- appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, workId, {
2803
- title: 'objective-specific no-change proof',
2804
- summary: noChangeProof,
2805
- detailLevel: 'summary',
2806
- });
2807
- }
2808
- if (prevalidatedNoChangeReceipt) {
2809
- const ownerPrincipal = terminalizationOwner.principalId?.trim() || terminalizationOwner.controllerId;
2810
- const ownerInstanceId = terminalizationOwner.controllerInstanceId?.trim() || '';
2811
- const ownerClaimGeneration = terminalizationOwner.claimGeneration;
2812
- if (!ownerInstanceId || typeof ownerClaimGeneration !== 'number' || ownerClaimGeneration < 1) {
2813
- throw new Error(`WORK_CONTROLLER_TERMINALIZATION_AUTHORITY_INVALID: ${workId}`);
2814
- }
2815
- const fencedCompletion = withControllerSessionTerminalizationFence(
2816
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
2817
- {
2818
- workId,
2819
- actor: `work-finalize-completion:${terminalizationOwner.controllerId}:${ownerInstanceId}`,
2820
- authority: {
2821
- controllerId: terminalizationOwner.controllerId, controllerType: terminalizationOwner.controllerType,
2822
- principalId: ownerPrincipal, controllerInstanceId: ownerInstanceId, claimGeneration: ownerClaimGeneration,
2823
- },
2824
- },
2825
- () => completeWorkWithReceipt(
2826
- { controllerHome: ctx.controllerHome, repoId: current.repositoryId }, workId, prevalidatedNoChangeReceipt,
2827
- 'completed_no_change', 'completed_no_change',
2828
- ),
2829
- );
2830
- if (!fencedCompletion.allowed) throw new Error(`WORK_TERMINALIZATION_AUTHORITY_FENCED: ${workId}:${fencedCompletion.reason}`);
2831
- } else {
2832
- completeFinalizedWorkContract({
2833
- ctx, handle: current, contract: completionContract, args, terminalizationOwner,
2834
- outcome: requestedOutcome === 'completed_no_change' ? 'completed_no_change' : 'completed_changed',
2835
- });
2836
- }
2837
- }
2838
- // Successful WorkContract completion always ends controller ownership.
2839
- // Physical branch/worktree retention is represented by finalization stages
2840
- // and completion-receipt warnings; it must not keep a mutation owner live.
2841
- releasePreparedWorkOwnership(ctx, current);
2842
- updateExecutionSession(ctx.controllerHome, identity, {
2843
- activeWorkId: undefined,
2844
- activeCheckoutId: current.sourceCheckoutId ?? session.activeCheckoutId,
2845
- });
2846
- }
2847
- return { work: compactHandle(current), stages: current.finalization, completed: complete, ...(remoteDelivery ? { remoteDelivery } : {}), idempotent: !wants.commit && !wants.merge && !wants.cleanup && current.finalization.validation === 'done' };
2848
- }
2849
-
2850
- // WORK_FINALIZATION_SERVICE_END