@moretea-labs/forge 1.6.0 → 1.7.0

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 (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,8 +1,10 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import {
3
3
  createHandoffItem,
4
+ listHandoffItems,
4
5
  type HandoffInboxStoreOptions,
5
6
  } from './handoff-inbox-store';
7
+ import { getControllerSession } from './controller-session-store';
6
8
  import {
7
9
  appendVerificationRecord,
8
10
  appendWorkEvidence,
@@ -10,6 +12,8 @@ import {
10
12
  createWorkContract,
11
13
  getWorkContract,
12
14
  listWorkContracts,
15
+ recordWorkCompletionReceipt,
16
+ recordWorkScopeEvidence,
13
17
  summarizeWorkContract,
14
18
  transitionWorkContractPhase,
15
19
  updateWorkContract,
@@ -21,6 +25,8 @@ import {
21
25
  getPlanContract,
22
26
  type PlanContractStoreOptions,
23
27
  } from './plan-contract-store';
28
+ import { withPrimaryWorkAdmissionLock } from './semantic-admission';
29
+ import { readRequirement } from '../persistence/requirement-store';
24
30
  import {
25
31
  classifyVerificationOutcome,
26
32
  normalizeCheckIds,
@@ -41,6 +47,7 @@ import type {
41
47
  SuggestedNextAction,
42
48
  VerificationRecord,
43
49
  WorkContract,
50
+ WorkKind,
44
51
  WorkRisk,
45
52
  } from './types';
46
53
  import { selectExecutionMode } from './types';
@@ -58,8 +65,12 @@ export interface GoalWorkloopContext {
58
65
  principalId?: string;
59
66
  controllerInstanceId?: string;
60
67
  workspaceFingerprint?: string;
68
+ /** Current net repository paths changed from the Work base plus dirty checkout paths. */
69
+ workspaceChangedPaths?: readonly string[];
61
70
  now?: () => string;
62
- materializeIsolatedWorkspace?: (input: { workId: string; title: string; baseRef?: string }) => { checkoutId: string; root: string; baseRevision?: string | null; managed: true };
71
+ /** True only while a Gateway caller holds the cross-process primary Work admission lock. */
72
+ semanticAdmissionLocked?: boolean;
73
+ materializeIsolatedWorkspace?: (input: { workId: string; title: string; baseRef?: string; needsDependencies?: boolean }) => { checkoutId: string; root: string; baseRevision?: string | null; managed: true };
63
74
  }
64
75
 
65
76
  export type WorkAdmissionRelation = 'continue' | 'extend' | 'parallel' | 'new_goal';
@@ -68,6 +79,8 @@ export interface GoalWorkloopStartInput {
68
79
  objective: string;
69
80
  acceptanceCriteria?: string[];
70
81
  allowedPaths?: string[];
82
+ /** Non-authoritative first-pass discovery candidates. */
83
+ initialLikelyPaths?: string[];
71
84
  forbiddenPaths?: string[];
72
85
  checks?: string[];
73
86
  constraints?: WorkContract['constraints'];
@@ -83,16 +96,35 @@ export interface GoalWorkloopStartInput {
83
96
  forceMode?: WorkContract['mode'];
84
97
  planId?: string;
85
98
  planStepId?: string;
99
+ /** Explicit technical evidence shape chosen by the semantic Controller. Never inferred from objective/check text. */
100
+ workKind?: Extract<WorkKind, 'repository_change' | 'completed_no_change' | 'investigation' | 'local_effect' | 'remote_effect' | 'reconciliation'>;
86
101
  }
87
102
 
88
103
  export interface GoalWorkloopContinueInput {
89
104
  workId: string;
90
105
  note?: string;
106
+ allowedPaths?: string[];
107
+ forbiddenPaths?: string[];
108
+ checks?: string[];
109
+ additionalLikelyPaths?: string[];
110
+ inspectedPaths?: string[];
111
+ /** Explicit Controller decision after deterministic acceptance failure; never inferred from note text. */
112
+ acceptanceFailureDecision?: 'repair' | 'rescope';
91
113
  }
92
114
 
93
115
  export interface GoalWorkloopVerifyInput {
94
116
  workId: string;
95
117
  checkId: string;
118
+ /** Exact Git revision observed by the authoritative Work-bound verification Process. */
119
+ sourceRevision?: string;
120
+ /** Exact dirty-workspace content identity observed by the authoritative check. */
121
+ workspaceFingerprint?: string;
122
+ /** Stable semantic check-input identity used for exact evidence reuse. */
123
+ verificationInputFingerprint?: string;
124
+ /** Bounded command/invocation audit identity; not part of reusable semantic inputs. */
125
+ commandFingerprint?: string;
126
+ /** Persistence-safe Process receipt. Accepted only when its Work/repo/check identity matches this verification. */
127
+ receipt?: VerificationRecord['receipt'];
96
128
  /** When true, simulate infrastructure failure rather than acceptance fail. */
97
129
  infrastructureFailed?: boolean;
98
130
  /** When true and check is valid, record acceptance failure. */
@@ -141,13 +173,13 @@ function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
141
173
  return 'medium';
142
174
  }
143
175
 
144
- function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
176
+ function suggestedForWorkIdentity(workId: string, checks: string[], extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
145
177
  const base: SuggestedNextAction[] = [
146
178
  {
147
179
  label: 'Continue workloop',
148
180
  tool: 'rh_work',
149
181
  operation: 'continue',
150
- payload: { work_id: work.workId },
182
+ payload: { work_id: workId },
151
183
  risk: 'readonly',
152
184
  confidence: 'high',
153
185
  },
@@ -155,24 +187,28 @@ function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []
155
187
  label: 'Verify registered checks',
156
188
  tool: 'rh_work',
157
189
  operation: 'verify',
158
- payload: { work_id: work.workId, check_id: work.checks[0] },
190
+ payload: { work_id: workId, check_id: checks[0] },
159
191
  risk: 'workspace_write',
160
- confidence: work.checks[0] ? 'high' : 'low',
192
+ confidence: checks[0] ? 'high' : 'low',
161
193
  },
162
194
  {
163
195
  label: 'Finalize when ready',
164
196
  tool: 'rh_work',
165
197
  operation: 'finalize',
166
- payload: { work_id: work.workId },
198
+ payload: { work_id: workId },
167
199
  risk: 'readonly',
168
200
  confidence: 'medium',
169
201
  },
170
202
  ];
171
203
  return validateSuggestedNextActions([...extras, ...base], {
172
- validCheckIds: work.checks,
204
+ validCheckIds: checks,
173
205
  }).actions;
174
206
  }
175
207
 
208
+ function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
209
+ return suggestedForWorkIdentity(work.workId, work.checks, extras);
210
+ }
211
+
176
212
  function initialEvidence(objective: string): EvidenceRef {
177
213
  return {
178
214
  title: 'work contract created',
@@ -191,14 +227,79 @@ interface WorkCompletionEvidenceEvaluation {
191
227
  reasons: string[];
192
228
  }
193
229
 
230
+ function verificationRecordAppliesToCurrentWorkspace(
231
+ record: VerificationRecord,
232
+ currentRevision?: string,
233
+ currentWorkspaceFingerprint?: string,
234
+ ): boolean {
235
+ if (currentRevision && record.sourceRevision && record.sourceRevision !== currentRevision) return false;
236
+ if (
237
+ currentWorkspaceFingerprint
238
+ && record.workspaceFingerprint
239
+ && record.workspaceFingerprint !== currentWorkspaceFingerprint
240
+ ) return false;
241
+ return true;
242
+ }
243
+
244
+ function isAuthoritativeCurrentWorkVerification(
245
+ work: WorkContract,
246
+ record: VerificationRecord,
247
+ currentRevision?: string,
248
+ ): boolean {
249
+ const receipt = record.receipt;
250
+ return Boolean(
251
+ currentRevision
252
+ && record.outcome === 'valid_pass'
253
+ && record.sourceRevision === currentRevision
254
+ && receipt
255
+ && receipt.repoId === work.repoId
256
+ && receipt.workId === work.workId
257
+ && receipt.checkId === record.checkId
258
+ && receipt.ok === true
259
+ && receipt.timedOut === false
260
+ && receipt.cancelled === false,
261
+ );
262
+ }
263
+
264
+ interface WorkImplementationEvidenceEvaluation {
265
+ status: 'complete' | 'incomplete';
266
+ changedPaths: string[];
267
+ reasons: string[];
268
+ }
269
+
270
+ function evaluateWorkImplementationEvidence(
271
+ work: WorkContract,
272
+ currentWorkspaceChangedPaths: readonly string[] = [],
273
+ ): WorkImplementationEvidenceEvaluation {
274
+ if (work.workKind !== 'repository_change') {
275
+ return { status: 'complete', changedPaths: [], reasons: [] };
276
+ }
277
+
278
+ const changedPaths = [...new Set(currentWorkspaceChangedPaths.map((path) => path.trim()).filter(Boolean))].sort();
279
+ if (changedPaths.length > 0) {
280
+ return { status: 'complete', changedPaths, reasons: [] };
281
+ }
282
+
283
+ return {
284
+ status: 'incomplete',
285
+ changedPaths: [],
286
+ reasons: ['Repository-change Work has no current net source changes relative to its base revision. Verification evidence cannot substitute for implementation evidence.'],
287
+ };
288
+ }
289
+
194
290
  function evaluateWorkCompletionEvidence(
195
291
  work: WorkContract,
196
- history: ReconciledVerificationHistory = reconcileVerificationHistory(
197
- work.checkRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
198
- ),
292
+ currentRevision?: string,
293
+ currentWorkspaceFingerprint?: string,
199
294
  ): WorkCompletionEvidenceEvaluation {
295
+ const applicableCheckRefs = work.checkRefs.filter((record) =>
296
+ verificationRecordAppliesToCurrentWorkspace(record, currentRevision, currentWorkspaceFingerprint));
297
+ const history = reconcileVerificationHistory(
298
+ applicableCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
299
+ );
200
300
  const missingChecks = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
201
- const durableResultEvidence = work.evidenceRefs.some((evidence) => Boolean(evidence.evidenceId || evidence.artifactId));
301
+ const durableResultEvidence = work.evidenceRefs.some((evidence) => Boolean(evidence.evidenceId || evidence.artifactId))
302
+ || applicableCheckRefs.some((record) => isAuthoritativeCurrentWorkVerification(work, record, currentRevision));
202
303
  const reasons: string[] = [];
203
304
 
204
305
  if (history.acceptanceFailures.length > 0) {
@@ -215,7 +316,12 @@ function evaluateWorkCompletionEvidence(
215
316
  reasons.push(`Declared checks are missing valid_pass evidence: ${missingChecks.join(', ')}.`);
216
317
  }
217
318
  if (work.checks.length === 0 && !durableResultEvidence) {
218
- reasons.push('No durable result evidence (evidenceId or artifactId) was recorded for this no-check WorkContract.');
319
+ const staleWorkVerification = work.checkRefs.some((record) =>
320
+ Boolean(record.receipt)
321
+ && !verificationRecordAppliesToCurrentWorkspace(record, currentRevision, currentWorkspaceFingerprint));
322
+ reasons.push(staleWorkVerification
323
+ ? 'Work-bound verification evidence is stale for the current source/workspace identity.'
324
+ : 'No durable result evidence (evidenceId, artifactId, or current Work-bound verification receipt) was recorded for this no-check WorkContract.');
219
325
  }
220
326
 
221
327
  const complete = history.infrastructureIssues.length === 0
@@ -252,6 +358,13 @@ export function routeWorkStart(
252
358
  ? false
253
359
  : input.modeInput.requiresUserApproval === true || strategyConflictRequiresApproval,
254
360
  };
361
+ if (effectiveModeInput.explicitMode === 'scale' && (!input.planId || !input.planStepId || !ctx.planStore)) {
362
+ return buildFacadeResult({
363
+ status: 'blocked',
364
+ summary: 'SCALE_PLAN_REQUIRED: explicit Scale execution requires a bound approved PlanContract step.',
365
+ data: { executionStarted: false, workContractCreated: false, planRequired: true, explicitMode: 'scale' },
366
+ });
367
+ }
255
368
  const applyForcedMode = (selected: ReturnType<typeof selectExecutionMode>) => input.forceMode
256
369
  ? {
257
370
  ...selected,
@@ -472,6 +585,16 @@ export function startGoalWorkloop(
472
585
  executionMode: 'direct_control' | 'goal_workloop' = 'goal_workloop',
473
586
  routeDecision = selectExecutionMode({ ...input.modeInput, objective: input.objective, knownPaths: input.allowedPaths }).routeDecision,
474
587
  ): FacadeResult {
588
+ const hasStrongAdmissionBinding = Boolean(input.planId || input.planStepId || input.requirementId || input.relatedWorkId || input.workRelation);
589
+ if (hasStrongAdmissionBinding && !ctx.semanticAdmissionLocked) {
590
+ return withPrimaryWorkAdmissionLock(ctx.workStore, () => startGoalWorkloop(
591
+ { ...ctx, semanticAdmissionLocked: true },
592
+ input,
593
+ policy,
594
+ executionMode,
595
+ routeDecision,
596
+ ));
597
+ }
475
598
  const at = nowIso(ctx);
476
599
  const available = ctx.availableChecks ?? [];
477
600
  const workspaceMode = input.constraints?.workspaceMode ?? 'auto';
@@ -487,6 +610,26 @@ export function startGoalWorkloop(
487
610
  : undefined;
488
611
  const plan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
489
612
  const planStep = plan && input.planStepId ? plan.steps.find((step) => step.id === input.planStepId) : undefined;
613
+ const requestedRequirementId = input.requirementId?.trim() || undefined;
614
+ if (plan && requestedRequirementId && requestedRequirementId !== plan.requirementId) {
615
+ return buildFacadeResult({
616
+ status: 'blocked',
617
+ summary: `PLAN_REQUIREMENT_MISMATCH: ${plan.planId} is bound to ${plan.requirementId ?? 'no Requirement'}, not ${requestedRequirementId}. Use the Plan relationship authority or replan explicitly.`,
618
+ data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planRequirementId: plan.requirementId, requestedRequirementId },
619
+ });
620
+ }
621
+ const effectiveRequirementId = requestedRequirementId || plan?.requirementId;
622
+ if (
623
+ effectiveRequirementId
624
+ && ctx.workStore.controllerHome
625
+ && !readRequirement({ controllerHome: ctx.workStore.controllerHome }, effectiveRequirementId)
626
+ ) {
627
+ return buildFacadeResult({
628
+ status: 'blocked',
629
+ summary: `${plan ? 'PLAN' : 'WORK'}_REQUIREMENT_NOT_FOUND: ${effectiveRequirementId}. Create or reconcile the Requirement authority before admitting Work.`,
630
+ data: { executionStarted: false, workContractCreated: false, requirementId: effectiveRequirementId, planId: plan?.planId },
631
+ });
632
+ }
490
633
  const sameStringSet = (provided: string[] | undefined, frozen: string[]): boolean => {
491
634
  if (provided === undefined) return true;
492
635
  const actual = [...new Set(provided)].sort();
@@ -513,17 +656,23 @@ export function startGoalWorkloop(
513
656
  const effectiveForbiddenPaths = planStep?.forbiddenPaths ?? input.forbiddenPaths ?? [];
514
657
  const effectiveChecks = planStep?.checks ?? input.checks ?? [];
515
658
  const normalized = normalizeCheckIds(effectiveChecks, available);
516
- const planStepWork = planStep?.workId ? activeWorks.find((candidate) => candidate.workId === planStep.workId) : undefined;
517
- const requirementWorks = input.requirementId
518
- ? activeWorks.filter((candidate) => candidate.requirementId === input.requirementId)
659
+ const boundPlanStepWorks = input.planId && input.planStepId
660
+ ? activeWorks.filter((candidate) => candidate.planId === input.planId && candidate.planStepId === input.planStepId)
661
+ : [];
662
+ const explicitPlanStepWork = planStep?.workId ? activeWorks.find((candidate) => candidate.workId === planStep.workId) : undefined;
663
+ const planStepWork = explicitPlanStepWork ?? (boundPlanStepWorks.length === 1 ? boundPlanStepWorks[0] : undefined);
664
+ const requirementWorks = effectiveRequirementId
665
+ ? activeWorks.filter((candidate) => candidate.requirementId === effectiveRequirementId)
519
666
  : [];
520
- const deterministicTarget = explicitRelatedWork ?? planStepWork ?? (requirementWorks.length === 1 ? requirementWorks[0] : undefined);
667
+ const deterministicTarget = input.relatedWorkId
668
+ ? explicitRelatedWork
669
+ : planStepWork ?? (requirementWorks.length === 1 ? requirementWorks[0] : undefined);
521
670
  const requestedRelation = input.workRelation ?? (input.modeInput.requiresParallelism === true ? 'parallel' : undefined);
522
671
  // Only strong semantic bindings participate in ownership resolution. An
523
672
  // unrelated active Work or a checkout writer is a placement fact, not a
524
673
  // semantic candidate for continue/extend/parallel/new_goal.
525
674
  const candidateWorks = [...new Map(
526
- [explicitRelatedWork, planStepWork, ...requirementWorks]
675
+ [explicitRelatedWork, planStepWork, ...boundPlanStepWorks, ...requirementWorks]
527
676
  .filter((candidate): candidate is WorkContract => Boolean(candidate))
528
677
  .map((candidate) => [candidate.workId, candidate]),
529
678
  ).values()].slice(0, 8);
@@ -556,6 +705,27 @@ export function startGoalWorkloop(
556
705
  rawAvailable: false,
557
706
  });
558
707
 
708
+ if (boundPlanStepWorks.length > 1) {
709
+ return resolutionRequired(`PLAN_STEP_MULTIPLE_PRIMARY_WORKS: ${input.planId}/${input.planStepId} has ${boundPlanStepWorks.length} active primary Work records and requires repair before execution.`);
710
+ }
711
+ if (planStep?.workId && !explicitPlanStepWork) {
712
+ return buildFacadeResult({
713
+ status: 'blocked',
714
+ summary: `PLAN_STEP_BOUND_WORK_MISSING: ${input.planId}/${input.planStepId} is bound to ${planStep.workId}, but that Work is not active. Repair the exact binding; do not create a replacement Work.`,
715
+ data: { executionStarted: false, workContractCreated: false, planId: input.planId, planStepId: input.planStepId, boundWorkId: planStep.workId, repairRequired: true },
716
+ suggestedNextActions: [{
717
+ label: 'Diagnose exact Plan step binding',
718
+ tool: 'rh_work',
719
+ operation: 'repair',
720
+ payload: { plan_id: input.planId, plan_step_id: input.planStepId, repair_operation: 'diagnose', dry_run: true },
721
+ risk: 'readonly',
722
+ confidence: 'high',
723
+ 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.',
724
+ }],
725
+ rawAvailable: false,
726
+ });
727
+ }
728
+
559
729
  if ((input.requestedBy ?? 'chatgpt') === 'scheduler') {
560
730
  return resolutionRequired(
561
731
  deterministicTarget
@@ -587,7 +757,7 @@ export function startGoalWorkloop(
587
757
  }
588
758
  if (requestedRelation === 'extend' && deterministicTarget.planId) {
589
759
  return resolutionRequired(
590
- `PLAN_EXTENSION_REQUIRES_REPLAN: Work ${deterministicTarget.workId} is governed by Plan ${deterministicTarget.planId}; extend the authoritative Plan/Requirement before continuing rather than silently widening Work scope.`,
760
+ `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.`,
591
761
  deterministicTarget,
592
762
  );
593
763
  }
@@ -597,6 +767,15 @@ export function startGoalWorkloop(
597
767
  allowedPaths: [...new Set([...deterministicTarget.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
598
768
  forbiddenPaths: [...new Set([...deterministicTarget.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
599
769
  checks: [...new Set([...deterministicTarget.checks, ...normalized.validCheckIds])].slice(0, 30),
770
+ scopeEvidence: {
771
+ initialLikelyPaths: [...new Set([
772
+ ...(deterministicTarget.scopeEvidence?.initialLikelyPaths ?? deterministicTarget.allowedPaths),
773
+ ...(input.initialLikelyPaths ?? input.allowedPaths ?? []),
774
+ ])].slice(0, 100),
775
+ inspectedPaths: deterministicTarget.scopeEvidence?.inspectedPaths ?? [],
776
+ actualChangedPaths: deterministicTarget.scopeEvidence?.actualChangedPaths ?? [],
777
+ recordedAt: at,
778
+ },
600
779
  scopeSummary: `Extended serially: ${input.objective}`.slice(0, 500),
601
780
  continuationPrompt: `Continue work ${ctx.repoId}: ${deterministicTarget.objective.slice(0, 160)}. Additional requested scope: ${input.objective.slice(0, 500)}`,
602
781
  })
@@ -627,6 +806,7 @@ export function startGoalWorkloop(
627
806
  const needsWorktree = executionMode === 'goal_workloop' && (input.constraints?.requireWorktree
628
807
  ?? (workspaceMode === 'isolated'
629
808
  || placementConflict
809
+ || input.modeInput.requiresParallelism === true
630
810
  || (workspaceMode === 'auto' && routeDecision.requiresIsolation === true)));
631
811
  if (!needsWorktree && workspaceOwner) {
632
812
  return buildFacadeResult({
@@ -643,74 +823,92 @@ export function startGoalWorkloop(
643
823
  });
644
824
  }
645
825
  const generatedWorkId = workIdFor(input.objective);
646
- let isolatedWorkspace: ReturnType<NonNullable<GoalWorkloopContext['materializeIsolatedWorkspace']>> | undefined;
647
- if (needsWorktree) {
648
- if (!ctx.materializeIsolatedWorkspace) {
649
- return buildFacadeResult({ status: 'blocked', summary: 'ISOLATED_WORKSPACE_MATERIALIZER_REQUIRED: isolated Work cannot enter running state without a concrete managed checkout.', data: { executionStarted: false, workContractCreated: false, worktreeRequired: true } });
650
- }
651
- try {
652
- isolatedWorkspace = ctx.materializeIsolatedWorkspace({ workId: generatedWorkId, title: input.objective, baseRef: ctx.sourceRevision });
653
- if (!isolatedWorkspace.checkoutId || !isolatedWorkspace.root || isolatedWorkspace.checkoutId === ctx.checkoutId) {
654
- throw new Error('ISOLATED_WORKSPACE_NOT_DISTINCT');
655
- }
656
- } catch (error) {
657
- return buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? `ISOLATED_WORKSPACE_MATERIALIZATION_FAILED: ${error.message}` : 'ISOLATED_WORKSPACE_MATERIALIZATION_FAILED', data: { executionStarted: false, workContractCreated: false, worktreeRequired: true } });
658
- }
659
- }
660
826
  if (input.planId || input.planStepId) {
661
- if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision) {
662
- return buildFacadeResult({ status: 'blocked', summary: 'PLAN_CONTEXT_REQUIRED: plan_id, plan_step_id and a current source revision are required.', data: { executionStarted: false } });
827
+ if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision || !plan || !planStep) {
828
+ 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 } });
663
829
  }
664
- let claimed: PlanContract;
665
- try {
666
- claimed = claimPlanStepForWork(ctx.planStore, {
830
+ if (plan.status !== 'approved' && plan.status !== 'executing') {
831
+ return buildFacadeResult({ status: 'blocked', summary: `PLAN_NOT_EXECUTABLE: ${plan.planId} is ${plan.status}`, data: { executionStarted: false, planId: plan.planId } });
832
+ }
833
+ if (plan.sourceRevision !== ctx.sourceRevision) {
834
+ const invalidated = claimPlanStepForWork(ctx.planStore, {
667
835
  planId: input.planId,
668
836
  stepId: input.planStepId,
669
837
  workId: generatedWorkId,
670
838
  sourceRevision: ctx.sourceRevision,
671
839
  });
672
- } catch (error) {
673
840
  return buildFacadeResult({
674
841
  status: 'blocked',
675
- summary: error instanceof Error ? error.message : 'PLAN_STEP_CLAIM_FAILED',
676
- data: { planId: input.planId, planStepId: input.planStepId, executionStarted: false },
842
+ summary: `PLAN_SOURCE_DRIFT: ${invalidated.planId} was invalidated because its source revision no longer matches. Replan before execution.`,
843
+ data: { planId: invalidated.planId, executionStarted: false, workContractCreated: false, replanRequired: true },
677
844
  });
678
845
  }
679
- if (claimed.status === 'invalidated_by_drift') {
680
- return buildFacadeResult({
681
- status: 'blocked',
682
- summary: `PLAN_SOURCE_DRIFT: ${claimed.planId} was invalidated because its source revision no longer matches. Replan before execution.`,
683
- data: { planId: claimed.planId, executionStarted: false, replanRequired: true },
684
- });
846
+ const unresolved = planStep.dependencies.filter((dependency) => plan.steps.find((candidate) => candidate.id === dependency)?.status !== 'completed');
847
+ if (unresolved.length > 0) {
848
+ return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_DEPENDENCIES_PENDING: ${unresolved.join(', ')}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
849
+ }
850
+ if (planStep.status === 'completed') {
851
+ return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_ALREADY_COMPLETED: ${planStep.id}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
685
852
  }
686
853
  }
854
+ const initialSuggestedNextActions = suggestedForWorkIdentity(
855
+ generatedWorkId,
856
+ normalized.validCheckIds,
857
+ normalized.suggestedNextActions,
858
+ );
859
+ // allowed_paths and initial_likely_paths are policy/discovery fences, not proof
860
+ // that this Work owns a repository mutation. External-effect Work therefore
861
+ // needs either an explicit repository_change semantic choice or concrete
862
+ // expected source-change evidence before Forge treats it as repository_change.
863
+ const repositoryChangeIntent = input.workKind === 'repository_change'
864
+ || (input.modeInput.expectedFiles ?? 0) > 0
865
+ || (input.modeInput.expectedChangedLines ?? 0) > 0;
866
+ const resolvedWorkKind = input.workKind ?? (
867
+ input.modeInput.requiresExternalEffect === true
868
+ && !repositoryChangeIntent
869
+ ? (input.modeInput.remoteWrite === true ? 'remote_effect' : 'local_effect')
870
+ : 'repository_change'
871
+ );
872
+ const remoteDeliveryRequired = resolvedWorkKind === 'repository_change'
873
+ && input.modeInput.requiresExternalEffect === true
874
+ && input.modeInput.remoteWrite === true;
687
875
  const work = createWorkContract(ctx.workStore, {
688
876
  workId: generatedWorkId,
689
877
  repoId: ctx.repoId,
690
- checkoutId: isolatedWorkspace?.checkoutId ?? ctx.checkoutId,
878
+ checkoutId: needsWorktree ? undefined : ctx.checkoutId,
691
879
  principalId: ctx.principalId,
692
880
  controllerInstanceId: ctx.controllerInstanceId,
693
- baseRevision: isolatedWorkspace?.baseRevision ?? ctx.sourceRevision,
694
- workspaceFingerprint: isolatedWorkspace ? undefined : ctx.workspaceFingerprint,
881
+ baseRevision: ctx.sourceRevision,
882
+ workspaceFingerprint: needsWorktree ? undefined : ctx.workspaceFingerprint,
695
883
  routeDecisionFingerprint: routeDecision.inputFingerprint,
696
884
  routeDecision,
697
885
  mode: executionMode,
698
886
  objective: input.objective,
699
887
  acceptanceCriteria: effectiveAcceptanceCriteria,
700
- constraints: input.constraints ?? { requireHandoffOnAmbiguity: true },
888
+ constraints: {
889
+ ...(input.constraints ?? { requireHandoffOnAmbiguity: true }),
890
+ ...(remoteDeliveryRequired ? { remoteDeliveryRequired: true } : {}),
891
+ },
701
892
  risk: workRiskFor(input),
702
- workKind: input.modeInput.requiresExternalEffect === true && input.modeInput.remoteWrite === true
703
- ? 'remote_effect'
704
- : undefined,
893
+ // Remote delivery is a risk/effect dimension, not a reason to erase a
894
+ // repository-change Work's semantic identity. Pure external actions with no
895
+ // repository-change signal remain remote_effect and keep plugin receipt semantics.
896
+ workKind: resolvedWorkKind,
705
897
  status: 'running',
706
898
  phase: 'implementation',
707
899
  issueId: input.issueId,
708
900
  taskId: input.taskId,
709
- requirementId: input.requirementId,
901
+ requirementId: effectiveRequirementId,
710
902
  planId: input.planId,
711
903
  planStepId: input.planStepId,
712
904
  planSourceRevision: input.planId ? ctx.sourceRevision : undefined,
713
905
  scopeSummary: input.modeInput.scopeClear ? 'scope declared at start' : 'scope incomplete',
906
+ scopeEvidence: {
907
+ initialLikelyPaths: [...new Set(input.initialLikelyPaths ?? effectiveAllowedPaths)].slice(0, 100),
908
+ inspectedPaths: [],
909
+ actualChangedPaths: [],
910
+ recordedAt: at,
911
+ },
714
912
  allowedPaths: effectiveAllowedPaths,
715
913
  forbiddenPaths: effectiveForbiddenPaths,
716
914
  checks: normalized.validCheckIds,
@@ -727,7 +925,7 @@ export function startGoalWorkloop(
727
925
  ? 'Isolation was explicitly requested or required for parallel execution.'
728
926
  : 'Current workspace is the stability-first default; isolation remains opt-in.',
729
927
  },
730
- worktreeRef: isolatedWorkspace?.root,
928
+ worktreeRef: undefined,
731
929
  evidencePolicy: {
732
930
  defaultDetailLevel: 'summary',
733
931
  allowRawOptIn: true,
@@ -746,21 +944,39 @@ export function startGoalWorkloop(
746
944
  requestedBy: input.requestedBy ?? 'chatgpt',
747
945
  evidenceRefs: [initialEvidence(input.objective)],
748
946
  policyDecisions: policy ? [policy] : [],
749
- suggestedNextActions: [],
947
+ suggestedNextActions: initialSuggestedNextActions,
750
948
  continuationPrompt: `Continue work ${ctx.repoId}: ${input.objective.slice(0, 200)}`,
751
949
  });
752
950
 
753
- const suggested = suggestedForWork(work, normalized.suggestedNextActions);
754
- const updated = updateWorkContract(ctx.workStore, work.workId, {
755
- suggestedNextActions: suggested,
756
- updatedAt: at,
757
- });
951
+ if (input.planId && input.planStepId && ctx.planStore && ctx.sourceRevision) {
952
+ try {
953
+ const claimed = claimPlanStepForWork(ctx.planStore, {
954
+ planId: input.planId,
955
+ stepId: input.planStepId,
956
+ workId: work.workId,
957
+ sourceRevision: ctx.sourceRevision,
958
+ });
959
+ if (claimed.status === 'invalidated_by_drift') {
960
+ return buildFacadeResult({
961
+ status: 'blocked',
962
+ summary: `PLAN_SOURCE_DRIFT: ${claimed.planId} was invalidated because its source revision no longer matches. Repair or replan before execution.`,
963
+ data: { executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), planId: claimed.planId, canonicalWorkRetained: true, replanRequired: true },
964
+ });
965
+ }
966
+ } catch (error) {
967
+ return buildFacadeResult({
968
+ status: 'blocked',
969
+ summary: error instanceof Error ? error.message : 'PLAN_STEP_CLAIM_FAILED',
970
+ data: { planId: input.planId, planStepId: input.planStepId, executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), canonicalWorkRetained: true },
971
+ });
972
+ }
973
+ }
758
974
 
759
975
  return buildFacadeResult({
760
976
  status: 'ok',
761
977
  summary: executionMode === 'direct_control'
762
- ? `Direct-control Work lineage started as ${updated.workId}.`
763
- : `Goal workloop started as ${updated.workId}.`,
978
+ ? `Direct-control Work lineage started as ${work.workId}.`
979
+ : `Goal workloop started as ${work.workId}.`,
764
980
  data: {
765
981
  mode: {
766
982
  mode: executionMode,
@@ -774,20 +990,20 @@ export function startGoalWorkloop(
774
990
  routeDecision,
775
991
  },
776
992
  workContractCreated: true,
777
- work: summarizeWorkContract(updated),
778
- worktreeRequired: updated.worktreePolicy.required,
993
+ work: summarizeWorkContract(work),
994
+ worktreeRequired: work.worktreePolicy.required,
779
995
  normalizedChecks: normalized,
780
996
  policy,
781
997
  },
782
- evidenceRefs: updated.evidenceRefs,
998
+ evidenceRefs: work.evidenceRefs,
783
999
  warnings: normalized.warnings,
784
- suggestedNextActions: suggested,
1000
+ suggestedNextActions: initialSuggestedNextActions,
785
1001
  rawAvailable: false,
786
1002
  });
787
1003
  }
788
1004
 
789
1005
  export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopContinueInput): FacadeResult {
790
- const work = getWorkContract(ctx.workStore, input.workId);
1006
+ let work = getWorkContract(ctx.workStore, input.workId);
791
1007
  if (!work) {
792
1008
  return buildFacadeResult({
793
1009
  status: 'not_found',
@@ -814,78 +1030,223 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
814
1030
  });
815
1031
  }
816
1032
 
1033
+ const explicitPolicyScope = input.allowedPaths !== undefined
1034
+ || input.forbiddenPaths !== undefined
1035
+ || input.checks !== undefined;
1036
+ if (explicitPolicyScope) {
1037
+ if (work.planId) {
1038
+ return buildFacadeResult({
1039
+ status: 'blocked',
1040
+ 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.`,
1041
+ data: { work: summarizeWorkContract(work), planId: work.planId, policyScopeUpdated: false },
1042
+ });
1043
+ }
1044
+ const normalizedChecks = normalizeCheckIds(input.checks ?? [], ctx.availableChecks ?? []);
1045
+ if (normalizedChecks.invalidCheckIds.length > 0) {
1046
+ return buildFacadeResult({
1047
+ status: 'blocked',
1048
+ summary: `WORK_CHECKS_INVALID: ${normalizedChecks.invalidCheckIds.join(', ')}. Work scope was not updated.`,
1049
+ data: { work: summarizeWorkContract(work), policyScopeUpdated: false, normalizedChecks },
1050
+ });
1051
+ }
1052
+ work = updateWorkContract(ctx.workStore, work.workId, {
1053
+ allowedPaths: [...new Set([...work.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
1054
+ forbiddenPaths: [...new Set([...work.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
1055
+ checks: [...new Set([...work.checks, ...normalizedChecks.validCheckIds])].slice(0, 30),
1056
+ scopeEvidence: {
1057
+ initialLikelyPaths: [...new Set([
1058
+ ...(work.scopeEvidence?.initialLikelyPaths ?? work.allowedPaths),
1059
+ ...(input.allowedPaths ?? []),
1060
+ ])].slice(0, 100),
1061
+ inspectedPaths: work.scopeEvidence?.inspectedPaths ?? [],
1062
+ actualChangedPaths: work.scopeEvidence?.actualChangedPaths ?? [],
1063
+ recordedAt: nowIso(ctx),
1064
+ },
1065
+ scopeSummary: `Continue adopted explicit policy scope for ${work.workId}.`.slice(0, 500),
1066
+ });
1067
+ }
1068
+
1069
+ if ((input.additionalLikelyPaths?.length ?? 0) > 0 || (input.inspectedPaths?.length ?? 0) > 0) {
1070
+ work = recordWorkScopeEvidence(ctx.workStore, work.workId, {
1071
+ initialLikelyPaths: input.additionalLikelyPaths,
1072
+ inspectedPaths: input.inspectedPaths,
1073
+ });
1074
+ }
1075
+
1076
+ const currentCheckRefs = work.checkRefs.filter((record) =>
1077
+ verificationRecordAppliesToCurrentWorkspace(record, ctx.sourceRevision, ctx.workspaceFingerprint));
817
1078
  const history = reconcileVerificationHistory(
818
- work.checkRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
1079
+ currentCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
819
1080
  );
820
1081
 
821
- // Ambiguous: acceptance failure present → handoff for ChatGPT review rather than pretend progress.
822
- if (history.acceptanceFailures.length > 0 && work.recoveryPolicy.handoffOnAmbiguity) {
823
- const continuation = buildWorkContinuationSnapshot(work);
824
- const handoff = createHandoffItem(ctx.handoffStore, {
825
- id: handoffIdFor('continue'),
826
- repoId: ctx.repoId,
827
- workId: work.workId,
828
- title: 'Acceptance failure needs review',
829
- severity: 'needs_review',
830
- creationReason: 'ambiguous_outcome',
831
- reason: `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`,
832
- summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
833
- currentState: {
834
- repoId: ctx.repoId,
835
- workId: work.workId,
836
- mode: work.mode,
837
- statusSummary: 'waiting_for_review after acceptance failure',
838
- checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
839
- workSemantics: continuation.semantics,
840
- reconciliationRequired: continuation.reconciliationRequired,
841
- nextSafeAction: continuation.nextSafeAction,
842
- },
843
- attemptedActions: ['continue'],
844
- evidenceRefs: work.evidenceRefs.slice(0, 5),
845
- blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
846
- recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
847
- recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
848
- recommendedContinuationPrompt: continuation.continuationPrompt,
849
- suggestedNextActions: [
850
- {
851
- label: 'Read work context',
852
- tool: 'rh_context',
853
- operation: 'get',
854
- payload: { work_id: work.workId },
855
- risk: 'readonly',
856
- },
857
- ],
1082
+ const explicitAcceptanceRepair = history.acceptanceFailures.length > 0
1083
+ && (input.acceptanceFailureDecision === 'repair' || input.acceptanceFailureDecision === 'rescope');
1084
+ if (explicitAcceptanceRepair) {
1085
+ const decision = input.acceptanceFailureDecision!;
1086
+ work = appendWorkEvidence(ctx.workStore, work.workId, {
1087
+ title: 'Controller acceptance-failure continuation decision',
1088
+ summary: `Controller explicitly chose bounded ${decision} after deterministic acceptance failure: ${history.acceptanceFailures.join(', ')}.`,
1089
+ detailLevel: 'summary',
858
1090
  });
859
1091
  transitionWorkContractPhase(ctx.workStore, work.workId, {
860
- status: 'ready',
861
- phase: 'verification',
862
- state: 'blocked',
863
- summary: `Acceptance failure requires review through handoff ${handoff.id}.`,
1092
+ status: 'running',
1093
+ phase: 'implementation',
1094
+ state: 'active',
1095
+ summary: `Controller chose bounded ${decision} after acceptance failure; implementation may resume before re-verification.`,
864
1096
  evidenceRefs: work.evidenceRefs,
865
1097
  });
866
- const updated = appendWorkHandoffRef(ctx.workStore, work.workId, handoff.id);
867
-
1098
+ work = getWorkContract(ctx.workStore, work.workId) ?? work;
868
1099
  return buildFacadeResult({
869
- status: 'blocked',
870
- summary: `Continue paused for ChatGPT review; handoff ${handoff.id} created. No background execution pretended.`,
1100
+ status: 'ok',
1101
+ summary: `Continue accepted explicit bounded ${decision} decision after acceptance failure; implementation may resume.`,
871
1102
  data: {
872
- work: summarizeWorkContract(updated),
873
- handoffId: handoff.id,
1103
+ work: summarizeWorkContract(work),
874
1104
  acceptanceFailures: history.acceptanceFailures,
875
1105
  infrastructureIssues: history.infrastructureIssues,
876
1106
  backgroundCompleted: false,
1107
+ nextStep: 'execute',
1108
+ acceptanceFailureDecision: decision,
877
1109
  },
878
1110
  evidenceRefs: work.evidenceRefs.slice(0, 5),
879
- suggestedNextActions: [
880
- {
1111
+ suggestedNextActions: suggestedForWork(work),
1112
+ });
1113
+ }
1114
+
1115
+ // Ambiguous acceptance failure without an explicit bounded Controller decision →
1116
+ // ask once per distinct failure evidence. Repeated continue calls must not
1117
+ // manufacture an unbounded handoff loop. A resolved handoff remains the decision
1118
+ // authority until newer valid-fail evidence; an unresolved matching handoff is reused.
1119
+ if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair && work.recoveryPolicy.handoffOnAmbiguity) {
1120
+ const failureReason = `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`;
1121
+ const latestFailureAt = work.checkRefs
1122
+ .filter((record) => record.outcome === 'valid_fail' && history.acceptanceFailures.includes(record.checkId))
1123
+ .map((record) => record.recordedAt)
1124
+ .sort((left, right) => right.localeCompare(left))[0];
1125
+ const matchingHandoffs = listHandoffItems({ ...ctx.handoffStore, status: 'all', limit: 100 })
1126
+ .filter((item) => (
1127
+ item.workId === input.workId
1128
+ && item.creationReason === 'ambiguous_outcome'
1129
+ && item.title === 'Acceptance failure needs review'
1130
+ && item.reason === failureReason
1131
+ && (!latestFailureAt || item.createdAt >= latestFailureAt)
1132
+ ));
1133
+ const activeHandoff = matchingHandoffs.find((item) => item.status === 'pending' || item.status === 'acknowledged');
1134
+ if (activeHandoff) {
1135
+ return buildFacadeResult({
1136
+ status: 'blocked',
1137
+ summary: `Continue remains paused for ChatGPT review through existing handoff ${activeHandoff.id}.`,
1138
+ data: {
1139
+ work: summarizeWorkContract(work),
1140
+ handoffId: activeHandoff.id,
1141
+ acceptanceFailures: history.acceptanceFailures,
1142
+ infrastructureIssues: history.infrastructureIssues,
1143
+ backgroundCompleted: false,
1144
+ },
1145
+ evidenceRefs: work.evidenceRefs.slice(0, 5),
1146
+ suggestedNextActions: [{
881
1147
  label: 'Get handoff',
882
1148
  tool: 'rh_inbox',
883
1149
  operation: 'get',
884
- payload: { handoff_id: handoff.id },
1150
+ payload: { handoff_id: activeHandoff.id },
885
1151
  risk: 'readonly',
886
1152
  confidence: 'high',
1153
+ }],
1154
+ });
1155
+ }
1156
+
1157
+ const resolvedHandoff = matchingHandoffs.find((item) => item.status === 'resolved');
1158
+ if (!resolvedHandoff) {
1159
+ const continuation = buildWorkContinuationSnapshot(work);
1160
+ const handoff = createHandoffItem(ctx.handoffStore, {
1161
+ id: handoffIdFor('continue'),
1162
+ repoId: ctx.repoId,
1163
+ workId: work.workId,
1164
+ title: 'Acceptance failure needs review',
1165
+ severity: 'needs_review',
1166
+ creationReason: 'ambiguous_outcome',
1167
+ reason: failureReason,
1168
+ summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
1169
+ currentState: {
1170
+ repoId: ctx.repoId,
1171
+ workId: work.workId,
1172
+ mode: work.mode,
1173
+ statusSummary: 'waiting_for_review after acceptance failure',
1174
+ checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
1175
+ workSemantics: continuation.semantics,
1176
+ reconciliationRequired: continuation.reconciliationRequired,
1177
+ nextSafeAction: continuation.nextSafeAction,
887
1178
  },
888
- ],
1179
+ attemptedActions: ['continue'],
1180
+ evidenceRefs: work.evidenceRefs.slice(0, 5),
1181
+ blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
1182
+ recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
1183
+ recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
1184
+ recommendedContinuationPrompt: continuation.continuationPrompt,
1185
+ suggestedNextActions: [
1186
+ {
1187
+ label: 'Read work context',
1188
+ tool: 'rh_context',
1189
+ operation: 'get',
1190
+ payload: { work_id: work.workId },
1191
+ risk: 'readonly',
1192
+ },
1193
+ ],
1194
+ });
1195
+ transitionWorkContractPhase(ctx.workStore, work.workId, {
1196
+ status: 'ready',
1197
+ phase: 'verification',
1198
+ state: 'blocked',
1199
+ summary: `Acceptance failure requires review through handoff ${handoff.id}.`,
1200
+ evidenceRefs: work.evidenceRefs,
1201
+ });
1202
+ const updated = appendWorkHandoffRef(ctx.workStore, work.workId, handoff.id);
1203
+
1204
+ return buildFacadeResult({
1205
+ status: 'blocked',
1206
+ summary: `Continue paused for ChatGPT review; handoff ${handoff.id} created. No background execution pretended.`,
1207
+ data: {
1208
+ work: summarizeWorkContract(updated),
1209
+ handoffId: handoff.id,
1210
+ acceptanceFailures: history.acceptanceFailures,
1211
+ infrastructureIssues: history.infrastructureIssues,
1212
+ backgroundCompleted: false,
1213
+ },
1214
+ evidenceRefs: work.evidenceRefs.slice(0, 5),
1215
+ suggestedNextActions: [
1216
+ {
1217
+ label: 'Get handoff',
1218
+ tool: 'rh_inbox',
1219
+ operation: 'get',
1220
+ payload: { handoff_id: handoff.id },
1221
+ risk: 'readonly',
1222
+ confidence: 'high',
1223
+ },
1224
+ ],
1225
+ });
1226
+ }
1227
+
1228
+ transitionWorkContractPhase(ctx.workStore, work.workId, {
1229
+ status: 'running',
1230
+ phase: 'verification',
1231
+ state: 'active',
1232
+ summary: `Resolved acceptance-failure handoff ${resolvedHandoff.id} authorizes bounded continuation to re-verification.`,
1233
+ evidenceRefs: work.evidenceRefs,
1234
+ });
1235
+ work = getWorkContract(ctx.workStore, work.workId) ?? work;
1236
+ return buildFacadeResult({
1237
+ status: 'ok',
1238
+ summary: `Continue accepted resolved acceptance-failure handoff ${resolvedHandoff.id}; re-verification may resume.`,
1239
+ data: {
1240
+ work: summarizeWorkContract(work),
1241
+ handoffId: resolvedHandoff.id,
1242
+ acceptanceFailures: history.acceptanceFailures,
1243
+ infrastructureIssues: history.infrastructureIssues,
1244
+ backgroundCompleted: false,
1245
+ nextStep: 'verify',
1246
+ remainingChecks: history.acceptanceFailures,
1247
+ },
1248
+ evidenceRefs: work.evidenceRefs.slice(0, 5),
1249
+ suggestedNextActions: suggestedForWork(work),
889
1250
  });
890
1251
  }
891
1252
 
@@ -927,6 +1288,47 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
927
1288
  });
928
1289
  }
929
1290
 
1291
+ const implementationEvidence = evaluateWorkImplementationEvidence(work, ctx.workspaceChangedPaths);
1292
+ if (implementationEvidence.status !== 'complete') {
1293
+ const suggested = validateSuggestedNextActions([
1294
+ {
1295
+ label: 'Implement the repository change before verification',
1296
+ tool: 'rh_context',
1297
+ operation: 'get',
1298
+ payload: { work_id: work.workId },
1299
+ risk: 'readonly',
1300
+ confidence: 'high',
1301
+ reason: 'Verification proves behavior only after the repository-change Work has current source changes.',
1302
+ },
1303
+ ]).actions;
1304
+ transitionWorkContractPhase(ctx.workStore, work.workId, {
1305
+ status: 'running',
1306
+ phase: 'implementation',
1307
+ state: 'active',
1308
+ summary: implementationEvidence.reasons.join(' '),
1309
+ evidenceRefs: work.evidenceRefs,
1310
+ });
1311
+ const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
1312
+ return buildFacadeResult({
1313
+ status: 'blocked',
1314
+ summary: `Continue requires implementation before verification. ${implementationEvidence.reasons.join(' ')}`,
1315
+ data: {
1316
+ work: summarizeWorkContract(updated),
1317
+ backgroundCompleted: false,
1318
+ nextStep: 'execute',
1319
+ implementationEvidencePresent: false,
1320
+ workspaceChangedPaths: implementationEvidence.changedPaths,
1321
+ },
1322
+ suggestedNextActions: suggested,
1323
+ });
1324
+ }
1325
+
1326
+ if (implementationEvidence.changedPaths.length > 0) {
1327
+ recordWorkScopeEvidence(ctx.workStore, work.workId, {
1328
+ actualChangedPaths: implementationEvidence.changedPaths,
1329
+ });
1330
+ }
1331
+
930
1332
  if (work.checks.length > 0 && history.validPasses.length < work.checks.length) {
931
1333
  const remaining = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
932
1334
  const suggested = validateSuggestedNextActions(
@@ -966,7 +1368,7 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
966
1368
  });
967
1369
  }
968
1370
 
969
- const completionEvidence = evaluateWorkCompletionEvidence(work, history);
1371
+ const completionEvidence = evaluateWorkCompletionEvidence(work, ctx.sourceRevision, ctx.workspaceFingerprint);
970
1372
  if (completionEvidence.status !== 'complete') {
971
1373
  const suggested = validateSuggestedNextActions([
972
1374
  {
@@ -1058,16 +1460,60 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
1058
1460
  checkFailed: input.checkFailed,
1059
1461
  skipped: input.skipped,
1060
1462
  });
1463
+ if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
1464
+ const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
1465
+ const existing = [...work.checkRefs].reverse().find((record) => record.checkId === resolvedCheckId);
1466
+ const completed = work.status === 'completed';
1467
+ return buildFacadeResult({
1468
+ status: completed ? 'ok' : 'blocked',
1469
+ summary: `WorkContract ${work.workId} is terminal (${work.status}); verification was not re-executed.`,
1470
+ data: {
1471
+ work: summarizeWorkContract(work),
1472
+ verification: {
1473
+ checkId: resolvedCheckId,
1474
+ ...(existing ? { outcome: existing.outcome } : {}),
1475
+ terminal: true,
1476
+ idempotent: true,
1477
+ reexecuted: false,
1478
+ isAcceptanceFailure: existing?.outcome === 'valid_fail',
1479
+ isInfrastructureIssue: existing?.outcome === 'invalid_check_id' || existing?.outcome === 'infrastructure_failure',
1480
+ doesNotRequestTaskChanges: existing?.outcome !== 'valid_fail',
1481
+ },
1482
+ backgroundCompleted: false,
1483
+ },
1484
+ evidenceRefs: existing?.evidenceRef ? [existing.evidenceRef] : [],
1485
+ warnings: classified.warnings,
1486
+ suggestedNextActions: [{ label: 'Inspect work via context', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }],
1487
+ });
1488
+ }
1061
1489
 
1062
1490
  const at = nowIso(ctx);
1491
+ const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
1492
+ const receipt = input.receipt
1493
+ && input.receipt.repoId === work.repoId
1494
+ && input.receipt.workId === work.workId
1495
+ && input.receipt.checkId === resolvedCheckId
1496
+ ? input.receipt
1497
+ : undefined;
1498
+ const sourceRevision = receipt && input.sourceRevision?.trim() ? input.sourceRevision.trim() : undefined;
1499
+ const verificationSummary = receipt
1500
+ ? `${classified.summary} Durable Process receipt ${receipt.receiptId}.`
1501
+ : classified.summary;
1063
1502
  const record: VerificationRecord = {
1064
- checkId: classified.normalizedCheckId ?? classified.checkId,
1503
+ checkId: resolvedCheckId,
1065
1504
  outcome: classified.outcome,
1066
- summary: classified.summary,
1505
+ summary: verificationSummary,
1067
1506
  recordedAt: at,
1507
+ sourceRevision,
1508
+ workspaceFingerprint: receipt && sourceRevision ? input.workspaceFingerprint : undefined,
1509
+ verificationInputFingerprint: receipt && sourceRevision ? input.verificationInputFingerprint : undefined,
1510
+ commandFingerprint: receipt && sourceRevision ? input.commandFingerprint : undefined,
1511
+ startedAt: receipt?.startedAt,
1512
+ completedAt: receipt?.finishedAt,
1513
+ receipt,
1068
1514
  evidenceRef: {
1069
1515
  title: `verification:${classified.outcome}`,
1070
- summary: classified.summary,
1516
+ summary: verificationSummary,
1071
1517
  detailLevel: 'summary',
1072
1518
  },
1073
1519
  };
@@ -1097,12 +1543,16 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
1097
1543
  ? 'failed'
1098
1544
  : 'ok';
1099
1545
 
1546
+ const completionEvidence = classified.outcome === 'valid_pass'
1547
+ ? evaluateWorkCompletionEvidence(updated, sourceRevision, input.workspaceFingerprint)
1548
+ : undefined;
1549
+ const validPassReadyToFinalize = completionEvidence?.status === 'complete';
1100
1550
  const suggested = validateSuggestedNextActions(
1101
1551
  classified.outcome === 'valid_pass'
1102
1552
  ? [{
1103
- label: 'Continue workloop',
1553
+ label: validPassReadyToFinalize ? 'Finalize work' : 'Continue workloop',
1104
1554
  tool: 'rh_work',
1105
- operation: 'continue',
1555
+ operation: validPassReadyToFinalize ? 'finalize' : 'continue',
1106
1556
  payload: { work_id: work.workId },
1107
1557
  risk: 'readonly',
1108
1558
  confidence: 'high',
@@ -1141,6 +1591,9 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
1141
1591
  doesNotRequestTaskChanges: !classified.isAcceptanceFailure,
1142
1592
  },
1143
1593
  backgroundCompleted: false,
1594
+ ...(classified.outcome === 'valid_pass'
1595
+ ? { nextStep: validPassReadyToFinalize ? 'finalize' : 'continue' }
1596
+ : {}),
1144
1597
  },
1145
1598
  warnings: classified.warnings,
1146
1599
  evidenceRefs: record.evidenceRef ? [record.evidenceRef] : [],
@@ -1189,7 +1642,7 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1189
1642
  });
1190
1643
  }
1191
1644
 
1192
- const completionEvidence = evaluateWorkCompletionEvidence(work);
1645
+ const completionEvidence = evaluateWorkCompletionEvidence(work, ctx.sourceRevision, ctx.workspaceFingerprint);
1193
1646
  const history = completionEvidence.history;
1194
1647
 
1195
1648
  if (input.forceFailed || completionEvidence.status === 'failed') {
@@ -1264,6 +1717,47 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
1264
1717
  });
1265
1718
  }
1266
1719
 
1720
+ // For a controller-local effect Work, the semantic Controller's explicit
1721
+ // finalize call is the terminalization decision, but it is admissible only
1722
+ // after durable result evidence exists. Record one canonical Work receipt here
1723
+ // instead of routing an effect-only Work through Git delivery. Remote effects
1724
+ // remain bound to their durable plugin action receipt in the plugin layer.
1725
+ if (work.workKind === 'local_effect' && !work.completionReceipt && completionEvidence.durableResultEvidence) {
1726
+ const recordedAt = nowIso(ctx);
1727
+ const completed = recordWorkCompletionReceipt(
1728
+ ctx.workStore,
1729
+ work.workId,
1730
+ {
1731
+ schemaVersion: 1,
1732
+ receiptId: `LFX-WORK-${randomUUID()}`,
1733
+ source: 'local_effect',
1734
+ workId: work.workId,
1735
+ operation: 'controller_work/local_effect',
1736
+ target: { kind: 'controller_local', id: work.workId },
1737
+ changed: true,
1738
+ recordedAt,
1739
+ },
1740
+ 'completed_local',
1741
+ 'local_effect',
1742
+ );
1743
+ if (completed.planId && completed.planStepId && ctx.planStore) {
1744
+ completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
1745
+ }
1746
+ return buildFacadeResult({
1747
+ status: 'ok',
1748
+ summary: `Finalize result: succeeded for ${work.workId}.`,
1749
+ data: {
1750
+ work: summarizeWorkContract(completed),
1751
+ finalStatus: 'completed',
1752
+ completionReceipt: completed.completionReceipt,
1753
+ validPasses: history.validPasses,
1754
+ hiddenFailure: false,
1755
+ },
1756
+ evidenceRefs: completed.evidenceRefs.slice(0, 5),
1757
+ suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
1758
+ });
1759
+ }
1760
+
1267
1761
  // Checks and result evidence are necessary but not sufficient. Delivery and
1268
1762
  // cleanup must produce the exact Work-owned receipt before Work can become
1269
1763
  // terminal; a Run exit or a missing receipt is never completion authority.
@@ -1318,6 +1812,38 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
1318
1812
  });
1319
1813
  }
1320
1814
 
1815
+ const owner = ctx.workStore.controllerHome
1816
+ ? getControllerSession({ controllerHome: ctx.workStore.controllerHome, repoId: ctx.repoId }, work.workId)
1817
+ : undefined;
1818
+ if (owner) {
1819
+ const ownerPrincipal = owner.principalId?.trim() || owner.controllerId;
1820
+ const ownerInstanceId = owner.controllerInstanceId?.trim();
1821
+ const callerPrincipal = ctx.principalId?.trim();
1822
+ const callerInstanceId = ctx.controllerInstanceId?.trim();
1823
+ const sameAuthority = Boolean(
1824
+ callerPrincipal
1825
+ && ownerPrincipal === callerPrincipal
1826
+ && (!ownerInstanceId || ownerInstanceId === callerInstanceId),
1827
+ );
1828
+ if (!sameAuthority) {
1829
+ return buildFacadeResult({
1830
+ status: 'blocked',
1831
+ summary: `WORK_TERMINALIZATION_ACTIVE_CONTROLLER_FENCE: ${work.workId} has a newer active Controller claim generation ${owner.claimGeneration ?? 1}.`,
1832
+ data: {
1833
+ work: summarizeWorkContract(work),
1834
+ activeController: {
1835
+ controllerId: owner.controllerId,
1836
+ principalId: ownerPrincipal,
1837
+ controllerInstanceId: owner.controllerInstanceId,
1838
+ sessionId: owner.sessionId,
1839
+ claimGeneration: owner.claimGeneration ?? 1,
1840
+ },
1841
+ finalStatus: work.status,
1842
+ },
1843
+ });
1844
+ }
1845
+ }
1846
+
1321
1847
  const destructiveCleanup = input.authorizeDestructiveCleanup === true;
1322
1848
  transitionWorkContractPhase(ctx.workStore, work.workId, {
1323
1849
  status: 'cancelled',
@@ -1334,9 +1860,17 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
1334
1860
  // cleanup handler verifies ownership, cleanliness and successful removal.
1335
1861
  worktreeRef: work.worktreeRef,
1336
1862
  });
1337
- const plan = updated.planId && updated.planStepId && ctx.planStore
1338
- ? completePlanStepForWork(ctx.planStore, { planId: updated.planId, stepId: updated.planStepId, work: updated })
1863
+ const planId = updated.planId;
1864
+ const planStepId = updated.planStepId;
1865
+ let plan = planId && ctx.planStore
1866
+ ? getPlanContract(ctx.planStore, planId)
1339
1867
  : undefined;
1868
+ if (plan && planId && planStepId && ctx.planStore) {
1869
+ const step = plan.steps.find((entry) => entry.id === planStepId);
1870
+ if (step?.workId === updated.workId) {
1871
+ plan = completePlanStepForWork(ctx.planStore, { planId, stepId: planStepId, work: updated });
1872
+ }
1873
+ }
1340
1874
 
1341
1875
  return buildFacadeResult({
1342
1876
  status: 'ok',
@@ -1373,6 +1907,7 @@ export function runGoalWorkloop(
1373
1907
  objective: String(args.objective ?? ''),
1374
1908
  acceptanceCriteria: Array.isArray(args.acceptance_criteria) ? args.acceptance_criteria.map(String) : undefined,
1375
1909
  allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
1910
+ initialLikelyPaths: Array.isArray(args.initial_likely_paths) ? args.initial_likely_paths.map(String) : undefined,
1376
1911
  forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
1377
1912
  checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
1378
1913
  modeInput: {
@@ -1383,6 +1918,7 @@ export function runGoalWorkloop(
1383
1918
  requiresInvestigation: args.requires_investigation === true,
1384
1919
  requiresLongRunningChecks: args.requires_long_running_checks === true,
1385
1920
  requiresParallelism: args.requires_parallelism === true,
1921
+ explicitMode: args.mode === 'scale' ? 'scale' : undefined,
1386
1922
  needsDependencies: args.needs_dependencies === true,
1387
1923
  requiresRecovery: args.requires_recovery === true,
1388
1924
  requiresWorker: args.requires_worker === true,
@@ -1402,13 +1938,34 @@ export function runGoalWorkloop(
1402
1938
  forceMode: args.force_mode === 'direct_control' || args.force_mode === 'goal_workloop' || args.force_mode === 'handoff_only'
1403
1939
  ? args.force_mode
1404
1940
  : undefined,
1941
+ relatedWorkId: typeof args.related_work_id === 'string' ? args.related_work_id : undefined,
1942
+ workRelation: args.work_relation === 'continue' || args.work_relation === 'extend' || args.work_relation === 'parallel' || args.work_relation === 'new_goal'
1943
+ ? args.work_relation
1944
+ : undefined,
1945
+ requirementId: typeof args.requirement_id === 'string' ? args.requirement_id : undefined,
1405
1946
  planId: typeof args.plan_id === 'string' ? args.plan_id : undefined,
1406
1947
  planStepId: typeof args.plan_step_id === 'string' ? args.plan_step_id : undefined,
1948
+ workKind: args.work_kind === 'repository_change'
1949
+ || args.work_kind === 'completed_no_change'
1950
+ || args.work_kind === 'investigation'
1951
+ || args.work_kind === 'local_effect'
1952
+ || args.work_kind === 'remote_effect'
1953
+ || args.work_kind === 'reconciliation'
1954
+ ? args.work_kind
1955
+ : undefined,
1407
1956
  });
1408
1957
  case 'continue':
1409
1958
  return continueGoalWorkloop(ctx, {
1410
1959
  workId: String(args.work_id ?? ''),
1411
1960
  note: typeof args.note === 'string' ? args.note : undefined,
1961
+ allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
1962
+ forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
1963
+ checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
1964
+ additionalLikelyPaths: Array.isArray(args.additional_likely_paths) ? args.additional_likely_paths.map(String) : undefined,
1965
+ inspectedPaths: Array.isArray(args.inspected_paths) ? args.inspected_paths.map(String) : undefined,
1966
+ acceptanceFailureDecision: args.acceptance_failure_decision === 'repair' || args.acceptance_failure_decision === 'rescope'
1967
+ ? args.acceptance_failure_decision
1968
+ : undefined,
1412
1969
  });
1413
1970
  case 'verify':
1414
1971
  return verifyGoalWorkloop(ctx, {