@moretea-labs/forge 1.7.2 → 1.8.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 (685) hide show
  1. package/AGENTS.md +58 -62
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +42 -62
  4. package/README.md +2 -2
  5. package/SKILL.md +0 -1
  6. package/THIRD_PARTY_NOTICES.md +5 -2
  7. package/adapters/browser/index.ts +2 -0
  8. package/adapters/chatgpt/browser-delivery-host.ts +71 -0
  9. package/adapters/chatgpt/browser-delivery-runtime.ts +783 -0
  10. package/adapters/chatgpt/controller-binding-store.ts +55 -0
  11. package/adapters/chatgpt/controller-host.ts +119 -0
  12. package/adapters/chatgpt/controller-round-host.ts +78 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +45 -0
  14. package/adapters/chatgpt/provider-delivery.ts +113 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +192 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/desktop-operator-contract.ts +7 -0
  18. package/adapters/computer/desktop-operator-discovery.ts +201 -0
  19. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  20. package/adapters/computer/desktop-operator-provider.ts +312 -0
  21. package/adapters/computer/index.ts +1 -0
  22. package/adapters/computer/interaction-target-authority.ts +944 -0
  23. package/adapters/controller-process/binding-store.ts +34 -0
  24. package/adapters/controller-process/controller-host.ts +26 -0
  25. package/adapters/mcp/access-mode.ts +132 -0
  26. package/adapters/mcp/audit.ts +32 -0
  27. package/adapters/mcp/auth.ts +407 -0
  28. package/adapters/mcp/controller-round-compatibility.ts +184 -0
  29. package/adapters/mcp/frozen-client-semantic-compatibility.ts +292 -0
  30. package/adapters/mcp/instructions.ts +36 -0
  31. package/adapters/mcp/legacy-context.ts +18 -0
  32. package/adapters/mcp/multi-repository.ts +437 -0
  33. package/adapters/mcp/oauth.ts +391 -0
  34. package/adapters/mcp/paths.ts +140 -0
  35. package/adapters/mcp/policy.ts +232 -0
  36. package/adapters/mcp/proxy-env.ts +62 -0
  37. package/adapters/mcp/readiness-projection.ts +24 -0
  38. package/adapters/mcp/redaction.ts +21 -0
  39. package/adapters/mcp/repo.ts +24 -0
  40. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  41. package/adapters/mcp/runtime-gateway/context-adapter.ts +829 -0
  42. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +191 -0
  43. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +147 -0
  44. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  45. package/adapters/mcp/runtime-gateway/execution-tools.ts +207 -0
  46. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  47. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +411 -0
  48. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  49. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  50. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  51. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  52. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +308 -0
  53. package/adapters/mcp/runtime-gateway/process-tools.ts +289 -0
  54. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +76 -0
  55. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +268 -0
  56. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  57. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  58. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +110 -0
  59. package/adapters/mcp/runtime-gateway/result-adapter.ts +108 -0
  60. package/adapters/mcp/runtime-gateway/router.ts +728 -0
  61. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1251 -0
  62. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +634 -0
  63. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  64. package/adapters/mcp/runtime-gateway/runtime-tools.ts +68 -0
  65. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +186 -0
  66. package/adapters/mcp/runtime-gateway/shared-adapter.ts +25 -0
  67. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +870 -0
  68. package/adapters/mcp/runtime-gateway/work-adapter.ts +3186 -0
  69. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +502 -0
  70. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  71. package/adapters/mcp/server.ts +1048 -0
  72. package/adapters/mcp/setup.ts +791 -0
  73. package/adapters/mcp/tool-capability-cache.ts +87 -0
  74. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  75. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5265 -0
  76. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +150 -0
  77. package/adapters/mcp/tool-mapping/repository-tools.ts +1691 -0
  78. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  79. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  80. package/adapters/mcp/toolset-names.ts +74 -0
  81. package/adapters/mcp/toolset-selection.ts +41 -0
  82. package/adapters/mcp/toolset.ts +336 -0
  83. package/adapters/mcp/transports/http.ts +1456 -0
  84. package/adapters/mcp/transports/session-registry.ts +513 -0
  85. package/adapters/mcp/transports/stdio.ts +8 -0
  86. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +139 -0
  87. package/adapters/mcp/types.ts +41 -0
  88. package/assets/AGENTS.md +2 -16
  89. package/assets/CLAUDE.md +2 -16
  90. package/assets/hooks/AGENTS.md +3 -14
  91. package/assets/hooks/CLAUDE.md +3 -14
  92. package/assets/hooks/hook-input.sh +9 -2
  93. package/assets/hooks/lib/workflow-state.sh +28 -10
  94. package/assets/hooks/prompt-guard.sh +63 -37
  95. package/assets/hooks/security-sentinel.sh +2 -1
  96. package/assets/hooks/session-start-context.sh +7 -5
  97. package/assets/hooks/stop-orchestrator.sh +13 -3
  98. package/assets/initializer-question-pack.v4.json +11 -7
  99. package/assets/partials/05-workflow.partial.md +18 -73
  100. package/assets/partials/07-footer.partial.md +1 -1
  101. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  102. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  103. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  104. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  105. package/assets/plan-map.json +11 -11
  106. package/assets/plugin-registry.v1.json +9 -3
  107. package/assets/plugin-trust-policy.v1.json +6 -0
  108. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  109. package/assets/recovery/forge-wsl-rescue +271 -0
  110. package/assets/reference-configs/development-protocol.md +11 -15
  111. package/assets/reference-configs/document-generation.md +1 -1
  112. package/assets/reference-configs/global-working-rules.md +9 -1
  113. package/assets/reference-configs/harness-overview.md +22 -112
  114. package/assets/reference-configs/hook-operations.md +1 -1
  115. package/assets/reference-configs/sprint-contracts.md +2 -2
  116. package/assets/skill-version.json +2 -2
  117. package/assets/templates/helpers/architecture-event.ts +3 -34
  118. package/assets/templates/helpers/architecture-queue.sh +61 -10
  119. package/assets/templates/helpers/archive-workflow.sh +90 -180
  120. package/assets/templates/helpers/capability-config.ts +8 -11
  121. package/assets/templates/helpers/capability-resolver.ts +37 -19
  122. package/assets/templates/helpers/capture-plan.sh +2 -2
  123. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  124. package/assets/templates/helpers/check-task-sync.sh +82 -11
  125. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  126. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  127. package/assets/templates/helpers/contract-worktree.sh +26 -24
  128. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  129. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  130. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  131. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  132. package/assets/templates/helpers/new-plan.sh +2 -2
  133. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  134. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  135. package/assets/templates/helpers/switch-plan.sh +1 -1
  136. package/assets/templates/helpers/workflow-contract.ts +1 -0
  137. package/assets/workflow-contract.v1.json +63 -171
  138. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  139. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  140. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  141. package/docs/architecture/CURRENT.md +148 -13
  142. package/docs/architecture/EVOLUTION.md +13 -0
  143. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  144. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  145. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  146. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  147. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +35 -0
  148. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +296 -0
  149. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  150. package/docs/architecture/domains/verification.md +7 -0
  151. package/docs/architecture/domains/workflow-engine.md +1 -1
  152. package/docs/architecture/index.md +3 -3
  153. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  154. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  155. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  156. package/docs/forge-plugin-management.md +20 -4
  157. package/docs/forge-runtime-self-healing-loop.md +2 -0
  158. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  159. package/docs/operations/controller-browser-plugin.md +3 -1
  160. package/docs/operations/homebrew.md +2 -2
  161. package/docs/operations/platform-support.md +16 -8
  162. package/docs/operations/platform-support.zh-CN.md +2 -2
  163. package/docs/operations/standalone-disaster-recovery.md +26 -1
  164. package/docs/operations/uu-remote-rescue.md +41 -0
  165. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  166. package/docs/public-usage-guide.md +1 -1
  167. package/docs/public-usage-guide.zh-CN.md +1 -1
  168. package/docs/tutorials/01-install-and-start.md +1 -1
  169. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  170. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  171. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  172. package/docs/wiki/Implementation.md +1 -1
  173. package/package.json +23 -14
  174. package/packages/kernel/controller/api/index.ts +2 -0
  175. package/packages/kernel/controller/application/controller-round-service.ts +29 -0
  176. package/packages/kernel/controller/application/controller-service.ts +30 -0
  177. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +308 -0
  178. package/packages/kernel/controller/domain/controller-round.ts +107 -0
  179. package/packages/kernel/controller/domain/execution-quality.ts +133 -0
  180. package/packages/kernel/controller/domain/types.ts +56 -0
  181. package/packages/kernel/controller/index.ts +1 -0
  182. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  183. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1384 -0
  184. package/packages/kernel/controller/infrastructure/controller-session-store.ts +578 -0
  185. package/packages/kernel/controller/ports/controller-host.ts +16 -0
  186. package/packages/kernel/goal/api/index.ts +1 -0
  187. package/packages/kernel/goal/domain/types.ts +40 -0
  188. package/packages/kernel/goal/index.ts +1 -0
  189. package/packages/kernel/identity/api/index.ts +5 -0
  190. package/packages/kernel/identity/application/identity-service.ts +67 -0
  191. package/packages/kernel/identity/domain/scope.ts +63 -0
  192. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  193. package/packages/kernel/identity/domain/types.ts +44 -0
  194. package/packages/kernel/identity/domain/workspace.ts +56 -0
  195. package/packages/kernel/identity/index.ts +1 -0
  196. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  197. package/packages/kernel/memory/api/index.ts +5 -0
  198. package/packages/kernel/memory/application/experience-service.ts +118 -0
  199. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  200. package/packages/kernel/memory/domain/experience.ts +103 -0
  201. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  202. package/packages/kernel/memory/index.ts +1 -0
  203. package/packages/kernel/progression/api/index.ts +2 -0
  204. package/packages/kernel/progression/application/projector.ts +211 -0
  205. package/packages/kernel/progression/domain/types.ts +116 -0
  206. package/packages/kernel/progression/index.ts +1 -0
  207. package/packages/kernel/scheduler/api/index.ts +6 -0
  208. package/packages/kernel/scheduler/application/continuation-service.ts +212 -0
  209. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  210. package/packages/kernel/scheduler/application/schedule-service.ts +24 -0
  211. package/packages/kernel/scheduler/application/settlement.ts +197 -0
  212. package/packages/kernel/scheduler/domain/continuation.ts +38 -0
  213. package/packages/kernel/scheduler/domain/schedule.ts +149 -0
  214. package/packages/kernel/scheduler/index.ts +1 -0
  215. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +38 -0
  216. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +785 -0
  217. package/packages/kernel/work/api/index.ts +15 -0
  218. package/packages/kernel/work/application/work-service.ts +148 -0
  219. package/packages/kernel/work/domain/admission-policy.ts +34 -0
  220. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  221. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  222. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  223. package/packages/kernel/work/domain/engineering-design.ts +281 -0
  224. package/packages/kernel/work/domain/engineering-profile.ts +242 -0
  225. package/packages/kernel/work/domain/execution-concurrency.ts +254 -0
  226. package/packages/kernel/work/domain/execution-snapshot.ts +33 -0
  227. package/packages/kernel/work/domain/implementation-review.ts +510 -0
  228. package/packages/kernel/work/domain/project-engineering-contract.ts +202 -0
  229. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  230. package/packages/kernel/work/domain/state-machine.ts +259 -0
  231. package/packages/kernel/work/domain/types.ts +528 -0
  232. package/packages/kernel/work/index.ts +1 -0
  233. package/packages/kernel/work/infrastructure/work-contract-store.ts +1876 -0
  234. package/packages/kernel/work/ports/work-contract-store.ts +18 -0
  235. package/packages/plugin-runtime/browser/index.ts +3 -0
  236. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  237. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  238. package/packages/plugin-runtime/browser/session-authority.ts +61 -0
  239. package/packages/plugin-runtime/computer/index.ts +6 -0
  240. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  241. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  242. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  243. package/packages/plugin-runtime/computer/provider.ts +19 -0
  244. package/packages/plugin-runtime/computer/target-authority.ts +190 -0
  245. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  246. package/packages/plugin-runtime/external/index.ts +1 -0
  247. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +457 -0
  248. package/packages/plugin-runtime/index.ts +3 -0
  249. package/packages/protocols/browser/index.ts +1 -0
  250. package/packages/protocols/browser/session.ts +98 -0
  251. package/packages/protocols/computer/contract.ts +199 -0
  252. package/packages/protocols/computer/index.ts +1 -0
  253. package/packages/protocols/failure/contract.ts +158 -0
  254. package/packages/protocols/failure/index.ts +1 -0
  255. package/packages/protocols/handoff/index.ts +1 -0
  256. package/packages/protocols/handoff/status.ts +22 -0
  257. package/packages/protocols/mcp/execution-context.ts +11 -0
  258. package/packages/protocols/mcp/index.ts +2 -0
  259. package/packages/protocols/mcp/tool-contract.ts +42 -0
  260. package/packages/workflow-runtime/api/index.ts +2 -0
  261. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  262. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  263. package/packages/workflow-runtime/index.ts +1 -0
  264. package/scripts/assemble-template.ts +1 -1
  265. package/scripts/benchmark-computer-hotpath.ts +255 -0
  266. package/scripts/benchmark-controller-round2.ts +54 -2
  267. package/scripts/benchmark-ios-agent-device.ts +3 -6
  268. package/scripts/benchmark-mcp-transport.ts +3 -4
  269. package/scripts/benchmark-operational-memory-activation.ts +235 -0
  270. package/scripts/benchmark-operational-shadow.ts +28 -0
  271. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  272. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  273. package/scripts/canary-controller-semantic-wait.ts +195 -0
  274. package/scripts/canary-global-hook.sh +2 -2
  275. package/scripts/check-mcp-compatibility.ts +173 -7
  276. package/scripts/check-platform-support.mjs +28 -2
  277. package/scripts/check-public-docs.sh +1 -1
  278. package/scripts/check-release-readiness.sh +1 -1
  279. package/scripts/check-repository-hygiene.mjs +92 -0
  280. package/scripts/check-runtime-architecture.mjs +1206 -95
  281. package/scripts/check-source-duplication.mjs +9 -1
  282. package/scripts/check-tarball-install-smoke.sh +98 -15
  283. package/scripts/controller-ngrok-rotation.sh +5 -4
  284. package/scripts/create-project-dirs.sh +10 -74
  285. package/scripts/forge-local-recovery-helper.mjs +204 -0
  286. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  287. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  288. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  289. package/scripts/hook-shim.sh +2 -2
  290. package/scripts/init-project.sh +12 -70
  291. package/scripts/install-desktop-operator-registration.ts +4 -2
  292. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  293. package/scripts/install-standalone-recovery.ts +122 -10
  294. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  295. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  296. package/scripts/lib/controller-home-env.sh +6 -3
  297. package/scripts/lib/project-init-lib.sh +39 -582
  298. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  299. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  300. package/scripts/open-source-audit-allowlist.txt +12 -1
  301. package/scripts/portability-bindings.mjs +70 -0
  302. package/scripts/run-bounded-command.mjs +55 -0
  303. package/scripts/run-bun-test-file.ts +22 -6
  304. package/scripts/run-governed-gate.ts +4 -1
  305. package/scripts/smoke-mcp-http-runtime.ts +1 -1
  306. package/scripts/smoke-runtime-recovery.ts +34 -1
  307. package/scripts/smoke-schedule-engine.ts +1 -1
  308. package/scripts/stage-runtime-release.ts +6 -1
  309. package/scripts/test-governance.ts +51 -2
  310. package/src/cli/agent-jobs/job-manager.ts +10 -1
  311. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  312. package/src/cli/chatgpt-browser/binding.ts +20 -10
  313. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  314. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  315. package/src/cli/chatgpt-browser/engine.ts +10 -11
  316. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  317. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  318. package/src/cli/chatgpt-browser/types.ts +1 -0
  319. package/src/cli/commands/bootstrap-control.ts +334 -0
  320. package/src/cli/commands/brain-assistant.ts +38 -0
  321. package/src/cli/commands/brain.ts +193 -40
  322. package/src/cli/commands/chatgpt.ts +127 -0
  323. package/src/cli/commands/computer.ts +437 -0
  324. package/src/cli/commands/doctor.ts +18 -1
  325. package/src/cli/commands/init-hook.ts +71 -61
  326. package/src/cli/commands/init.ts +3 -1
  327. package/src/cli/commands/mcp.ts +16 -10
  328. package/src/cli/commands/plugin.ts +38 -12
  329. package/src/cli/commands/recovery.ts +493 -80
  330. package/src/cli/commands/runtime.ts +9 -4
  331. package/src/cli/commands/setup-profile.ts +89 -39
  332. package/src/cli/commands/status.ts +2 -1
  333. package/src/cli/controller/check-runner.ts +487 -98
  334. package/src/cli/controller/context/types.ts +7 -1
  335. package/src/cli/controller/context-pack.ts +23 -3
  336. package/src/cli/controller/issue-store.ts +1 -1
  337. package/src/cli/controller/systemd-user.ts +110 -0
  338. package/src/cli/controller/types.ts +10 -70
  339. package/src/cli/controller/worklog.ts +67 -13
  340. package/src/cli/editing/edit-operation-contract.ts +92 -0
  341. package/src/cli/editing/edit-session.ts +289 -13
  342. package/src/cli/github/plugin.ts +17 -12
  343. package/src/cli/hook/route-registry.ts +3 -5
  344. package/src/cli/hook/runtime.ts +49 -3
  345. package/src/cli/index.ts +3 -0
  346. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  347. package/src/cli/local-bridge/console-automations.ts +2 -2
  348. package/src/cli/local-bridge/facade-api.ts +41 -14
  349. package/src/cli/local-bridge/job-store.ts +1 -1
  350. package/src/cli/local-bridge/server.ts +24 -27
  351. package/src/cli/mcp/access-mode.ts +2 -132
  352. package/src/cli/mcp/access-tools.ts +2 -387
  353. package/src/cli/mcp/audit.ts +2 -32
  354. package/src/cli/mcp/auth.ts +2 -404
  355. package/src/cli/mcp/instructions.ts +2 -34
  356. package/src/cli/mcp/legacy-context.ts +2 -16
  357. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  358. package/src/cli/mcp/multi-repository.ts +2 -436
  359. package/src/cli/mcp/oauth.ts +2 -375
  360. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  361. package/src/cli/mcp/paths.ts +2 -140
  362. package/src/cli/mcp/policy.ts +2 -232
  363. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  364. package/src/cli/mcp/proxy-env.ts +2 -62
  365. package/src/cli/mcp/readiness-projection.ts +2 -24
  366. package/src/cli/mcp/redaction.ts +2 -21
  367. package/src/cli/mcp/repo.ts +2 -24
  368. package/src/cli/mcp/repository-tools.ts +2 -1613
  369. package/src/cli/mcp/server.ts +2 -951
  370. package/src/cli/mcp/setup.ts +2 -737
  371. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  372. package/src/cli/mcp/tool-contract.ts +2 -14
  373. package/src/cli/mcp/tools.ts +2 -9
  374. package/src/cli/mcp/toolset-names.ts +2 -70
  375. package/src/cli/mcp/toolset-selection.ts +2 -41
  376. package/src/cli/mcp/toolset.ts +2 -336
  377. package/src/cli/mcp/transports/AGENTS.md +1 -10
  378. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  379. package/src/cli/mcp/transports/http.ts +2 -1414
  380. package/src/cli/mcp/transports/session-registry.ts +2 -490
  381. package/src/cli/mcp/transports/stdio.ts +2 -8
  382. package/src/cli/mcp/types.ts +2 -41
  383. package/src/cli/mcp/umbrella-tools.ts +2 -117
  384. package/src/cli/repositories/command-executor.ts +64 -18
  385. package/src/cli/repositories/command-scope.ts +117 -1
  386. package/src/cli/repositories/controller-home.ts +119 -1
  387. package/src/cli/repositories/identity.ts +13 -0
  388. package/src/cli/repositories/registry.ts +68 -49
  389. package/src/cli/repositories/repository-snapshot.ts +53 -7
  390. package/src/cli/repositories/runtime-storage.ts +27 -2
  391. package/src/cli/repositories/safe-patch.ts +34 -38
  392. package/src/cli/repositories/selected-path-actions.ts +22 -2
  393. package/src/cli/repositories/structured-git.ts +105 -25
  394. package/src/cli/runtime/helper-runner.ts +7 -0
  395. package/src/cli/runtime-invocation.ts +8 -1
  396. package/src/cli/tools/codegraph.ts +44 -25
  397. package/src/core/adoption/manifest-templates.ts +1 -1
  398. package/src/core/adoption/plan.ts +3 -26
  399. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  400. package/src/effects/process-runner.ts +8 -14
  401. package/src/runtime/context/assistant-context.ts +154 -0
  402. package/src/runtime/context/assistant-work-context.ts +147 -0
  403. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  404. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  405. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  406. package/src/runtime/context/context-closure.ts +268 -0
  407. package/src/runtime/context/context-plane.ts +211 -0
  408. package/src/runtime/context/project-engineering-contract.ts +60 -0
  409. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  410. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  411. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  412. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  413. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  414. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  415. package/src/runtime/control-plane/cleanup-artifact-retention.ts +328 -0
  416. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  417. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +320 -0
  418. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +169 -0
  419. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +350 -0
  420. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +85 -0
  421. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +369 -99
  422. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +103 -15
  423. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  424. package/src/runtime/control-plane/execution/repository-work-attribution.ts +2 -2
  425. package/src/runtime/control-plane/execution/retained-work-resume.ts +336 -6
  426. package/src/runtime/control-plane/execution/validation.ts +28 -3
  427. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +116 -0
  428. package/src/runtime/control-plane/execution/work-completion-authority.ts +44 -2
  429. package/src/runtime/control-plane/execution/work-evidence-policy.ts +34 -3
  430. package/src/runtime/control-plane/execution/work-execution-support.ts +42 -22
  431. package/src/runtime/control-plane/execution/work-finalization-service.ts +1103 -166
  432. package/src/runtime/control-plane/execution/work-handle-authority.ts +219 -22
  433. package/src/runtime/control-plane/execution/work-handle-store.ts +77 -0
  434. package/src/runtime/control-plane/execution/work-head-settlement.ts +75 -0
  435. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +145 -23
  436. package/src/runtime/control-plane/execution/work-operation-service.ts +49 -13
  437. package/src/runtime/control-plane/execution/work-preparation-service.ts +31 -15
  438. package/src/runtime/control-plane/execution/work-remote-delivery.ts +1 -1
  439. package/src/runtime/control-plane/execution/work-task-receipt.ts +20 -1
  440. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +407 -76
  441. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +88 -18
  442. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  443. package/src/runtime/control-plane/execution/work-verification-service.ts +258 -4
  444. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +200 -15
  445. package/src/runtime/control-plane/facade/capability-registry.ts +21 -10
  446. package/src/runtime/control-plane/facade/controller-round-relay.ts +3 -1118
  447. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  448. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -3
  449. package/src/runtime/control-plane/facade/goal-workloop.ts +972 -137
  450. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +158 -0
  451. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +10 -0
  452. package/src/runtime/control-plane/facade/index.ts +4 -2
  453. package/src/runtime/control-plane/facade/operation-digest.ts +22 -58
  454. package/src/runtime/control-plane/facade/plan-contract-store.ts +1004 -38
  455. package/src/runtime/control-plane/facade/repository-work-admission.ts +19 -1
  456. package/src/runtime/control-plane/facade/requirement-authority.ts +218 -0
  457. package/src/runtime/control-plane/facade/requirement-board.ts +6 -1
  458. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +48 -0
  459. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  460. package/src/runtime/control-plane/facade/suggested-actions.ts +4 -1
  461. package/src/runtime/control-plane/facade/types.ts +189 -532
  462. package/src/runtime/control-plane/facade/work-admission-policy.ts +84 -35
  463. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  464. package/src/runtime/control-plane/facade/work-implementation-review.ts +2 -0
  465. package/src/runtime/control-plane/facade/work-state-machine.ts +2 -250
  466. package/src/runtime/control-plane/generated-cache-retention.ts +239 -18
  467. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +12 -1
  468. package/src/runtime/control-plane/global-scheduler/maintenance.ts +76 -13
  469. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  470. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  471. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -1
  472. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  473. package/src/runtime/control-plane/global-scheduler/scheduler.ts +173 -81
  474. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  475. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +397 -0
  476. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  477. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +301 -560
  478. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +20 -4
  479. package/src/runtime/control-plane/launcher/thin-launcher.ts +50 -4
  480. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  481. package/src/runtime/control-plane/persistence/experience-store.ts +169 -0
  482. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  483. package/src/runtime/control-plane/persistence/requirement-store.ts +30 -22
  484. package/src/runtime/control-plane/persistence/sqlite-store.ts +545 -76
  485. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  486. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  487. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  488. package/src/runtime/control-plane/release-retention.ts +56 -16
  489. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  490. package/src/runtime/control-plane/routing/route-policy.ts +19 -0
  491. package/src/runtime/control-plane/runtime-cleanup.ts +574 -37
  492. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  493. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +465 -0
  494. package/src/runtime/control-plane/workspace/workspace-store.ts +80 -0
  495. package/src/runtime/diagnostics/incident-repair.ts +406 -0
  496. package/src/runtime/diagnostics/mcp-timing.ts +125 -6
  497. package/src/runtime/diagnostics/performance.ts +6 -14
  498. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  499. package/src/runtime/evidence/artifact-store.ts +65 -2
  500. package/src/runtime/evidence/operational-shadow.ts +38 -0
  501. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  502. package/src/runtime/evidence/sensitive-output.ts +4 -4
  503. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  504. package/src/runtime/execution/jobs/store.ts +121 -2
  505. package/src/runtime/execution/managed-workspace.ts +55 -5
  506. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  507. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  508. package/src/runtime/execution/process-runtime/check-facade.ts +23 -9
  509. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  510. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  511. package/src/runtime/execution/process-runtime/check-result.ts +73 -2
  512. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -3
  513. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  514. package/src/runtime/execution/process-runtime/command-facade.ts +38 -87
  515. package/src/runtime/execution/process-runtime/index.ts +1 -0
  516. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  517. package/src/runtime/execution/process-runtime/lightweight-managed.ts +32 -1
  518. package/src/runtime/execution/process-runtime/persisted-check.ts +128 -21
  519. package/src/runtime/execution/process-runtime/resource-claims.ts +60 -3
  520. package/src/runtime/execution/process-runtime/runtime.ts +132 -15
  521. package/src/runtime/execution/process-runtime/types.ts +22 -0
  522. package/src/runtime/execution/workers/executor.ts +16 -13
  523. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  524. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  525. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  526. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  527. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  528. package/src/runtime/gateway/mcp/router.ts +2 -1125
  529. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  530. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  531. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  532. package/src/runtime/maintenance/cleanup.ts +38 -24
  533. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  534. package/src/runtime/platform/computer-platform.ts +14 -0
  535. package/src/runtime/platform/executable-discovery.ts +54 -0
  536. package/src/runtime/platform/fetch-http-server.ts +90 -0
  537. package/src/runtime/platform/service-manager.ts +138 -0
  538. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  539. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  540. package/src/runtime/plugins/action-application.ts +135 -0
  541. package/src/runtime/plugins/app-store-connect-adapter.ts +9 -21
  542. package/src/runtime/plugins/browser-adapter.ts +140 -100
  543. package/src/runtime/plugins/browser-automation-service.ts +15 -36
  544. package/src/runtime/plugins/browser-macos-bridge.ts +69 -21
  545. package/src/runtime/plugins/browser-manifest-surface.ts +30 -6
  546. package/src/runtime/plugins/browser-native-session-service.ts +1 -1
  547. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  548. package/src/runtime/plugins/browser-registration.ts +16 -0
  549. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  550. package/src/runtime/plugins/browser-runtime.ts +41 -9
  551. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  552. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  553. package/src/runtime/plugins/browser-session-store.ts +229 -36
  554. package/src/runtime/plugins/browser-session-types.ts +2 -98
  555. package/src/runtime/plugins/capability-authorization-grants.ts +17 -0
  556. package/src/runtime/plugins/catalog-trust.ts +82 -0
  557. package/src/runtime/plugins/computer-registration.ts +839 -0
  558. package/src/runtime/plugins/config-store.ts +119 -0
  559. package/src/runtime/plugins/desktop-operator-external-policy.ts +932 -0
  560. package/src/runtime/plugins/desktop-operator-registration.ts +111 -8
  561. package/src/runtime/plugins/errors.ts +8 -1
  562. package/src/runtime/plugins/external-adapter.ts +45 -322
  563. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  564. package/src/runtime/plugins/external-registration.ts +9 -1
  565. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  566. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  567. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  568. package/src/runtime/plugins/first-party-registry.ts +3 -1
  569. package/src/runtime/plugins/github-adapter.ts +1 -2
  570. package/src/runtime/plugins/gmail-adapter.ts +6 -6
  571. package/src/runtime/plugins/google-calendar-adapter.ts +6 -6
  572. package/src/runtime/plugins/google-shared.ts +31 -46
  573. package/src/runtime/plugins/google-tasks-adapter.ts +6 -6
  574. package/src/runtime/plugins/ios-adapter.ts +1 -2
  575. package/src/runtime/plugins/local-system-adapter.ts +180 -0
  576. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  577. package/src/runtime/plugins/resend-adapter.ts +15 -24
  578. package/src/runtime/plugins/store.ts +493 -51
  579. package/src/runtime/plugins/types.ts +8 -1
  580. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  581. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  582. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  583. package/src/runtime/plugins/xiaohongshu-publish.ts +86 -430
  584. package/src/runtime/projections/controller-context.ts +12 -6
  585. package/src/runtime/projections/git-status-sampler.ts +73 -22
  586. package/src/runtime/projections/materialized-view.ts +14 -8
  587. package/src/runtime/recovery/capability-action-application.ts +228 -0
  588. package/src/runtime/recovery/index.ts +3 -0
  589. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  590. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  591. package/src/runtime/recovery/maintenance-executor.ts +358 -76
  592. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  593. package/src/runtime/release/identity.ts +6 -0
  594. package/src/runtime/resources/leases/store.ts +1 -1
  595. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  596. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  597. package/src/runtime/root/browser-session-composition.ts +59 -0
  598. package/src/runtime/root/computer-composition.ts +191 -0
  599. package/src/runtime/root/computer-target-composition.ts +16 -0
  600. package/src/runtime/root/computer-target-persistence.ts +39 -0
  601. package/src/runtime/root/controller-round-composition.ts +107 -0
  602. package/src/runtime/root/controller-services.ts +2 -0
  603. package/src/runtime/root/entry.ts +4 -3
  604. package/src/runtime/root/gateway-adapter.ts +3 -3
  605. package/src/runtime/root/local-bridge.ts +5 -2
  606. package/src/runtime/root/mcp-transport.ts +5 -5
  607. package/src/runtime/root/package-connector-service.ts +108 -56
  608. package/src/runtime/root/package-runtime-release.ts +85 -18
  609. package/src/runtime/root/package-runtime-service.ts +60 -97
  610. package/src/runtime/root/release-execution-canary.ts +71 -0
  611. package/src/runtime/root/release-loader.ts +68 -0
  612. package/src/runtime/root/release-manifest.ts +106 -2
  613. package/src/runtime/root/release-materialize.ts +103 -4
  614. package/src/runtime/root/release-store.ts +31 -2
  615. package/src/runtime/root/runtime.ts +240 -25
  616. package/src/runtime/root/scheduled-controller-composition.ts +51 -0
  617. package/src/runtime/root/scheduler.ts +12 -4
  618. package/src/runtime/root/service-runner.ts +1 -1
  619. package/src/runtime/root/service.ts +41 -16
  620. package/src/runtime/root/status.ts +1 -0
  621. package/src/runtime/root/types.ts +24 -1
  622. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  623. package/src/runtime/shared/process-environment.ts +19 -4
  624. package/src/runtime/shared/storage-capacity.ts +68 -0
  625. package/src/runtime/standalone-recovery/controller-home-migration.ts +1116 -0
  626. package/src/runtime/standalone-recovery/core.ts +984 -230
  627. package/src/runtime/standalone-recovery/entry.ts +182 -51
  628. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  629. package/src/runtime/standalone-recovery/installer.ts +212 -104
  630. package/src/runtime/standalone-recovery/mcp-server.ts +243 -0
  631. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  632. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  633. package/src/runtime/standalone-recovery/performance.ts +135 -0
  634. package/src/runtime/standalone-recovery/release.ts +5 -0
  635. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  636. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -1
  637. package/src/runtime/workflow/schedules/engine.ts +191 -343
  638. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  639. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  640. package/src/runtime/workflow/schedules/store.ts +2 -607
  641. package/src/runtime/workflow/schedules/types.ts +2 -127
  642. package/src/runtime/workflow/schedules/work-continuation.ts +180 -15
  643. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  644. package/src/runtime/workflows/runtime.ts +486 -0
  645. package/src/testing/test-governance.ts +90 -29
  646. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  647. package/docs/architecture/history/README.md +0 -7
  648. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  649. package/docs/architecture/history/global-hook-runtime.md +0 -166
  650. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  651. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  652. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  653. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  654. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  655. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  656. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  657. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  658. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  659. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  660. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  661. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  662. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  663. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  664. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  665. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  666. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  667. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  668. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  669. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  670. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  671. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  672. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  673. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  674. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  675. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  676. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  677. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  678. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  679. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  680. package/docs/architecture/requests/archive/2026/root.md +0 -68
  681. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  682. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  683. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  684. package/scripts/archive-architecture-request.sh +0 -4
  685. package/scripts/run-skill-evals.ts +0 -1022
@@ -2,10 +2,13 @@ import { createHash, randomUUID } from 'crypto';
2
2
  import { createServer, type IncomingMessage, type ServerResponse } from 'http';
3
3
  import { basename, join, resolve } from 'path';
4
4
  import { pathToFileURL } from 'url';
5
- import { readMcpServiceOAuthPassphrase } from '../../cli/mcp/auth';
5
+ import { readMcpServiceOAuthPassphrase } from '../../../adapters/mcp/auth';
6
6
  import { FORGE_VERSION } from '../../version';
7
+ import type { Tool } from '@modelcontextprotocol/server';
8
+ import { RecoveryMcpSessionServer } from './mcp-server';
7
9
  import {
8
10
  activateRuntimeRelease,
11
+ assertRecoveryMutationIdentity,
9
12
  attestKnownGood,
10
13
  diagnose,
11
14
  gatewayToken,
@@ -15,6 +18,7 @@ import {
15
18
  saveWatchdogState,
16
19
  reconnectMain,
17
20
  recoverPrimaryRuntime,
21
+ recoveryMachineIdentity,
18
22
  repairPublicTunnel,
19
23
  restartPrimaryConnector,
20
24
  restartPrimaryRuntime,
@@ -27,6 +31,7 @@ import {
27
31
  watchdogTick,
28
32
  type WatchdogState,
29
33
  type RecoveryConfig,
34
+ type RecoveryMachineIdentity,
30
35
  } from './core';
31
36
  import {
32
37
  createRecoveryWatchdogHeartbeat,
@@ -40,6 +45,7 @@ import {
40
45
  type RecoveryRuntimeIdentity,
41
46
  type RecoveryRuntimeRole,
42
47
  } from './release';
48
+ import { RECOVERY_MUTATION_IDENTITY_CONTRACT, RECOVERY_MUTATION_IDENTITY_FIELDS } from './mutation-identity-contract';
43
49
 
44
50
  function option(name: string): string | undefined {
45
51
  const index = process.argv.indexOf(name);
@@ -54,6 +60,8 @@ function controllerHome(): string {
54
60
 
55
61
  function output(value: unknown): void { process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); }
56
62
 
63
+ import { runRecoveryControllerHomeMigrationWorker, scheduleRecoveryControllerHomeMigration } from './controller-home-migration';
64
+
57
65
  export const RECOVERY_CLI_COMMANDS = [
58
66
  'status',
59
67
  'verify',
@@ -66,6 +74,7 @@ export const RECOVERY_CLI_COMMANDS = [
66
74
  'recover-primary-runtime',
67
75
  'activate-runtime-release',
68
76
  'stage-and-activate-runtime-release',
77
+ 'migrate-controller-home-worker',
69
78
  'restart-public-tunnel',
70
79
  'diagnose',
71
80
  'reconnect-main',
@@ -128,6 +137,20 @@ async function cli(): Promise<void> {
128
137
  return;
129
138
  }
130
139
  case 'stage-and-activate-runtime-release': output(await stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-cli:${process.pid}:${Date.now()}`)); return;
140
+ case 'migrate-controller-home-worker': {
141
+ const canonicalSourceRoot = option('--canonical-source-root');
142
+ const expectedSourceRevision = option('--expected-source-revision');
143
+ const migrationRequestId = option('--request-id');
144
+ if (!canonicalSourceRoot) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REQUIRED');
145
+ if (!expectedSourceRevision) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REVISION_REQUIRED');
146
+ if (!migrationRequestId) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
147
+ output(await runRecoveryControllerHomeMigrationWorker(config, {
148
+ requestId: migrationRequestId,
149
+ canonicalSourceRoot,
150
+ expectedSourceRevision,
151
+ }));
152
+ return;
153
+ }
131
154
  case 'restart-public-tunnel': output(await repairPublicTunnel(config)); return;
132
155
  case 'diagnose': output(await diagnose(config)); return;
133
156
  case 'reconnect-main': output(await reconnectMain(config)); return;
@@ -224,7 +247,7 @@ function html(response: ServerResponse, status: number, payload: string): void {
224
247
 
225
248
  function setCorsHeaders(response: ServerResponse): void {
226
249
  response.setHeader('access-control-allow-origin', '*');
227
- response.setHeader('access-control-allow-methods', 'GET, POST, OPTIONS');
250
+ response.setHeader('access-control-allow-methods', 'GET, POST, DELETE, OPTIONS');
228
251
  response.setHeader('access-control-allow-headers', 'authorization, content-type, mcp-session-id, mcp-protocol-version');
229
252
  response.setHeader('access-control-expose-headers', 'www-authenticate, mcp-session-id');
230
253
  }
@@ -245,23 +268,46 @@ function matchesAnyPath(url: string | undefined, paths: string[]): boolean {
245
268
  return paths.some((path) => matchesPath(url, path));
246
269
  }
247
270
 
271
+ function mutationInputSchema(
272
+ extraProperties: Record<string, unknown> = {},
273
+ extraRequired: string[] = [],
274
+ ): Record<string, unknown> {
275
+ return {
276
+ type: 'object',
277
+ properties: {
278
+ request_id: { type: 'string', minLength: 8, maxLength: 120 },
279
+ ...RECOVERY_MUTATION_IDENTITY_CONTRACT,
280
+ ...extraProperties,
281
+ },
282
+ required: ['request_id', ...RECOVERY_MUTATION_IDENTITY_FIELDS, ...extraRequired],
283
+ additionalProperties: false,
284
+ };
285
+ }
286
+
248
287
  export const RECOVERY_TOOLS = [
249
- { name: 'runtime_status', description: 'Read canonical Runtime ownership, readiness, endpoint, and release observation.', inputSchema: { type: 'object', additionalProperties: false } },
288
+ { name: 'runtime_status', description: 'Read canonical Runtime ownership, readiness, endpoint, release observation, and exact Recovery machine identity.', inputSchema: { type: 'object', additionalProperties: false } },
250
289
  { name: 'list_releases', description: 'Read active, previous, and known-good whole-Runtime release evidence.', inputSchema: { type: 'object', additionalProperties: false } },
251
290
  { name: 'verify_stable_runtime', description: 'Run independent stable runtime verification.', inputSchema: { type: 'object', additionalProperties: false } },
252
291
  { name: 'verify_external_runtime', description: 'Verify the external primary MCP endpoint.', inputSchema: { type: 'object', additionalProperties: false } },
253
- { name: 'attest_known_good', description: 'Record the active release as known-good only after full independent verification succeeds.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
254
- { name: 'rollback_previous', description: 'While Canonical Runtime is stopped, atomically restore its attested previous whole-Runtime release and SQLite backup.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
255
- { name: 'restart_primary_runtime', description: 'Restart the installed canonical Forge Runtime service and require whole-Runtime verification.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
256
- { name: 'restart_primary_connector', description: 'Restart the explicitly configured primary OAuth/Connector launchd service only after local Canonical Runtime verification succeeds.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
257
- { name: 'recover_primary_runtime', description: 'Stop the canonical Runtime, restore the attested previous whole release and SQLite backup, restart it, and require verification.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
258
- { name: 'activate_runtime_release', description: 'Activate an already staged immutable Runtime release only if the caller-observed active release/authority revision are still current. Reverse activation of current.previous is rejected; use rollback_previous/recover_primary_runtime instead.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 }, release_path: { type: 'string', minLength: 8, maxLength: 1024, description: 'Absolute path to the staged immutable Runtime release directory.' }, expected_active_release_id: { type: 'string', minLength: 1, maxLength: 256 }, expected_authority_revision: { type: 'integer', minimum: 1 } }, required: ['request_id', 'release_path', 'expected_active_release_id', 'expected_authority_revision'], additionalProperties: false } },
259
- { name: 'stage_and_activate_runtime_release', description: 'Build one immutable Runtime release from the fixed Recovery-configured source root, then activate it transactionally with rollback protection. No arbitrary source path is accepted.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
260
- { name: 'restart_public_tunnel', description: 'Restart the explicitly configured public tunnel only after local runtime verification succeeds and the external endpoint is unavailable.', inputSchema: { type: 'object', properties: { request_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['request_id'], additionalProperties: false } },
292
+ { name: 'attest_known_good', description: 'Record the active release as known-good only after full independent verification succeeds.', inputSchema: mutationInputSchema() },
293
+ { name: 'rollback_previous', description: 'While Canonical Runtime is stopped, atomically restore its attested previous whole-Runtime release and SQLite backup.', inputSchema: mutationInputSchema() },
294
+ { name: 'restart_primary_runtime', description: 'Restart the installed canonical Forge Runtime service only after exact Recovery machine identity matches.', inputSchema: mutationInputSchema() },
295
+ { name: 'restart_primary_connector', description: 'Restart the explicitly configured primary OAuth/Connector service only after exact Recovery machine identity and local Canonical Runtime verification succeed.', inputSchema: mutationInputSchema() },
296
+ { name: 'recover_primary_runtime', description: 'Stop the canonical Runtime, restore the attested previous whole-Runtime release and SQLite backup, restart it, and require verification.', inputSchema: mutationInputSchema() },
297
+ { name: 'activate_runtime_release', description: 'Activate an already staged immutable Runtime release only if machine identity and caller-observed active release/authority revision are still current. Reverse activation of current.previous is rejected; use rollback_previous/recover_primary_runtime instead.', inputSchema: mutationInputSchema({ release_path: { type: 'string', minLength: 8, maxLength: 1024, description: 'Absolute path to the staged immutable Runtime release directory.' }, expected_active_release_id: { type: 'string', minLength: 1, maxLength: 256 }, expected_authority_revision: { type: 'integer', minimum: 1 } }, ['release_path', 'expected_active_release_id', 'expected_authority_revision']) },
298
+ { name: 'stage_and_activate_runtime_release', description: 'Build one immutable Runtime release from the fixed Recovery-configured source root, then activate it transactionally with rollback protection. No arbitrary source path is accepted.', inputSchema: mutationInputSchema() },
299
+ { name: 'migrate_controller_home', description: 'Schedule a Linux-only standalone Recovery transaction that relocates this Forge installation to the stable user-level Controller Home, reinstalls immutable Runtime/Connector/Recovery owners, verifies them, and rolls back on failure.', inputSchema: mutationInputSchema({ canonical_source_root: { type: 'string', minLength: 1, maxLength: 1024 }, expected_source_revision: { type: 'string', minLength: 7, maxLength: 80 } }, ['canonical_source_root', 'expected_source_revision']) },
300
+ { name: 'restart_public_tunnel', description: 'Restart the explicitly configured public tunnel only after exact Recovery machine identity and local runtime verification succeeds and the external endpoint is unavailable.', inputSchema: mutationInputSchema() },
261
301
  { name: 'reconnect_primary_connector', description: 'Check canonical Runtime Gateway and primary MCP reconnection readiness without publishing a release.', inputSchema: { type: 'object', additionalProperties: false } },
262
302
  ] as const;
263
303
 
264
304
  const RECOVERY_OAUTH_SCOPE = 'forge';
305
+ export const RECOVERY_VERIFIER_OAUTH_CLIENT_ID = 'forge-recovery-verifier-v1';
306
+ export const RECOVERY_VERIFIER_OAUTH_CLIENT_NAME = 'Forge Recovery Verification';
307
+ export const RECOVERY_VERIFIER_OAUTH_REDIRECT_URI = 'http://127.0.0.1/forge-recovery-oauth-callback';
308
+ const RECOVERY_OAUTH_CODE_TTL_MS = 10 * 60_000;
309
+ const RECOVERY_MAX_PENDING_OAUTH_CODES = 512;
310
+ const RECOVERY_MAX_PENDING_OAUTH_CODES_PER_CLIENT = 32;
265
311
  const RECOVERY_ACCESS_TOKEN_EXPIRES_IN_SECONDS = 10 * 365 * 24 * 60 * 60;
266
312
  const TOOL_SECURITY_SCHEMES = [{ type: 'oauth2', scopes: [RECOVERY_OAUTH_SCOPE] }] as const;
267
313
  const SUPPORTED_TOKEN_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none'] as const;
@@ -281,9 +327,55 @@ type OAuthClient = {
281
327
  clientId: string;
282
328
  clientSecret?: string;
283
329
  tokenEndpointAuthMethod: TokenAuthMethod;
330
+ owner: 'recovery_verifier' | 'external';
284
331
  createdAt: number;
285
332
  };
286
333
 
334
+ function stringArray(value: unknown): string[] {
335
+ return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [];
336
+ }
337
+
338
+ export function recoveryOAuthClientRegistrationIdentity(
339
+ body: Record<string, unknown>,
340
+ generatedClientId: string = randomUUID(),
341
+ ): { clientId: string; owner: 'recovery_verifier' | 'external' } {
342
+ const requestedClientId = typeof body.client_id === 'string' && body.client_id.trim() ? body.client_id.trim() : '';
343
+ if (requestedClientId === RECOVERY_VERIFIER_OAUTH_CLIENT_ID) {
344
+ const validVerifierMetadata = body.client_name === RECOVERY_VERIFIER_OAUTH_CLIENT_NAME
345
+ && stringArray(body.redirect_uris).includes(RECOVERY_VERIFIER_OAUTH_REDIRECT_URI)
346
+ && body.token_endpoint_auth_method === 'none'
347
+ && stringArray(body.grant_types).includes('authorization_code')
348
+ && stringArray(body.response_types).includes('code');
349
+ if (!validVerifierMetadata) throw new Error('RECOVERY_OAUTH_VERIFIER_CLIENT_METADATA_INVALID');
350
+ return { clientId: RECOVERY_VERIFIER_OAUTH_CLIENT_ID, owner: 'recovery_verifier' };
351
+ }
352
+ return { clientId: requestedClientId || generatedClientId, owner: 'external' };
353
+ }
354
+
355
+ function reserveRecoveryOAuthCodeCapacity(
356
+ codes: Map<string, PendingOAuthCode>,
357
+ clientId: string,
358
+ now = Date.now(),
359
+ ): void {
360
+ for (const [code, pending] of codes) {
361
+ if (now - pending.createdAt > RECOVERY_OAUTH_CODE_TTL_MS) codes.delete(code);
362
+ }
363
+ const evictOldest = (predicate: (pending: PendingOAuthCode) => boolean): boolean => {
364
+ let oldest: { code: string; createdAt: number } | undefined;
365
+ for (const [code, pending] of codes) {
366
+ if (!predicate(pending)) continue;
367
+ if (!oldest || pending.createdAt < oldest.createdAt) oldest = { code, createdAt: pending.createdAt };
368
+ }
369
+ return oldest ? codes.delete(oldest.code) : false;
370
+ };
371
+ while ([...codes.values()].filter((pending) => pending.clientId === clientId).length >= RECOVERY_MAX_PENDING_OAUTH_CODES_PER_CLIENT) {
372
+ if (!evictOldest((pending) => pending.clientId === clientId)) break;
373
+ }
374
+ while (codes.size >= RECOVERY_MAX_PENDING_OAUTH_CODES) {
375
+ if (!evictOldest(() => true)) break;
376
+ }
377
+ }
378
+
287
379
  function readBody(request: IncomingMessage): Promise<string> {
288
380
  return new Promise((resolveBody, reject) => {
289
381
  let body = '';
@@ -365,7 +457,7 @@ export function classifyRecoveryMcpRequest(
365
457
  if (!matchesAnyPath(request.url, ['/mcp', '/recovery/mcp'])) return 'not_mcp';
366
458
  const supplied = request.headers.authorization?.replace(/^Bearer\s+/i, '').trim();
367
459
  if (!expectedToken || !supplied || !secureEqual(supplied, expectedToken)) return 'auth_required';
368
- if (request.method !== 'POST') return 'method_not_supported';
460
+ if (request.method !== 'POST' && request.method !== 'GET' && request.method !== 'DELETE') return 'method_not_supported';
369
461
  return 'mcp';
370
462
  }
371
463
 
@@ -485,6 +577,17 @@ function requestId(value: unknown): string | undefined {
485
577
  return typeof value === 'string' && /^[A-Za-z0-9._:-]{8,120}$/.test(value) ? value : undefined;
486
578
  }
487
579
 
580
+ function mutationResponse(config: RecoveryConfig, payload: unknown): Record<string, unknown> {
581
+ const result = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : { result: payload };
582
+ return { ...result, identity: recoveryMachineIdentity(config) };
583
+ }
584
+
585
+ function assertRecoveryGatewayMutationIdentity(config: RecoveryConfig, args: Record<string, unknown>): RecoveryMachineIdentity {
586
+ const suppliedFields = RECOVERY_MUTATION_IDENTITY_FIELDS.filter((field) => typeof args[field] === 'string' && String(args[field]).trim());
587
+ if (suppliedFields.length === 0) return recoveryMachineIdentity(config);
588
+ return assertRecoveryMutationIdentity(config, args);
589
+ }
590
+
488
591
  export async function dispatchRecoveryTool(config: RecoveryConfig, name: string, args: Record<string, unknown>): Promise<unknown> {
489
592
  switch (name) {
490
593
  case 'runtime_status': return runtimeStatus(config);
@@ -496,44 +599,64 @@ export async function dispatchRecoveryTool(config: RecoveryConfig, name: string,
496
599
  }
497
600
  case 'attest_known_good': {
498
601
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
499
- return attestKnownGood(config);
602
+ assertRecoveryGatewayMutationIdentity(config, args);
603
+ return mutationResponse(config, await attestKnownGood(config));
500
604
  }
501
605
  case 'rollback_previous': {
502
606
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
503
- return rollbackPrevious(config, `recovery-gateway:${args.request_id}`);
607
+ assertRecoveryGatewayMutationIdentity(config, args);
608
+ return mutationResponse(config, await rollbackPrevious(config, `recovery-gateway:${args.request_id}`));
504
609
  }
505
610
  case 'restart_primary_runtime': {
506
611
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
507
- return restartPrimaryRuntime(config);
612
+ assertRecoveryGatewayMutationIdentity(config, args);
613
+ return mutationResponse(config, await restartPrimaryRuntime(config));
508
614
  }
509
615
  case 'restart_primary_connector': {
510
616
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
511
- return restartPrimaryConnector(config, { requestId: `recovery-gateway:${args.request_id}` });
617
+ assertRecoveryGatewayMutationIdentity(config, args);
618
+ return mutationResponse(config, await restartPrimaryConnector(config, { requestId: `recovery-gateway:${args.request_id}` }));
512
619
  }
513
620
  case 'recover_primary_runtime': {
514
621
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
515
- return recoverPrimaryRuntime(config, `recovery-gateway:${args.request_id}`);
622
+ assertRecoveryGatewayMutationIdentity(config, args);
623
+ return mutationResponse(config, await recoverPrimaryRuntime(config, `recovery-gateway:${args.request_id}`));
516
624
  }
517
625
  case 'activate_runtime_release': {
518
626
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
627
+ assertRecoveryGatewayMutationIdentity(config, args);
519
628
  if (typeof args.release_path !== 'string' || !args.release_path.trim()) throw new Error('RECOVERY_RELEASE_PATH_REQUIRED');
520
629
  if (typeof args.expected_active_release_id !== 'string' || !args.expected_active_release_id.trim()) throw new Error('RECOVERY_EXPECTED_ACTIVE_RELEASE_REQUIRED');
521
630
  if (!Number.isInteger(args.expected_authority_revision) || Number(args.expected_authority_revision) < 1) throw new Error('RECOVERY_EXPECTED_AUTHORITY_REVISION_REQUIRED');
522
631
  const releasePath = args.release_path.trim();
523
632
  const manifestPath = basename(releasePath) === 'manifest.json' ? releasePath : join(releasePath, 'manifest.json');
524
- return activateRuntimeRelease(config, manifestPath, {}, {
633
+ return mutationResponse(config, await activateRuntimeRelease(config, manifestPath, {}, {
525
634
  requestId: `recovery-gateway:${args.request_id}`,
526
635
  expectedActiveReleaseId: args.expected_active_release_id.trim(),
527
636
  expectedAuthorityRevision: Number(args.expected_authority_revision),
528
- });
637
+ }));
529
638
  }
530
639
  case 'stage_and_activate_runtime_release': {
531
640
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
532
- return stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-gateway:${args.request_id}`);
641
+ assertRecoveryGatewayMutationIdentity(config, args);
642
+ return mutationResponse(config, await stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-gateway:${args.request_id}`));
643
+ }
644
+ case 'migrate_controller_home': {
645
+ const migrationRequestId = requestId(args.request_id);
646
+ if (!migrationRequestId) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
647
+ assertRecoveryGatewayMutationIdentity(config, args);
648
+ if (typeof args.canonical_source_root !== 'string' || !args.canonical_source_root.trim()) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REQUIRED');
649
+ if (typeof args.expected_source_revision !== 'string' || !args.expected_source_revision.trim()) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REVISION_REQUIRED');
650
+ return scheduleRecoveryControllerHomeMigration(config, {
651
+ requestId: migrationRequestId,
652
+ canonicalSourceRoot: args.canonical_source_root.trim(),
653
+ expectedSourceRevision: args.expected_source_revision.trim(),
654
+ });
533
655
  }
534
656
  case 'restart_public_tunnel': {
535
657
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
536
- return repairPublicTunnel(config);
658
+ assertRecoveryGatewayMutationIdentity(config, args);
659
+ return mutationResponse(config, await repairPublicTunnel(config));
537
660
  }
538
661
  case 'reconnect_primary_connector': return reconnectMain(config);
539
662
  default: throw new Error('RECOVERY_TOOL_NOT_FOUND');
@@ -550,6 +673,24 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
550
673
  const recentMutations = new Map<string, number[]>();
551
674
  const oauthCodes = new Map<string, PendingOAuthCode>();
552
675
  const oauthClients = new Map<string, OAuthClient>();
676
+ const recoveryTools = RECOVERY_TOOLS.map((tool) => ({
677
+ ...tool,
678
+ securitySchemes: TOOL_SECURITY_SCHEMES,
679
+ _meta: { securitySchemes: TOOL_SECURITY_SCHEMES },
680
+ })) as unknown as Tool[];
681
+ const recoveryMcp = new RecoveryMcpSessionServer({
682
+ tools: recoveryTools,
683
+ dispatchTool: async (name, args, context) => {
684
+ if (name === 'attest_known_good' || name === 'rollback_previous' || name === 'restart_primary_runtime' || name === 'restart_primary_connector' || name === 'recover_primary_runtime' || name === 'activate_runtime_release' || name === 'stage_and_activate_runtime_release' || name === 'migrate_controller_home' || name === 'restart_public_tunnel') {
685
+ const now = Date.now();
686
+ const window = (recentMutations.get(context.remoteAddress) ?? []).filter((at) => now - at < 60_000);
687
+ if (window.length >= 3) throw new Error('Recovery mutation rate limit exceeded.');
688
+ window.push(now);
689
+ recentMutations.set(context.remoteAddress, window);
690
+ }
691
+ return await dispatchRecoveryTool(config, name, args);
692
+ },
693
+ });
553
694
  const server = createServer(async (request, response) => {
554
695
  if (request.method === 'OPTIONS') {
555
696
  response.statusCode = 204;
@@ -597,6 +738,7 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
597
738
  html(response, 401, renderAuthorizeForm(params, 'Invalid passphrase.'));
598
739
  return;
599
740
  }
741
+ reserveRecoveryOAuthCodeCapacity(oauthCodes, clientId);
600
742
  const code = randomUUID();
601
743
  oauthCodes.set(code, {
602
744
  clientId,
@@ -622,16 +764,26 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
622
764
  const raw = await readBody(request);
623
765
  body = raw.trim() ? JSON.parse(raw) as Record<string, unknown> : {};
624
766
  } catch { /* tolerate minimal DCR clients */ }
625
- const clientId = typeof body.client_id === 'string' && body.client_id ? body.client_id : randomUUID();
767
+ let registrationIdentity: ReturnType<typeof recoveryOAuthClientRegistrationIdentity>;
768
+ try {
769
+ registrationIdentity = recoveryOAuthClientRegistrationIdentity(body);
770
+ } catch (error) {
771
+ auditGateway({ oauth: 'register', outcome: 'invalid_client_metadata' });
772
+ json(response, 400, { error: 'invalid_client_metadata', error_description: error instanceof Error ? error.message : String(error) });
773
+ return;
774
+ }
775
+ const { clientId, owner } = registrationIdentity;
776
+ const reused = oauthClients.has(clientId);
626
777
  const clientSecret = randomUUID();
627
778
  const authMethod = tokenAuthMethod(body.token_endpoint_auth_method);
628
779
  oauthClients.set(clientId, {
629
780
  clientId,
630
781
  clientSecret: authMethod === 'none' ? undefined : clientSecret,
631
782
  tokenEndpointAuthMethod: authMethod,
783
+ owner,
632
784
  createdAt: Date.now(),
633
785
  });
634
- auditGateway({ oauth: 'register', outcome: 'created', token_endpoint_auth_method: authMethod });
786
+ auditGateway({ oauth: 'register', outcome: reused ? 'reused' : 'created', client_owner: owner, token_endpoint_auth_method: authMethod });
635
787
  json(response, 201, {
636
788
  ...body,
637
789
  client_id: clientId,
@@ -641,6 +793,7 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
641
793
  token_endpoint_auth_method: authMethod,
642
794
  grant_types: ['authorization_code'],
643
795
  response_types: ['code'],
796
+ ...(owner === 'recovery_verifier' ? { forge_client_owner: owner, forge_client_reused: reused } : {}),
644
797
  });
645
798
  return;
646
799
  }
@@ -665,7 +818,7 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
665
818
  const redirectUri = params.get('redirect_uri') ?? '';
666
819
  const verifier = params.get('code_verifier') ?? '';
667
820
  const pending = code ? oauthCodes.get(code) : undefined;
668
- if (!code || !pending || pending.clientId !== clientId || pending.redirectUri !== redirectUri || Date.now() - pending.createdAt > 10 * 60_000) {
821
+ if (!code || !pending || pending.clientId !== clientId || pending.redirectUri !== redirectUri || Date.now() - pending.createdAt > RECOVERY_OAUTH_CODE_TTL_MS) {
669
822
  auditGateway({ oauth: 'token', outcome: 'invalid_grant' });
670
823
  json(response, 400, { error: 'invalid_grant' });
671
824
  return;
@@ -701,34 +854,12 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
701
854
  const mcpRequest = classifyRecoveryMcpRequest(request, gatewayToken(config));
702
855
  if (mcpRequest === 'not_mcp' || mcpRequest === 'method_not_supported') { json(response, 404, { error: 'NOT_FOUND' }); return; }
703
856
  if (mcpRequest === 'auth_required') { response.setHeader('www-authenticate', recoveryWwwAuthenticate(request, config)); json(response, 401, recoveryUnauthorizedBody()); return; }
704
- if (!/^application\/json(?:\s*;|$)/i.test(String(request.headers['content-type'] ?? ''))) { json(response, 415, { error: 'RECOVERY_CONTENT_TYPE_REQUIRED' }); return; }
705
- let message: { id?: unknown; method?: unknown; params?: { name?: unknown; arguments?: unknown } };
706
- try { message = JSON.parse(await readBody(request)) as typeof message; } catch { json(response, 400, rpcError(null, -32700, 'Invalid JSON.')); return; }
707
- const id = message.id ?? null;
708
- if (message.method === 'initialize') { json(response, 200, { jsonrpc: '2.0', id, result: { protocolVersion: '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: 'forge-standalone-recovery', version: FORGE_VERSION } } }); return; }
709
- if (message.method === 'notifications/initialized') { response.statusCode = 202; response.end(); return; }
710
- if (message.method === 'tools/list') {
711
- const tools = RECOVERY_TOOLS.map((tool) => ({
712
- ...tool,
713
- securitySchemes: TOOL_SECURITY_SCHEMES,
714
- _meta: { securitySchemes: TOOL_SECURITY_SCHEMES },
715
- }));
716
- json(response, 200, { jsonrpc: '2.0', id, result: { tools } });
717
- return;
857
+ let body: unknown;
858
+ if (request.method === 'POST') {
859
+ if (!/^application\/json(?:\s*;|$)/i.test(String(request.headers['content-type'] ?? ''))) { json(response, 415, { error: 'RECOVERY_CONTENT_TYPE_REQUIRED' }); return; }
860
+ try { body = JSON.parse(await readBody(request)); } catch { json(response, 400, rpcError(null, -32700, 'Invalid JSON.')); return; }
718
861
  }
719
- if (message.method !== 'tools/call' || typeof message.params?.name !== 'string') { json(response, 200, rpcError(id, -32601, 'Unsupported MCP method.')); return; }
720
- const name = message.params.name;
721
- const args = message.params.arguments && typeof message.params.arguments === 'object' && !Array.isArray(message.params.arguments) ? message.params.arguments as Record<string, unknown> : {};
722
- if (name === 'attest_known_good' || name === 'rollback_previous' || name === 'restart_primary_runtime' || name === 'restart_primary_connector' || name === 'recover_primary_runtime' || name === 'activate_runtime_release' || name === 'stage_and_activate_runtime_release' || name === 'restart_public_tunnel') {
723
- const address = request.socket.remoteAddress ?? 'unknown'; const now = Date.now();
724
- const window = (recentMutations.get(address) ?? []).filter((at) => now - at < 60_000);
725
- if (window.length >= 3) { json(response, 429, rpcError(id, -32029, 'Recovery mutation rate limit exceeded.')); return; }
726
- window.push(now); recentMutations.set(address, window);
727
- }
728
- try {
729
- const payload = await dispatchRecoveryTool(config, name, args);
730
- json(response, 200, { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(payload) }], structuredContent: payload } });
731
- } catch (error) { json(response, 200, rpcError(id, -32602, error instanceof Error ? error.message : 'Recovery request rejected')); }
862
+ await recoveryMcp.handle(request, response, body);
732
863
  });
733
864
  await new Promise<void>((resolveListen, reject) => { server.once('error', reject); server.listen(gateway.port, gateway.host, () => resolveListen()); });
734
865
  runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'gateway');
@@ -0,0 +1,172 @@
1
+ import { createHash } from 'crypto';
2
+ import { isAbsolute, resolve } from 'path';
3
+
4
+ export const INDEPENDENT_HOST_RESCUE_ACTIONS = [
5
+ 'host_status',
6
+ 'wsl_status', 'wsl_start',
7
+ 'forge_source_status', 'controller_status',
8
+ 'runtime_status', 'runtime_start', 'runtime_restart',
9
+ 'connector_status', 'connector_start', 'connector_restart',
10
+ 'recovery_status', 'recovery_start', 'recovery_restart',
11
+ 'tunnel_status', 'tunnel_start', 'tunnel_restart',
12
+ 'forge_cloud_verify', 'full_recover',
13
+ ] as const;
14
+
15
+ export interface IndependentHostRescueConfig {
16
+ schemaVersion: 1;
17
+ wslDistro: string;
18
+ controllerHome: string;
19
+ sourceRoot: string;
20
+ rescueRoot: string;
21
+ runtimeUnit: string;
22
+ connectorUnit: string;
23
+ recoveryUnit: string;
24
+ tunnelClient: string;
25
+ tunnelAlias: string;
26
+ tunnelId: string;
27
+ tunnelRuntimeApiKeyRef: string;
28
+ tunnelProfile: string;
29
+ tunnelProfileDir: string;
30
+ tunnelAdminProfile: string;
31
+ localMcpUrl: string;
32
+ }
33
+
34
+ function required(value: string | undefined, code: string): string {
35
+ const normalized = value?.trim();
36
+ if (!normalized) throw new Error(code);
37
+ return normalized;
38
+ }
39
+
40
+ function safeToken(value: string, code: string): string {
41
+ if (/[\r\n'"`$;&|<>\\]/.test(value)) throw new Error(code);
42
+ return value;
43
+ }
44
+
45
+ function safeAbsolutePath(value: string | undefined, code: string): string {
46
+ const path = required(value, code);
47
+ if (!isAbsolute(path) || /[\r\n'"`$;&|<>]/.test(path)) throw new Error(code);
48
+ return resolve(path);
49
+ }
50
+
51
+ export function controllerServiceUnit(prefix: 'runtime' | 'connector', controllerHome: string): string {
52
+ const suffix = createHash('sha256').update(resolve(controllerHome)).digest('hex').slice(0, 12);
53
+ return prefix === 'runtime'
54
+ ? `com.moretea.forge.runtime.${suffix}.service`
55
+ : `com.moretea.forge.mcp-gateway.${suffix}.service`;
56
+ }
57
+
58
+ export function createIndependentHostRescueConfig(input: {
59
+ wslDistro: string;
60
+ controllerHome: string;
61
+ sourceRoot: string;
62
+ rescueRoot: string;
63
+ tunnelClient: string;
64
+ tunnelAlias: string;
65
+ tunnelId: string;
66
+ tunnelRuntimeApiKeyRef: string;
67
+ tunnelProfile?: string;
68
+ tunnelProfileDir: string;
69
+ tunnelAdminProfile?: string;
70
+ localMcpUrl?: string;
71
+ }): IndependentHostRescueConfig {
72
+ const wslDistro = safeToken(required(input.wslDistro, 'HOST_RESCUE_WSL_DISTRO_REQUIRED'), 'HOST_RESCUE_WSL_DISTRO_INVALID');
73
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(wslDistro)) throw new Error('HOST_RESCUE_WSL_DISTRO_INVALID');
74
+ const controllerHome = safeAbsolutePath(input.controllerHome, 'HOST_RESCUE_CONTROLLER_HOME_INVALID');
75
+ if (!/\/\.forge\/controller$/.test(controllerHome)) throw new Error('HOST_RESCUE_CONTROLLER_HOME_CANONICAL_REQUIRED');
76
+ const sourceRoot = safeAbsolutePath(input.sourceRoot, 'HOST_RESCUE_SOURCE_ROOT_INVALID');
77
+ const rescueRoot = safeAbsolutePath(input.rescueRoot, 'HOST_RESCUE_ROOT_INVALID');
78
+ if (!/\/\.forge-recovery$/.test(rescueRoot)) throw new Error('HOST_RESCUE_ROOT_CANONICAL_REQUIRED');
79
+ const tunnelClient = safeAbsolutePath(input.tunnelClient, 'HOST_RESCUE_TUNNEL_CLIENT_INVALID');
80
+ const tunnelAlias = safeToken(required(input.tunnelAlias, 'HOST_RESCUE_TUNNEL_ALIAS_REQUIRED'), 'HOST_RESCUE_TUNNEL_ALIAS_INVALID');
81
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(tunnelAlias)) throw new Error('HOST_RESCUE_TUNNEL_ALIAS_INVALID');
82
+ const tunnelId = safeToken(required(input.tunnelId, 'HOST_RESCUE_TUNNEL_ID_REQUIRED'), 'HOST_RESCUE_TUNNEL_ID_INVALID');
83
+ if (!/^tunnel_[0-9a-f]{32}$/.test(tunnelId)) throw new Error('HOST_RESCUE_TUNNEL_ID_INVALID');
84
+ const tunnelRuntimeApiKeyRef = safeToken(required(input.tunnelRuntimeApiKeyRef, 'HOST_RESCUE_TUNNEL_RUNTIME_KEY_REF_REQUIRED'), 'HOST_RESCUE_TUNNEL_RUNTIME_KEY_REF_INVALID');
85
+ if (!tunnelRuntimeApiKeyRef.startsWith(`file:${rescueRoot}/secrets/`)) throw new Error('HOST_RESCUE_TUNNEL_RUNTIME_KEY_REF_EXTERNAL_REQUIRED');
86
+ const tunnelProfile = safeToken(input.tunnelProfile?.trim() || tunnelAlias, 'HOST_RESCUE_TUNNEL_PROFILE_INVALID');
87
+ const tunnelProfileDir = safeAbsolutePath(input.tunnelProfileDir, 'HOST_RESCUE_TUNNEL_PROFILE_DIR_INVALID');
88
+ const tunnelAdminProfile = safeToken(input.tunnelAdminProfile?.trim() || 'default', 'HOST_RESCUE_TUNNEL_ADMIN_PROFILE_INVALID');
89
+ const localMcpUrl = input.localMcpUrl?.trim() || 'http://127.0.0.1:8767/mcp';
90
+ if (!/^http:\/\/127\.0\.0\.1:\d{1,5}\/mcp$/.test(localMcpUrl)) throw new Error('HOST_RESCUE_LOCAL_MCP_URL_INVALID');
91
+
92
+ return {
93
+ schemaVersion: 1,
94
+ wslDistro,
95
+ controllerHome,
96
+ sourceRoot,
97
+ rescueRoot,
98
+ runtimeUnit: controllerServiceUnit('runtime', controllerHome),
99
+ connectorUnit: controllerServiceUnit('connector', controllerHome),
100
+ recoveryUnit: 'com.moretea.forge.independent-recovery.service',
101
+ tunnelClient,
102
+ tunnelAlias,
103
+ tunnelId,
104
+ tunnelRuntimeApiKeyRef,
105
+ tunnelProfile,
106
+ tunnelProfileDir,
107
+ tunnelAdminProfile,
108
+ localMcpUrl,
109
+ };
110
+ }
111
+
112
+ function shellQuote(value: string): string {
113
+ return `'${value.replaceAll("'", "'\\''")}'`;
114
+ }
115
+
116
+ /** This config contains identities and paths only; tunnel credentials remain in tunnel-client's own profile store. */
117
+ export function renderIndependentHostRescueEnv(config: IndependentHostRescueConfig): string {
118
+ const values: Record<string, string> = {
119
+ WSL_DISTRO: config.wslDistro,
120
+ CONTROLLER_HOME: config.controllerHome,
121
+ SOURCE_ROOT: config.sourceRoot,
122
+ RUNTIME_UNIT: config.runtimeUnit,
123
+ CONNECTOR_UNIT: config.connectorUnit,
124
+ RECOVERY_UNIT: config.recoveryUnit,
125
+ TUNNEL_CLIENT: config.tunnelClient,
126
+ TUNNEL_ALIAS: config.tunnelAlias,
127
+ TUNNEL_ID: config.tunnelId,
128
+ TUNNEL_RUNTIME_API_KEY_REF: config.tunnelRuntimeApiKeyRef,
129
+ TUNNEL_PROFILE: config.tunnelProfile,
130
+ TUNNEL_PROFILE_DIR: config.tunnelProfileDir,
131
+ TUNNEL_ADMIN_PROFILE: config.tunnelAdminProfile,
132
+ MCP_LOCAL_URL: config.localMcpUrl,
133
+ };
134
+ return `${Object.entries(values).map(([key, value]) => `${key}=${shellQuote(value)}`).join('\n')}\n`;
135
+ }
136
+
137
+ export function renderIndependentHostRescueSystemdUnit(config: IndependentHostRescueConfig): string {
138
+ const executable = `${config.rescueRoot}/bin/forge-wsl-rescue`;
139
+ return [
140
+ '[Unit]',
141
+ 'Description=Forge independent Windows/WSL host rescue',
142
+ 'After=network-online.target',
143
+ '',
144
+ '[Service]',
145
+ 'Type=simple',
146
+ `Environment="FORGE_RESCUE_ROOT=${config.rescueRoot}"`,
147
+ `ExecStart=${executable} watch`,
148
+ // tunnel-client deliberately owns its managed tmux process. It must not
149
+ // keep this observer unit in deactivating state or be killed solely
150
+ // because the rescue watchdog itself is restarted.
151
+ 'KillMode=process',
152
+ 'Restart=always',
153
+ 'RestartSec=10',
154
+ '',
155
+ '[Install]',
156
+ 'WantedBy=default.target',
157
+ '',
158
+ ].join('\n');
159
+ }
160
+
161
+ export function renderWindowsHostRescueConfig(config: IndependentHostRescueConfig): string {
162
+ return `${JSON.stringify({
163
+ schemaVersion: config.schemaVersion,
164
+ distro: config.wslDistro,
165
+ wslRescuePath: `${config.rescueRoot}/bin/forge-wsl-rescue`,
166
+ controllerHome: config.controllerHome,
167
+ runtimeUnit: config.runtimeUnit,
168
+ connectorUnit: config.connectorUnit,
169
+ recoveryUnit: config.recoveryUnit,
170
+ tunnel: { alias: config.tunnelAlias, id: config.tunnelId, localMcpUrl: config.localMcpUrl },
171
+ }, null, 2)}\n`;
172
+ }