@moretea-labs/forge 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/AGENTS.md +70 -7
  2. package/CHANGELOG.md +14 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/THIRD_PARTY_NOTICES.md +1 -1
  6. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
  8. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  9. package/adapters/chatgpt/controller-round-host.ts +15 -25
  10. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  11. package/adapters/chatgpt/provider-delivery.ts +228 -1
  12. package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
  13. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  14. package/adapters/computer/chatgpt-target-router.ts +103 -0
  15. package/adapters/computer/chrome-extension/background.js +385 -0
  16. package/adapters/computer/chrome-extension/content.js +378 -0
  17. package/adapters/computer/chrome-extension/core.js +24 -0
  18. package/adapters/computer/chrome-extension/manifest.json +30 -0
  19. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  20. package/adapters/computer/interaction-target-authority.ts +142 -18
  21. package/adapters/mcp/auth.ts +0 -1
  22. package/adapters/mcp/instructions.ts +5 -4
  23. package/adapters/mcp/multi-repository.ts +3 -19
  24. package/adapters/mcp/oauth.ts +73 -0
  25. package/adapters/mcp/paths.ts +4 -1
  26. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  27. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  28. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  29. package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
  30. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  31. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  32. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  33. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  34. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  35. package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
  36. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  37. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
  38. package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
  39. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  40. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  41. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  42. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  43. package/adapters/mcp/runtime-gateway/router.ts +18 -504
  44. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  45. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
  46. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  47. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
  48. package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
  49. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
  50. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  51. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
  52. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  53. package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
  54. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  55. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  56. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  57. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  58. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  59. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  60. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  61. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  62. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  63. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  64. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  65. package/adapters/mcp/server.ts +67 -23
  66. package/adapters/mcp/setup.ts +1 -3
  67. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
  68. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
  69. package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
  70. package/adapters/mcp/toolset-names.ts +5 -0
  71. package/adapters/mcp/toolset.ts +6 -5
  72. package/adapters/mcp/transports/http-observation.ts +365 -0
  73. package/adapters/mcp/transports/http.ts +154 -677
  74. package/adapters/mcp/transports/oauth-http.ts +322 -0
  75. package/adapters/mcp/transports/session-registry.ts +6 -0
  76. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  77. package/assets/plugin-registry.v1.json +6 -4
  78. package/assets/reference-configs/agentic-development-flow.md +25 -0
  79. package/assets/reference-configs/global-working-rules.md +19 -0
  80. package/assets/skill-version.json +2 -2
  81. package/docs/architecture/CURRENT.md +111 -41
  82. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  83. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  84. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  85. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  86. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  87. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  88. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  89. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  90. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  91. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  92. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  93. package/docs/architecture/domains/verification.md +1 -0
  94. package/docs/architecture/domains/workflow-engine.md +6 -0
  95. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  96. package/docs/forge-plugin-management.md +2 -2
  97. package/docs/operations/controller-performance-and-502.md +17 -0
  98. package/docs/operations/controller-reliability-runbook.md +2 -2
  99. package/docs/operations/features.md +1 -1
  100. package/docs/operations/features.zh-CN.md +1 -1
  101. package/docs/operations/homebrew.md +2 -2
  102. package/docs/operations/mcp-tool-exposure.md +2 -0
  103. package/docs/operations/releasing.md +2 -0
  104. package/docs/operations/standalone-disaster-recovery.md +14 -6
  105. package/docs/wiki/Implementation.md +1 -1
  106. package/package.json +25 -14
  107. package/packages/kernel/cognition/api/index.ts +6 -0
  108. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  109. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  110. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  111. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  112. package/packages/kernel/cognition/domain/learning.ts +106 -0
  113. package/packages/kernel/cognition/domain/memory.ts +254 -0
  114. package/packages/kernel/controller/api/index.ts +1 -0
  115. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  116. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  117. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  118. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  119. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  120. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  121. package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
  122. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  123. package/packages/kernel/identity/api/index.ts +10 -0
  124. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  125. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  126. package/packages/kernel/identity/domain/grant.ts +72 -0
  127. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  128. package/packages/kernel/identity/domain/user-request.ts +93 -0
  129. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  130. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  131. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  132. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  133. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  134. package/packages/kernel/scheduler/api/index.ts +0 -2
  135. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  136. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  137. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  138. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
  139. package/packages/kernel/work/api/index.ts +3 -2
  140. package/packages/kernel/work/application/work-service.ts +17 -42
  141. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  142. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  143. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  144. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  145. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  146. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  147. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  148. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  149. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  150. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  151. package/packages/kernel/work/domain/state-machine.ts +25 -63
  152. package/packages/kernel/work/domain/types.ts +57 -51
  153. package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
  154. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  155. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  156. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  157. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  158. package/packages/plugin-runtime/computer/index.ts +2 -0
  159. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  160. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  161. package/packages/protocols/computer/contract.ts +27 -1
  162. package/scripts/activate-source-baseline.ts +7 -9
  163. package/scripts/canary-controller-semantic-wait.ts +12 -24
  164. package/scripts/check-mcp-compatibility.ts +48 -136
  165. package/scripts/check-runtime-architecture.mjs +622 -217
  166. package/scripts/forge-local-recovery-helper.mjs +93 -15
  167. package/scripts/lib/project-init-lib.sh +3 -0
  168. package/scripts/open-source-audit-allowlist.txt +1 -2
  169. package/scripts/run-bounded-command.mjs +5 -0
  170. package/scripts/run-governed-gate.ts +0 -1
  171. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  172. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  173. package/scripts/smoke-mcp-http-runtime.ts +5 -0
  174. package/scripts/smoke-runtime-recovery.ts +12 -43
  175. package/scripts/smoke-schedule-engine.ts +23 -15
  176. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  177. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  178. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  179. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  180. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  181. package/scripts/smoke-workflow-supervisor.ts +103 -0
  182. package/scripts/verify-forge-runtime.sh +2 -1
  183. package/src/cli/agent-jobs/executor-health.ts +11 -34
  184. package/src/cli/agent-jobs/integration.ts +18 -49
  185. package/src/cli/agent-jobs/job-manager.ts +7 -3
  186. package/src/cli/commands/brain-assistant.ts +27 -13
  187. package/src/cli/commands/brain.ts +13 -15
  188. package/src/cli/commands/chatgpt.ts +1 -163
  189. package/src/cli/commands/recovery.ts +135 -121
  190. package/src/cli/commands/runtime.ts +3 -3
  191. package/src/cli/commands/setup-profile.ts +8 -0
  192. package/src/cli/controller/check-runner.ts +13 -2
  193. package/src/cli/controller/completion-backlog.ts +3 -3
  194. package/src/cli/controller/completion-orchestrator.ts +4 -43
  195. package/src/cli/controller/completion-target.ts +3 -72
  196. package/src/cli/controller/context/source-materializer.ts +81 -100
  197. package/src/cli/controller/context-pack.ts +10 -8
  198. package/src/cli/controller/execution-completion.ts +4 -7
  199. package/src/cli/controller/execution-policy.ts +36 -153
  200. package/src/cli/controller/governance.ts +2 -3
  201. package/src/cli/controller/issue-store.ts +4 -99
  202. package/src/cli/controller/launch-agents.ts +59 -18
  203. package/src/cli/controller/operational-plan.ts +24 -51
  204. package/src/cli/controller/runtime-config.ts +5 -1
  205. package/src/cli/controller/stuck-state-migration.ts +2 -3
  206. package/src/cli/controller/systemd-user.ts +39 -1
  207. package/src/cli/editing/edit-session.ts +1 -7
  208. package/src/cli/local-bridge/console-automations.ts +18 -13
  209. package/src/cli/local-bridge/console-view-models.ts +3 -12
  210. package/src/cli/local-bridge/facade-api.ts +64 -191
  211. package/src/cli/local-bridge/server.ts +24 -147
  212. package/src/cli/local-bridge/types.ts +0 -1
  213. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  214. package/src/cli/repositories/command-executor.ts +29 -23
  215. package/src/cli/repositories/command-scope.ts +35 -16
  216. package/src/cli/repositories/controller-home.ts +48 -0
  217. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  218. package/src/cli/repositories/git-command-executor.ts +2 -2
  219. package/src/cli/repositories/registry.ts +77 -11
  220. package/src/cli/repositories/structured-git.ts +62 -2
  221. package/src/cli/repository/session-cache.ts +92 -6
  222. package/src/runtime/context/assistant-context.ts +20 -7
  223. package/src/runtime/context/assistant-work-context.ts +89 -27
  224. package/src/runtime/context/automatic-learning.ts +565 -0
  225. package/src/runtime/context/cognitive-attention.ts +173 -0
  226. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  227. package/src/runtime/context/context-closure.ts +46 -9
  228. package/src/runtime/context/context-pack-process.ts +103 -0
  229. package/src/runtime/context/context-plane.ts +1 -9
  230. package/src/runtime/context/direct-controller-learning.ts +298 -0
  231. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  232. package/src/runtime/context/semantic-navigation.ts +6 -34
  233. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  234. package/src/runtime/context/typescript-navigation.ts +89 -3
  235. package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
  236. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  237. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  238. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  239. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  240. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  241. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  242. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  243. package/src/runtime/control-plane/execution/validation.ts +6 -8
  244. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  245. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  246. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  247. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  248. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  249. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  250. package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
  251. package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
  252. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  253. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  254. package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
  255. package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
  256. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  257. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  258. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
  259. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
  260. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  261. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  262. package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
  263. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  264. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  265. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  266. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  267. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  268. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  269. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  270. package/src/runtime/control-plane/facade/index.ts +0 -1
  271. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  272. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  273. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  274. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  275. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  276. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  277. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  278. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  279. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  280. package/src/runtime/control-plane/facade/types.ts +49 -108
  281. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  282. package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
  283. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  284. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  285. package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
  286. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
  287. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  288. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  289. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  290. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  291. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  292. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
  293. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  294. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  295. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  296. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  297. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  298. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  299. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  300. package/src/runtime/control-plane/release-retention.ts +135 -47
  301. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  302. package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
  303. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  304. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  305. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  306. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  307. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  308. package/src/runtime/evidence/artifact-store.ts +158 -40
  309. package/src/runtime/evidence/evidence-store.ts +112 -8
  310. package/src/runtime/evidence/output-handle-index.ts +225 -0
  311. package/src/runtime/evidence/result-store.ts +158 -44
  312. package/src/runtime/execution/jobs/store.ts +3 -4
  313. package/src/runtime/execution/managed-workspace.ts +57 -0
  314. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  315. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  316. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  317. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  318. package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
  319. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  320. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  321. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  322. package/src/runtime/execution/process-runtime/index.ts +3 -0
  323. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  324. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  325. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  326. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  327. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  328. package/src/runtime/execution/process-runtime/runtime.ts +35 -2
  329. package/src/runtime/execution/process-runtime/store.ts +30 -2
  330. package/src/runtime/execution/process-runtime/types.ts +6 -1
  331. package/src/runtime/execution/workers/executor.ts +2 -6
  332. package/src/runtime/platform/service-inventory.ts +130 -0
  333. package/src/runtime/platform/service-manager.ts +8 -0
  334. package/src/runtime/plugins/action-application.ts +85 -28
  335. package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
  336. package/src/runtime/plugins/browser-adapter.ts +757 -34
  337. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  338. package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
  339. package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
  340. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  341. package/src/runtime/plugins/browser-registration.ts +16 -0
  342. package/src/runtime/plugins/browser-runtime.ts +14 -1
  343. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  344. package/src/runtime/plugins/browser-session-store.ts +46 -76
  345. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  346. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  347. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  348. package/src/runtime/plugins/computer-registration.ts +222 -11
  349. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  350. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  351. package/src/runtime/plugins/external-adapter.ts +42 -6
  352. package/src/runtime/plugins/external-registration.ts +21 -1
  353. package/src/runtime/plugins/gmail-adapter.ts +23 -2
  354. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  355. package/src/runtime/plugins/google-shared.ts +14 -7
  356. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  357. package/src/runtime/plugins/ios-adapter.ts +1 -1
  358. package/src/runtime/plugins/lightweight-action.ts +208 -2
  359. package/src/runtime/plugins/local-system-adapter.ts +12 -2
  360. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  361. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  362. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  363. package/src/runtime/plugins/store.ts +602 -428
  364. package/src/runtime/plugins/types.ts +20 -0
  365. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  366. package/src/runtime/projections/invalidation.ts +7 -3
  367. package/src/runtime/projections/materialized-view.ts +80 -36
  368. package/src/runtime/recovery/actions.ts +3 -3
  369. package/src/runtime/recovery/capability-action-application.ts +46 -45
  370. package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
  371. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  372. package/src/runtime/recovery/store.ts +29 -11
  373. package/src/runtime/release/release-coordinator.ts +296 -0
  374. package/src/runtime/release/release-session.ts +716 -0
  375. package/src/runtime/release/release-state-migration.ts +25 -0
  376. package/src/runtime/resources/leases/store.ts +56 -25
  377. package/src/runtime/root/computer-composition.ts +191 -6
  378. package/src/runtime/root/controller-progression-composition.ts +192 -0
  379. package/src/runtime/root/controller-round-composition.ts +20 -1
  380. package/src/runtime/root/deployment-topology.ts +106 -0
  381. package/src/runtime/root/entry.ts +4 -3
  382. package/src/runtime/root/known-good-recovery.ts +184 -0
  383. package/src/runtime/root/local-bridge.ts +0 -1
  384. package/src/runtime/root/mcp-transport.ts +111 -18
  385. package/src/runtime/root/ownership.ts +261 -9
  386. package/src/runtime/root/package-connector-service.ts +105 -19
  387. package/src/runtime/root/package-runtime-release.ts +117 -18
  388. package/src/runtime/root/package-runtime-service.ts +17 -11
  389. package/src/runtime/root/release-execution-canary.ts +44 -12
  390. package/src/runtime/root/release-loader.ts +90 -8
  391. package/src/runtime/root/release-manifest.ts +80 -6
  392. package/src/runtime/root/release-materialize.ts +372 -28
  393. package/src/runtime/root/release-store.ts +395 -36
  394. package/src/runtime/root/runtime-lane.ts +191 -0
  395. package/src/runtime/root/runtime.ts +159 -27
  396. package/src/runtime/root/scheduled-controller-composition.ts +28 -3
  397. package/src/runtime/root/service-runner.ts +2 -2
  398. package/src/runtime/root/service.ts +36 -33
  399. package/src/runtime/root/status.ts +48 -0
  400. package/src/runtime/root/types.ts +32 -3
  401. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  402. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  403. package/src/runtime/root/write-fence.ts +50 -2
  404. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  405. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  406. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  407. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  408. package/src/runtime/shared/process-environment.ts +20 -9
  409. package/src/runtime/shared/response-budget.ts +1 -3
  410. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  411. package/src/runtime/standalone-recovery/core.ts +3702 -342
  412. package/src/runtime/standalone-recovery/entry.ts +469 -39
  413. package/src/runtime/standalone-recovery/installer.ts +262 -148
  414. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  415. package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
  416. package/src/runtime/standalone-recovery/performance.ts +54 -16
  417. package/src/runtime/standalone-recovery/release.ts +53 -5
  418. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  419. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  420. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  421. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
  422. package/src/runtime/workflow/schedules/engine.ts +138 -172
  423. package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
  424. package/src/runtime/workflows/runtime.ts +15 -19
  425. package/src/testing/test-governance.ts +5 -1
  426. package/supervisor/chatgpt-conversation.ts +23 -0
  427. package/supervisor/client.ts +201 -0
  428. package/supervisor/control-plane.ts +868 -0
  429. package/supervisor/entry.ts +15 -0
  430. package/supervisor/forge-validators.ts +80 -0
  431. package/supervisor/index.ts +5 -0
  432. package/supervisor/native-browser-adapter.ts +786 -0
  433. package/supervisor/native-messaging/host.ts +39 -0
  434. package/supervisor/native-messaging/manifest.ts +26 -0
  435. package/supervisor/native-messaging/protocol.ts +27 -0
  436. package/supervisor/paths.ts +18 -0
  437. package/supervisor/protocol.ts +202 -0
  438. package/supervisor/server.ts +404 -0
  439. package/supervisor/store.ts +1451 -0
  440. package/supervisor/tsconfig.json +8 -0
  441. package/supervisor/types.ts +255 -0
  442. package/adapters/chatgpt/controller-host.ts +0 -119
  443. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  444. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  445. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  446. package/packages/kernel/progression/api/index.ts +0 -2
  447. package/packages/kernel/progression/application/projector.ts +0 -211
  448. package/packages/kernel/progression/domain/types.ts +0 -116
  449. package/packages/kernel/progression/index.ts +0 -1
  450. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  451. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  452. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  453. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  454. package/scripts/benchmark-background-check-overlap.ts +0 -228
  455. package/scripts/benchmark-computer-hotpath.ts +0 -255
  456. package/scripts/benchmark-controller-hotpath.ts +0 -212
  457. package/scripts/benchmark-controller-round2.ts +0 -191
  458. package/scripts/benchmark-ios-agent-device.ts +0 -511
  459. package/scripts/benchmark-mcp-host.sh +0 -117
  460. package/scripts/benchmark-mcp-transport.ts +0 -169
  461. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  462. package/scripts/benchmark-operational-shadow.ts +0 -28
  463. package/scripts/benchmark-quality-harness.ts +0 -146
  464. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  465. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  466. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  467. package/scripts/benchmark-thin-harness.ts +0 -251
  468. package/scripts/benchmark-typescript-navigation.ts +0 -186
  469. package/scripts/benchmark-work-efficiency.ts +0 -334
  470. package/scripts/build-controller-ui.ts +0 -28
  471. package/scripts/check-npm-release.sh +0 -32
  472. package/scripts/check-public-release-surface.sh +0 -19
  473. package/scripts/check-release-published.sh +0 -57
  474. package/scripts/check-release-readiness.sh +0 -37
  475. package/scripts/check-release-version.mjs +0 -74
  476. package/scripts/check-tarball-install-smoke.sh +0 -150
  477. package/scripts/package-source-archive.sh +0 -147
  478. package/scripts/public-release-files.txt +0 -39
  479. package/scripts/public-release.ts +0 -174
  480. package/scripts/publish-release-tarball.sh +0 -27
  481. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  482. package/scripts/stage-runtime-release.ts +0 -44
  483. package/src/cli/controller/progress.ts +0 -656
  484. package/src/cli/controller/work-mode.ts +0 -181
  485. package/src/cli/local-bridge/console-assets.ts +0 -17
  486. package/src/cli/local-bridge/dashboard.ts +0 -17
  487. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  488. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  489. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  490. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  491. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  492. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  493. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  494. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  495. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  496. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  497. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  498. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  499. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  500. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  501. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  502. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  503. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  504. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  505. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  506. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  507. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  508. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  509. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  510. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  511. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  512. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  513. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  514. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  515. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  516. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  517. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  518. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  519. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  520. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  521. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  522. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  523. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  524. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  525. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  526. package/src/runtime/control-plane/routing/index.ts +0 -1
  527. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  528. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,89 +1,64 @@
1
1
  import { createHash, randomUUID } from 'crypto';
2
- import { globMatches } from '../../../cli/mcp/paths';
3
2
  import {
4
3
  createHandoffItem,
5
- listHandoffItems,
6
4
  type HandoffInboxStoreOptions,
7
5
  } from './handoff-inbox-store';
8
- import { getControllerSession } from '../../../../packages/kernel/controller/api/index';
9
6
  import { executionPlacement, readForgeInstanceIdentity } from '../../../../packages/kernel/identity/api/index';
10
- import { projectAutonomousGoalProgression, type ProgressionWorkSnapshot } from '../../../../packages/kernel/progression/api/index';
11
7
  import {
12
8
  applyEngineeringBlockerDisposition,
13
9
  buildEngineeringBlockerDispositionReceipt,
14
10
  buildEngineeringContextReceipt,
15
11
  engineeringWorkProfileForRisk,
16
- evaluateEngineeringAdmission,
17
- appendVerificationRecord,
18
12
  appendWorkEvidence,
19
13
  appendWorkHandoffRef,
20
- cancelWorkContract,
21
14
  createWorkContract,
22
15
  failWorkContract,
23
16
  getWorkContract,
24
- isTerminalWorkContractStatus,
17
+ semanticWorkState,
25
18
  listWorkContracts,
26
19
  readActiveWorkCandidates,
27
20
  recordWorkEvidenceState,
28
21
  recordWorkScopeEvidence,
29
- recordWorkImplementationReview,
30
- requestWorkImplementationReview,
22
+ reviseWorkSemanticContext,
31
23
  summarizeWorkContract,
32
24
  transitionWorkContractPhase,
33
25
  updateWorkContract,
34
26
  type EngineeringAdmissionEvidence,
27
+ type EvaluationPromotionReceipt,
35
28
  type WorkContractStoreOptions,
36
29
  } from '../../../../packages/kernel/work/api/index';
37
30
  import {
38
- claimPlanStepForWork,
39
- completePlanStepForWork,
40
31
  getPlanContract,
32
+ currentPlanSemanticRevision,
41
33
  type PlanContractStoreOptions,
42
34
  } from './plan-contract-store';
43
35
  import { withPrimaryWorkAdmissionLock } from './semantic-admission';
44
- import { completeWorkWithReceipt } from '../execution/work-completion-authority';
45
- import { effectiveCurrentWorkVerificationRecords, evaluateReadOnlyReviewSourceIdentity, evaluateWorkCompletionEvidence, evaluateWorkImplementationEvidence } from '../execution/work-evidence-policy';
46
- import { readRequirement } from '../persistence/requirement-store';
36
+ import { effectiveCurrentWorkVerificationRecords, evaluateWorkCompletionEvidence, evaluateWorkImplementationEvidence } from '../execution/work-evidence-policy';
37
+ import { hasSettledWorkDeliveryReceipt } from '../execution/work-completion-authority';
38
+ import { currentRequirementSemanticRevision, readRequirement } from '../persistence/requirement-store';
47
39
  import {
48
- classifyVerificationOutcome,
49
40
  normalizeCheckIds,
50
41
  reconcileVerificationHistory,
51
42
  type CheckDefinitionLike,
52
43
  } from './check-normalization';
53
44
  import { evaluatePolicyGate } from './policy-gate';
54
- import {
55
- assertImplementationReviewPreDeliveryBoundary,
56
- authoritativeImplementationReviewVerificationEvidence,
57
- implementationReviewChangedPathDigest,
58
- latestImplementationReview,
59
- normalizeImplementationReviewChangedPaths,
60
- workRequiresImplementationReview,
61
- type ImplementationReviewDecision,
62
- type ImplementationReviewCandidateIdentity,
63
- type WorkImplementationReviewFinding,
64
- type WorkImplementationReviewRecord,
65
- } from '../../../../packages/kernel/work/api/index';
66
45
  import { buildFacadeResult } from './facade-result';
67
46
  import { validateSuggestedNextActions } from './suggested-actions';
68
47
  import { buildWorkContinuationSnapshot } from './work-continuation';
69
48
  import type {
70
49
  CapabilityRisk,
71
50
  EvidenceRef,
72
- ExecutionModeSelectionInput,
73
51
  FacadeResult,
74
52
  PlanContract,
75
53
  PolicyDecision,
76
54
  SuggestedNextAction,
77
- VerificationRecord,
78
55
  WorkContract,
56
+ WorkStartFacts,
79
57
  WorkKind,
80
58
  WorkRisk,
81
59
  } from './types';
82
- import { selectExecutionMode } from './types';
83
- import { resolveWorkspaceAdmissionConstraint } from '../routing/workspace-admission';
84
- import { resolveContextPlane, type ContextScope } from '../../context/context-plane';
85
60
 
86
- export type GoalWorkloopOperation = 'start' | 'continue' | 'verify' | 'review' | 'finalize' | 'stop';
61
+ export type GoalWorkloopOperation = 'start' | 'continue' | 'stop';
87
62
 
88
63
  export interface GoalWorkloopContext {
89
64
  workStore: WorkContractStoreOptions;
@@ -125,7 +100,8 @@ export interface GoalWorkloopStartInput {
125
100
  forbiddenPaths?: string[];
126
101
  checks?: string[];
127
102
  constraints?: WorkContract['constraints'];
128
- modeInput: ExecutionModeSelectionInput;
103
+ /** Concrete mechanical request facts. This capability is itself the explicit durable-Work choice. */
104
+ request: WorkStartFacts;
129
105
  requestedBy?: WorkContract['requestedBy'];
130
106
  /** Stable durable admission identity for system-originated Work such as recurrent incident repair. */
131
107
  requestId?: string;
@@ -136,7 +112,6 @@ export interface GoalWorkloopStartInput {
136
112
  issueId?: string;
137
113
  approvalConfirmed?: boolean;
138
114
  dryRun?: boolean;
139
- forceMode?: WorkContract['mode'];
140
115
  planId?: string;
141
116
  planStepId?: string;
142
117
  /** Explicit technical evidence shape chosen by the semantic Controller. Never inferred from objective/check text. */
@@ -170,45 +145,14 @@ export interface GoalWorkloopContinueInput {
170
145
  /** Semantic Controller blocker classification; Forge derives the permitted action and persists the receipt. */
171
146
  engineeringBlocker?: {
172
147
  blockerId: string;
173
- classification: 'same_root_cause' | 'unrelated';
148
+ classification: 'same_root_cause' | 'same_root_cause_scope_extension' | 'unrelated';
174
149
  rationale: string;
175
150
  semanticScopeKeys?: string[];
151
+ /** Required for classification=unrelated: the exact Work that already owns the blocker. */
152
+ linkedWorkId?: string;
176
153
  };
177
154
  }
178
155
 
179
- export interface GoalWorkloopVerifyInput {
180
- workId: string;
181
- checkId: string;
182
- /** Exact Git revision observed by the authoritative Work-bound verification Process. */
183
- sourceRevision?: string;
184
- /** Exact dirty-workspace content identity observed by the authoritative check. */
185
- workspaceFingerprint?: string;
186
- /** Stable semantic check-input identity used for exact evidence reuse. */
187
- verificationInputFingerprint?: string;
188
- /** Bounded command/invocation audit identity; not part of reusable semantic inputs. */
189
- commandFingerprint?: string;
190
- /** Persistence-safe Process receipt. Accepted only when its Work/repo/check identity matches this verification. */
191
- receipt?: VerificationRecord['receipt'];
192
- /** When true, simulate infrastructure failure rather than acceptance fail. */
193
- infrastructureFailed?: boolean;
194
- /** When true and check is valid, record acceptance failure. */
195
- checkFailed?: boolean;
196
- /** When true, skip without acceptance implication. */
197
- skipped?: boolean;
198
- }
199
-
200
- export interface GoalWorkloopReviewInput {
201
- workId: string;
202
- decision: ImplementationReviewDecision;
203
- rationale: string;
204
- findings?: WorkImplementationReviewFinding[];
205
- }
206
-
207
- export interface GoalWorkloopFinalizeInput {
208
- workId: string;
209
- forceFailed?: boolean;
210
- }
211
-
212
156
  export interface GoalWorkloopStopInput {
213
157
  workId: string;
214
158
  reason?: string;
@@ -220,57 +164,6 @@ function nowIso(ctx: GoalWorkloopContext): string {
220
164
  return ctx.now?.() ?? new Date().toISOString();
221
165
  }
222
166
 
223
- function currentImplementationReviewCandidate(
224
- ctx: GoalWorkloopContext,
225
- work: WorkContract,
226
- ): ImplementationReviewCandidateIdentity {
227
- const sourceRevision = ctx.sourceRevision?.trim() ?? '';
228
- const verificationWorkspaceFingerprint = ctx.workspaceFingerprint?.trim() ?? '';
229
- const workspaceFingerprint = ctx.implementationReviewWorkspaceFingerprint?.trim() ?? '';
230
- const changedPaths = normalizeImplementationReviewChangedPaths(
231
- ctx.workspaceChangedPaths ?? work.scopeEvidence?.actualChangedPaths ?? [],
232
- );
233
- if (!sourceRevision || !verificationWorkspaceFingerprint || !workspaceFingerprint) {
234
- throw new Error('WORK_IMPLEMENTATION_REVIEW_SOURCE_IDENTITY_REQUIRED');
235
- }
236
- const verification = authoritativeImplementationReviewVerificationEvidence({
237
- repoId: work.repoId,
238
- workId: work.workId,
239
- requiredCheckIds: work.checks,
240
- records: work.checkRefs,
241
- sourceRevision,
242
- workspaceFingerprint: verificationWorkspaceFingerprint,
243
- });
244
- if (verification.missingCheckIds.length > 0) {
245
- throw new Error(`WORK_IMPLEMENTATION_REVIEW_VERIFICATION_REQUIRED: ${verification.missingCheckIds.join(', ')}`);
246
- }
247
- return {
248
- sourceRevision,
249
- workspaceFingerprint,
250
- verificationWorkspaceFingerprint,
251
- changedPaths,
252
- verificationEvidence: verification.evidence,
253
- architectureEvidence: [],
254
- };
255
- }
256
-
257
- function implementationReviewAcceptanceSummary(work: WorkContract): string {
258
- const summary = work.acceptanceCriteria.map((criterion) => criterion.trim()).filter(Boolean).join(' | ');
259
- return (summary || 'No explicit acceptance criteria; reviewer assessed the Work objective and exact current implementation evidence.').slice(0, 2_000);
260
- }
261
-
262
- function implementationReviewSuggestedAction(workId: string): SuggestedNextAction {
263
- return {
264
- label: 'Review implementation',
265
- tool: 'rh_work',
266
- operation: 'review',
267
- payload: { work_id: workId },
268
- risk: 'workspace_write',
269
- confidence: 'high',
270
- reason: 'Verification is complete; an explicit Controller implementation decision is required before delivery.',
271
- };
272
- }
273
-
274
167
  function workIdFor(objective: string): string {
275
168
  const slug = objective
276
169
  .toLowerCase()
@@ -280,71 +173,47 @@ function workIdFor(objective: string): string {
280
173
  return `work-${slug}-${randomUUID().slice(0, 8)}`;
281
174
  }
282
175
 
283
- function linkedEngineeringBlockerWorkId(parentWorkId: string, blockerId: string): string {
284
- const digest = createHash('sha256').update(`${parentWorkId}\0${blockerId.trim()}`).digest('hex').slice(0, 24);
285
- return `work-linked-engineering-blocker-${digest}`;
286
- }
287
-
288
176
  function handoffIdFor(prefix: string): string {
289
177
  return `hnd-${prefix}-${randomUUID().slice(0, 8)}`;
290
178
  }
291
179
 
292
180
  function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
293
181
  if (input.workKind === 'read_only_review') return 'readonly';
294
- const risk = input.modeInput.risk;
295
- if (input.modeInput.secretAccess === true || input.modeInput.destructive === true) return 'destructive';
296
- if (input.modeInput.remoteWrite === true) return 'high';
182
+ const risk = input.request.risk;
183
+ if (input.request.secretAccess === true || input.request.destructive === true) return 'destructive';
184
+ if (input.request.remoteWrite === true) return 'high';
297
185
  if (risk === 'readonly') return 'readonly';
298
186
  if (risk === 'destructive' || risk === 'destructive_remote' || risk === 'raw_secret_config') return 'destructive';
299
187
  if (risk === 'remote_write') return 'high';
300
188
  if (risk === 'local_repo_write') return 'low';
189
+ if (risk === undefined) return 'low';
301
190
  return 'medium';
302
191
  }
303
192
 
304
193
  function resolvedWorkKindFor(input: GoalWorkloopStartInput): WorkKind {
305
194
  if (input.workKind) return input.workKind;
306
- // allowed_paths and discovery hints do not prove repository mutation. External
307
- // effects become repository_change only when source-change intent is explicit.
308
- const repositoryChangeIntent = (input.modeInput.expectedFiles ?? 0) > 0
309
- || (input.modeInput.expectedChangedLines ?? 0) > 0;
310
- const typedRecoverableReadOnlyReview = input.modeInput.mutation === false
311
- && input.modeInput.requiresInvestigation === true
312
- && input.modeInput.requiresRecovery === true;
313
- if (typedRecoverableReadOnlyReview) return 'read_only_review';
314
- if (input.modeInput.requiresExternalEffect === true && !repositoryChangeIntent) {
315
- return input.modeInput.remoteWrite === true ? 'remote_effect' : 'local_effect';
195
+ if (input.request.requiresExternalEffect === true) {
196
+ // Mechanical effect classification only: a remote effect differs from a local
197
+ // effect in receipt identity. Predicted scope size is model strategy and is
198
+ // never consulted; callers declare `work_kind` when a repository change is
199
+ // also involved.
200
+ return input.request.remoteWrite === true ? 'remote_effect' : 'local_effect';
316
201
  }
317
202
  return 'repository_change';
318
203
  }
319
204
 
320
205
  function suggestedForWorkIdentity(workId: string, checks: string[], extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
321
- const base: SuggestedNextAction[] = [
322
- {
323
- label: 'Continue workloop',
324
- tool: 'rh_work',
325
- operation: 'continue',
326
- payload: { work_id: workId },
327
- risk: 'readonly',
328
- confidence: 'high',
329
- },
330
- {
331
- label: 'Verify registered checks',
332
- tool: 'rh_work',
333
- operation: 'verify',
334
- payload: { work_id: workId, check_id: checks[0] },
335
- risk: 'workspace_write',
336
- confidence: checks[0] ? 'high' : 'low',
337
- },
338
- {
339
- label: 'Finalize when ready',
340
- tool: 'rh_work',
341
- operation: 'finalize',
342
- payload: { work_id: workId },
343
- risk: 'readonly',
344
- confidence: 'medium',
345
- },
346
- ];
347
- return validateSuggestedNextActions([...extras, ...base], {
206
+ const semanticRead: SuggestedNextAction = {
207
+ label: 'Read current Work semantic context',
208
+ tool: 'rh_work',
209
+ operation: 'get',
210
+ payload: { work_id: workId },
211
+ risk: 'readonly',
212
+ confidence: 'high',
213
+ };
214
+ // Suggested actions expose facts/capabilities only. The model decides whether
215
+ // implementation, checks, review, delivery, or semantic completion is next.
216
+ return validateSuggestedNextActions([...extras, semanticRead], {
348
217
  validCheckIds: checks,
349
218
  }).actions;
350
219
  }
@@ -369,21 +238,10 @@ export function routeWorkStart(
369
238
  ctx: GoalWorkloopContext,
370
239
  input: GoalWorkloopStartInput,
371
240
  ): FacadeResult {
372
- const placementResolution = resolveWorkspaceAdmissionConstraint(input.constraints);
373
- if (placementResolution.ok === false) {
374
- return buildFacadeResult({
375
- status: 'blocked',
376
- summary: `${placementResolution.code}: ${placementResolution.message}`,
377
- data: { executionStarted: false, workContractCreated: false, placementConstraintConflict: true },
378
- rawAvailable: false,
379
- });
380
- }
381
- const placementConstraint = placementResolution.constraint;
241
+ const workspaceMode: 'current' | 'isolated' = input.constraints?.workspaceMode === 'isolated' || input.constraints?.workspaceMode === 'auto' ? 'isolated' : 'current';
382
242
  const canonicalConstraints: WorkContract['constraints'] = {
383
243
  ...(input.constraints ?? {}),
384
- workspaceMode: placementConstraint.workspaceMode,
385
- requireWorktree: placementConstraint.requireWorktree,
386
- directMainProhibited: placementConstraint.directMainProhibited,
244
+ workspaceMode,
387
245
  };
388
246
  const strategyConflictRequiresApproval = input.constraints?.architectureStrategyChange === true
389
247
  || input.constraints?.conflictsWithThinHarnessPolicy === true
@@ -409,7 +267,7 @@ export function routeWorkStart(
409
267
  input.workKind === undefined
410
268
  && input.workRelation === 'new_goal'
411
269
  && relatedLifecycleSource?.workKind === 'read_only_review'
412
- && (relatedLifecycleSource.status === 'cancelled' || relatedLifecycleSource.status === 'completed' || relatedLifecycleSource.status === 'failed'),
270
+ && semanticWorkState(relatedLifecycleSource) !== 'open',
413
271
  );
414
272
  if (
415
273
  inheritedReadOnlyReview
@@ -430,24 +288,17 @@ export function routeWorkStart(
430
288
  const normalized = value.trim().replace(/\\/g, '/');
431
289
  return normalized === '*' || normalized === '**' || normalized === '**/*';
432
290
  });
433
- const typedReadOnlyReviewRequested = requestedWorkKind === undefined
434
- && (
435
- explicitSourceMutationFence
436
- || (
437
- (input.modeInput.mutation === false || (input.modeInput.mutation === undefined && input.modeInput.risk === 'readonly'))
438
- && input.modeInput.requiresInvestigation === true
439
- && input.modeInput.requiresRecovery === true
440
- )
441
- );
291
+ // A read-only review is an explicit `work_kind`, or an explicit source-mutation
292
+ // fence that proves no repository path may change. Forge never infers it from
293
+ // caller hints about investigation depth or recovery needs.
294
+ const typedReadOnlyReviewRequested = requestedWorkKind === undefined && explicitSourceMutationFence;
442
295
  const effectiveWorkKind = requestedWorkKind ?? (typedReadOnlyReviewRequested ? 'read_only_review' : undefined);
443
296
  const readOnlyReviewRequested = effectiveWorkKind === 'read_only_review';
444
297
  const readOnlyMutationConflict = readOnlyReviewRequested && (
445
- input.modeInput.mutation === true
446
- || (input.modeInput.expectedFiles ?? 0) > 0
447
- || (input.modeInput.expectedChangedLines ?? 0) > 0
448
- || input.modeInput.requiresExternalEffect === true
449
- || input.modeInput.remoteWrite === true
450
- || input.modeInput.destructive === true
298
+ input.request.mutation === true
299
+ || input.request.requiresExternalEffect === true
300
+ || input.request.remoteWrite === true
301
+ || input.request.destructive === true
451
302
  );
452
303
  if (readOnlyMutationConflict) {
453
304
  return buildFacadeResult({
@@ -456,185 +307,49 @@ export function routeWorkStart(
456
307
  data: { executionStarted: false, workContractCreated: false, workKind: effectiveWorkKind },
457
308
  });
458
309
  }
459
- const contextScopes: ContextScope[] = [];
460
- if (input.requirementId?.trim()) contextScopes.push({ schemaVersion: 1, kind: 'requirement', id: input.requirementId.trim() });
461
- if (input.planId?.trim()) contextScopes.push({ schemaVersion: 1, kind: 'plan', id: input.planId.trim() });
462
- if (input.relatedWorkId?.trim()) contextScopes.push({ schemaVersion: 1, kind: 'work', id: input.relatedWorkId.trim() });
463
- const resolvedContext = ctx.workStore.controllerHome
464
- ? resolveContextPlane({
465
- controllerHome: ctx.workStore.controllerHome,
466
- scopes: contextScopes,
467
- intent: input.modeInput.routePolicyInput?.intent.taskIntent ?? 'implementation',
468
- now: nowIso(ctx),
469
- })
470
- : undefined;
471
310
  // A terminal continuation is not a new semantic request. Its durable predecessor
472
311
  // and Plan lineage already define the goal scope, while the successor Plan step
473
- // becomes the Work objective later in startGoalWorkloop. Do not force the external
474
- // Controller to duplicate semantic text merely to satisfy generic route selection.
475
- // This fallback is routing context only and never becomes successor Work authority.
312
+ // becomes the Work objective later in startGoalWorkloop.
476
313
  const routeObjective = input.objective.trim()
477
- || (input.workRelation === 'continue' && relatedLifecycleSource?.status === 'completed'
314
+ || (input.workRelation === 'continue' && relatedLifecycleSource && semanticWorkState(relatedLifecycleSource) === 'completed'
478
315
  ? relatedLifecycleSource.objective.trim()
479
316
  : '');
480
- const effectiveModeInput: ExecutionModeSelectionInput = {
481
- ...input.modeInput,
482
- contextRouteHints: input.modeInput.contextRouteHints ?? resolvedContext?.routeHints,
317
+ const effectiveRequest: WorkStartFacts = {
318
+ ...input.request,
483
319
  objective: routeObjective,
484
- knownPaths: input.allowedPaths,
485
- workspacePlacement: placementConstraint.workspaceMode,
486
- directMainProhibited: placementConstraint.directMainProhibited,
487
- workspaceDirty: ctx.workspaceDirty ?? input.modeInput.workspaceDirty,
488
- mutation: readOnlyReviewRequested ? false : input.modeInput.mutation ?? input.modeInput.risk !== 'readonly',
489
- risk: readOnlyReviewRequested ? 'readonly' : input.modeInput.risk,
320
+ workspaceDirty: ctx.workspaceDirty ?? input.request.workspaceDirty,
321
+ mutation: readOnlyReviewRequested ? false : input.request.mutation ?? input.request.risk !== 'readonly',
322
+ risk: readOnlyReviewRequested ? 'readonly' : input.request.risk,
490
323
  approvalConfirmed: input.approvalConfirmed === true,
491
324
  requiresUserApproval: input.approvalConfirmed === true
492
325
  ? false
493
- : input.modeInput.requiresUserApproval === true || strategyConflictRequiresApproval,
326
+ : input.request.requiresUserApproval === true || strategyConflictRequiresApproval,
494
327
  };
495
- if (effectiveModeInput.explicitMode === 'scale' && (!input.planId || !input.planStepId || !ctx.planStore)) {
496
- return buildFacadeResult({
497
- status: 'blocked',
498
- summary: 'SCALE_PLAN_REQUIRED: explicit Scale execution requires a bound approved PlanContract step.',
499
- data: { executionStarted: false, workContractCreated: false, planRequired: true, explicitMode: 'scale' },
500
- });
501
- }
502
- const applyForcedMode = (selected: ReturnType<typeof selectExecutionMode>) => input.forceMode
503
- ? {
504
- ...selected,
505
- mode: input.forceMode,
506
- reason: `Forced mode: ${input.forceMode}. ${selected.reason}`,
507
- createWorkContract: input.forceMode === 'handoff_only' ? false : selected.requiresWork,
508
- createHandoff: input.forceMode === 'handoff_only',
509
- }
510
- : selected;
511
- const evaluateAccessPolicy = (selected: ReturnType<typeof selectExecutionMode>) => evaluatePolicyGate({
512
- capabilityId: selected.mode === 'direct_control' ? 'repository.direct_edit' : selected.mode === 'goal_workloop' ? 'controller.goal_workloop' : 'controller.handoff_inbox',
513
- risk: effectiveModeInput.risk
514
- ?? (input.modeInput.secretAccess === true ? 'raw_secret_config'
515
- : input.modeInput.destructive === true ? 'destructive'
516
- : input.modeInput.remoteWrite === true ? 'remote_write'
517
- : input.modeInput.requiresApproval === true || input.modeInput.requiresUserApproval === true ? 'workspace_write'
518
- : selected.mode === 'direct_control' ? 'local_repo_write'
519
- : selected.mode === 'goal_workloop' ? 'workspace_write'
520
- : 'readonly'),
328
+ const evaluateAccessPolicy = () => evaluatePolicyGate({
329
+ capabilityId: 'controller.work_contract',
330
+ risk: effectiveRequest.risk
331
+ ?? (input.request.secretAccess === true ? 'raw_secret_config'
332
+ : input.request.destructive === true ? 'destructive'
333
+ : input.request.remoteWrite === true ? 'remote_write'
334
+ : input.request.requiresApproval === true || input.request.requiresUserApproval === true ? 'workspace_write'
335
+ : 'workspace_write'),
521
336
  accessMode: input.constraints?.accessMode,
522
337
  approvalConfirmed: input.approvalConfirmed === true,
523
338
  dryRun: input.dryRun === true,
524
- directEditBoundary: {
525
- scopeClear: effectiveModeInput.scopeClear,
526
- maxChangedFiles: effectiveModeInput.expectedFiles,
527
- maxChangedLines: effectiveModeInput.expectedChangedLines,
528
- pathsExplicit: effectiveModeInput.scopeClear,
529
- },
530
339
  });
340
+ // Missing mechanical admission fields are reported to the caller verbatim.
341
+ // Forge does not translate them into an engineering-mode recommendation.
342
+ const missingContractFields: string[] = [];
343
+ if (!input.request.scopeClear) missingContractFields.push('scopeSummary', 'acceptanceCriteria', 'allowedPaths');
344
+ if (input.objective.trim().length === 0) missingContractFields.push('objective');
531
345
 
532
- let selectedMode = selectExecutionMode(effectiveModeInput);
533
- if (input.forceMode === 'direct_control' && selectedMode.routeDecision.requiresIsolation) {
534
- return buildFacadeResult({
535
- status: 'blocked',
536
- summary: 'WORKSPACE_PLACEMENT_DIRECT_CONTROL_FORBIDDEN: typed placement requires isolation, so force_mode=direct_control cannot override admission policy.',
537
- data: { executionStarted: false, workContractCreated: false, routeDecision: selectedMode.routeDecision },
538
- rawAvailable: false,
539
- });
540
- }
541
- let policy = evaluateAccessPolicy(selectedMode);
542
- if (policy.decision === 'allowed' && effectiveModeInput.requiresUserApproval !== true) {
543
- selectedMode = selectExecutionMode({ ...effectiveModeInput, approvalConfirmed: true });
544
- policy = evaluateAccessPolicy(selectedMode);
545
- }
546
- let mode = applyForcedMode(selectedMode);
547
- const routeApprovalRequired = mode.routeDecision.requiresApproval
548
- && mode.routeDecision.waitForUser
549
- && mode.routeDecision.approvalState !== 'blocked_by_policy';
550
- const approvalRequired = policy.decision === 'approval_required' || routeApprovalRequired;
551
-
552
- // Route Policy is the sole execution-depth authority. Existing Work is
553
- // ownership context, not a reason to upgrade an otherwise Direct operation
554
- // into Durable Work. Explicit Work/Plan ownership is resolved only after a
555
- // durable route has already been selected; checkout writer conflicts affect
556
- // placement, not semantic task identity.
346
+ const policy = evaluateAccessPolicy();
347
+ const approvalRequired = policy.decision === 'approval_required';
557
348
 
558
- if (mode.mode === 'direct_control') {
559
- const available = ctx.availableChecks ?? [];
560
- const normalized = normalizeCheckIds(input.checks ?? [], available);
561
- // Explicit ownership may annotate a Direct operation, but it never changes
562
- // the RouteDecision. The actual repository mutation can pass this Work id
563
- // to repository_safe_patch_apply / repository_command_execute for evidence
564
- // attribution while staying on the Direct/Process path.
565
- const directAdmissionSnapshot = input.relatedWorkId || (input.planId && input.planStepId)
566
- ? readActiveWorkCandidates({ ...ctx.workStore, limit: 100 })
567
- : { contracts: [], invalid: [] };
568
- const invalidDirectOwner = directAdmissionSnapshot.invalid.find((candidate) =>
569
- candidate.workId === input.relatedWorkId
570
- || (input.planId && input.planStepId && candidate.planId === input.planId && candidate.planStepId === input.planStepId));
571
- if (invalidDirectOwner) {
572
- return buildFacadeResult({
573
- status: 'blocked',
574
- summary: `WORK_ADMISSION_INVALID_OWNER: ${invalidDirectOwner.workId} is malformed and cannot be used as Direct Control ownership authority.`,
575
- data: { executionStarted: false, workContractCreated: false, invalidWorkId: invalidDirectOwner.workId, invalidWorkError: invalidDirectOwner.error },
576
- });
577
- }
578
- const directOwnershipCandidates = directAdmissionSnapshot.contracts
579
- .filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary');
580
- const directPlan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
581
- const directPlanStep = directPlan && input.planStepId ? directPlan.steps.find((step) => step.id === input.planStepId) : undefined;
582
- const directOwner = (input.relatedWorkId
583
- ? directOwnershipCandidates.find((candidate) => candidate.workId === input.relatedWorkId)
584
- : undefined)
585
- ?? (directPlanStep?.workId
586
- ? directOwnershipCandidates.find((candidate) => candidate.workId === directPlanStep.workId)
587
- : undefined);
588
- const suggested = validateSuggestedNextActions([
589
- {
590
- label: 'Apply bounded direct edit',
591
- tool: 'rh_work',
592
- operation: 'start',
593
- payload: { mode: 'direct_control', objective: input.objective.slice(0, 200) },
594
- risk: 'local_repo_write',
595
- confidence: 'high',
596
- reason: 'Small supervised task stays on Direct Control; no WorkContract created.',
597
- },
598
- ...normalized.suggestedNextActions,
599
- {
600
- label: 'Read repository context',
601
- tool: 'rh_context',
602
- operation: 'get',
603
- risk: 'readonly',
604
- confidence: 'medium',
605
- },
606
- ], { validCheckIds: normalized.validCheckIds }).actions;
607
-
608
- return buildFacadeResult({
609
- status: policy.decision === 'denied' ? 'blocked' : 'ok',
610
- summary: `Direct control recommended. No WorkContract created. ${mode.reason}`,
611
- data: {
612
- mode,
613
- policy,
614
- workContractCreated: false,
615
- directControlPreserved: true,
616
- objective: input.objective.slice(0, 1_000),
617
- normalizedChecks: normalized,
618
- ...(directOwner ? {
619
- ownership: {
620
- workId: directOwner.workId,
621
- relation: input.workRelation ?? 'continue',
622
- executionDepthPreserved: true,
623
- },
624
- } : {}),
625
- },
626
- warnings: [...policy.warnings, ...normalized.warnings],
627
- suggestedNextActions: suggested,
628
- rawAvailable: false,
629
- });
630
- }
631
-
632
- // Policy approval decisions stop before Work creation. Ordinary host-managed local work
633
- // reaches this point only after the same Route Policy has been replayed with the Access
634
- // Policy's explicit allowed decision as authorization evidence.
349
+ // Policy approval decisions stop before Work creation. Ordinary host-managed
350
+ // local work reaches this point only after the Access Policy authorized it.
635
351
  const blockForHandoff =
636
- mode.mode === 'handoff_only'
637
- || policy.decision === 'denied'
352
+ policy.decision === 'denied'
638
353
  || policy.decision === 'approval_required';
639
354
 
640
355
  if (blockForHandoff) {
@@ -643,20 +358,19 @@ export function routeWorkStart(
643
358
  repoId: ctx.repoId,
644
359
  title: 'Work blocked pending decision',
645
360
  severity: policy.decision === 'denied' ? 'blocked' : 'needs_review',
646
- creationReason: !input.modeInput.scopeClear
361
+ creationReason: !input.request.scopeClear
647
362
  ? 'invalid_objective'
648
363
  : approvalRequired
649
- ? (input.modeInput.destructive ? 'destructive_action_requires_confirmation' : 'policy_approval_required')
364
+ ? (input.request.destructive ? 'destructive_action_requires_confirmation' : 'policy_approval_required')
650
365
  : 'missing_authorization',
651
- reason: mode.reason,
652
- summary: `Handoff-only routing: ${mode.reason}`,
366
+ reason: policy.reason,
367
+ summary: `Execution blocked by access policy: ${policy.reason}`,
653
368
  currentState: {
654
369
  repoId: ctx.repoId,
655
- mode: 'handoff_only',
656
370
  statusSummary: 'waiting for ChatGPT or user decision; no execution started',
657
- blockedBy: mode.missingContractFields,
371
+ blockedBy: missingContractFields,
658
372
  },
659
- attemptedActions: ['route_execution_mode'],
373
+ attemptedActions: ['work_start'],
660
374
  evidenceRefs: [],
661
375
  blockingDecision: approvalRequired
662
376
  ? 'Approve side effects or restate a safer objective.'
@@ -669,27 +383,19 @@ export function routeWorkStart(
669
383
  operation: 'start',
670
384
  label: 'Approve and start work',
671
385
  summary: 'Create the work contract with the original scope and explicit approval.',
672
- risk: input.modeInput.destructive ? 'destructive' : 'workspace_write',
386
+ risk: input.request.destructive ? 'destructive' : 'workspace_write',
673
387
  payload: {
674
388
  objective: input.objective,
675
389
  acceptanceCriteria: input.acceptanceCriteria,
676
390
  allowedPaths: input.allowedPaths,
677
391
  forbiddenPaths: input.forbiddenPaths,
678
392
  checkIds: input.checks,
679
- expectedFiles: input.modeInput.expectedFiles,
680
- expectedChangedLines: input.modeInput.expectedChangedLines,
681
- scopeClear: input.modeInput.scopeClear,
682
- requiresInvestigation: input.modeInput.requiresInvestigation,
683
- requiresLongRunningChecks: input.modeInput.requiresLongRunningChecks,
684
- requiresWorker: input.modeInput.requiresWorker,
685
- requiresApproval: input.modeInput.requiresApproval === true || input.modeInput.requiresUserApproval === true,
686
- destructive: input.modeInput.destructive === true,
393
+ scopeClear: input.request.scopeClear,
394
+ requiresApproval: input.request.requiresApproval === true || input.request.requiresUserApproval === true,
395
+ destructive: input.request.destructive === true,
687
396
  accessMode: input.constraints?.accessMode,
688
- workspaceMode: placementConstraint.workspaceMode,
689
- requireWorktree: placementConstraint.requireWorktree,
690
- directMainProhibited: placementConstraint.directMainProhibited,
397
+ workspaceMode,
691
398
  approvalConfirmed: true,
692
- forceMode: 'goal_workloop',
693
399
  },
694
400
  }
695
401
  : undefined,
@@ -706,9 +412,8 @@ export function routeWorkStart(
706
412
 
707
413
  return buildFacadeResult({
708
414
  status: policy.decision === 'denied' ? 'blocked' : approvalRequired ? 'approval_required' : 'blocked',
709
- summary: `Handoff-only: no WorkContract created and no execution started. ${mode.reason}`,
415
+ summary: 'Blocked: no WorkContract created and no execution started until the policy decision is resolved.',
710
416
  data: {
711
- mode,
712
417
  policy,
713
418
  workContractCreated: false,
714
419
  handoffId: handoff.id,
@@ -738,33 +443,20 @@ export function routeWorkStart(
738
443
  return startGoalWorkloop(ctx, {
739
444
  ...input,
740
445
  constraints: canonicalConstraints,
741
- modeInput: effectiveModeInput,
446
+ request: effectiveRequest,
742
447
  workKind: effectiveWorkKind,
743
- }, policy, 'goal_workloop', mode.routeDecision);
448
+ }, policy);
744
449
  }
745
450
 
746
451
  export function startGoalWorkloop(
747
452
  ctx: GoalWorkloopContext,
748
453
  input: GoalWorkloopStartInput,
749
454
  policy?: PolicyDecision,
750
- executionMode: 'direct_control' | 'goal_workloop' = 'goal_workloop',
751
- routeDecision = selectExecutionMode({ ...input.modeInput, objective: input.objective, knownPaths: input.allowedPaths }).routeDecision,
752
455
  ): FacadeResult {
753
- const placementResolution = resolveWorkspaceAdmissionConstraint(input.constraints);
754
- if (placementResolution.ok === false) {
755
- return buildFacadeResult({
756
- status: 'blocked',
757
- summary: `${placementResolution.code}: ${placementResolution.message}`,
758
- data: { executionStarted: false, workContractCreated: false, placementConstraintConflict: true },
759
- rawAvailable: false,
760
- });
761
- }
762
- const placementConstraint = placementResolution.constraint;
456
+ const workspaceMode: 'current' | 'isolated' = input.constraints?.workspaceMode === 'isolated' || input.constraints?.workspaceMode === 'auto' ? 'isolated' : 'current';
763
457
  const canonicalConstraints: WorkContract['constraints'] = {
764
458
  ...(input.constraints ?? {}),
765
- workspaceMode: placementConstraint.workspaceMode,
766
- requireWorktree: placementConstraint.requireWorktree,
767
- directMainProhibited: placementConstraint.directMainProhibited,
459
+ workspaceMode,
768
460
  };
769
461
  const hasStrongAdmissionBinding = Boolean(input.planId || input.planStepId || input.requirementId || input.relatedWorkId || input.workRelation);
770
462
  if (hasStrongAdmissionBinding && !ctx.semanticAdmissionLocked) {
@@ -772,12 +464,20 @@ export function startGoalWorkloop(
772
464
  { ...ctx, semanticAdmissionLocked: true },
773
465
  input,
774
466
  policy,
775
- executionMode,
776
- routeDecision,
777
467
  ));
778
468
  }
779
469
  const at = nowIso(ctx);
780
- const resolvedWorkKind = resolvedWorkKindFor(input);
470
+ let resolvedWorkKind: WorkKind;
471
+ try {
472
+ resolvedWorkKind = resolvedWorkKindFor(input);
473
+ } catch (error) {
474
+ return buildFacadeResult({
475
+ status: 'blocked',
476
+ summary: error instanceof Error ? error.message : 'WORK_KIND_RESOLUTION_INVALID',
477
+ data: { executionStarted: false, workContractCreated: false },
478
+ rawAvailable: false,
479
+ });
480
+ }
781
481
  const workRisk = workRiskFor(input);
782
482
  // External-effect risk is governed by authorization/confirmation/receipt
783
483
  // semantics. Engineering admission is specifically for repository mutation.
@@ -804,27 +504,11 @@ export function startGoalWorkloop(
804
504
  });
805
505
  }
806
506
  const engineeringMutation = resolvedWorkKind === 'repository_change'
807
- && (input.modeInput.mutation ?? input.modeInput.risk !== 'readonly');
808
- const engineeringAdmission = evaluateEngineeringAdmission({
809
- profile: engineeringProfile,
810
- receipt: engineeringContext,
811
- mutation: engineeringMutation,
812
- });
813
- if (!engineeringAdmission.allowed) {
814
- return buildFacadeResult({
815
- status: 'blocked',
816
- summary: `${engineeringAdmission.code}: ${engineeringProfile.riskClass} mutation is missing required pre-mutation engineering evidence.`,
817
- data: {
818
- executionStarted: false,
819
- workContractCreated: false,
820
- engineeringProfile,
821
- engineeringContext,
822
- missingEngineeringEvidence: engineeringAdmission.missing,
823
- },
824
- });
825
- }
507
+ && (input.request.mutation ?? input.request.risk !== 'readonly');
508
+ // Engineering context is durable model/project guidance only. Safety and
509
+ // capability admission is enforced by the concrete executor, never here.
510
+ void engineeringMutation;
826
511
  const available = ctx.availableChecks ?? [];
827
- const workspaceMode = placementConstraint.workspaceMode;
828
512
  const activeAdmissionSnapshot = readActiveWorkCandidates({ ...ctx.workStore, limit: 100 });
829
513
  const activeWorks = activeAdmissionSnapshot.contracts
830
514
  .filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary');
@@ -841,14 +525,9 @@ export function startGoalWorkloop(
841
525
  const relatedLifecycleSource = input.relatedWorkId
842
526
  ? getWorkContract(ctx.workStore, input.relatedWorkId)
843
527
  : undefined;
844
- const workspaceOwner = ctx.checkoutId
845
- ? activeWorks.find((candidate) => candidate.checkoutId === ctx.checkoutId
846
- && candidate.worktreePolicy.required !== true
847
- && !candidate.worktreeRef)
848
- : undefined;
849
528
  const terminalContinuationSource = input.workRelation === 'continue'
850
529
  && relatedLifecycleSource
851
- && isTerminalWorkContractStatus(relatedLifecycleSource.status)
530
+ && semanticWorkState(relatedLifecycleSource) !== 'open'
852
531
  ? relatedLifecycleSource
853
532
  : undefined;
854
533
  if (terminalContinuationSource && !terminalContinuationSource.requirementId && !terminalContinuationSource.planId) {
@@ -863,7 +542,7 @@ export function startGoalWorkloop(
863
542
  : undefined;
864
543
 
865
544
  let resolvedPlanId = input.planId?.trim() || undefined;
866
- let resolvedPlanStepId = input.planStepId?.trim() || undefined;
545
+ const resolvedPlanStepId = input.planStepId?.trim() || undefined;
867
546
  if (terminalContinuationSource?.planId) {
868
547
  if (resolvedPlanId && resolvedPlanId !== terminalContinuationSource.planId) {
869
548
  return buildFacadeResult({
@@ -874,109 +553,25 @@ export function startGoalWorkloop(
874
553
  }
875
554
  resolvedPlanId = terminalContinuationSource.planId;
876
555
  }
556
+ // Plan and Plan item references are recorded provenance for this Work. They
557
+ // never select the successor, own the scope, gate admission or require
558
+ // acceptance; only the Plan's existence is validated so a Work cannot record a
559
+ // reference to a Plan authority that does not exist.
877
560
  const plan = resolvedPlanId && ctx.planStore ? getPlanContract(ctx.planStore, resolvedPlanId) : undefined;
878
- if (terminalContinuationSource?.planId && !plan) {
561
+ if (resolvedPlanId && !plan) {
879
562
  return buildFacadeResult({
880
563
  status: 'blocked',
881
- summary: `WORK_SUCCESSOR_PLAN_NOT_FOUND: ${terminalContinuationSource.planId}. Recover the durable Plan authority before creating a successor Work.`,
882
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: terminalContinuationSource.planId },
883
- });
884
- }
885
- if (terminalContinuationSource?.planStepId && plan && !resolvedPlanStepId) {
886
- const predecessorStep = plan.steps.find((step) => step.id === terminalContinuationSource.planStepId);
887
- if (!predecessorStep) {
888
- return buildFacadeResult({
889
- status: 'blocked',
890
- summary: `WORK_SUCCESSOR_PLAN_STEP_NOT_FOUND: predecessor ${terminalContinuationSource.workId} references ${terminalContinuationSource.planStepId}.`,
891
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, predecessorPlanStepId: terminalContinuationSource.planStepId },
892
- });
893
- }
894
- const requirementRecord = plan.requirementId && ctx.workStore.controllerHome
895
- ? readRequirement({ controllerHome: ctx.workStore.controllerHome }, plan.requirementId)
896
- : undefined;
897
- const progressionWorks: ProgressionWorkSnapshot[] = plan.steps
898
- .flatMap((candidate) => candidate.workId ? [getWorkContract(ctx.workStore, candidate.workId)] : [])
899
- .filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate))
900
- .map((candidate) => ({
901
- workId: candidate.workId,
902
- requirementId: candidate.requirementId,
903
- planId: candidate.planId,
904
- planStepId: candidate.planStepId,
905
- status: candidate.status,
906
- baseRevision: candidate.baseRevision,
907
- completionTargetRevision: candidate.completionReceipt && 'targetRevision' in candidate.completionReceipt
908
- ? candidate.completionReceipt.targetRevision
909
- : undefined,
910
- }));
911
- const progression = projectAutonomousGoalProgression({
912
- requirement: {
913
- requirementId: plan.requirementId ?? terminalContinuationSource.requirementId ?? `plan:${plan.planId}`,
914
- state: requirementRecord?.value.state ?? 'active',
915
- revision: requirementRecord?.revision ?? 0,
916
- },
917
- plan: {
918
- planId: plan.planId,
919
- requirementId: plan.requirementId,
920
- sourceRevision: plan.sourceRevision,
921
- status: plan.status,
922
- steps: plan.steps.map((candidate) => ({ id: candidate.id, dependencies: candidate.dependencies, status: candidate.status, workId: candidate.workId })),
564
+ summary: terminalContinuationSource?.planId
565
+ ? `WORK_SUCCESSOR_PLAN_NOT_FOUND: ${terminalContinuationSource.planId}. Recover the durable Plan authority before creating a successor Work.`
566
+ : `PLAN_NOT_FOUND: ${resolvedPlanId}. Recover or create the semantic Plan before using it as Work provenance.`,
567
+ data: {
568
+ executionStarted: false,
569
+ workContractCreated: false,
570
+ planId: resolvedPlanId,
571
+ ...(terminalContinuationSource?.planId ? { predecessorWorkId: terminalContinuationSource.workId } : {}),
923
572
  },
924
- currentSourceRevision: ctx.sourceRevision ?? plan.sourceRevision,
925
- works: progressionWorks,
926
- controllerRounds: [],
927
573
  });
928
- if (progression.kind === 'request_controller_acceptance') {
929
- return buildFacadeResult({
930
- status: 'blocked',
931
- summary: `PLAN_STEP_SEMANTIC_ACCEPTANCE_REQUIRED: ${plan.planId}/${progression.planStepId ?? predecessorStep.id} has machine-complete delivery. Accept the delivered step before advancing the same semantic goal.`,
932
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, planStepId: progression.planStepId ?? predecessorStep.id, semanticAcceptanceRequired: true, progression },
933
- });
934
- }
935
- if (progression.reasonCode === 'PLAN_STEP_TERMINAL_WORK_RECONCILIATION_REQUIRED') {
936
- return buildFacadeResult({
937
- status: 'blocked',
938
- summary: `PLAN_STEP_TERMINAL_WORK_RECONCILIATION_REQUIRED: ${plan.planId}/${progression.planStepId ?? predecessorStep.id} must project terminal Work ${terminalContinuationSource.workId} before semantic progression.`,
939
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, planStepId: progression.planStepId ?? predecessorStep.id, repairRequired: true, progression },
940
- });
941
- }
942
- if (progression.kind === 'start_next_plan_step' && progression.planStepId) {
943
- resolvedPlanStepId = progression.planStepId;
944
- } else if (progression.kind === 'request_requirement_acceptance') {
945
- return buildFacadeResult({
946
- status: 'ok',
947
- summary: `CONTINUATION_REQUIREMENT_ACCEPTANCE_REQUIRED: ${plan.planId} is finalized; the semantic Controller must record goal_complete before the Requirement becomes done.`,
948
- data: { executionStarted: false, workContractCreated: false, admissionDecision: 'requirement_acceptance_required', requirementAcceptanceRequired: true, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, requirementId: plan.requirementId, progression },
949
- });
950
- } else if (progression.kind === 'goal_complete') {
951
- return buildFacadeResult({
952
- status: 'ok',
953
- summary: `CONTINUATION_GOAL_COMPLETE: ${plan.planId} Requirement is already semantically complete; no successor Work was created.`,
954
- data: { executionStarted: false, workContractCreated: false, admissionDecision: 'goal_complete', goalComplete: true, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, requirementId: plan.requirementId, progression },
955
- });
956
- } else if (progression.reasonCode === 'MULTIPLE_READY_PLAN_STEPS') {
957
- const candidates = (progression.dependencyStepIds ?? [])
958
- .map((stepId) => plan.steps.find((candidate) => candidate.id === stepId))
959
- .filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate));
960
- return buildFacadeResult({
961
- status: 'ok',
962
- summary: `PLAN_SUCCESSOR_STEP_RESOLUTION_REQUIRED: ${plan.planId} has ${candidates.length} executable successor steps. The semantic Controller must select one; Forge will not invent ordering.`,
963
- data: { executionStarted: false, workContractCreated: false, admissionDecision: 'resolution_required', resolutionRequired: true, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, candidatePlanSteps: candidates.map((candidate) => ({ id: candidate.id, objective: candidate.objective })), progression },
964
- });
965
- } else if (progression.kind === 'request_replan') {
966
- return buildFacadeResult({
967
- status: 'blocked',
968
- summary: `PLAN_SUCCESSOR_REPLAN_REQUIRED: ${plan.planId} ${progression.reasonCode}.`,
969
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, progression },
970
- });
971
- } else if (progression.kind !== 'start_next_plan_step') {
972
- return buildFacadeResult({
973
- status: 'blocked',
974
- summary: `PLAN_SUCCESSOR_PROGRESSION_BLOCKED: ${plan.planId} ${progression.reasonCode}.`,
975
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, progression },
976
- });
977
- }
978
574
  }
979
- const planStep = plan && resolvedPlanStepId ? plan.steps.find((step) => step.id === resolvedPlanStepId) : undefined;
980
575
  const requestedRequirementId = input.requirementId?.trim() || undefined;
981
576
  const predecessorRequirementId = terminalContinuationSource?.requirementId;
982
577
  if (predecessorRequirementId && requestedRequirementId && predecessorRequirementId !== requestedRequirementId) {
@@ -986,25 +581,17 @@ export function startGoalWorkloop(
986
581
  data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, predecessorRequirementId, requestedRequirementId },
987
582
  });
988
583
  }
989
- if (plan && requestedRequirementId && requestedRequirementId !== plan.requirementId) {
990
- return buildFacadeResult({
991
- status: 'blocked',
992
- summary: `PLAN_REQUIREMENT_MISMATCH: ${plan.planId} is bound to ${plan.requirementId ?? 'no Requirement'}, not ${requestedRequirementId}. Use the Plan relationship authority or replan explicitly.`,
993
- data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planRequirementId: plan.requirementId, requestedRequirementId },
994
- });
995
- }
996
- if (plan?.requirementId && predecessorRequirementId && plan.requirementId !== predecessorRequirementId) {
997
- return buildFacadeResult({
998
- status: 'blocked',
999
- summary: `WORK_SUCCESSOR_PLAN_REQUIREMENT_MISMATCH: predecessor ${terminalContinuationSource!.workId} belongs to ${predecessorRequirementId}, while ${plan.planId} belongs to ${plan.requirementId}.`,
1000
- data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource!.workId, predecessorRequirementId, planId: plan.planId, planRequirementId: plan.requirementId },
1001
- });
584
+ // A selected Plan supplies its Requirement when no explicit/predecessor link exists.
585
+ // Requirement membership alone never selects sibling Plans or successor Works.
586
+ const effectiveRequirementId = requestedRequirementId || predecessorRequirementId || plan?.requirementId;
587
+ if (plan?.requirementId && effectiveRequirementId && plan.requirementId !== effectiveRequirementId) {
588
+ return buildFacadeResult({ status: 'blocked', summary: `WORK_PLAN_REQUIREMENT_MISMATCH: ${resolvedPlanId} belongs to ${plan.requirementId}, not ${effectiveRequirementId}.`, data: { executionStarted: false, workContractCreated: false } });
1002
589
  }
1003
- const effectiveRequirementId = requestedRequirementId || plan?.requirementId || predecessorRequirementId;
590
+ const effectiveRequirementRecord = effectiveRequirementId && ctx.workStore.controllerHome ? readRequirement({ controllerHome: ctx.workStore.controllerHome }, effectiveRequirementId) : undefined;
1004
591
  if (
1005
592
  effectiveRequirementId
1006
593
  && ctx.workStore.controllerHome
1007
- && !readRequirement({ controllerHome: ctx.workStore.controllerHome }, effectiveRequirementId)
594
+ && !effectiveRequirementRecord
1008
595
  ) {
1009
596
  return buildFacadeResult({
1010
597
  status: 'blocked',
@@ -1012,36 +599,16 @@ export function startGoalWorkloop(
1012
599
  data: { executionStarted: false, workContractCreated: false, requirementId: effectiveRequirementId, planId: plan?.planId },
1013
600
  });
1014
601
  }
1015
- const sameStringSet = (provided: string[] | undefined, frozen: string[]): boolean => {
1016
- if (provided === undefined) return true;
1017
- const actual = [...new Set(provided)].sort();
1018
- const expected = [...new Set(frozen)].sort();
1019
- return actual.length === expected.length && actual.every((value, index) => value === expected[index]);
1020
- };
1021
- if (planStep) {
1022
- const mismatches = [
1023
- !sameStringSet(input.acceptanceCriteria, planStep.acceptanceCriteria) && 'acceptance_criteria',
1024
- !sameStringSet(input.allowedPaths, planStep.allowedPaths) && 'allowed_paths',
1025
- !sameStringSet(input.forbiddenPaths, planStep.forbiddenPaths) && 'forbidden_paths',
1026
- !sameStringSet(input.checks, planStep.checks) && 'check_ids',
1027
- ].filter((value): value is string => Boolean(value));
1028
- if (mismatches.length > 0) {
1029
- return buildFacadeResult({
1030
- status: 'blocked',
1031
- summary: `PLAN_STEP_WORK_CONTRACT_MISMATCH: ${resolvedPlanId}/${resolvedPlanStepId} conflicts with frozen Plan step field(s): ${mismatches.join(', ')}. Replan instead of widening or narrowing Work admission.`,
1032
- data: { executionStarted: false, workContractCreated: false, planId: resolvedPlanId, planStepId: resolvedPlanStepId, mismatches },
1033
- });
1034
- }
1035
- }
1036
- const effectiveObjective = planStep?.objective ?? input.objective;
1037
- const effectiveAcceptanceCriteria = planStep?.acceptanceCriteria ?? input.acceptanceCriteria ?? [];
1038
- const effectiveAllowedPaths = planStep?.allowedPaths ?? input.allowedPaths ?? [];
1039
- const effectiveForbiddenPaths = planStep?.forbiddenPaths ?? input.forbiddenPaths ?? [];
1040
- const effectiveChecks = planStep?.checks ?? input.checks ?? [];
602
+ // Work scope is authored by the caller. A referenced Plan item contributes no
603
+ // objective, paths, checks or acceptance criteria.
604
+ const effectiveObjective = input.objective;
605
+ const effectiveAcceptanceCriteria = input.acceptanceCriteria ?? [];
606
+ const effectiveAllowedPaths = input.allowedPaths ?? [];
607
+ const effectiveForbiddenPaths = input.forbiddenPaths ?? [];
608
+ const effectiveChecks = input.checks ?? [];
1041
609
  const normalized = normalizeCheckIds(effectiveChecks, available);
1042
610
  const invalidLineageWork = activeAdmissionSnapshot.invalid.find((candidate) =>
1043
- (input.relatedWorkId && candidate.workId === input.relatedWorkId)
1044
- || (resolvedPlanId && resolvedPlanStepId && candidate.planId === resolvedPlanId && candidate.planStepId === resolvedPlanStepId));
611
+ (input.relatedWorkId && candidate.workId === input.relatedWorkId));
1045
612
  if (invalidLineageWork) {
1046
613
  return buildFacadeResult({
1047
614
  status: 'blocked',
@@ -1057,10 +624,7 @@ export function startGoalWorkloop(
1057
624
  },
1058
625
  });
1059
626
  }
1060
- const newWorkWillBeIsolated = placementConstraint.requireWorktree
1061
- || placementConstraint.workspaceMode === 'isolated'
1062
- || input.modeInput.requiresParallelism === true
1063
- || routeDecision.requiresIsolation === true;
627
+ const newWorkWillBeIsolated = workspaceMode === 'isolated';
1064
628
  const invalidSharedWorkspaceOwner = !newWorkWillBeIsolated
1065
629
  ? activeAdmissionSnapshot.invalid.find((candidate) => candidate.isolation === 'shared'
1066
630
  && (!candidate.checkoutId || candidate.checkoutId === ctx.checkoutId))
@@ -1072,24 +636,18 @@ export function startGoalWorkloop(
1072
636
  data: { executionStarted: false, workContractCreated: false, invalidWorkId: invalidSharedWorkspaceOwner.workId, invalidWorkError: invalidSharedWorkspaceOwner.error },
1073
637
  });
1074
638
  }
1075
- const boundPlanStepWorks = resolvedPlanId && resolvedPlanStepId
1076
- ? activeWorks.filter((candidate) => candidate.planId === resolvedPlanId && candidate.planStepId === resolvedPlanStepId)
1077
- : [];
1078
- const explicitPlanStepWork = planStep?.workId ? activeWorks.find((candidate) => candidate.workId === planStep.workId) : undefined;
1079
- const planStepWork = explicitPlanStepWork ?? (boundPlanStepWorks.length === 1 ? boundPlanStepWorks[0] : undefined);
1080
639
  // Requirement membership is portfolio ownership, not semantic Work identity.
1081
- // Only an explicit related Work or the exact bound Plan step may select an
1082
- // existing Work authority. Siblings under one Requirement remain unrelated
1083
- // for semantic admission and meet only in placement/resource arbitration.
1084
- const deterministicTarget = input.relatedWorkId
1085
- ? explicitRelatedWork
1086
- : planStepWork;
1087
- const requestedRelation = input.workRelation ?? (input.modeInput.requiresParallelism === true ? 'parallel' : undefined);
640
+ // Only an explicit related Work may select an existing Work authority. A Plan
641
+ // item never selects, owns or reuses Work. Siblings under one Requirement
642
+ // remain unrelated for semantic admission and meet only in
643
+ // placement/resource arbitration.
644
+ const deterministicTarget = input.relatedWorkId ? explicitRelatedWork : undefined;
645
+ const requestedRelation = input.workRelation;
1088
646
  // Only strong semantic bindings participate in ownership resolution. An
1089
647
  // unrelated active Work or a checkout writer is a placement fact, not a
1090
648
  // semantic candidate for continue/extend/parallel/new_goal.
1091
649
  const candidateWorks = [...new Map(
1092
- [explicitRelatedWork, planStepWork, ...boundPlanStepWorks]
650
+ [explicitRelatedWork]
1093
651
  .filter((candidate): candidate is WorkContract => Boolean(candidate))
1094
652
  .map((candidate) => [candidate.workId, candidate]),
1095
653
  ).values()].slice(0, 8);
@@ -1110,63 +668,18 @@ export function startGoalWorkloop(
1110
668
  evidenceRefs: target?.evidenceRefs ?? [],
1111
669
  suggestedNextActions: target
1112
670
  ? [{
1113
- label: 'Continue existing Work',
671
+ label: 'Inspect existing Work',
1114
672
  tool: 'rh_work',
1115
- operation: 'continue',
673
+ operation: 'get',
1116
674
  payload: { work_id: target.workId },
1117
675
  risk: 'readonly',
1118
- confidence: planStepWork || explicitRelatedWork ? 'high' : 'medium',
1119
- reason: 'Resolve intent before execution; do not create a second Work until the relationship is explicit.',
676
+ confidence: explicitRelatedWork ? 'high' : 'medium',
677
+ reason: 'Resolve intent before execution; semantic Work inspection does not progress lifecycle state.',
1120
678
  }]
1121
679
  : [],
1122
680
  rawAvailable: false,
1123
681
  });
1124
682
 
1125
- if (boundPlanStepWorks.length > 1) {
1126
- return resolutionRequired(`PLAN_STEP_MULTIPLE_PRIMARY_WORKS: ${resolvedPlanId}/${resolvedPlanStepId} has ${boundPlanStepWorks.length} active primary Work records and requires repair before execution.`);
1127
- }
1128
- if (terminalContinuationSource && requestedRelation === 'continue' && planStepWork) {
1129
- if (planStepWork.predecessorWorkId !== terminalContinuationSource.workId) {
1130
- return resolutionRequired(
1131
- `PLAN_STEP_SUCCESSOR_LINEAGE_CONFLICT: ${resolvedPlanId}/${resolvedPlanStepId} is already owned by ${planStepWork.workId}, but its predecessor is ${planStepWork.predecessorWorkId ?? 'none'} rather than ${terminalContinuationSource.workId}.`,
1132
- planStepWork,
1133
- );
1134
- }
1135
- return buildFacadeResult({
1136
- status: 'ok',
1137
- summary: `PLAN_STEP_REUSES_SUCCESSOR_WORK: ${resolvedPlanId}/${resolvedPlanStepId} already continues terminal ${terminalContinuationSource.workId} as ${planStepWork.workId}.`,
1138
- data: {
1139
- executionStarted: false,
1140
- workContractCreated: false,
1141
- admissionDecision: 'reuse_existing',
1142
- resolutionRequired: false,
1143
- predecessorWorkId: terminalContinuationSource.workId,
1144
- work: summarizeWorkContract(planStepWork),
1145
- },
1146
- evidenceRefs: planStepWork.evidenceRefs,
1147
- suggestedNextActions: [{ label: 'Continue successor Work', tool: 'rh_work', operation: 'continue', payload: { work_id: planStepWork.workId }, risk: 'readonly', confidence: 'high' }],
1148
- rawAvailable: false,
1149
- });
1150
- }
1151
-
1152
- if (planStep?.workId && !explicitPlanStepWork) {
1153
- return buildFacadeResult({
1154
- status: 'blocked',
1155
- summary: `PLAN_STEP_BOUND_WORK_MISSING: ${resolvedPlanId}/${resolvedPlanStepId} is bound to ${planStep.workId}, but that Work is not active. Repair the exact binding; do not create a replacement Work.`,
1156
- data: { executionStarted: false, workContractCreated: false, planId: resolvedPlanId, planStepId: resolvedPlanStepId, boundWorkId: planStep.workId, repairRequired: true },
1157
- suggestedNextActions: [{
1158
- label: 'Diagnose exact Plan step binding',
1159
- tool: 'rh_work',
1160
- operation: 'repair',
1161
- payload: { plan_id: resolvedPlanId, plan_step_id: resolvedPlanStepId, repair_operation: 'diagnose', dry_run: true },
1162
- risk: 'readonly',
1163
- confidence: 'high',
1164
- reason: 'The Plan step has an exact Work identity but the active Work view cannot resolve it. Diagnose that binding before any replacement Work can be admitted.',
1165
- }],
1166
- rawAvailable: false,
1167
- });
1168
- }
1169
-
1170
683
  if ((input.requestedBy ?? 'chatgpt') === 'scheduler') {
1171
684
  return resolutionRequired(
1172
685
  deterministicTarget
@@ -1175,42 +688,17 @@ export function startGoalWorkloop(
1175
688
  );
1176
689
  }
1177
690
 
1178
- if (planStepWork && !requestedRelation) {
1179
- return buildFacadeResult({
1180
- status: 'ok',
1181
- summary: `PLAN_STEP_REUSES_ACTIVE_WORK: ${resolvedPlanId}/${resolvedPlanStepId} already executes as ${planStepWork.workId}; reuse that Work instead of creating another.`,
1182
- data: {
1183
- executionStarted: false,
1184
- workContractCreated: false,
1185
- admissionDecision: 'reuse_existing',
1186
- resolutionRequired: false,
1187
- work: summarizeWorkContract(planStepWork),
1188
- },
1189
- evidenceRefs: planStepWork.evidenceRefs,
1190
- suggestedNextActions: [{ label: 'Continue existing Work', tool: 'rh_work', operation: 'continue', payload: { work_id: planStepWork.workId }, risk: 'readonly', confidence: 'high' }],
1191
- rawAvailable: false,
1192
- });
1193
- }
1194
-
1195
691
  if ((requestedRelation === 'continue' || requestedRelation === 'extend') && !terminalContinuationSource) {
1196
692
  if (!deterministicTarget) {
1197
- return resolutionRequired(`${requestedRelation.toUpperCase()}_TARGET_REQUIRED: select related_work_id or bind the request to an exact active Plan step before execution.`);
1198
- }
1199
- if (requestedRelation === 'extend' && deterministicTarget.planId) {
1200
- return resolutionRequired(
1201
- `PLAN_EXTENSION_REQUIRES_REPLAN: Work ${deterministicTarget.workId} is governed by Plan ${deterministicTarget.planId}; record additional discovery in scopeEvidence, but update the authoritative Plan before widening policy or acceptance scope.`,
1202
- deterministicTarget,
1203
- );
693
+ return resolutionRequired(`${requestedRelation.toUpperCase()}_TARGET_REQUIRED: select related_work_id before execution.`);
1204
694
  }
1205
695
  const selected = requestedRelation === 'extend'
1206
696
  ? updateWorkContract(ctx.workStore, deterministicTarget.workId, {
1207
697
  acceptanceCriteria: [...new Set([...deterministicTarget.acceptanceCriteria, ...(input.acceptanceCriteria ?? [])])].slice(0, 30),
1208
- allowedPaths: [...new Set([...deterministicTarget.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
1209
- forbiddenPaths: [...new Set([...deterministicTarget.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
1210
698
  checks: [...new Set([...deterministicTarget.checks, ...normalized.validCheckIds])].slice(0, 30),
1211
699
  scopeEvidence: {
1212
700
  initialLikelyPaths: [...new Set([
1213
- ...(deterministicTarget.scopeEvidence?.initialLikelyPaths ?? deterministicTarget.allowedPaths),
701
+ ...(deterministicTarget.scopeEvidence?.initialLikelyPaths ?? []),
1214
702
  ...(input.initialLikelyPaths ?? input.allowedPaths ?? []),
1215
703
  ])].slice(0, 100),
1216
704
  inspectedPaths: deterministicTarget.scopeEvidence?.inspectedPaths ?? [],
@@ -1232,7 +720,7 @@ export function startGoalWorkloop(
1232
720
  work: summarizeWorkContract(selected),
1233
721
  },
1234
722
  evidenceRefs: selected.evidenceRefs,
1235
- suggestedNextActions: [{ label: 'Continue existing Work', tool: 'rh_work', operation: 'continue', payload: { work_id: selected.workId }, risk: 'readonly', confidence: 'high' }],
723
+ suggestedNextActions: [{ label: 'Read existing Work context', tool: 'rh_work', operation: 'get', payload: { work_id: selected.workId }, risk: 'readonly', confidence: 'high' }],
1236
724
  rawAvailable: false,
1237
725
  });
1238
726
  }
@@ -1243,51 +731,10 @@ export function startGoalWorkloop(
1243
731
  );
1244
732
  }
1245
733
 
1246
- // Pure remote effects do not participate in repository workspace ownership.
1247
- // An unrelated canonical writer, dirty checkout, or repository parallelism must
1248
- // not manufacture a Git worktree for browser/API work that has no source authority.
1249
- // Explicit typed isolation remains authoritative for the rare remote effect that
1250
- // genuinely declares a repository workspace requirement.
1251
- const repositoryWorkspaceParticipant = resolvedWorkKind !== 'remote_effect';
1252
- const placementConflict = repositoryWorkspaceParticipant && Boolean(workspaceOwner);
1253
- const trustedDirtyPaths = ctx.workspaceChangedPaths
1254
- ? [...new Set(ctx.workspaceChangedPaths.map((path) => path.trim()).filter(Boolean))].sort()
1255
- : undefined;
1256
- const dirtyWorkspaceOwnershipConflict = repositoryWorkspaceParticipant
1257
- && input.modeInput.workspaceDirty === true
1258
- && (
1259
- !trustedDirtyPaths
1260
- || trustedDirtyPaths.length === 0
1261
- || effectiveAllowedPaths.length === 0
1262
- || trustedDirtyPaths.some((path) => (
1263
- effectiveForbiddenPaths.some((pattern) => globMatches(pattern, path))
1264
- || !effectiveAllowedPaths.some((pattern) => globMatches(pattern, path))
1265
- ))
1266
- );
1267
- const automaticRepositoryIsolation = repositoryWorkspaceParticipant && (
1268
- placementConflict
1269
- || dirtyWorkspaceOwnershipConflict
1270
- || input.modeInput.requiresParallelism === true
1271
- );
1272
- const needsWorktree = executionMode === 'goal_workloop' && (
1273
- placementConstraint.requireWorktree
1274
- || automaticRepositoryIsolation
1275
- || routeDecision.requiresIsolation === true
1276
- );
1277
- if (!needsWorktree && workspaceOwner && repositoryWorkspaceParticipant) {
1278
- return buildFacadeResult({
1279
- status: 'blocked',
1280
- summary: `WORKSPACE_OWNERSHIP_INVARIANT: unresolved admission reached checkout ${ctx.checkoutId} while active Work ${workspaceOwner.workId} owns it. This is an internal routing invariant, not a normal fallback path.`,
1281
- data: {
1282
- executionStarted: false,
1283
- workContractCreated: false,
1284
- conflictType: 'workspace_single_writer_invariant',
1285
- existingWork: summarizeWorkContract(workspaceOwner),
1286
- },
1287
- evidenceRefs: workspaceOwner.evidenceRefs,
1288
- rawAvailable: false,
1289
- });
1290
- }
734
+ // Repository topology is caller-owned. Forge records the selected placement
735
+ // and lets concrete WorkHandle/Lease/Git authorities report real conflicts at
736
+ // the mutation boundary instead of inferring isolation from semantic context.
737
+ const needsWorktree = workspaceMode === 'isolated';
1291
738
  const requestedWorkId = input.workId?.trim();
1292
739
  if (requestedWorkId && !/^work-[a-z0-9][a-z0-9-]{0,199}$/i.test(requestedWorkId)) {
1293
740
  return buildFacadeResult({
@@ -1297,56 +744,21 @@ export function startGoalWorkloop(
1297
744
  });
1298
745
  }
1299
746
  const generatedWorkId = requestedWorkId ?? workIdFor(effectiveObjective);
1300
- if (resolvedPlanId || resolvedPlanStepId) {
1301
- if (!resolvedPlanId || !resolvedPlanStepId || !ctx.planStore || !ctx.sourceRevision || !plan || !planStep) {
1302
- return buildFacadeResult({ status: 'blocked', summary: 'PLAN_CONTEXT_REQUIRED: plan_id, plan_step_id, an executable Plan step and a current source revision are required.', data: { executionStarted: false } });
1303
- }
1304
- if (plan.status !== 'approved' && plan.status !== 'executing') {
1305
- return buildFacadeResult({ status: 'blocked', summary: `PLAN_NOT_EXECUTABLE: ${plan.planId} is ${plan.status}`, data: { executionStarted: false, planId: plan.planId } });
1306
- }
1307
- if (plan.sourceRevision !== ctx.sourceRevision) {
1308
- const invalidated = claimPlanStepForWork(ctx.planStore, {
1309
- planId: resolvedPlanId,
1310
- stepId: resolvedPlanStepId,
1311
- workId: generatedWorkId,
1312
- sourceRevision: ctx.sourceRevision,
1313
- });
1314
- return buildFacadeResult({
1315
- status: 'blocked',
1316
- summary: `PLAN_SOURCE_DRIFT: ${invalidated.planId} was invalidated because its source revision no longer matches. Replan before execution.`,
1317
- data: { planId: invalidated.planId, executionStarted: false, workContractCreated: false, replanRequired: true },
1318
- });
1319
- }
1320
- const unresolved = planStep.dependencies.filter((dependency) => plan.steps.find((candidate) => candidate.id === dependency)?.status !== 'completed');
1321
- if (unresolved.length > 0) {
1322
- return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_DEPENDENCIES_PENDING: ${unresolved.join(', ')}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
1323
- }
1324
- if (planStep.status === 'completed') {
1325
- return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_ALREADY_COMPLETED: ${planStep.id}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
1326
- }
1327
- }
1328
747
  const initialSuggestedNextActions = suggestedForWorkIdentity(
1329
748
  generatedWorkId,
1330
749
  normalized.validCheckIds,
1331
750
  normalized.suggestedNextActions,
1332
751
  );
1333
752
  const remoteDeliveryRequired = resolvedWorkKind === 'repository_change'
1334
- && input.modeInput.requiresExternalEffect === true
1335
- && input.modeInput.remoteWrite === true;
753
+ && input.request.requiresExternalEffect === true
754
+ && input.request.remoteWrite === true;
1336
755
  const effectiveConstraints: WorkContract['constraints'] = {
1337
756
  ...canonicalConstraints,
1338
- ...(needsWorktree ? { workspaceMode: 'isolated' as const, requireWorktree: true } : {}),
1339
757
  ...(remoteDeliveryRequired ? { remoteDeliveryRequired: true } : {}),
1340
758
  };
1341
- const worktreeReason = placementConstraint.requireWorktree || routeDecision.requiresIsolation === true
1342
- ? 'Typed placement or Route Policy requires isolated execution.'
1343
- : dirtyWorkspaceOwnershipConflict
1344
- ? 'Trusted repository observation found dirty paths outside or ambiguous to the Work path fence; isolated placement prevents unrelated changes from entering Work ownership or verification.'
1345
- : placementConflict
1346
- ? 'The selected checkout is already owned by another active Work; this Work requires isolated placement.'
1347
- : repositoryWorkspaceParticipant && input.modeInput.requiresParallelism === true
1348
- ? 'Parallel Work requires isolated placement.'
1349
- : 'Current workspace is the stability-first default; isolation remains opt-in.';
759
+ const worktreeReason = needsWorktree
760
+ ? 'Caller explicitly selected isolated repository placement.'
761
+ : 'Caller selected the current repository checkout.';
1350
762
  const forgeInstanceId = ctx.workStore.controllerHome
1351
763
  ? readForgeInstanceIdentity(ctx.workStore.controllerHome)?.instanceId
1352
764
  : undefined;
@@ -1364,9 +776,6 @@ export function startGoalWorkloop(
1364
776
  baseRevision: ctx.sourceRevision,
1365
777
  repositoryBaseState: ctx.sourceBaseState ?? (ctx.sourceRevision ? 'revision' : undefined),
1366
778
  workspaceFingerprint: needsWorktree ? undefined : ctx.workspaceFingerprint,
1367
- routeDecisionFingerprint: routeDecision.inputFingerprint,
1368
- routeDecision,
1369
- mode: executionMode,
1370
779
  objective: effectiveObjective,
1371
780
  acceptanceCriteria: effectiveAcceptanceCriteria,
1372
781
  constraints: effectiveConstraints,
@@ -1376,16 +785,18 @@ export function startGoalWorkloop(
1376
785
  // repository-change Work's semantic identity. Pure external actions with no
1377
786
  // repository-change signal remain remote_effect and keep plugin receipt semantics.
1378
787
  workKind: resolvedWorkKind,
1379
- status: 'running',
788
+ dispatchState: 'running',
1380
789
  phase: 'implementation',
1381
790
  issueId: input.issueId,
1382
791
  taskId: input.taskId,
1383
792
  requirementId: effectiveRequirementId,
793
+ requirementRevision: effectiveRequirementRecord ? currentRequirementSemanticRevision(effectiveRequirementRecord.value) : undefined,
1384
794
  predecessorWorkId: terminalContinuationSource?.workId,
1385
795
  planId: resolvedPlanId,
796
+ planRevision: plan ? currentPlanSemanticRevision(plan) : undefined,
1386
797
  planStepId: resolvedPlanStepId,
1387
- planSourceRevision: resolvedPlanId ? ctx.sourceRevision : undefined,
1388
- scopeSummary: input.modeInput.scopeClear ? 'scope declared at start' : 'scope incomplete',
798
+ planSourceRevision: resolvedPlanStepId ? plan?.sourceRevision : undefined,
799
+ scopeSummary: input.request.scopeClear ? 'scope declared at start' : 'scope incomplete',
1389
800
  scopeEvidence: {
1390
801
  initialLikelyPaths: [...new Set(input.initialLikelyPaths ?? effectiveAllowedPaths)].slice(0, 100),
1391
802
  inspectedPaths: [],
@@ -1395,13 +806,6 @@ export function startGoalWorkloop(
1395
806
  allowedPaths: effectiveAllowedPaths,
1396
807
  forbiddenPaths: effectiveForbiddenPaths,
1397
808
  checks: normalized.validCheckIds,
1398
- driver: {
1399
- preferred: executionMode === 'direct_control'
1400
- ? 'direct_edit'
1401
- : input.modeInput.requiresWorker === true ? 'external_controller' : needsWorktree ? 'isolated_worktree' : 'direct_edit',
1402
- allowWorker: false,
1403
- allowDirectEdit: executionMode === 'direct_control' || (input.modeInput.requiresWorker !== true && !needsWorktree),
1404
- },
1405
809
  worktreePolicy: {
1406
810
  required: needsWorktree,
1407
811
  reason: worktreeReason,
@@ -1413,8 +817,8 @@ export function startGoalWorkloop(
1413
817
  maxEvidenceRefs: 20,
1414
818
  },
1415
819
  approvalPolicy: {
1416
- required: input.modeInput.requiresApproval === true || input.modeInput.requiresUserApproval === true,
1417
- reasons: input.modeInput.requiresApproval || input.modeInput.requiresUserApproval ? ['approval requested at start'] : [],
820
+ required: input.request.requiresApproval === true || input.request.requiresUserApproval === true,
821
+ reasons: input.request.requiresApproval || input.request.requiresUserApproval ? ['approval requested at start'] : [],
1418
822
  confirmed: input.approvalConfirmed === true,
1419
823
  },
1420
824
  recoveryPolicy: {
@@ -1430,48 +834,19 @@ export function startGoalWorkloop(
1430
834
  continuationPrompt: `Continue work ${ctx.repoId}: ${effectiveObjective.slice(0, 200)}`,
1431
835
  });
1432
836
 
1433
- if (resolvedPlanId && resolvedPlanStepId && ctx.planStore && ctx.sourceRevision) {
1434
- try {
1435
- const claimed = claimPlanStepForWork(ctx.planStore, {
1436
- planId: resolvedPlanId,
1437
- stepId: resolvedPlanStepId,
1438
- workId: work.workId,
1439
- sourceRevision: ctx.sourceRevision,
1440
- });
1441
- if (claimed.status === 'invalidated_by_drift') {
1442
- return buildFacadeResult({
1443
- status: 'blocked',
1444
- summary: `PLAN_SOURCE_DRIFT: ${claimed.planId} was invalidated because its source revision no longer matches. Repair or replan before execution.`,
1445
- data: { executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), planId: claimed.planId, canonicalWorkRetained: true, replanRequired: true },
1446
- });
1447
- }
1448
- } catch (error) {
1449
- return buildFacadeResult({
1450
- status: 'blocked',
1451
- summary: error instanceof Error ? error.message : 'PLAN_STEP_CLAIM_FAILED',
1452
- data: { planId: resolvedPlanId, planStepId: resolvedPlanStepId, executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), canonicalWorkRetained: true },
1453
- });
1454
- }
1455
- }
1456
-
1457
837
  return buildFacadeResult({
1458
838
  status: 'ok',
1459
839
  summary: terminalContinuationSource
1460
840
  ? `Successor Work ${work.workId} continues semantic lineage from terminal ${terminalContinuationSource.workId}${resolvedPlanId && resolvedPlanStepId ? ` at ${resolvedPlanId}/${resolvedPlanStepId}` : ''}.`
1461
- : executionMode === 'direct_control'
1462
- ? `Direct-control Work lineage started as ${work.workId}.`
1463
- : `Goal workloop started as ${work.workId}.`,
841
+ : `Work started as ${work.workId}.`,
1464
842
  data: {
1465
- mode: {
1466
- mode: executionMode,
1467
- reason: executionMode === 'direct_control'
1468
- ? 'A lightweight WorkContract records the mutation while execution stays on Direct Control.'
1469
- : 'WorkContract created for multi-step recoverable work.',
1470
- createWorkContract: true,
1471
- createHandoff: false,
1472
- missingContractFields: [],
1473
- requiresWork: true,
1474
- routeDecision,
843
+ // Caller-visible placement facts only. Execution mode tokens, route reasons
844
+ // and RouteDecision are legacy orchestration metadata, not Work semantics.
845
+ placement: {
846
+ workContractCreated: true,
847
+ worktreeRequired: work.worktreePolicy.required,
848
+ isolated: work.worktreePolicy.required,
849
+ requiresRecovery: work.recoveryPolicy.allowSelfHealing === true || false,
1475
850
  },
1476
851
  workContractCreated: true,
1477
852
  work: summarizeWorkContract(work),
@@ -1497,10 +872,10 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1497
872
  });
1498
873
  }
1499
874
 
1500
- if (work.status === 'cancelled' || work.status === 'completed' || work.status === 'failed') {
875
+ if (semanticWorkState(work) !== 'open') {
1501
876
  return buildFacadeResult({
1502
877
  status: 'blocked',
1503
- summary: `WorkContract ${work.workId} is terminal (${work.status}); continue is not allowed.`,
878
+ summary: `WorkContract ${work.workId} is terminal (${semanticWorkState(work)}); continue is not allowed.`,
1504
879
  data: { work: summarizeWorkContract(work) },
1505
880
  suggestedNextActions: [
1506
881
  {
@@ -1514,6 +889,21 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1514
889
  });
1515
890
  }
1516
891
 
892
+ if (semanticWorkState(work) === 'open' && hasSettledWorkDeliveryReceipt(work)) {
893
+ return buildFacadeResult({
894
+ status: 'ok',
895
+ summary: `Continue: physical delivery is settled for ${work.workId}; semantic Work remains open for explicit Controller judgment.`,
896
+ data: {
897
+ work: summarizeWorkContract(work),
898
+ backgroundCompleted: false,
899
+ nextStep: 'semantic_decision',
900
+ deliverySettled: true,
901
+ semanticWorkState: 'open',
902
+ },
903
+ evidenceRefs: work.evidenceRefs.slice(0, 5),
904
+ });
905
+ }
906
+
1517
907
  if (input.verifiedEngineeringEvidence) {
1518
908
  const sourceIdentity = ctx.sourceRevision?.trim()
1519
909
  ? { kind: 'revision' as const, revision: ctx.sourceRevision.trim() }
@@ -1535,42 +925,9 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1535
925
  data: { work: summarizeWorkContract(work), engineeringEvidenceUpdated: false },
1536
926
  });
1537
927
  }
1538
- const priorDesign = work.engineeringContext?.evidence.designDecisionReceipt;
1539
- if (work.engineeringContext?.designState === 'revisit_required' && priorDesign) {
1540
- const nextDesign = refreshedEngineeringContext.evidence.designDecisionReceipt;
1541
- if (!nextDesign || nextDesign.supersedesReceiptId !== priorDesign.receiptId) {
1542
- return buildFacadeResult({
1543
- status: 'blocked',
1544
- summary: 'ENGINEERING_DESIGN_SUPERSESSION_REQUIRED: same-root-cause re-entry requires a new design receipt that explicitly supersedes the prior design.',
1545
- data: { work: summarizeWorkContract(work), priorDesignReceiptId: priorDesign.receiptId },
1546
- });
1547
- }
1548
- if (refreshedEngineeringContext.evidence.independentCritiqueReceipt?.decision !== 'approved') {
1549
- return buildFacadeResult({
1550
- status: 'blocked',
1551
- summary: 'ENGINEERING_DESIGN_CRITIQUE_APPROVAL_REQUIRED: same-root-cause re-entry requires an independently approved critique of the superseding design.',
1552
- data: { work: summarizeWorkContract(work), priorDesignReceiptId: priorDesign.receiptId, nextDesignReceiptId: nextDesign.receiptId },
1553
- });
1554
- }
1555
- }
1556
928
  work = updateWorkContract(ctx.workStore, work.workId, { engineeringContext: refreshedEngineeringContext });
1557
929
  }
1558
930
 
1559
- if (work.engineeringContext?.designState === 'revisit_required') {
1560
- return buildFacadeResult({
1561
- status: 'blocked',
1562
- summary: 'ENGINEERING_DESIGN_REVISIT_REQUIRED: same-root-cause design authority must be explicitly superseded and independently approved before further Work mutation.',
1563
- data: { work: summarizeWorkContract(work) },
1564
- suggestedNextActions: [{
1565
- label: 'Refresh design evidence',
1566
- tool: 'rh_context',
1567
- operation: 'search',
1568
- payload: { work_id: work.workId, query: 'Refresh current source and design evidence for this Work before engineering re-entry.' },
1569
- risk: 'readonly',
1570
- }],
1571
- });
1572
- }
1573
-
1574
931
  if (input.engineeringBlocker) {
1575
932
  if (!work.engineeringContext || work.engineeringContext.sourceIdentity.kind !== 'revision') {
1576
933
  return buildFacadeResult({ status: 'blocked', summary: 'ENGINEERING_BLOCKER_SOURCE_IDENTITY_REQUIRED', data: { work: summarizeWorkContract(work) } });
@@ -1580,43 +937,34 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1580
937
  try {
1581
938
  let linkedWorkId: string | undefined;
1582
939
  if (input.engineeringBlocker.classification === 'unrelated') {
1583
- linkedWorkId = linkedEngineeringBlockerWorkId(work.workId, input.engineeringBlocker.blockerId);
1584
- linkedWork = getWorkContract(ctx.workStore, linkedWorkId);
940
+ // Forge does not decompose work on the model's behalf. An unrelated blocker
941
+ // must name the Work that already owns it; the caller creates that Work
942
+ // explicitly (rh_work start) and passes its exact id here.
943
+ const declaredLinkedWorkId = input.engineeringBlocker.linkedWorkId?.trim();
944
+ if (!declaredLinkedWorkId) {
945
+ return buildFacadeResult({
946
+ status: 'blocked',
947
+ summary: 'ENGINEERING_BLOCKER_LINKED_WORK_REQUIRED: an unrelated blocker must name the Work that owns it via linked_work_id; Forge never creates a child Work automatically.',
948
+ data: { work: summarizeWorkContract(work), linkedWorkCreated: false },
949
+ suggestedNextActions: [{
950
+ label: 'Start the owning Work explicitly',
951
+ tool: 'rh_work',
952
+ operation: 'start',
953
+ payload: { objective: `Resolve unrelated blocker ${input.engineeringBlocker.blockerId}: ${input.engineeringBlocker.rationale}` },
954
+ risk: 'readonly',
955
+ confidence: 'medium',
956
+ }],
957
+ });
958
+ }
959
+ linkedWork = getWorkContract(ctx.workStore, declaredLinkedWorkId);
1585
960
  if (!linkedWork) {
1586
- const linked = routeWorkStart(ctx, {
1587
- workId: linkedWorkId,
1588
- objective: `Resolve unrelated blocker ${input.engineeringBlocker.blockerId}: ${input.engineeringBlocker.rationale}`,
1589
- acceptanceCriteria: [
1590
- `Resolve blocker ${input.engineeringBlocker.blockerId} without widening Work ${work.workId}.`,
1591
- 'Return bounded evidence or a precise wake/blocker condition to the owning Controller.',
1592
- ],
1593
- allowedPaths: [],
1594
- initialLikelyPaths: [],
1595
- forbiddenPaths: [],
1596
- checks: [],
1597
- modeInput: {
1598
- scopeClear: false,
1599
- mutation: false,
1600
- requiresInvestigation: true,
1601
- requiresRecovery: true,
1602
- requiresParallelism: true,
1603
- },
1604
- requestedBy: 'chatgpt',
1605
- relatedWorkId: work.workId,
1606
- workRelation: 'parallel',
1607
- requirementId: work.requirementId,
1608
- workKind: 'investigation',
1609
- constraints: { accessMode: work.constraints.accessMode },
961
+ return buildFacadeResult({
962
+ status: 'blocked',
963
+ summary: `ENGINEERING_BLOCKER_LINKED_WORK_UNKNOWN: ${declaredLinkedWorkId} is not a readable Work.`,
964
+ data: { work: summarizeWorkContract(work), linkedWorkId: declaredLinkedWorkId, linkedWorkCreated: false },
1610
965
  });
1611
- linkedWork = getWorkContract(ctx.workStore, linkedWorkId);
1612
- if (linked.status !== 'ok' || !linkedWork) {
1613
- return buildFacadeResult({
1614
- status: 'blocked',
1615
- summary: `ENGINEERING_LINKED_WORK_ADMISSION_FAILED: ${linked.summary}`,
1616
- data: { work: summarizeWorkContract(work), linkedWorkId, linkedWorkCreated: false },
1617
- });
1618
- }
1619
966
  }
967
+ linkedWorkId = linkedWork.workId;
1620
968
  }
1621
969
  blocker = buildEngineeringBlockerDispositionReceipt({
1622
970
  sourceRevision: work.engineeringContext.sourceIdentity.revision,
@@ -1632,30 +980,19 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1632
980
  } catch (error) {
1633
981
  return buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? error.message : 'ENGINEERING_BLOCKER_INVALID', data: { work: summarizeWorkContract(work) } });
1634
982
  }
1635
- const returnToDesign = blocker.action === 'return_to_design';
1636
- return buildFacadeResult({
1637
- status: 'blocked',
1638
- summary: returnToDesign
1639
- ? `Same-root-cause blocker ${blocker.blockerId} requires Product/Design re-entry before further mutation.`
1640
- : `Unrelated blocker ${blocker.blockerId} is linked to ${blocker.linkedWorkId}; current Work semantic scope is unchanged.`,
1641
- data: {
1642
- work: summarizeWorkContract(work),
1643
- engineeringBlocker: blocker,
1644
- ...(linkedWork ? { linkedWork: summarizeWorkContract(linkedWork), linkedWorkCreated: true } : {}),
1645
- nextStep: blocker.action,
1646
- },
1647
- suggestedNextActions: returnToDesign
1648
- ? [{
1649
- label: 'Refresh design evidence',
1650
- tool: 'rh_context',
1651
- operation: 'search',
1652
- payload: { work_id: work.workId, query: 'Refresh current source and design evidence for this Work before engineering re-entry.' },
1653
- risk: 'readonly',
1654
- }]
1655
- : linkedWork
1656
- ? [{ label: 'Continue linked Work', tool: 'rh_work', operation: 'continue', payload: { work_id: linkedWork.workId }, risk: 'workspace_write' }]
1657
- : [],
1658
- });
983
+ if (blocker.action === 'extend_candidate') {
984
+ work = appendWorkEvidence(ctx.workStore, work.workId, {
985
+ title: 'same-root candidate scope extension',
986
+ summary: `Blocker ${blocker.blockerId} remains inside the current architecture authority; continue the exact candidate without Design re-entry or sibling Work.`,
987
+ detailLevel: 'summary',
988
+ });
989
+ } else {
990
+ work = appendWorkEvidence(ctx.workStore, work.workId, {
991
+ title: 'engineering blocker observation',
992
+ summary: `Recorded ${blocker.classification} blocker ${blocker.blockerId}; it is model guidance, not a Work continuation gate.`,
993
+ detailLevel: 'summary',
994
+ });
995
+ }
1659
996
  }
1660
997
 
1661
998
  if ((input.reviewFindings?.length ?? 0) > 0 && work.workKind !== 'read_only_review') {
@@ -1665,28 +1002,10 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1665
1002
  data: { work: summarizeWorkContract(work), reviewFindingsRecorded: false },
1666
1003
  });
1667
1004
  }
1668
- if (work.workKind === 'read_only_review') {
1669
- const sourceIdentity = evaluateReadOnlyReviewSourceIdentity(work, ctx.sourceRevision, ctx.workspaceChangedPaths);
1670
- if (sourceIdentity.status !== 'complete') {
1671
- return buildFacadeResult({
1672
- status: 'blocked',
1673
- summary: `READ_ONLY_REVIEW_SOURCE_IDENTITY_REQUIRED: ${sourceIdentity.reasons.join(' ')}`,
1674
- data: { work: summarizeWorkContract(work), sourceIdentityProven: false },
1675
- });
1676
- }
1677
- }
1678
-
1679
1005
  const explicitPolicyScope = input.allowedPaths !== undefined
1680
1006
  || input.forbiddenPaths !== undefined
1681
1007
  || input.checks !== undefined;
1682
1008
  if (explicitPolicyScope) {
1683
- if (work.planId) {
1684
- return buildFacadeResult({
1685
- status: 'blocked',
1686
- summary: `PLAN_EXTENSION_REQUIRES_REPLAN: Work ${work.workId} is governed by Plan ${work.planId}; continue cannot widen frozen policy scope. Replan the authoritative Plan instead.`,
1687
- data: { work: summarizeWorkContract(work), planId: work.planId, policyScopeUpdated: false },
1688
- });
1689
- }
1690
1009
  const normalizedChecks = normalizeCheckIds(input.checks ?? [], ctx.availableChecks ?? []);
1691
1010
  if (normalizedChecks.invalidCheckIds.length > 0) {
1692
1011
  return buildFacadeResult({
@@ -1696,12 +1015,10 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1696
1015
  });
1697
1016
  }
1698
1017
  work = updateWorkContract(ctx.workStore, work.workId, {
1699
- allowedPaths: [...new Set([...work.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
1700
- forbiddenPaths: [...new Set([...work.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
1701
1018
  checks: [...new Set([...work.checks, ...normalizedChecks.validCheckIds])].slice(0, 30),
1702
1019
  scopeEvidence: {
1703
1020
  initialLikelyPaths: [...new Set([
1704
- ...(work.scopeEvidence?.initialLikelyPaths ?? work.allowedPaths),
1021
+ ...(work.scopeEvidence?.initialLikelyPaths ?? []),
1705
1022
  ...(input.allowedPaths ?? []),
1706
1023
  ])].slice(0, 100),
1707
1024
  inspectedPaths: work.scopeEvidence?.inspectedPaths ?? [],
@@ -1836,7 +1153,6 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1836
1153
  detailLevel: 'summary',
1837
1154
  });
1838
1155
  transitionWorkContractPhase(ctx.workStore, work.workId, {
1839
- status: 'running',
1840
1156
  phase: 'implementation',
1841
1157
  state: 'active',
1842
1158
  summary: `Controller chose bounded ${decision} after acceptance failure; implementation may resume before re-verification.`,
@@ -1859,148 +1175,36 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1859
1175
  });
1860
1176
  }
1861
1177
 
1862
- // Ambiguous acceptance failure without an explicit bounded Controller decision →
1863
- // ask once per distinct failure evidence. Repeated continue calls must not
1864
- // manufacture an unbounded handoff loop. A resolved handoff remains the decision
1865
- // authority until newer valid-fail evidence; an unresolved matching handoff is reused.
1866
- if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair && work.recoveryPolicy.handoffOnAmbiguity) {
1178
+ // Acceptance failure is a model/controller decision point, not a human blocker.
1179
+ // Do not manufacture Handoff/UserRequest state for repair vs re-scope.
1180
+ if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair) {
1867
1181
  const failureReason = `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`;
1868
- const latestFailureAt = work.checkRefs
1869
- .filter((record) => record.outcome === 'valid_fail' && history.acceptanceFailures.includes(record.checkId))
1870
- .map((record) => record.recordedAt)
1871
- .sort((left, right) => right.localeCompare(left))[0];
1872
- const matchingHandoffs = listHandoffItems({ ...ctx.handoffStore, status: 'all', limit: 100 })
1873
- .filter((item) => (
1874
- item.workId === input.workId
1875
- && item.creationReason === 'ambiguous_outcome'
1876
- && item.title === 'Acceptance failure needs review'
1877
- && item.reason === failureReason
1878
- && (!latestFailureAt || item.createdAt >= latestFailureAt)
1879
- ));
1880
- const activeHandoff = matchingHandoffs.find((item) => item.status === 'pending' || item.status === 'acknowledged');
1881
- if (activeHandoff) {
1882
- return buildFacadeResult({
1883
- status: 'blocked',
1884
- summary: `Continue remains paused for ChatGPT review through existing handoff ${activeHandoff.id}.`,
1885
- data: {
1886
- work: summarizeWorkContract(work),
1887
- handoffId: activeHandoff.id,
1888
- acceptanceFailures: history.acceptanceFailures,
1889
- infrastructureIssues: history.infrastructureIssues,
1890
- backgroundCompleted: false,
1891
- },
1892
- evidenceRefs: work.evidenceRefs.slice(0, 5),
1893
- suggestedNextActions: [{
1894
- label: 'Get handoff',
1895
- tool: 'rh_inbox',
1896
- operation: 'get',
1897
- payload: { handoff_id: activeHandoff.id },
1898
- risk: 'readonly',
1899
- confidence: 'high',
1900
- }],
1901
- });
1902
- }
1903
-
1904
- const resolvedHandoff = matchingHandoffs.find((item) => item.status === 'resolved');
1905
- if (!resolvedHandoff) {
1906
- const continuation = buildWorkContinuationSnapshot(work);
1907
- const handoff = createHandoffItem(ctx.handoffStore, {
1908
- id: handoffIdFor('continue'),
1909
- repoId: ctx.repoId,
1910
- workId: work.workId,
1911
- title: 'Acceptance failure needs review',
1912
- severity: 'needs_review',
1913
- creationReason: 'ambiguous_outcome',
1914
- reason: failureReason,
1915
- summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
1916
- currentState: {
1917
- repoId: ctx.repoId,
1918
- workId: work.workId,
1919
- mode: work.mode,
1920
- statusSummary: 'waiting_for_review after acceptance failure',
1921
- checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
1922
- workSemantics: continuation.semantics,
1923
- reconciliationRequired: continuation.reconciliationRequired,
1924
- nextSafeAction: continuation.nextSafeAction,
1925
- },
1926
- attemptedActions: ['continue'],
1927
- evidenceRefs: work.evidenceRefs.slice(0, 5),
1928
- blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
1929
- recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
1930
- recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
1931
- recommendedContinuationPrompt: continuation.continuationPrompt,
1932
- suggestedNextActions: [
1933
- {
1934
- label: 'Read work context',
1935
- tool: 'rh_context',
1936
- operation: 'get',
1937
- payload: { work_id: work.workId },
1938
- risk: 'readonly',
1939
- },
1940
- ],
1941
- });
1942
- transitionWorkContractPhase(ctx.workStore, work.workId, {
1943
- status: 'ready',
1944
- phase: 'verification',
1945
- state: 'blocked',
1946
- summary: `Acceptance failure requires review through handoff ${handoff.id}.`,
1947
- evidenceRefs: work.evidenceRefs,
1948
- });
1949
- const updated = appendWorkHandoffRef(ctx.workStore, work.workId, handoff.id);
1950
-
1951
- return buildFacadeResult({
1952
- status: 'blocked',
1953
- summary: `Continue paused for ChatGPT review; handoff ${handoff.id} created. No background execution pretended.`,
1954
- data: {
1955
- work: summarizeWorkContract(updated),
1956
- handoffId: handoff.id,
1957
- acceptanceFailures: history.acceptanceFailures,
1958
- infrastructureIssues: history.infrastructureIssues,
1959
- backgroundCompleted: false,
1960
- },
1961
- evidenceRefs: work.evidenceRefs.slice(0, 5),
1962
- suggestedNextActions: [
1963
- {
1964
- label: 'Get handoff',
1965
- tool: 'rh_inbox',
1966
- operation: 'get',
1967
- payload: { handoff_id: handoff.id },
1968
- risk: 'readonly',
1969
- confidence: 'high',
1970
- },
1971
- ],
1972
- });
1973
- }
1974
-
1975
- transitionWorkContractPhase(ctx.workStore, work.workId, {
1976
- status: 'running',
1977
- phase: 'verification',
1978
- state: 'active',
1979
- summary: `Resolved acceptance-failure handoff ${resolvedHandoff.id} authorizes bounded continuation to re-verification.`,
1980
- evidenceRefs: work.evidenceRefs,
1981
- });
1982
- work = getWorkContract(ctx.workStore, work.workId) ?? work;
1983
1182
  return buildFacadeResult({
1984
- status: 'ok',
1985
- summary: `Continue accepted resolved acceptance-failure handoff ${resolvedHandoff.id}; re-verification may resume.`,
1183
+ status: 'blocked',
1184
+ summary: `${failureReason}. The model must explicitly choose bounded repair or re-scope before continuing; no human Handoff was created.`,
1986
1185
  data: {
1987
1186
  work: summarizeWorkContract(work),
1988
- handoffId: resolvedHandoff.id,
1989
1187
  acceptanceFailures: history.acceptanceFailures,
1990
1188
  infrastructureIssues: history.infrastructureIssues,
1991
1189
  backgroundCompleted: false,
1992
- nextStep: 'verify',
1993
- remainingChecks: history.acceptanceFailures,
1190
+ acceptanceFailureDecisionRequired: true,
1994
1191
  },
1995
1192
  evidenceRefs: work.evidenceRefs.slice(0, 5),
1996
- suggestedNextActions: suggestedForWork(work),
1193
+ suggestedNextActions: [{
1194
+ label: 'Inspect Work before repair or re-scope',
1195
+ tool: 'rh_context',
1196
+ operation: 'get',
1197
+ payload: { work_id: work.workId },
1198
+ risk: 'readonly',
1199
+ confidence: 'high',
1200
+ reason: 'Acceptance evidence is factual input; the model chooses the next capability without a Work lifecycle transition.',
1201
+ }],
1997
1202
  });
1998
1203
  }
1999
1204
 
2000
1205
  // Infrastructure issues: suggest self-healing, not acceptance failure.
2001
1206
  if (history.infrastructureIssues.length > 0) {
2002
1207
  transitionWorkContractPhase(ctx.workStore, work.workId, {
2003
- status: 'running',
2004
1208
  phase: 'implementation',
2005
1209
  state: 'active',
2006
1210
  summary: `Infrastructure issues require repair: ${history.infrastructureIssues.join(', ')}.`,
@@ -2049,7 +1253,6 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
2049
1253
  },
2050
1254
  ]).actions;
2051
1255
  transitionWorkContractPhase(ctx.workStore, work.workId, {
2052
- status: 'running',
2053
1256
  phase: 'implementation',
2054
1257
  state: 'active',
2055
1258
  summary: implementationEvidence.reasons.join(' '),
@@ -2078,24 +1281,15 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
2078
1281
 
2079
1282
  if (work.checks.length > 0 && history.validPasses.length < work.checks.length) {
2080
1283
  const remaining = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
2081
- const suggested = validateSuggestedNextActions(
2082
- remaining.map((checkId) => ({
2083
- label: `Verify ${checkId}`,
2084
- tool: 'rh_work' as const,
2085
- operation: 'verify',
2086
- payload: { work_id: work.workId, check_id: checkId },
2087
- risk: 'workspace_write' as const,
2088
- confidence: 'high' as const,
2089
- })),
2090
- { validCheckIds: work.checks },
2091
- ).actions;
2092
- transitionWorkContractPhase(ctx.workStore, work.workId, {
2093
- status: 'running',
2094
- phase: 'verification',
2095
- state: 'active',
2096
- summary: `Verification remains for: ${remaining.join(', ')}.`,
2097
- evidenceRefs: work.evidenceRefs,
2098
- });
1284
+ const suggested = validateSuggestedNextActions([{
1285
+ label: 'Inspect Work and check evidence',
1286
+ tool: 'rh_context',
1287
+ operation: 'get',
1288
+ payload: { work_id: work.workId },
1289
+ risk: 'readonly',
1290
+ confidence: 'high',
1291
+ reason: 'Registered checks are independent execution evidence; they do not progress Work lifecycle state.',
1292
+ }]).actions;
2099
1293
  const updated = updateWorkContract(ctx.workStore, work.workId, {
2100
1294
  suggestedNextActions: suggested,
2101
1295
  continuationPrompt: input.note
@@ -2104,12 +1298,12 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
2104
1298
  });
2105
1299
  return buildFacadeResult({
2106
1300
  status: 'ok',
2107
- summary: `Continue: next step is verification of ${remaining[0]}. No background work was completed.`,
1301
+ summary: `Registered check evidence is still missing for: ${remaining.join(', ')}. Run checks through the check capability; Work semantic state is unchanged.`,
2108
1302
  data: {
2109
1303
  work: summarizeWorkContract(updated),
2110
1304
  remainingChecks: remaining,
2111
1305
  backgroundCompleted: false,
2112
- nextStep: 'verify',
1306
+ nextStep: 'run_check',
2113
1307
  },
2114
1308
  suggestedNextActions: suggested,
2115
1309
  });
@@ -2135,7 +1329,6 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
2135
1329
  },
2136
1330
  ]).actions;
2137
1331
  transitionWorkContractPhase(ctx.workStore, work.workId, {
2138
- status: 'running',
2139
1332
  phase: work.workKind === 'read_only_review' ? 'verification' : 'implementation',
2140
1333
  state: 'active',
2141
1334
  summary: work.workKind === 'read_only_review'
@@ -2165,293 +1358,13 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
2165
1358
  });
2166
1359
  }
2167
1360
 
2168
- const changedPaths = normalizeImplementationReviewChangedPaths(ctx.workspaceChangedPaths ?? work.scopeEvidence?.actualChangedPaths ?? []);
2169
- if (workRequiresImplementationReview(work.workKind, changedPaths)) {
2170
- recordWorkScopeEvidence(ctx.workStore, work.workId, { actualChangedPaths: changedPaths });
2171
- transitionWorkContractPhase(ctx.workStore, work.workId, {
2172
- status: 'running',
2173
- phase: 'verification',
2174
- state: 'satisfied',
2175
- summary: 'Exact Work completion evidence proves the current candidate is verified and eligible for implementation review.',
2176
- evidenceRefs: work.evidenceRefs,
2177
- });
2178
- requestWorkImplementationReview(ctx.workStore, work.workId, 'Implementation and verification evidence are complete; explicit Controller implementation review is required before delivery.');
2179
- const suggested = validateSuggestedNextActions([implementationReviewSuggestedAction(work.workId)]).actions;
2180
- const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
2181
- return buildFacadeResult({
2182
- status: 'ok',
2183
- summary: 'Continue: implementation and verification evidence are complete; explicit implementation review is next.',
2184
- data: { work: summarizeWorkContract(updated), backgroundCompleted: false, nextStep: 'review' },
2185
- suggestedNextActions: suggested,
2186
- });
2187
- }
2188
- const suggested = validateSuggestedNextActions([{
2189
- label: 'Finalize work', tool: 'rh_work', operation: 'finalize', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high',
2190
- }]).actions;
2191
- transitionWorkContractPhase(ctx.workStore, work.workId, {
2192
- status: 'running', phase: 'delivery', state: 'active',
2193
- summary: 'Source-free Work evidence is complete; implementation review is not required and semantic finalization is next.', evidenceRefs: work.evidenceRefs,
2194
- });
2195
- const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
2196
- return buildFacadeResult({ status: 'ok', summary: 'Continue: evidence is complete; ready to finalize.', data: { work: summarizeWorkContract(updated), backgroundCompleted: false, nextStep: 'finalize' }, suggestedNextActions: suggested });
2197
- }
2198
-
2199
- export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopVerifyInput): FacadeResult {
2200
- const work = getWorkContract(ctx.workStore, input.workId);
2201
- if (!work) {
2202
- return buildFacadeResult({
2203
- status: 'not_found',
2204
- summary: `WorkContract ${input.workId} not found.`,
2205
- data: { workId: input.workId },
2206
- });
2207
- }
2208
-
2209
- const available = ctx.availableChecks ?? work.checks.map((id) => ({ id }));
2210
- const classified = classifyVerificationOutcome({
2211
- checkId: input.checkId,
2212
- available,
2213
- infrastructureFailed: input.infrastructureFailed,
2214
- checkFailed: input.checkFailed,
2215
- skipped: input.skipped,
2216
- });
2217
- if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
2218
- const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
2219
- const existing = [...work.checkRefs].reverse().find((record) => record.checkId === resolvedCheckId);
2220
- const completed = work.status === 'completed';
2221
- return buildFacadeResult({
2222
- status: completed ? 'ok' : 'blocked',
2223
- summary: `WorkContract ${work.workId} is terminal (${work.status}); verification was not re-executed.`,
2224
- data: {
2225
- work: summarizeWorkContract(work),
2226
- verification: {
2227
- checkId: resolvedCheckId,
2228
- ...(existing ? { outcome: existing.outcome } : {}),
2229
- terminal: true,
2230
- idempotent: true,
2231
- reexecuted: false,
2232
- isAcceptanceFailure: existing?.outcome === 'valid_fail',
2233
- isInfrastructureIssue: existing?.outcome === 'invalid_check_id' || existing?.outcome === 'infrastructure_failure',
2234
- doesNotRequestTaskChanges: existing?.outcome !== 'valid_fail',
2235
- },
2236
- backgroundCompleted: false,
2237
- },
2238
- evidenceRefs: existing?.evidenceRef ? [existing.evidenceRef] : [],
2239
- warnings: classified.warnings,
2240
- suggestedNextActions: [{ label: 'Inspect work via context', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }],
2241
- });
2242
- }
2243
-
2244
- const at = nowIso(ctx);
2245
- const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
2246
- const receipt = input.receipt
2247
- && input.receipt.repoId === work.repoId
2248
- && input.receipt.workId === work.workId
2249
- && input.receipt.checkId === resolvedCheckId
2250
- ? input.receipt
2251
- : undefined;
2252
- const sourceRevision = receipt && input.sourceRevision?.trim() ? input.sourceRevision.trim() : undefined;
2253
- const verificationSummary = receipt
2254
- ? `${classified.summary} Durable Process receipt ${receipt.receiptId}.`
2255
- : classified.summary;
2256
- const record: VerificationRecord = {
2257
- checkId: resolvedCheckId,
2258
- outcome: classified.outcome,
2259
- summary: verificationSummary,
2260
- recordedAt: at,
2261
- sourceRevision,
2262
- workspaceFingerprint: receipt && sourceRevision ? input.workspaceFingerprint : undefined,
2263
- verificationInputFingerprint: receipt && sourceRevision ? input.verificationInputFingerprint : undefined,
2264
- commandFingerprint: receipt && sourceRevision ? input.commandFingerprint : undefined,
2265
- startedAt: receipt?.startedAt,
2266
- completedAt: receipt?.finishedAt,
2267
- receipt,
2268
- evidenceRef: {
2269
- title: `verification:${classified.outcome}`,
2270
- summary: verificationSummary,
2271
- detailLevel: 'summary',
2272
- },
2273
- };
2274
-
2275
- // Supersede prior invalid/infrastructure noise when a valid outcome arrives for the same check.
2276
- let checkRefs = work.checkRefs;
2277
- if (classified.outcome === 'valid_pass' || classified.outcome === 'valid_fail') {
2278
- checkRefs = work.checkRefs.map((existing) => {
2279
- if (
2280
- existing.checkId === record.checkId
2281
- && (existing.outcome === 'invalid_check_id' || existing.outcome === 'infrastructure_failure')
2282
- ) {
2283
- return { ...existing, outcome: 'superseded' as const, summary: `Superseded by ${classified.outcome} at ${at}` };
2284
- }
2285
- return existing;
2286
- });
2287
- updateWorkContract(ctx.workStore, work.workId, { checkRefs });
2288
- }
2289
-
2290
- const updated = appendVerificationRecord(ctx.workStore, work.workId, record);
2291
- if (record.evidenceRef) appendWorkEvidence(ctx.workStore, work.workId, record.evidenceRef);
2292
-
2293
- const status =
2294
- classified.outcome === 'invalid_check_id' || classified.outcome === 'infrastructure_failure'
2295
- ? 'ok'
2296
- : classified.outcome === 'valid_fail'
2297
- ? 'failed'
2298
- : 'ok';
2299
-
2300
- const completionEvidence = classified.outcome === 'valid_pass'
2301
- ? evaluateWorkCompletionEvidence(updated, sourceRevision, input.workspaceFingerprint)
2302
- : undefined;
2303
- const validPassReadyForNextBoundary = completionEvidence?.status === 'complete';
2304
- const currentChangedPaths = normalizeImplementationReviewChangedPaths(ctx.workspaceChangedPaths ?? updated.scopeEvidence?.actualChangedPaths ?? []);
2305
- const latestReview = latestImplementationReview(updated.implementationReviews);
2306
- const approvedReviewRemainsAuthoritative = Boolean(
2307
- validPassReadyForNextBoundary
2308
- && updated.phase === 'delivery'
2309
- && updated.phaseEvidence.review.state === 'satisfied'
2310
- && latestReview?.decision === 'approved'
2311
- && sourceRevision
2312
- && input.workspaceFingerprint
2313
- && latestReview.sourceRevision === sourceRevision
2314
- && latestReview.verificationWorkspaceFingerprint === input.workspaceFingerprint
2315
- && latestReview.changedPathDigest === implementationReviewChangedPathDigest(currentChangedPaths)
2316
- );
2317
- const reviewRequiredAfterPass = validPassReadyForNextBoundary
2318
- && !approvedReviewRemainsAuthoritative
2319
- && workRequiresImplementationReview(updated.workKind, currentChangedPaths);
2320
- if (approvedReviewRemainsAuthoritative && updated.evidenceState !== 'valid') {
2321
- recordWorkEvidenceState(ctx.workStore, updated.workId, 'valid');
2322
- }
2323
- if (reviewRequiredAfterPass) {
2324
- recordWorkScopeEvidence(ctx.workStore, updated.workId, { actualChangedPaths: [...(ctx.workspaceChangedPaths ?? [])] });
2325
- transitionWorkContractPhase(ctx.workStore, updated.workId, {
2326
- status: 'running',
2327
- phase: 'verification',
2328
- state: 'satisfied',
2329
- summary: 'All required exact Work verification receipts are current; implementation review admission is now allowed.',
2330
- evidenceRefs: updated.evidenceRefs,
2331
- });
2332
- requestWorkImplementationReview(ctx.workStore, updated.workId, 'Verification is complete; explicit Controller implementation review is required before delivery.');
2333
- }
2334
- const suggested = validateSuggestedNextActions(
2335
- classified.outcome === 'valid_pass'
2336
- ? [reviewRequiredAfterPass
2337
- ? implementationReviewSuggestedAction(work.workId)
2338
- : {
2339
- label: validPassReadyForNextBoundary ? 'Finalize work' : 'Continue workloop',
2340
- tool: 'rh_work',
2341
- operation: validPassReadyForNextBoundary ? 'finalize' : 'continue',
2342
- payload: { work_id: work.workId },
2343
- risk: 'readonly',
2344
- confidence: 'high',
2345
- }]
2346
- : classified.outcome === 'valid_fail'
2347
- ? [{
2348
- label: 'Continue for review handoff',
2349
- tool: 'rh_work',
2350
- operation: 'continue',
2351
- payload: { work_id: work.workId },
2352
- risk: 'readonly',
2353
- confidence: 'high',
2354
- }]
2355
- : [{
2356
- label: 'Diagnose infrastructure (dry-run)',
2357
- tool: 'rh_work',
2358
- operation: 'repair',
2359
- payload: { work_id: work.workId, repair_operation: 'diagnose', dry_run: true },
2360
- risk: 'readonly',
2361
- confidence: 'high',
2362
- }],
2363
- { validCheckIds: work.checks },
2364
- ).actions;
2365
-
2366
- return buildFacadeResult({
2367
- status: status === 'failed' ? 'failed' : 'ok',
2368
- summary: classified.summary,
2369
- data: {
2370
- work: summarizeWorkContract(updated),
2371
- verification: {
2372
- checkId: record.checkId,
2373
- outcome: classified.outcome,
2374
- isAcceptanceFailure: classified.isAcceptanceFailure,
2375
- isInfrastructureIssue: classified.isInfrastructureIssue,
2376
- // Explicitly separate pollution classes for ChatGPT.
2377
- doesNotRequestTaskChanges: !classified.isAcceptanceFailure,
2378
- },
2379
- backgroundCompleted: false,
2380
- ...(classified.outcome === 'valid_pass'
2381
- ? { nextStep: reviewRequiredAfterPass ? 'review' : validPassReadyForNextBoundary ? 'finalize' : 'continue' }
2382
- : {}),
2383
- },
2384
- warnings: classified.warnings,
2385
- evidenceRefs: record.evidenceRef ? [record.evidenceRef] : [],
2386
- suggestedNextActions: suggested,
2387
- });
2388
- }
2389
-
2390
- export function reviewGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopReviewInput): FacadeResult {
2391
- let work = getWorkContract(ctx.workStore, input.workId);
2392
- if (!work) return buildFacadeResult({ status: 'not_found', summary: `WorkContract ${input.workId} not found.`, data: { workId: input.workId } });
2393
- if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
2394
- return buildFacadeResult({ status: 'blocked', summary: `WorkContract ${work.workId} is terminal (${work.status}); implementation review was not recorded.`, data: { work: summarizeWorkContract(work) } });
2395
- }
2396
- const reviewerPrincipalId = ctx.principalId?.trim() ?? '';
2397
- if (!reviewerPrincipalId) {
2398
- return buildFacadeResult({ status: 'blocked', summary: 'WORK_IMPLEMENTATION_REVIEW_REVIEWER_REQUIRED: authenticated reviewer principal is required.', data: { work: summarizeWorkContract(work) } });
2399
- }
2400
- if (!input.rationale?.trim()) {
2401
- return buildFacadeResult({ status: 'blocked', summary: 'WORK_IMPLEMENTATION_REVIEW_RATIONALE_REQUIRED: explicit review rationale is required.', data: { work: summarizeWorkContract(work) } });
2402
- }
2403
- try {
2404
- const candidate = currentImplementationReviewCandidate(ctx, work);
2405
- recordWorkScopeEvidence(ctx.workStore, work.workId, { actualChangedPaths: [...candidate.changedPaths] });
2406
- work = getWorkContract(ctx.workStore, work.workId) ?? work;
2407
- if (work.phase !== 'review') {
2408
- throw new Error('WORK_IMPLEMENTATION_REVIEW_PHASE_REQUIRED: verify or continue the exact candidate into review before recording a decision.');
2409
- }
2410
- const at = nowIso(ctx);
2411
- const review: WorkImplementationReviewRecord = {
2412
- schemaVersion: 1,
2413
- reviewId: `impl-review-${randomUUID().slice(0, 12)}`,
2414
- workId: work.workId,
2415
- reviewerPrincipalId,
2416
- decision: input.decision,
2417
- rationale: input.rationale.trim().slice(0, 4_000),
2418
- findings: (input.findings ?? []).slice(0, 100).map((finding) => ({
2419
- severity: finding.severity,
2420
- category: finding.category.trim().slice(0, 160),
2421
- summary: finding.summary.trim().slice(0, 1_000),
2422
- ...(finding.path?.trim() ? { path: finding.path.trim() } : {}),
2423
- ...(finding.symbol?.trim() ? { symbol: finding.symbol.trim().slice(0, 240) } : {}),
2424
- })),
2425
- sourceRevision: candidate.sourceRevision,
2426
- workspaceFingerprint: candidate.workspaceFingerprint,
2427
- verificationWorkspaceFingerprint: candidate.verificationWorkspaceFingerprint,
2428
- changedPaths: [...candidate.changedPaths],
2429
- changedPathDigest: implementationReviewChangedPathDigest(candidate.changedPaths),
2430
- acceptanceCriteriaSummary: implementationReviewAcceptanceSummary(work),
2431
- verificationEvidence: [...candidate.verificationEvidence],
2432
- architectureEvidence: [...(candidate.architectureEvidence ?? [])],
2433
- recordedAt: at,
2434
- };
2435
- const updated = recordWorkImplementationReview(ctx.workStore, work.workId, review);
2436
- const suggested = validateSuggestedNextActions(input.decision === 'approved'
2437
- ? [{ label: 'Finalize reviewed work', tool: 'rh_work', operation: 'finalize', payload: { work_id: work.workId }, risk: 'workspace_write', confidence: 'high' }]
2438
- : input.decision === 'changes_required'
2439
- ? [{ label: 'Continue implementation repairs', tool: 'rh_work', operation: 'continue', payload: { work_id: work.workId }, risk: 'workspace_write', confidence: 'high' }]
2440
- : [{ label: 'Inspect blocked review evidence', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }]).actions;
2441
- const persisted = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
2442
- return buildFacadeResult({
2443
- status: input.decision === 'blocked' ? 'blocked' : 'ok',
2444
- summary: `Implementation review ${review.reviewId}: ${input.decision}.`,
2445
- data: { work: summarizeWorkContract(persisted), review, nextStep: input.decision === 'approved' ? 'finalize' : input.decision === 'changes_required' ? 'continue' : 'blocked' },
2446
- suggestedNextActions: suggested,
2447
- });
2448
- } catch (error) {
2449
- const message = error instanceof Error ? error.message : String(error);
2450
- return buildFacadeResult({ status: 'blocked', summary: message, data: { work: summarizeWorkContract(work) }, suggestedNextActions: suggestedForWork(work) });
2451
- }
1361
+ // Completion evidence is observational. It cannot choose a review/finalize
1362
+ // transition or tell the model which capability to invoke next.
1363
+ const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: [] });
1364
+ return buildFacadeResult({ status: 'ok', summary: 'Continue: current Work evidence recorded.', data: { work: summarizeWorkContract(updated), backgroundCompleted: false }, suggestedNextActions: [] });
2452
1365
  }
2453
1366
 
2454
- export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopFinalizeInput): FacadeResult {
1367
+ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopStopInput): FacadeResult {
2455
1368
  const work = getWorkContract(ctx.workStore, input.workId);
2456
1369
  if (!work) {
2457
1370
  return buildFacadeResult({
@@ -2461,380 +1374,38 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
2461
1374
  });
2462
1375
  }
2463
1376
 
2464
- if (work.status === 'cancelled') {
1377
+ const destructiveCleanup = input.authorizeDestructiveCleanup === true;
1378
+ if (semanticWorkState(work) === 'completed') {
2465
1379
  return buildFacadeResult({
2466
1380
  status: 'blocked',
2467
- summary: `WorkContract ${work.workId} was cancelled; finalize is not allowed.`,
1381
+ summary: `WORK_CANCEL_COMPLETED: ${work.workId}`,
2468
1382
  data: { work: summarizeWorkContract(work) },
2469
1383
  });
2470
1384
  }
2471
-
2472
- // A Work-owned completion receipt is the strongest delivery/cleanup authority.
2473
- // Physical finalization records status=completed + receipt atomically. A facade
2474
- // retry must be idempotent and must never re-run weaker pre-delivery evidence
2475
- // evaluation that could attempt to demote an already completed Work.
2476
- if (work.status === 'completed' && work.completionReceipt) {
2477
- if (work.planId && work.planStepId && ctx.planStore) {
2478
- completePlanStepForWork(ctx.planStore, { planId: work.planId, stepId: work.planStepId, work });
2479
- }
2480
- return buildFacadeResult({
2481
- status: 'ok',
2482
- summary: `Finalize result: succeeded for ${work.workId}.`,
2483
- data: {
2484
- work: summarizeWorkContract(work),
2485
- finalStatus: 'completed',
2486
- completionReceipt: work.completionReceipt,
2487
- idempotent: true,
2488
- hiddenFailure: false,
2489
- },
2490
- evidenceRefs: work.evidenceRefs.slice(0, 5),
2491
- suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
2492
- });
2493
- }
2494
-
2495
- const currentChangedPaths = normalizeImplementationReviewChangedPaths(ctx.workspaceChangedPaths ?? work.scopeEvidence?.actualChangedPaths ?? []);
2496
- if (workRequiresImplementationReview(work.workKind, currentChangedPaths)) {
2497
- try {
2498
- const candidate = currentImplementationReviewCandidate(ctx, work);
2499
- assertImplementationReviewPreDeliveryBoundary({
2500
- repoId: work.repoId,
2501
- workId: work.workId,
2502
- workKind: work.workKind,
2503
- reviews: work.implementationReviews,
2504
- candidate,
2505
- requiredCheckIds: work.checks,
2506
- verificationRecords: work.checkRefs,
2507
- });
2508
- } catch (error) {
2509
- const message = error instanceof Error ? error.message : String(error);
2510
- const reviewDecisionBlocked = /WORK_IMPLEMENTATION_REVIEW_(REQUIRED|STALE|CHANGES_REQUIRED|BLOCKED)/.test(message);
2511
- if (reviewDecisionBlocked) {
2512
- transitionWorkContractPhase(ctx.workStore, work.workId, {
2513
- status: 'running',
2514
- phase: 'verification',
2515
- state: 'satisfied',
2516
- summary: `Current exact verification remains authoritative, but implementation review must be renewed before delivery: ${message}`,
2517
- evidenceRefs: work.evidenceRefs,
2518
- });
2519
- requestWorkImplementationReview(ctx.workStore, work.workId, `Pre-delivery implementation review gate requires a renewed Controller decision: ${message}`);
2520
- const suggested = validateSuggestedNextActions([implementationReviewSuggestedAction(work.workId)]).actions;
2521
- const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
2522
- return buildFacadeResult({
2523
- status: 'blocked',
2524
- summary: message,
2525
- data: { work: summarizeWorkContract(updated), nextStep: 'review' },
2526
- suggestedNextActions: suggested,
2527
- });
2528
- }
2529
- transitionWorkContractPhase(ctx.workStore, work.workId, {
2530
- status: 'running',
2531
- phase: 'verification',
2532
- state: 'active',
2533
- summary: `Pre-delivery review could not prove an exact verified candidate: ${message}`,
2534
- evidenceRefs: work.evidenceRefs,
2535
- });
2536
- const suggested = validateSuggestedNextActions([{
2537
- label: 'Verify exact candidate',
2538
- tool: 'rh_work',
2539
- operation: 'verify',
2540
- payload: { work_id: work.workId, check_id: work.checks[0] },
2541
- risk: 'workspace_write',
2542
- confidence: work.checks[0] ? 'high' : 'medium',
2543
- }], { validCheckIds: work.checks }).actions;
2544
- const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
2545
- return buildFacadeResult({
2546
- status: 'blocked',
2547
- summary: message,
2548
- data: { work: summarizeWorkContract(updated), nextStep: 'verify' },
2549
- suggestedNextActions: suggested,
2550
- });
2551
- }
2552
- }
2553
-
2554
- const completionEvidence = evaluateWorkCompletionEvidence(
2555
- work,
2556
- ctx.sourceRevision,
2557
- ctx.workspaceFingerprint,
2558
- ctx.workBoundProcessEvidenceIds,
2559
- ctx.workspaceChangedPaths,
2560
- );
2561
- const history = completionEvidence.history;
2562
-
2563
- if (input.forceFailed || completionEvidence.status === 'failed') {
2564
- const updated = failWorkContract(ctx.workStore, work.workId, {
2565
- phase: work.phase,
2566
- summary: `Work failed acceptance/finalization while in ${work.phase}: ${history.acceptanceFailures.join(', ') || 'forced failure'}.`,
2567
- evidenceRefs: work.evidenceRefs,
2568
- });
2569
- if (updated.planId && updated.planStepId && ctx.planStore) {
2570
- completePlanStepForWork(ctx.planStore, { planId: updated.planId, stepId: updated.planStepId, work: updated });
2571
- }
2572
- return buildFacadeResult({
2573
- status: 'failed',
2574
- summary: `Finalize result: failed. Acceptance failures: ${history.acceptanceFailures.join(', ') || 'forced'}.`,
2575
- data: {
2576
- work: summarizeWorkContract(updated),
2577
- finalStatus: 'failed',
2578
- acceptanceFailures: history.acceptanceFailures,
2579
- infrastructureIssues: history.infrastructureIssues,
2580
- invalidCheckIds: history.invalidCheckIds,
2581
- // Failures are not hidden.
2582
- hiddenFailure: false,
2583
- },
2584
- suggestedNextActions: [
2585
- {
2586
- label: 'List handoffs',
2587
- tool: 'rh_inbox',
2588
- operation: 'list',
2589
- risk: 'readonly',
2590
- },
2591
- ],
2592
- });
2593
- }
2594
-
2595
- // Weak refs, partial checks, invalid ids, and infrastructure issues never imply successful completion.
2596
- if (completionEvidence.status === 'incomplete') {
2597
- const updated = transitionWorkContractPhase(ctx.workStore, work.workId, {
2598
- status: 'ready',
2599
- phase: 'verification',
2600
- state: 'blocked',
2601
- summary: `Completion evidence remains incomplete: ${completionEvidence.reasons.join(' ')}`,
2602
- evidenceRefs: work.evidenceRefs,
2603
- });
2604
- return buildFacadeResult({
2605
- status: 'blocked',
2606
- summary: `Finalize result: waiting_for_review. ${completionEvidence.reasons.join(' ')}`,
2607
- data: {
2608
- work: summarizeWorkContract(updated),
2609
- finalStatus: 'ready',
2610
- infrastructureIssues: history.infrastructureIssues,
2611
- invalidCheckIds: history.invalidCheckIds,
2612
- validPasses: history.validPasses,
2613
- missingChecks: completionEvidence.missingChecks,
2614
- durableResultEvidence: completionEvidence.durableResultEvidence,
2615
- ignoredWeakReferences: {
2616
- workerRef: Boolean(work.workerRef),
2617
- worktreeRef: Boolean(work.worktreeRef),
2618
- },
2619
- hiddenFailure: false,
2620
- },
2621
- suggestedNextActions: [
2622
- {
2623
- label: 'Continue workloop',
2624
- tool: 'rh_work',
2625
- operation: 'continue',
2626
- payload: { work_id: work.workId },
2627
- risk: 'readonly',
2628
- },
2629
- ],
2630
- });
2631
- }
2632
-
2633
- // Read-only review completion is semantic no-change authority, not Git delivery.
2634
- // The exact source/workspace fence is re-evaluated above at finalize time; only
2635
- // a clean review with persisted inspected paths and zero findings can close.
2636
- if (work.workKind === 'read_only_review' && !work.completionReceipt) {
2637
- const reviewEvidence = work.readOnlyReviewEvidence!;
2638
- const recordedAt = nowIso(ctx);
2639
- const completed = completeWorkWithReceipt(
2640
- ctx.workStore,
2641
- work.workId,
2642
- {
2643
- schemaVersion: 1,
2644
- receiptId: `ROR-WORK-${randomUUID()}`,
2645
- source: 'read_only_review',
2646
- workId: work.workId,
2647
- baseRevision: work.baseRevision!,
2648
- sourceRevision: ctx.sourceRevision!,
2649
- ...(ctx.workspaceFingerprint ? { workspaceFingerprint: ctx.workspaceFingerprint } : {}),
2650
- workspaceChangedPaths: [],
2651
- inspectedPaths: reviewEvidence.inspectedPaths,
2652
- findingCount: 0,
2653
- recordedAt,
2654
- },
2655
- 'completed_no_change',
2656
- 'read_only_review',
2657
- );
2658
- if (completed.planId && completed.planStepId && ctx.planStore) {
2659
- completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
2660
- }
2661
- return buildFacadeResult({
2662
- status: 'ok',
2663
- summary: `Finalize result: clean read-only review completed with no source change for ${work.workId}.`,
2664
- data: {
2665
- work: summarizeWorkContract(completed),
2666
- finalStatus: 'completed',
2667
- completionOutcome: 'completed_no_change',
2668
- completionReceipt: completed.completionReceipt,
2669
- inspectedPathCount: reviewEvidence.inspectedPaths.length,
2670
- hiddenFailure: false,
2671
- },
2672
- evidenceRefs: completed.evidenceRefs.slice(0, 5),
2673
- suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
2674
- });
2675
- }
2676
-
2677
- // For a controller-local effect Work, the semantic Controller's explicit
2678
- // finalize call is the terminalization decision, but it is admissible only
2679
- // after durable result evidence exists. Record one canonical Work receipt here
2680
- // instead of routing an effect-only Work through Git delivery. Remote effects
2681
- // remain bound to their durable plugin action receipt in the plugin layer.
2682
- if (work.workKind === 'local_effect' && !work.completionReceipt && completionEvidence.durableResultEvidence) {
2683
- const recordedAt = nowIso(ctx);
2684
- const completed = completeWorkWithReceipt(
2685
- ctx.workStore,
2686
- work.workId,
2687
- {
2688
- schemaVersion: 1,
2689
- receiptId: `LFX-WORK-${randomUUID()}`,
2690
- source: 'local_effect',
2691
- workId: work.workId,
2692
- operation: 'controller_work/local_effect',
2693
- target: { kind: 'controller_local', id: work.workId },
2694
- changed: true,
2695
- recordedAt,
2696
- },
2697
- 'completed_local',
2698
- 'local_effect',
2699
- );
2700
- if (completed.planId && completed.planStepId && ctx.planStore) {
2701
- completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
2702
- }
2703
- return buildFacadeResult({
2704
- status: 'ok',
2705
- summary: `Finalize result: succeeded for ${work.workId}.`,
2706
- data: {
2707
- work: summarizeWorkContract(completed),
2708
- finalStatus: 'completed',
2709
- completionReceipt: completed.completionReceipt,
2710
- validPasses: history.validPasses,
2711
- hiddenFailure: false,
2712
- },
2713
- evidenceRefs: completed.evidenceRefs.slice(0, 5),
2714
- suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
2715
- });
2716
- }
2717
-
2718
- // Checks and result evidence are necessary but not sufficient. Delivery and
2719
- // cleanup must produce the exact Work-owned receipt before Work can become
2720
- // terminal; a Run exit or a missing receipt is never completion authority.
2721
- if (!work.completionReceipt) {
2722
- const updated = transitionWorkContractPhase(ctx.workStore, work.workId, {
2723
- status: 'ready',
2724
- phase: 'delivery',
2725
- state: 'blocked',
2726
- summary: 'Exact delivery and cleanup completion receipt is required.',
2727
- evidenceRefs: work.evidenceRefs,
2728
- });
2729
- return buildFacadeResult({
2730
- status: 'blocked',
2731
- summary: `Finalize blocked for ${work.workId}: an exact delivery and cleanup completion receipt is required.`,
2732
- data: { work: summarizeWorkContract(updated), finalStatus: 'ready', completionReceiptRequired: true },
2733
- suggestedNextActions: [{ label: 'Complete Work delivery and cleanup', tool: 'rh_work', operation: 'finalize', payload: { work_id: work.workId }, risk: 'local_repo_write' }],
2734
- });
2735
- }
2736
-
2737
- const updated = getWorkContract(ctx.workStore, work.workId)!;
2738
- if (updated.planId && updated.planStepId && ctx.planStore) {
2739
- completePlanStepForWork(ctx.planStore, { planId: updated.planId, stepId: updated.planStepId, work: updated });
2740
- }
2741
- return buildFacadeResult({
2742
- status: 'ok',
2743
- summary: `Finalize result: succeeded for ${work.workId}.`,
2744
- data: {
2745
- work: summarizeWorkContract(updated),
2746
- finalStatus: 'completed',
2747
- validPasses: history.validPasses,
2748
- hiddenFailure: false,
2749
- },
2750
- evidenceRefs: work.evidenceRefs.slice(0, 5),
2751
- suggestedNextActions: [
2752
- {
2753
- label: 'Read controller status',
2754
- tool: 'rh_status',
2755
- operation: 'get',
2756
- risk: 'readonly',
2757
- },
2758
- ],
2759
- });
2760
- }
2761
-
2762
- export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopStopInput): FacadeResult {
2763
- const work = getWorkContract(ctx.workStore, input.workId);
2764
- if (!work) {
2765
- return buildFacadeResult({
2766
- status: 'not_found',
2767
- summary: `WorkContract ${input.workId} not found.`,
2768
- data: { workId: input.workId },
2769
- });
2770
- }
2771
-
2772
- const owner = ctx.workStore.controllerHome
2773
- ? getControllerSession({ controllerHome: ctx.workStore.controllerHome, repoId: ctx.repoId }, work.workId)
2774
- : undefined;
2775
- if (owner) {
2776
- const ownerPrincipal = owner.principalId?.trim() || owner.controllerId;
2777
- const ownerInstanceId = owner.controllerInstanceId?.trim();
2778
- const callerPrincipal = ctx.principalId?.trim();
2779
- const callerInstanceId = ctx.controllerInstanceId?.trim();
2780
- const sameAuthority = Boolean(
2781
- callerPrincipal
2782
- && ownerPrincipal === callerPrincipal
2783
- && (!ownerInstanceId || ownerInstanceId === callerInstanceId),
2784
- );
2785
- if (!sameAuthority) {
2786
- return buildFacadeResult({
2787
- status: 'blocked',
2788
- summary: `WORK_TERMINALIZATION_ACTIVE_CONTROLLER_FENCE: ${work.workId} has a newer active Controller claim generation ${owner.claimGeneration ?? 1}.`,
2789
- data: {
2790
- work: summarizeWorkContract(work),
2791
- activeController: {
2792
- controllerId: owner.controllerId,
2793
- principalId: ownerPrincipal,
2794
- controllerInstanceId: owner.controllerInstanceId,
2795
- sessionId: owner.sessionId,
2796
- claimGeneration: owner.claimGeneration ?? 1,
2797
- },
2798
- finalStatus: work.status,
2799
- },
2800
- });
2801
- }
2802
- }
2803
-
2804
- const destructiveCleanup = input.authorizeDestructiveCleanup === true;
2805
- cancelWorkContract(ctx.workStore, work.workId, {
2806
- summary: input.reason ? `Stopped: ${input.reason}` : 'Work stopped without destructive cleanup.',
2807
- evidenceRefs: work.evidenceRefs,
2808
- });
2809
- const updated = updateWorkContract(ctx.workStore, work.workId, {
2810
- continuationPrompt: input.reason
2811
- ? `Stopped: ${input.reason}`.slice(0, 2_000)
2812
- : work.continuationPrompt,
2813
- // Authorization is not proof of cleanup. Preserve the reference until a real
2814
- // cleanup handler verifies ownership, cleanliness and successful removal.
2815
- worktreeRef: work.worktreeRef,
1385
+ const semanticRevision = Number.isInteger(work.semanticRevision) && Number(work.semanticRevision) > 0
1386
+ ? Number(work.semanticRevision)
1387
+ : 1;
1388
+ const updated = reviseWorkSemanticContext(ctx.workStore, work.workId, {
1389
+ expectedRevision: semanticRevision,
1390
+ state: 'cancelled',
1391
+ resultRefs: work.evidenceRefs
1392
+ .flatMap((evidence) => [evidence.evidenceId, evidence.artifactId])
1393
+ .map((value) => value?.trim())
1394
+ .filter((value): value is string => Boolean(value)),
2816
1395
  });
2817
1396
  const planId = updated.planId;
2818
1397
  const planStepId = updated.planStepId;
2819
1398
  let plan = planId && ctx.planStore
2820
1399
  ? getPlanContract(ctx.planStore, planId)
2821
1400
  : undefined;
2822
- if (plan && planId && planStepId && ctx.planStore) {
2823
- const step = plan.steps.find((entry) => entry.id === planStepId);
2824
- if (step?.workId === updated.workId) {
2825
- plan = completePlanStepForWork(ctx.planStore, { planId, stepId: planStepId, work: updated });
2826
- }
2827
- }
2828
1401
 
2829
1402
  return buildFacadeResult({
2830
1403
  status: 'ok',
2831
- summary: `WorkContract ${work.workId} cancelled/stopped. Evidence retained. Worktree cleanup ${destructiveCleanup ? 'authorized but pending verification' : 'not performed'}.`,
1404
+ summary: `WorkContract ${work.workId} cancelled/stopped. Evidence retained. Managed resource cleanup/retention is settled by the canonical Work finalization service.`,
2832
1405
  data: {
2833
1406
  work: summarizeWorkContract(updated),
2834
1407
  finalStatus: 'cancelled',
2835
1408
  evidenceRetained: true,
2836
- worktreeDeleted: false,
2837
- cleanupPending: destructiveCleanup && Boolean(work.worktreeRef),
2838
1409
  destructiveCleanupAuthorized: destructiveCleanup,
2839
1410
  ...(plan ? { plan } : {}),
2840
1411
  },
@@ -2853,6 +1424,11 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
2853
1424
  export interface GoalWorkloopTrustedInput {
2854
1425
  /** Runtime-composed source-bound evidence. This channel is intentionally separate from raw MCP/tool arguments. */
2855
1426
  verifiedEngineeringEvidence?: EngineeringAdmissionEvidence;
1427
+ /**
1428
+ * Evaluator-minted evidence for one exact candidate. Kept off the raw MCP
1429
+ * argument surface so callers cannot manufacture architecture evidence.
1430
+ */
1431
+ evaluationPromotionReceipt?: EvaluationPromotionReceipt;
2856
1432
  }
2857
1433
 
2858
1434
  export function runGoalWorkloop(
@@ -2871,18 +1447,10 @@ export function runGoalWorkloop(
2871
1447
  initialLikelyPaths: Array.isArray(args.initial_likely_paths) ? args.initial_likely_paths.map(String) : undefined,
2872
1448
  forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
2873
1449
  checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
2874
- modeInput: {
1450
+ request: {
2875
1451
  objective: typeof args.objective === 'string' ? args.objective : undefined,
2876
- expectedFiles: typeof args.expected_files === 'number' ? args.expected_files : undefined,
2877
- expectedChangedLines: typeof args.expected_changed_lines === 'number' ? args.expected_changed_lines : undefined,
2878
1452
  scopeClear: args.scope_clear === undefined ? true : args.scope_clear === true,
2879
- requiresInvestigation: args.requires_investigation === true,
2880
- requiresLongRunningChecks: args.requires_long_running_checks === true,
2881
- requiresParallelism: args.requires_parallelism === true,
2882
- explicitMode: args.mode === 'plan' || args.mode === 'scale' ? args.mode : undefined,
2883
- needsDependencies: args.needs_dependencies === true,
2884
1453
  requiresRecovery: args.requires_recovery === true,
2885
- requiresWorker: args.requires_worker === true,
2886
1454
  requiresExternalEffect: args.requires_external_effect === true,
2887
1455
  requiresApproval: args.requires_approval === true,
2888
1456
  requiresUserApproval: args.requires_user_approval === true,
@@ -2896,9 +1464,6 @@ export function runGoalWorkloop(
2896
1464
  issueId: typeof args.issue_id === 'string' ? args.issue_id : undefined,
2897
1465
  approvalConfirmed: args.approval_confirmed === true,
2898
1466
  dryRun: args.dry_run === true,
2899
- forceMode: args.force_mode === 'direct_control' || args.force_mode === 'goal_workloop' || args.force_mode === 'handoff_only'
2900
- ? args.force_mode
2901
- : undefined,
2902
1467
  relatedWorkId: typeof args.related_work_id === 'string' ? args.related_work_id : undefined,
2903
1468
  workRelation: args.work_relation === 'continue' || args.work_relation === 'extend' || args.work_relation === 'parallel' || args.work_relation === 'new_goal'
2904
1469
  ? args.work_relation
@@ -2925,7 +1490,7 @@ export function runGoalWorkloop(
2925
1490
  }
2926
1491
  const rawBlocker = args.engineering_blocker as Record<string, unknown>;
2927
1492
  const classification = rawBlocker.classification;
2928
- if (classification !== 'same_root_cause' && classification !== 'unrelated') {
1493
+ if (classification !== 'same_root_cause' && classification !== 'same_root_cause_scope_extension' && classification !== 'unrelated') {
2929
1494
  return buildFacadeResult({ status: 'blocked', summary: 'ENGINEERING_BLOCKER_CLASSIFICATION_INVALID', data: { workId: String(args.work_id ?? '') } });
2930
1495
  }
2931
1496
  engineeringBlocker = {
@@ -2933,6 +1498,7 @@ export function runGoalWorkloop(
2933
1498
  classification,
2934
1499
  rationale: String(rawBlocker.rationale ?? ''),
2935
1500
  semanticScopeKeys: Array.isArray(rawBlocker.semantic_scope_keys) ? rawBlocker.semantic_scope_keys.map(String) : undefined,
1501
+ linkedWorkId: typeof rawBlocker.linked_work_id === 'string' ? rawBlocker.linked_work_id : undefined,
2936
1502
  };
2937
1503
  }
2938
1504
  return continueGoalWorkloop(ctx, {
@@ -2958,44 +1524,6 @@ export function runGoalWorkloop(
2958
1524
  engineeringBlocker,
2959
1525
  });
2960
1526
  }
2961
- case 'verify':
2962
- return verifyGoalWorkloop(ctx, {
2963
- workId: String(args.work_id ?? ''),
2964
- checkId: String(args.check_id ?? args.checkId ?? ''),
2965
- infrastructureFailed: args.infrastructure_failed === true,
2966
- checkFailed: args.check_failed === true,
2967
- skipped: args.skipped === true,
2968
- });
2969
- case 'review': {
2970
- if (args.review_decision !== 'approved' && args.review_decision !== 'changes_required' && args.review_decision !== 'blocked') {
2971
- return buildFacadeResult({
2972
- status: 'blocked',
2973
- summary: 'WORK_IMPLEMENTATION_REVIEW_DECISION_REQUIRED: operation=review requires an explicit approved, changes_required, or blocked decision.',
2974
- data: { workId: String(args.work_id ?? '') },
2975
- });
2976
- }
2977
- return reviewGoalWorkloop(ctx, {
2978
- workId: String(args.work_id ?? ''),
2979
- decision: args.review_decision,
2980
- rationale: typeof args.review_rationale === 'string' ? args.review_rationale : '',
2981
- findings: Array.isArray(args.implementation_review_findings)
2982
- ? args.implementation_review_findings
2983
- .filter((value): value is Record<string, unknown> => Boolean(value) && typeof value === 'object')
2984
- .map((value) => ({
2985
- severity: value.severity === 'critical' || value.severity === 'high' || value.severity === 'medium' || value.severity === 'low' || value.severity === 'info' ? value.severity : 'info',
2986
- category: typeof value.category === 'string' ? value.category : '',
2987
- summary: typeof value.summary === 'string' ? value.summary : '',
2988
- ...(typeof value.path === 'string' ? { path: value.path } : {}),
2989
- ...(typeof value.symbol === 'string' ? { symbol: value.symbol } : {}),
2990
- }))
2991
- : undefined,
2992
- });
2993
- }
2994
- case 'finalize':
2995
- return finalizeGoalWorkloop(ctx, {
2996
- workId: String(args.work_id ?? ''),
2997
- forceFailed: args.force_failed === true,
2998
- });
2999
1527
  case 'stop':
3000
1528
  return stopGoalWorkloop(ctx, {
3001
1529
  workId: String(args.work_id ?? ''),