@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
@@ -0,0 +1,226 @@
1
+ import { statSync } from 'fs';
2
+ import { dirname, relative, resolve } from 'path';
3
+ import * as ts from 'typescript';
4
+
5
+ export type TypeScriptNavigationKind = 'definition' | 'references' | 'implementations';
6
+
7
+ export interface TypeScriptNavigationRequest {
8
+ navigation: TypeScriptNavigationKind;
9
+ path: string;
10
+ line: number;
11
+ column: number;
12
+ tsconfigPath?: string;
13
+ }
14
+
15
+ export interface TypeScriptNavigationLocation {
16
+ path: string;
17
+ line: number;
18
+ column: number;
19
+ name?: string;
20
+ kind?: string;
21
+ }
22
+
23
+ export interface TypeScriptNavigationResult {
24
+ navigation: TypeScriptNavigationKind;
25
+ target: { path: string; line: number; column: number };
26
+ locations: TypeScriptNavigationLocation[];
27
+ }
28
+
29
+ export interface TypeScriptNavigationAccess {
30
+ /** Stable identity for one read-policy scope. Restricted and unrestricted projects must never share a Language Service. */
31
+ cacheScope: string;
32
+ /** Source identity observed by the caller. A different identity must not reuse project membership. */
33
+ sourceIdentity?: string;
34
+ /** Return true only for repository-relative paths that this navigation call may read. */
35
+ allowRepositoryPath(relativePath: string): boolean;
36
+ }
37
+
38
+ interface CachedProject {
39
+ repoRoot: string;
40
+ configPath: string;
41
+ service: ts.LanguageService;
42
+ }
43
+
44
+ const projects = new Map<string, CachedProject>();
45
+ const MAX_CACHED_TYPESCRIPT_PROJECTS = 32;
46
+
47
+ function normalizePath(path: string): string {
48
+ return path.replace(/\\/g, '/');
49
+ }
50
+
51
+ function scriptVersion(path: string): string {
52
+ try {
53
+ const stat = statSync(path);
54
+ return `${stat.mtimeMs}:${stat.size}`;
55
+ } catch {
56
+ return 'missing';
57
+ }
58
+ }
59
+
60
+ function repositoryRelativePath(repoRoot: string, fileName: string): string | undefined {
61
+ const normalized = normalizePath(relative(repoRoot, resolve(fileName)));
62
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../')) return undefined;
63
+ return normalized;
64
+ }
65
+
66
+ function loadProject(repoRoot: string, tsconfigPath = 'tsconfig.json', access?: TypeScriptNavigationAccess): CachedProject {
67
+ const root = resolve(repoRoot);
68
+ const configPath = resolve(root, tsconfigPath);
69
+ const configRelative = repositoryRelativePath(root, configPath);
70
+ if (!configRelative) throw new Error('TypeScript navigation tsconfig must be inside the repository.');
71
+ if (access && !access.allowRepositoryPath(configRelative)) {
72
+ throw new Error(`TypeScript navigation tsconfig is denied by read policy: ${configRelative}.`);
73
+ }
74
+ const cacheKey = `${root}\0${configPath}\0${scriptVersion(configPath)}\0${access?.cacheScope ?? 'unrestricted'}\0${access?.sourceIdentity ?? 'unbound-source'}`;
75
+ const cached = projects.get(cacheKey);
76
+ if (cached) {
77
+ projects.delete(cacheKey);
78
+ projects.set(cacheKey, cached);
79
+ return cached;
80
+ }
81
+
82
+ const canReadAbsolute = (fileName: string): boolean => {
83
+ if (!access) return true;
84
+ const repoRelative = repositoryRelativePath(root, fileName);
85
+ return repoRelative === undefined || access.allowRepositoryPath(repoRelative);
86
+ };
87
+ const readAllowedFile = (fileName: string): string | undefined => canReadAbsolute(fileName) ? ts.sys.readFile(fileName) : undefined;
88
+ const fileExistsAllowed = (fileName: string): boolean => canReadAbsolute(fileName) && ts.sys.fileExists(fileName);
89
+ const readDirectoryAllowed: typeof ts.sys.readDirectory = (path, extensions, exclude, include, depth) =>
90
+ ts.sys.readDirectory(path, extensions, exclude, include, depth).filter(canReadAbsolute);
91
+
92
+ const config = ts.readConfigFile(configPath, readAllowedFile);
93
+ if (config.error) {
94
+ throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'));
95
+ }
96
+ const parseHost: ts.ParseConfigHost = {
97
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
98
+ readDirectory: readDirectoryAllowed,
99
+ fileExists: fileExistsAllowed,
100
+ readFile: readAllowedFile,
101
+ };
102
+ const parsed = ts.parseJsonConfigFileContent(config.config, parseHost, dirname(configPath), undefined, configPath);
103
+ if (parsed.errors.length > 0) {
104
+ throw new Error(parsed.errors.map((entry) => ts.flattenDiagnosticMessageText(entry.messageText, '\n')).join('\n'));
105
+ }
106
+ const scriptFileNames = parsed.fileNames.filter(canReadAbsolute);
107
+
108
+ const host: ts.LanguageServiceHost = {
109
+ getCompilationSettings: () => parsed.options,
110
+ getScriptFileNames: () => scriptFileNames,
111
+ getScriptVersion: scriptVersion,
112
+ getScriptSnapshot: (fileName) => {
113
+ if (!fileExistsAllowed(fileName)) return undefined;
114
+ const content = readAllowedFile(fileName);
115
+ return content === undefined ? undefined : ts.ScriptSnapshot.fromString(content);
116
+ },
117
+ getCurrentDirectory: () => root,
118
+ getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
119
+ fileExists: fileExistsAllowed,
120
+ readFile: readAllowedFile,
121
+ readDirectory: readDirectoryAllowed,
122
+ directoryExists: ts.sys.directoryExists,
123
+ getDirectories: ts.sys.getDirectories,
124
+ realpath: ts.sys.realpath,
125
+ useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
126
+ getNewLine: () => ts.sys.newLine,
127
+ };
128
+
129
+ const project = {
130
+ repoRoot: root,
131
+ configPath,
132
+ service: ts.createLanguageService(host, ts.createDocumentRegistry()),
133
+ };
134
+ projects.set(cacheKey, project);
135
+ while (projects.size > MAX_CACHED_TYPESCRIPT_PROJECTS) {
136
+ const oldestKey = projects.keys().next().value as string | undefined;
137
+ if (!oldestKey) break;
138
+ const oldest = projects.get(oldestKey);
139
+ projects.delete(oldestKey);
140
+ oldest?.service.dispose();
141
+ }
142
+ return project;
143
+ }
144
+
145
+ function sourcePosition(project: CachedProject, program: ts.Program, fileName: string, line: number, column: number): number {
146
+ if (!Number.isInteger(line) || line < 1 || !Number.isInteger(column) || column < 1) {
147
+ throw new Error('TypeScript navigation line and column must be positive 1-based integers.');
148
+ }
149
+ const source = program.getSourceFile(fileName);
150
+ if (!source) throw new Error(`TypeScript project does not include ${normalizePath(relative(project.repoRoot, fileName))}.`);
151
+ if (line > source.getLineAndCharacterOfPosition(source.getEnd()).line + 1) {
152
+ throw new Error(`TypeScript navigation line ${line} is outside ${normalizePath(relative(project.repoRoot, fileName))}.`);
153
+ }
154
+ return source.getPositionOfLineAndCharacter(line - 1, column - 1);
155
+ }
156
+
157
+ function location(
158
+ project: CachedProject,
159
+ program: ts.Program,
160
+ fileName: string,
161
+ textSpan: ts.TextSpan,
162
+ name?: string,
163
+ kind?: ts.ScriptElementKind,
164
+ ): TypeScriptNavigationLocation {
165
+ const source = program.getSourceFile(fileName);
166
+ if (!source) {
167
+ return { path: normalizePath(relative(project.repoRoot, fileName)), line: 1, column: 1, ...(name ? { name } : {}), ...(kind ? { kind } : {}) };
168
+ }
169
+ const point = source.getLineAndCharacterOfPosition(textSpan.start);
170
+ return {
171
+ path: normalizePath(relative(project.repoRoot, fileName)),
172
+ line: point.line + 1,
173
+ column: point.character + 1,
174
+ ...(name ? { name } : {}),
175
+ ...(kind ? { kind } : {}),
176
+ };
177
+ }
178
+
179
+ function dedupe(locations: TypeScriptNavigationLocation[]): TypeScriptNavigationLocation[] {
180
+ const seen = new Set<string>();
181
+ return locations.filter((entry) => {
182
+ const key = `${entry.path}:${entry.line}:${entry.column}:${entry.name ?? ''}:${entry.kind ?? ''}`;
183
+ if (seen.has(key)) return false;
184
+ seen.add(key);
185
+ return true;
186
+ });
187
+ }
188
+
189
+ export function navigateTypeScriptSymbol(
190
+ repoRoot: string,
191
+ request: TypeScriptNavigationRequest,
192
+ access?: TypeScriptNavigationAccess,
193
+ ): TypeScriptNavigationResult {
194
+ const project = loadProject(repoRoot, request.tsconfigPath, access);
195
+ const fileName = resolve(project.repoRoot, request.path);
196
+ // A references response may materialize hundreds of locations. Acquire the
197
+ // Language Service Program once per navigation so that location formatting
198
+ // does not repeatedly re-enter the service while the result is built.
199
+ const program = project.service.getProgram();
200
+ if (!program) throw new Error('TypeScript Language Service did not produce a Program.');
201
+ const position = sourcePosition(project, program, fileName, request.line, request.column);
202
+ let locations: TypeScriptNavigationLocation[] = [];
203
+
204
+ if (request.navigation === 'definition') {
205
+ locations = (project.service.getDefinitionAtPosition(fileName, position) ?? [])
206
+ .map((entry) => location(project, program, entry.fileName, entry.textSpan, entry.name, entry.kind));
207
+ } else if (request.navigation === 'implementations') {
208
+ locations = (project.service.getImplementationAtPosition(fileName, position) ?? [])
209
+ .map((entry) => location(project, program, entry.fileName, entry.textSpan, undefined, entry.kind));
210
+ } else {
211
+ locations = (project.service.findReferences(fileName, position) ?? []).flatMap((group) =>
212
+ group.references.map((entry) => location(project, program, entry.fileName, entry.textSpan, group.definition.name, group.definition.kind)),
213
+ );
214
+ }
215
+
216
+ return {
217
+ navigation: request.navigation,
218
+ target: { path: normalizePath(request.path), line: request.line, column: request.column },
219
+ locations: dedupe(locations),
220
+ };
221
+ }
222
+
223
+ export function clearTypeScriptNavigationCache(): void {
224
+ for (const project of projects.values()) project.service.dispose();
225
+ projects.clear();
226
+ }
@@ -65,3 +65,31 @@ export function compareExecutionJobDispatchRanks(
65
65
  || left.queuedAtMs - right.queuedAtMs
66
66
  || left.jobId.localeCompare(right.jobId);
67
67
  }
68
+
69
+ export function selectExecutionJobDispatchRepositories(
70
+ activeJobs: readonly ExecutionJob[],
71
+ scheduleNow: number,
72
+ lastRepoDispatch: ReadonlyMap<string, number>,
73
+ ): string[] {
74
+ const waiting = activeJobs.filter(isExecutionJobDispatchCandidate);
75
+ const rankByJobId = new Map(
76
+ waiting.map((job) => [job.jobId, rankExecutionJobForDispatch(job, scheduleNow)] as const),
77
+ );
78
+ const compareWaiting = (left: ExecutionJob, right: ExecutionJob): number =>
79
+ compareExecutionJobDispatchRanks(rankByJobId.get(left.jobId)!, rankByJobId.get(right.jobId)!);
80
+ const topByRepo = new Map<string, ExecutionJob>();
81
+ for (const job of waiting.slice().sort(compareWaiting)) {
82
+ if (!topByRepo.has(job.repoId)) topByRepo.set(job.repoId, job);
83
+ }
84
+ return [...topByRepo.keys()].sort((left, right) => {
85
+ const leftRank = rankByJobId.get(topByRepo.get(left)!.jobId)!;
86
+ const rightRank = rankByJobId.get(topByRepo.get(right)!.jobId)!;
87
+ const priority = leftRank.effectivePriority - rightRank.effectivePriority;
88
+ if (priority !== 0) return priority;
89
+ const fairness = (lastRepoDispatch.get(left) ?? 0) - (lastRepoDispatch.get(right) ?? 0);
90
+ return fairness
91
+ || leftRank.queuedAtMs - rightRank.queuedAtMs
92
+ || leftRank.jobId.localeCompare(rightRank.jobId)
93
+ || left.localeCompare(right);
94
+ });
95
+ }
@@ -3,9 +3,10 @@ import { getEditSession, listEditSessions, type EditSession } from '../../../cli
3
3
  import { globMatches } from '../../../cli/mcp/paths';
4
4
  import { runProcess } from '../../../effects/process-runner';
5
5
  import { completeRequirementFromWork } from '../persistence/requirement-store';
6
- import { getWorkContract, recordWorkCompletionReceipt, updateWorkContract } from '../facade/work-contract-store';
6
+ import { appendWorkEvidence, getWorkContract, recordWorkCompletionReceipt, updateWorkContract } from '../facade/work-contract-store';
7
7
  import { isDirectEditWorkCompletionReceipt, isTerminalWorkContractStatus, type DirectEditWorkCompletionReceipt, type WorkReconciliationRecord } from '../facade/types';
8
- import { effectiveVerificationEvidence } from './verification-evidence';
8
+ import { historicalVerificationEvidenceAtRevision } from './verification-evidence';
9
+ import { readWorkHandle } from './work-handle-store';
9
10
 
10
11
  export interface DirectEditWorkCompletionReconciliation {
11
12
  completedWorkIds: string[];
@@ -209,7 +210,16 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
209
210
  }
210
211
  throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_TERMINAL: ${input.workId}`);
211
212
  }
212
- if (isTerminalWorkContractStatus(work.status) || work.workKind !== 'repository_change') {
213
+ const failedHandle = work.status === 'failed'
214
+ ? readWorkHandle(input.controllerHome, input.repoId, input.workId)
215
+ : undefined;
216
+ const failedReviewedRecovery = work.workKind === 'repository_change'
217
+ && work.status === 'failed'
218
+ && failedHandle?.managedWorktree === false
219
+ && failedHandle.state === 'failed'
220
+ && failedHandle.finalization.validation === 'failed'
221
+ && String(failedHandle.finalization.lastError ?? failedHandle.failureReason ?? '').includes('WORK_HANDLE_HEAD_CHANGED');
222
+ if ((isTerminalWorkContractStatus(work.status) && !failedReviewedRecovery) || work.workKind !== 'repository_change') {
213
223
  throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_NOT_ELIGIBLE: ${input.workId}`);
214
224
  }
215
225
  if (!work.baseRevision?.trim()) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_BASE_REVISION_MISSING');
@@ -226,7 +236,15 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
226
236
  }
227
237
 
228
238
  const comparedPaths = normalizedComparedPaths(input.comparedPaths);
229
- const actualPaths = comparedRevisionPaths(input.repoRoot, baseRevision, targetRevision);
239
+ let comparisonBaseRevision = baseRevision;
240
+ if (failedReviewedRecovery) {
241
+ const targetParent = exactCommit(input.repoRoot, `${targetRevision}^`, 'TARGET_PARENT_REVISION');
242
+ if (!git(input.repoRoot, ['merge-base', '--is-ancestor', baseRevision, targetParent]).ok) {
243
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_TARGET_PARENT_NOT_DESCENDANT');
244
+ }
245
+ comparisonBaseRevision = targetParent;
246
+ }
247
+ const actualPaths = comparedRevisionPaths(input.repoRoot, comparisonBaseRevision, targetRevision);
230
248
  if (comparedPaths.length !== actualPaths.length || comparedPaths.some((path, index) => path !== actualPaths[index])) {
231
249
  throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_PATH_COMPARISON_MISMATCH');
232
250
  }
@@ -239,12 +257,18 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
239
257
 
240
258
  let verifiedAt = new Date().toISOString();
241
259
  for (const checkId of work.checks) {
242
- const current = effectiveVerificationEvidence(work.checkRefs, {
260
+ const candidates = historicalVerificationEvidenceAtRevision(work.checkRefs, {
243
261
  sourceRevision: targetRevision,
262
+ repoId: input.repoId,
263
+ workId: work.workId,
264
+ checkoutId: work.checkoutId,
244
265
  checkId,
245
266
  requestedChecks: work.checks,
246
- }).find((entry) => entry.current && entry.record.outcome === 'valid_pass');
247
- if (!current) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_STALE: ${checkId}`);
267
+ }).filter((entry) => entry.current && entry.record.outcome === 'valid_pass');
268
+ const distinctReceipts = new Map(candidates.map((entry) => [entry.record.receipt!.receiptId, entry]));
269
+ if (distinctReceipts.size === 0) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_STALE: ${checkId}`);
270
+ if (distinctReceipts.size > 1) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_AMBIGUOUS: ${checkId}`);
271
+ const current = [...distinctReceipts.values()][0]!;
248
272
  verifiedAt = current.record.completedAt ?? current.record.recordedAt ?? verifiedAt;
249
273
  }
250
274
 
@@ -301,6 +325,21 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
301
325
  'completed_changed',
302
326
  'repository_change',
303
327
  );
304
- if (recorded.requirementId) completeRequirementFromWork({ controllerHome: input.controllerHome }, { requirementId: recorded.requirementId, work: recorded });
328
+ if (recorded.requirementId) {
329
+ try {
330
+ completeRequirementFromWork({ controllerHome: input.controllerHome }, { requirementId: recorded.requirementId, work: recorded });
331
+ } catch (error) {
332
+ try {
333
+ appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
334
+ title: 'requirement completion projection pending',
335
+ summary: `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000),
336
+ detailLevel: 'summary',
337
+ });
338
+ } catch {
339
+ // Reconciliation already recorded the terminal receipt. A diagnostic
340
+ // write failure cannot make that completed Work non-terminal again.
341
+ }
342
+ }
343
+ }
305
344
  return { workId: input.workId, reconciliation, receipt };
306
345
  }
@@ -9,6 +9,7 @@ import {
9
9
  listControllerChecks,
10
10
  } from '../../../cli/controller/check-runner';
11
11
  import type { RepositoryRecord } from '../../../cli/repositories/types';
12
+ import { selectRepositoryCheckout } from '../../../cli/repositories/registry';
12
13
  import { executionIdentityForRepository } from './execution-identity';
13
14
  import {
14
15
  claimsForCheck,
@@ -266,6 +267,57 @@ function failedRun(
266
267
  return coordinatorResult(controllerHome, failed);
267
268
  }
268
269
 
270
+ export function resolveEditValidationRepository(
271
+ repository: RepositoryRecord,
272
+ editSessionId: string,
273
+ ): { repository: RepositoryRecord; session: EditSession } {
274
+ const checkoutIds = [...new Set([
275
+ repository.activeCheckoutId,
276
+ ...repository.checkouts.map((checkout) => checkout.checkoutId),
277
+ ].filter(Boolean))];
278
+ for (const checkoutId of checkoutIds) {
279
+ let candidate: RepositoryRecord;
280
+ try {
281
+ candidate = selectRepositoryCheckout(repository, checkoutId);
282
+ } catch {
283
+ continue;
284
+ }
285
+ let session: EditSession;
286
+ try {
287
+ session = getEditSession(candidate.canonicalRoot, editSessionId);
288
+ } catch (error) {
289
+ if (error instanceof Error && error.message === `edit session not found: ${editSessionId}`) continue;
290
+ throw error;
291
+ }
292
+ if (session.repoId && session.repoId !== candidate.repoId) {
293
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: repoId ${session.repoId} != ${candidate.repoId}`);
294
+ }
295
+ if (session.checkoutId && session.checkoutId !== candidate.activeCheckoutId) {
296
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: checkoutId ${session.checkoutId} != ${candidate.activeCheckoutId}`);
297
+ }
298
+ return { repository: candidate, session };
299
+ }
300
+ throw new Error(`edit session not found: ${editSessionId}`);
301
+ }
302
+
303
+ function validationRunRepository(
304
+ repository: RepositoryRecord,
305
+ run: EditValidationRunState,
306
+ ): RepositoryRecord {
307
+ if (run.repositoryId !== repository.repoId) {
308
+ throw new Error(`EDIT_VALIDATION_REPOSITORY_MISMATCH: ${run.repositoryId} != ${repository.repoId}`);
309
+ }
310
+ const candidate = selectRepositoryCheckout(repository, run.checkoutId);
311
+ const session = getEditSession(candidate.canonicalRoot, run.editSessionId);
312
+ if (session.repoId && session.repoId !== run.repositoryId) {
313
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: repoId ${session.repoId} != ${run.repositoryId}`);
314
+ }
315
+ if (session.checkoutId && session.checkoutId !== run.checkoutId) {
316
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: checkoutId ${session.checkoutId} != ${run.checkoutId}`);
317
+ }
318
+ return candidate;
319
+ }
320
+
269
321
  function receiptFor(
270
322
  controllerHome: string,
271
323
  repository: RepositoryRecord,
@@ -312,10 +364,18 @@ export async function reconcileEditValidationRun(
312
364
  ): Promise<EditValidationCoordinatorResult> {
313
365
  let run = readRun(controllerHome, repository.repoId, runInput.validationId) ?? runInput;
314
366
  if (run.status !== 'running') return coordinatorResult(controllerHome, run);
315
- if (run.checkoutId !== repository.activeCheckoutId) {
316
- return failedRun(controllerHome, run, 'EDIT_VALIDATION_CHECKOUT_CHANGED', `Validation checkout ${run.checkoutId} no longer matches ${repository.activeCheckoutId}.`);
367
+ let validationRepository: RepositoryRecord;
368
+ try {
369
+ validationRepository = validationRunRepository(repository, run);
370
+ } catch (error) {
371
+ return failedRun(
372
+ controllerHome,
373
+ run,
374
+ 'EDIT_VALIDATION_CHECKOUT_UNAVAILABLE',
375
+ error instanceof Error ? error.message : String(error),
376
+ );
317
377
  }
318
- const session = getEditSession(repository.canonicalRoot, run.editSessionId);
378
+ const session = getEditSession(validationRepository.canonicalRoot, run.editSessionId);
319
379
  if (session.currentRevision !== run.editRevision) {
320
380
  return failedRun(controllerHome, run, 'EDIT_VALIDATION_STALE_REVISION', `Validation targets revision ${run.editRevision}, current edit revision is ${session.currentRevision}.`);
321
381
  }
@@ -334,10 +394,10 @@ export async function reconcileEditValidationRun(
334
394
  const checkRequestId = `${run.requestId}:check:${index + 1}:${createHash('sha256').update(checkId).digest('hex').slice(0, 12)}`;
335
395
  const facade = await runPersistedCheckViaProcessRuntime({
336
396
  controllerHome,
337
- repoId: repository.repoId,
338
- checkoutId: repository.activeCheckoutId,
339
- repoRoot: repository.canonicalRoot,
340
- executionIdentity: executionIdentityForRepository(repository),
397
+ repoId: validationRepository.repoId,
398
+ checkoutId: validationRepository.activeCheckoutId,
399
+ repoRoot: validationRepository.canonicalRoot,
400
+ executionIdentity: executionIdentityForRepository(validationRepository),
341
401
  checkId,
342
402
  timeoutMs: run.timeoutMs,
343
403
  interactiveWaitMs: 0,
@@ -384,10 +444,10 @@ export async function reconcileEditValidationRun(
384
444
  }
385
445
 
386
446
  try {
387
- const receipts = run.checkIds.map((checkId) => receiptFor(controllerHome, repository, run, checkId));
388
- const checked = recordEditSessionProcessCheckReceipts(repository.canonicalRoot, run.editSessionId, {
389
- repoId: repository.repoId,
390
- checkoutId: repository.activeCheckoutId,
447
+ const receipts = run.checkIds.map((checkId) => receiptFor(controllerHome, validationRepository, run, checkId));
448
+ const checked = recordEditSessionProcessCheckReceipts(validationRepository.canonicalRoot, run.editSessionId, {
449
+ repoId: validationRepository.repoId,
450
+ checkoutId: validationRepository.activeCheckoutId,
391
451
  receipts,
392
452
  reviewer: run.reviewer,
393
453
  note: run.note,
@@ -415,7 +475,9 @@ export async function startOrJoinEditValidation(
415
475
  repository: RepositoryRecord,
416
476
  input: EditValidationStartInput,
417
477
  ): Promise<EditValidationCoordinatorResult> {
418
- const session = getEditSession(repository.canonicalRoot, input.editSessionId);
478
+ const resolved = resolveEditValidationRepository(repository, input.editSessionId);
479
+ const validationRepository = resolved.repository;
480
+ const session = resolved.session;
419
481
  const checkIds = normalized(input.checkIds ?? session.requestedChecks);
420
482
  const requestId = input.validationRequestId?.trim()
421
483
  || input.requestId?.trim()
@@ -426,14 +488,14 @@ export async function startOrJoinEditValidation(
426
488
  if (existing.checkIds.join('\n') !== checkIds.join('\n')) {
427
489
  return failedRun(controllerHome, existing, 'EDIT_VALIDATION_REQUEST_CONFLICT', 'The validation request id is already bound to a different check set.');
428
490
  }
429
- return reconcileEditValidationRun(controllerHome, repository, existing);
491
+ return reconcileEditValidationRun(controllerHome, validationRepository, existing);
430
492
  }
431
493
 
432
494
  if (checkIds.length === 0) {
433
495
  try {
434
- const checked = recordEditSessionProcessCheckReceipts(repository.canonicalRoot, session.sessionId, {
435
- repoId: repository.repoId,
436
- checkoutId: repository.activeCheckoutId,
496
+ const checked = recordEditSessionProcessCheckReceipts(validationRepository.canonicalRoot, session.sessionId, {
497
+ repoId: validationRepository.repoId,
498
+ checkoutId: validationRepository.activeCheckoutId,
437
499
  receipts: [],
438
500
  reviewer: input.reviewer,
439
501
  note: input.note,
@@ -441,8 +503,8 @@ export async function startOrJoinEditValidation(
441
503
  const completed = saveRun(controllerHome, {
442
504
  schemaVersion: 1,
443
505
  validationId: id,
444
- repositoryId: repository.repoId,
445
- checkoutId: repository.activeCheckoutId,
506
+ repositoryId: validationRepository.repoId,
507
+ checkoutId: validationRepository.activeCheckoutId,
446
508
  editSessionId: session.sessionId,
447
509
  editRevision: session.currentRevision,
448
510
  requestId,
@@ -464,8 +526,8 @@ export async function startOrJoinEditValidation(
464
526
  const run: EditValidationRunState = {
465
527
  schemaVersion: 1,
466
528
  validationId: id,
467
- repositoryId: repository.repoId,
468
- checkoutId: repository.activeCheckoutId,
529
+ repositoryId: validationRepository.repoId,
530
+ checkoutId: validationRepository.activeCheckoutId,
469
531
  editSessionId: session.sessionId,
470
532
  editRevision: session.currentRevision,
471
533
  requestId,
@@ -487,13 +549,13 @@ export async function startOrJoinEditValidation(
487
549
  const run = saveRun(controllerHome, {
488
550
  schemaVersion: 1,
489
551
  validationId: id,
490
- repositoryId: repository.repoId,
491
- checkoutId: repository.activeCheckoutId,
552
+ repositoryId: validationRepository.repoId,
553
+ checkoutId: validationRepository.activeCheckoutId,
492
554
  editSessionId: session.sessionId,
493
555
  editRevision: session.currentRevision,
494
556
  requestId,
495
557
  checkIds,
496
- lanes: checkLanes(repository, checkIds),
558
+ lanes: checkLanes(validationRepository, checkIds),
497
559
  processes: {},
498
560
  reviewer: input.reviewer,
499
561
  note: input.note,
@@ -505,7 +567,7 @@ export async function startOrJoinEditValidation(
505
567
  createdAt: now(),
506
568
  updatedAt: now(),
507
569
  });
508
- return reconcileEditValidationRun(controllerHome, repository, run);
570
+ return reconcileEditValidationRun(controllerHome, validationRepository, run);
509
571
  }
510
572
 
511
573
  export interface PendingEditValidationSummary {
@@ -163,6 +163,24 @@ export function updateExecutionSession(
163
163
  return updated;
164
164
  }
165
165
 
166
+ export function invalidateExecutionSession(
167
+ controllerHome: string,
168
+ sessionId: string,
169
+ reason: string,
170
+ ): ExecutionSessionContext | undefined {
171
+ const current = peekExecutionSession(controllerHome, sessionId);
172
+ if (!current || current.invalidatedAt) return current;
173
+ const at = now();
174
+ const invalidated: ExecutionSessionContext = {
175
+ ...current,
176
+ updatedAt: at,
177
+ invalidatedAt: at,
178
+ invalidationReason: reason.trim().slice(0, 512) || 'transport session closed',
179
+ };
180
+ writeExecutionSession(controllerHome, invalidated, 'session_invalidated');
181
+ return invalidated;
182
+ }
183
+
166
184
  function writeExecutionSession(controllerHome: string, session: ExecutionSessionContext, action: string): void {
167
185
  writeControlPlaneRecord(controllerHome, {
168
186
  namespace: 'execution_session',
@@ -14,7 +14,6 @@ export interface VerificationEvidenceIdentity {
14
14
  workspaceFingerprint?: string;
15
15
  checkId: string;
16
16
  requestedChecks: string[];
17
- commandId?: string;
18
17
  }
19
18
 
20
19
  export interface EffectiveVerificationEvidence {
@@ -23,6 +22,12 @@ export interface EffectiveVerificationEvidence {
23
22
  staleReason?: string;
24
23
  }
25
24
 
25
+ export interface HistoricalVerificationEvidenceIdentity extends Omit<VerificationEvidenceIdentity, 'workspaceFingerprint'> {
26
+ repoId: string;
27
+ workId: string;
28
+ checkoutId?: string;
29
+ }
30
+
26
31
  function decodeGitQuotedPath(rawPath: string): string {
27
32
  if (!rawPath.startsWith('"') && !rawPath.endsWith('"')) return rawPath;
28
33
  if (!rawPath.startsWith('"') || !rawPath.endsWith('"')) {
@@ -146,14 +151,13 @@ export function workValidationInputFingerprint(
146
151
  })).digest('hex');
147
152
  }
148
153
 
149
- /** Stable identity for inputs that make a check result reusable. */
154
+ /** Stable identity for semantic inputs that make a check result reusable. Invocation ids are audited separately. */
150
155
  export function verificationInputFingerprint(input: VerificationEvidenceIdentity): string {
151
156
  return createHash('sha256').update(JSON.stringify({
152
157
  sourceRevision: input.sourceRevision,
153
158
  workspaceFingerprint: input.workspaceFingerprint ?? null,
154
159
  checkId: input.checkId,
155
160
  requestedChecks: [...input.requestedChecks].sort(),
156
- commandId: input.commandId ?? null,
157
161
  })).digest('hex');
158
162
  }
159
163
 
@@ -161,6 +165,57 @@ export function commandFingerprint(checkId: string, commandId: string | undefine
161
165
  return createHash('sha256').update(JSON.stringify({ checkId, commandId: commandId ?? null })).digest('hex');
162
166
  }
163
167
 
168
+ /**
169
+ * Selects immutable Work-bound verification evidence for an explicit historical
170
+ * revision. Unlike current-workspace reuse, the historical workspace identity
171
+ * comes from each durable receipt record and is verified against the record's
172
+ * persisted semantic and command fingerprints.
173
+ */
174
+ export function historicalVerificationEvidenceAtRevision(
175
+ records: VerificationRecord[],
176
+ expected: HistoricalVerificationEvidenceIdentity,
177
+ ): EffectiveVerificationEvidence[] {
178
+ return records
179
+ .filter((record) => record.checkId === expected.checkId)
180
+ .map((record) => {
181
+ if (record.supersedes || record.outcome === 'superseded') {
182
+ return { record, current: false, staleReason: 'superseded' };
183
+ }
184
+ if (!record.sourceRevision || !record.workspaceFingerprint || !record.verificationInputFingerprint || !record.commandFingerprint) {
185
+ return { record, current: false, staleReason: 'historical evidence has no exact input identity' };
186
+ }
187
+ if (record.sourceRevision !== expected.sourceRevision) {
188
+ return { record, current: false, staleReason: `source revision changed: ${record.sourceRevision} -> ${expected.sourceRevision}` };
189
+ }
190
+ const receipt = record.receipt;
191
+ if (
192
+ !receipt
193
+ || receipt.repoId !== expected.repoId
194
+ || receipt.workId !== expected.workId
195
+ || receipt.checkId !== expected.checkId
196
+ || (expected.checkoutId !== undefined && receipt.checkoutId !== expected.checkoutId)
197
+ ) {
198
+ return { record, current: false, staleReason: 'historical receipt identity changed' };
199
+ }
200
+ if (receipt.status !== 'passed' || receipt.runtimeStatus !== 'succeeded' || !receipt.ok || receipt.timedOut || receipt.cancelled) {
201
+ return { record, current: false, staleReason: 'historical receipt is not a valid pass' };
202
+ }
203
+ const expectedFingerprint = verificationInputFingerprint({
204
+ sourceRevision: expected.sourceRevision,
205
+ workspaceFingerprint: record.workspaceFingerprint,
206
+ checkId: expected.checkId,
207
+ requestedChecks: expected.requestedChecks,
208
+ });
209
+ if (record.verificationInputFingerprint !== expectedFingerprint) {
210
+ return { record, current: false, staleReason: 'verification inputs changed' };
211
+ }
212
+ if (record.commandFingerprint !== commandFingerprint(record.checkId, receipt.commandId)) {
213
+ return { record, current: false, staleReason: 'check command identity changed' };
214
+ }
215
+ return { record, current: true };
216
+ });
217
+ }
218
+
164
219
  /**
165
220
  * Keeps historical evidence auditable while preventing a previous revision or
166
221
  * changed check inputs from being selected as proof for the current Work.