@moretea-labs/forge 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/AGENTS.md +70 -7
  2. package/CHANGELOG.md +14 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/THIRD_PARTY_NOTICES.md +1 -1
  6. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
  8. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  9. package/adapters/chatgpt/controller-round-host.ts +15 -25
  10. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  11. package/adapters/chatgpt/provider-delivery.ts +228 -1
  12. package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
  13. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  14. package/adapters/computer/chatgpt-target-router.ts +103 -0
  15. package/adapters/computer/chrome-extension/background.js +385 -0
  16. package/adapters/computer/chrome-extension/content.js +378 -0
  17. package/adapters/computer/chrome-extension/core.js +24 -0
  18. package/adapters/computer/chrome-extension/manifest.json +30 -0
  19. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  20. package/adapters/computer/interaction-target-authority.ts +142 -18
  21. package/adapters/mcp/auth.ts +0 -1
  22. package/adapters/mcp/instructions.ts +5 -4
  23. package/adapters/mcp/multi-repository.ts +3 -19
  24. package/adapters/mcp/oauth.ts +73 -0
  25. package/adapters/mcp/paths.ts +4 -1
  26. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  27. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  28. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  29. package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
  30. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  31. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  32. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  33. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  34. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  35. package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
  36. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  37. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
  38. package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
  39. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  40. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  41. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  42. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  43. package/adapters/mcp/runtime-gateway/router.ts +18 -504
  44. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  45. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
  46. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  47. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
  48. package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
  49. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
  50. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  51. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
  52. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  53. package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
  54. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  55. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  56. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  57. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  58. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  59. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  60. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  61. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  62. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  63. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  64. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  65. package/adapters/mcp/server.ts +67 -23
  66. package/adapters/mcp/setup.ts +1 -3
  67. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
  68. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
  69. package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
  70. package/adapters/mcp/toolset-names.ts +5 -0
  71. package/adapters/mcp/toolset.ts +6 -5
  72. package/adapters/mcp/transports/http-observation.ts +365 -0
  73. package/adapters/mcp/transports/http.ts +154 -677
  74. package/adapters/mcp/transports/oauth-http.ts +322 -0
  75. package/adapters/mcp/transports/session-registry.ts +6 -0
  76. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  77. package/assets/plugin-registry.v1.json +6 -4
  78. package/assets/reference-configs/agentic-development-flow.md +25 -0
  79. package/assets/reference-configs/global-working-rules.md +19 -0
  80. package/assets/skill-version.json +2 -2
  81. package/docs/architecture/CURRENT.md +111 -41
  82. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  83. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  84. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  85. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  86. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  87. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  88. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  89. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  90. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  91. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  92. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  93. package/docs/architecture/domains/verification.md +1 -0
  94. package/docs/architecture/domains/workflow-engine.md +6 -0
  95. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  96. package/docs/forge-plugin-management.md +2 -2
  97. package/docs/operations/controller-performance-and-502.md +17 -0
  98. package/docs/operations/controller-reliability-runbook.md +2 -2
  99. package/docs/operations/features.md +1 -1
  100. package/docs/operations/features.zh-CN.md +1 -1
  101. package/docs/operations/homebrew.md +2 -2
  102. package/docs/operations/mcp-tool-exposure.md +2 -0
  103. package/docs/operations/releasing.md +2 -0
  104. package/docs/operations/standalone-disaster-recovery.md +14 -6
  105. package/docs/wiki/Implementation.md +1 -1
  106. package/package.json +25 -14
  107. package/packages/kernel/cognition/api/index.ts +6 -0
  108. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  109. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  110. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  111. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  112. package/packages/kernel/cognition/domain/learning.ts +106 -0
  113. package/packages/kernel/cognition/domain/memory.ts +254 -0
  114. package/packages/kernel/controller/api/index.ts +1 -0
  115. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  116. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  117. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  118. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  119. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  120. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  121. package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
  122. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  123. package/packages/kernel/identity/api/index.ts +10 -0
  124. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  125. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  126. package/packages/kernel/identity/domain/grant.ts +72 -0
  127. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  128. package/packages/kernel/identity/domain/user-request.ts +93 -0
  129. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  130. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  131. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  132. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  133. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  134. package/packages/kernel/scheduler/api/index.ts +0 -2
  135. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  136. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  137. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  138. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
  139. package/packages/kernel/work/api/index.ts +3 -2
  140. package/packages/kernel/work/application/work-service.ts +17 -42
  141. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  142. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  143. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  144. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  145. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  146. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  147. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  148. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  149. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  150. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  151. package/packages/kernel/work/domain/state-machine.ts +25 -63
  152. package/packages/kernel/work/domain/types.ts +57 -51
  153. package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
  154. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  155. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  156. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  157. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  158. package/packages/plugin-runtime/computer/index.ts +2 -0
  159. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  160. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  161. package/packages/protocols/computer/contract.ts +27 -1
  162. package/scripts/activate-source-baseline.ts +7 -9
  163. package/scripts/canary-controller-semantic-wait.ts +12 -24
  164. package/scripts/check-mcp-compatibility.ts +48 -136
  165. package/scripts/check-runtime-architecture.mjs +622 -217
  166. package/scripts/forge-local-recovery-helper.mjs +93 -15
  167. package/scripts/lib/project-init-lib.sh +3 -0
  168. package/scripts/open-source-audit-allowlist.txt +1 -2
  169. package/scripts/run-bounded-command.mjs +5 -0
  170. package/scripts/run-governed-gate.ts +0 -1
  171. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  172. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  173. package/scripts/smoke-mcp-http-runtime.ts +5 -0
  174. package/scripts/smoke-runtime-recovery.ts +12 -43
  175. package/scripts/smoke-schedule-engine.ts +23 -15
  176. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  177. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  178. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  179. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  180. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  181. package/scripts/smoke-workflow-supervisor.ts +103 -0
  182. package/scripts/verify-forge-runtime.sh +2 -1
  183. package/src/cli/agent-jobs/executor-health.ts +11 -34
  184. package/src/cli/agent-jobs/integration.ts +18 -49
  185. package/src/cli/agent-jobs/job-manager.ts +7 -3
  186. package/src/cli/commands/brain-assistant.ts +27 -13
  187. package/src/cli/commands/brain.ts +13 -15
  188. package/src/cli/commands/chatgpt.ts +1 -163
  189. package/src/cli/commands/recovery.ts +135 -121
  190. package/src/cli/commands/runtime.ts +3 -3
  191. package/src/cli/commands/setup-profile.ts +8 -0
  192. package/src/cli/controller/check-runner.ts +13 -2
  193. package/src/cli/controller/completion-backlog.ts +3 -3
  194. package/src/cli/controller/completion-orchestrator.ts +4 -43
  195. package/src/cli/controller/completion-target.ts +3 -72
  196. package/src/cli/controller/context/source-materializer.ts +81 -100
  197. package/src/cli/controller/context-pack.ts +10 -8
  198. package/src/cli/controller/execution-completion.ts +4 -7
  199. package/src/cli/controller/execution-policy.ts +36 -153
  200. package/src/cli/controller/governance.ts +2 -3
  201. package/src/cli/controller/issue-store.ts +4 -99
  202. package/src/cli/controller/launch-agents.ts +59 -18
  203. package/src/cli/controller/operational-plan.ts +24 -51
  204. package/src/cli/controller/runtime-config.ts +5 -1
  205. package/src/cli/controller/stuck-state-migration.ts +2 -3
  206. package/src/cli/controller/systemd-user.ts +39 -1
  207. package/src/cli/editing/edit-session.ts +1 -7
  208. package/src/cli/local-bridge/console-automations.ts +18 -13
  209. package/src/cli/local-bridge/console-view-models.ts +3 -12
  210. package/src/cli/local-bridge/facade-api.ts +64 -191
  211. package/src/cli/local-bridge/server.ts +24 -147
  212. package/src/cli/local-bridge/types.ts +0 -1
  213. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  214. package/src/cli/repositories/command-executor.ts +29 -23
  215. package/src/cli/repositories/command-scope.ts +35 -16
  216. package/src/cli/repositories/controller-home.ts +48 -0
  217. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  218. package/src/cli/repositories/git-command-executor.ts +2 -2
  219. package/src/cli/repositories/registry.ts +77 -11
  220. package/src/cli/repositories/structured-git.ts +62 -2
  221. package/src/cli/repository/session-cache.ts +92 -6
  222. package/src/runtime/context/assistant-context.ts +20 -7
  223. package/src/runtime/context/assistant-work-context.ts +89 -27
  224. package/src/runtime/context/automatic-learning.ts +565 -0
  225. package/src/runtime/context/cognitive-attention.ts +173 -0
  226. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  227. package/src/runtime/context/context-closure.ts +46 -9
  228. package/src/runtime/context/context-pack-process.ts +103 -0
  229. package/src/runtime/context/context-plane.ts +1 -9
  230. package/src/runtime/context/direct-controller-learning.ts +298 -0
  231. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  232. package/src/runtime/context/semantic-navigation.ts +6 -34
  233. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  234. package/src/runtime/context/typescript-navigation.ts +89 -3
  235. package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
  236. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  237. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  238. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  239. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  240. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  241. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  242. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  243. package/src/runtime/control-plane/execution/validation.ts +6 -8
  244. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  245. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  246. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  247. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  248. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  249. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  250. package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
  251. package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
  252. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  253. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  254. package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
  255. package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
  256. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  257. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  258. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
  259. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
  260. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  261. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  262. package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
  263. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  264. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  265. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  266. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  267. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  268. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  269. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  270. package/src/runtime/control-plane/facade/index.ts +0 -1
  271. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  272. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  273. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  274. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  275. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  276. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  277. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  278. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  279. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  280. package/src/runtime/control-plane/facade/types.ts +49 -108
  281. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  282. package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
  283. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  284. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  285. package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
  286. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
  287. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  288. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  289. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  290. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  291. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  292. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
  293. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  294. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  295. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  296. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  297. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  298. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  299. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  300. package/src/runtime/control-plane/release-retention.ts +135 -47
  301. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  302. package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
  303. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  304. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  305. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  306. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  307. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  308. package/src/runtime/evidence/artifact-store.ts +158 -40
  309. package/src/runtime/evidence/evidence-store.ts +112 -8
  310. package/src/runtime/evidence/output-handle-index.ts +225 -0
  311. package/src/runtime/evidence/result-store.ts +158 -44
  312. package/src/runtime/execution/jobs/store.ts +3 -4
  313. package/src/runtime/execution/managed-workspace.ts +57 -0
  314. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  315. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  316. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  317. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  318. package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
  319. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  320. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  321. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  322. package/src/runtime/execution/process-runtime/index.ts +3 -0
  323. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  324. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  325. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  326. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  327. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  328. package/src/runtime/execution/process-runtime/runtime.ts +35 -2
  329. package/src/runtime/execution/process-runtime/store.ts +30 -2
  330. package/src/runtime/execution/process-runtime/types.ts +6 -1
  331. package/src/runtime/execution/workers/executor.ts +2 -6
  332. package/src/runtime/platform/service-inventory.ts +130 -0
  333. package/src/runtime/platform/service-manager.ts +8 -0
  334. package/src/runtime/plugins/action-application.ts +85 -28
  335. package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
  336. package/src/runtime/plugins/browser-adapter.ts +757 -34
  337. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  338. package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
  339. package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
  340. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  341. package/src/runtime/plugins/browser-registration.ts +16 -0
  342. package/src/runtime/plugins/browser-runtime.ts +14 -1
  343. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  344. package/src/runtime/plugins/browser-session-store.ts +46 -76
  345. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  346. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  347. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  348. package/src/runtime/plugins/computer-registration.ts +222 -11
  349. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  350. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  351. package/src/runtime/plugins/external-adapter.ts +42 -6
  352. package/src/runtime/plugins/external-registration.ts +21 -1
  353. package/src/runtime/plugins/gmail-adapter.ts +23 -2
  354. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  355. package/src/runtime/plugins/google-shared.ts +14 -7
  356. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  357. package/src/runtime/plugins/ios-adapter.ts +1 -1
  358. package/src/runtime/plugins/lightweight-action.ts +208 -2
  359. package/src/runtime/plugins/local-system-adapter.ts +12 -2
  360. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  361. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  362. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  363. package/src/runtime/plugins/store.ts +602 -428
  364. package/src/runtime/plugins/types.ts +20 -0
  365. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  366. package/src/runtime/projections/invalidation.ts +7 -3
  367. package/src/runtime/projections/materialized-view.ts +80 -36
  368. package/src/runtime/recovery/actions.ts +3 -3
  369. package/src/runtime/recovery/capability-action-application.ts +46 -45
  370. package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
  371. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  372. package/src/runtime/recovery/store.ts +29 -11
  373. package/src/runtime/release/release-coordinator.ts +296 -0
  374. package/src/runtime/release/release-session.ts +716 -0
  375. package/src/runtime/release/release-state-migration.ts +25 -0
  376. package/src/runtime/resources/leases/store.ts +56 -25
  377. package/src/runtime/root/computer-composition.ts +191 -6
  378. package/src/runtime/root/controller-progression-composition.ts +192 -0
  379. package/src/runtime/root/controller-round-composition.ts +20 -1
  380. package/src/runtime/root/deployment-topology.ts +106 -0
  381. package/src/runtime/root/entry.ts +4 -3
  382. package/src/runtime/root/known-good-recovery.ts +184 -0
  383. package/src/runtime/root/local-bridge.ts +0 -1
  384. package/src/runtime/root/mcp-transport.ts +111 -18
  385. package/src/runtime/root/ownership.ts +261 -9
  386. package/src/runtime/root/package-connector-service.ts +105 -19
  387. package/src/runtime/root/package-runtime-release.ts +117 -18
  388. package/src/runtime/root/package-runtime-service.ts +17 -11
  389. package/src/runtime/root/release-execution-canary.ts +44 -12
  390. package/src/runtime/root/release-loader.ts +90 -8
  391. package/src/runtime/root/release-manifest.ts +80 -6
  392. package/src/runtime/root/release-materialize.ts +372 -28
  393. package/src/runtime/root/release-store.ts +395 -36
  394. package/src/runtime/root/runtime-lane.ts +191 -0
  395. package/src/runtime/root/runtime.ts +159 -27
  396. package/src/runtime/root/scheduled-controller-composition.ts +28 -3
  397. package/src/runtime/root/service-runner.ts +2 -2
  398. package/src/runtime/root/service.ts +36 -33
  399. package/src/runtime/root/status.ts +48 -0
  400. package/src/runtime/root/types.ts +32 -3
  401. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  402. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  403. package/src/runtime/root/write-fence.ts +50 -2
  404. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  405. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  406. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  407. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  408. package/src/runtime/shared/process-environment.ts +20 -9
  409. package/src/runtime/shared/response-budget.ts +1 -3
  410. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  411. package/src/runtime/standalone-recovery/core.ts +3702 -342
  412. package/src/runtime/standalone-recovery/entry.ts +469 -39
  413. package/src/runtime/standalone-recovery/installer.ts +262 -148
  414. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  415. package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
  416. package/src/runtime/standalone-recovery/performance.ts +54 -16
  417. package/src/runtime/standalone-recovery/release.ts +53 -5
  418. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  419. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  420. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  421. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
  422. package/src/runtime/workflow/schedules/engine.ts +138 -172
  423. package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
  424. package/src/runtime/workflows/runtime.ts +15 -19
  425. package/src/testing/test-governance.ts +5 -1
  426. package/supervisor/chatgpt-conversation.ts +23 -0
  427. package/supervisor/client.ts +201 -0
  428. package/supervisor/control-plane.ts +868 -0
  429. package/supervisor/entry.ts +15 -0
  430. package/supervisor/forge-validators.ts +80 -0
  431. package/supervisor/index.ts +5 -0
  432. package/supervisor/native-browser-adapter.ts +786 -0
  433. package/supervisor/native-messaging/host.ts +39 -0
  434. package/supervisor/native-messaging/manifest.ts +26 -0
  435. package/supervisor/native-messaging/protocol.ts +27 -0
  436. package/supervisor/paths.ts +18 -0
  437. package/supervisor/protocol.ts +202 -0
  438. package/supervisor/server.ts +404 -0
  439. package/supervisor/store.ts +1451 -0
  440. package/supervisor/tsconfig.json +8 -0
  441. package/supervisor/types.ts +255 -0
  442. package/adapters/chatgpt/controller-host.ts +0 -119
  443. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  444. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  445. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  446. package/packages/kernel/progression/api/index.ts +0 -2
  447. package/packages/kernel/progression/application/projector.ts +0 -211
  448. package/packages/kernel/progression/domain/types.ts +0 -116
  449. package/packages/kernel/progression/index.ts +0 -1
  450. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  451. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  452. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  453. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  454. package/scripts/benchmark-background-check-overlap.ts +0 -228
  455. package/scripts/benchmark-computer-hotpath.ts +0 -255
  456. package/scripts/benchmark-controller-hotpath.ts +0 -212
  457. package/scripts/benchmark-controller-round2.ts +0 -191
  458. package/scripts/benchmark-ios-agent-device.ts +0 -511
  459. package/scripts/benchmark-mcp-host.sh +0 -117
  460. package/scripts/benchmark-mcp-transport.ts +0 -169
  461. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  462. package/scripts/benchmark-operational-shadow.ts +0 -28
  463. package/scripts/benchmark-quality-harness.ts +0 -146
  464. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  465. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  466. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  467. package/scripts/benchmark-thin-harness.ts +0 -251
  468. package/scripts/benchmark-typescript-navigation.ts +0 -186
  469. package/scripts/benchmark-work-efficiency.ts +0 -334
  470. package/scripts/build-controller-ui.ts +0 -28
  471. package/scripts/check-npm-release.sh +0 -32
  472. package/scripts/check-public-release-surface.sh +0 -19
  473. package/scripts/check-release-published.sh +0 -57
  474. package/scripts/check-release-readiness.sh +0 -37
  475. package/scripts/check-release-version.mjs +0 -74
  476. package/scripts/check-tarball-install-smoke.sh +0 -150
  477. package/scripts/package-source-archive.sh +0 -147
  478. package/scripts/public-release-files.txt +0 -39
  479. package/scripts/public-release.ts +0 -174
  480. package/scripts/publish-release-tarball.sh +0 -27
  481. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  482. package/scripts/stage-runtime-release.ts +0 -44
  483. package/src/cli/controller/progress.ts +0 -656
  484. package/src/cli/controller/work-mode.ts +0 -181
  485. package/src/cli/local-bridge/console-assets.ts +0 -17
  486. package/src/cli/local-bridge/dashboard.ts +0 -17
  487. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  488. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  489. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  490. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  491. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  492. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  493. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  494. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  495. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  496. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  497. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  498. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  499. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  500. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  501. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  502. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  503. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  504. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  505. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  506. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  507. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  508. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  509. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  510. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  511. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  512. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  513. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  514. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  515. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  516. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  517. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  518. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  519. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  520. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  521. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  522. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  523. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  524. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  525. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  526. package/src/runtime/control-plane/routing/index.ts +0 -1
  527. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  528. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,18 +1,31 @@
1
1
  import { createHash, randomUUID } from 'crypto';
2
+ import { existsSync } from 'fs';
2
3
  import { createServer, type IncomingMessage, type ServerResponse } from 'http';
3
4
  import { basename, join, resolve } from 'path';
4
5
  import { pathToFileURL } from 'url';
5
6
  import { readMcpServiceOAuthPassphrase } from '../../../adapters/mcp/auth';
6
7
  import { FORGE_VERSION } from '../../version';
7
8
  import type { Tool } from '@modelcontextprotocol/server';
8
- import { RecoveryMcpSessionServer } from './mcp-server';
9
+ import { RecoveryMcpServer } from './mcp-server';
9
10
  import {
10
11
  activateRuntimeRelease,
12
+ activatePinnedRuntimeRelease,
13
+ bootAndVerifyConfiguredRuntimeReleaseSessionCandidate,
14
+ cancelConfiguredRuntimeReleaseSession,
15
+ cutoverConfiguredRuntimeReleaseSession,
11
16
  assertRecoveryMutationIdentity,
12
17
  attestKnownGood,
18
+ configuredRuntimeReleaseSourceState,
19
+ measureConfiguredRuntimePerformance,
20
+ RECOVERY_INTERNAL_PERFORMANCE_COMMAND,
13
21
  diagnose,
14
22
  gatewayToken,
23
+ listRecoveryHistory,
15
24
  listReleases,
25
+ pinRuntimeRelease,
26
+ prepareConfiguredRuntimeReleaseSession,
27
+ promoteConfiguredRuntimeReleaseSessionKnownGood,
28
+ rollbackConfiguredRuntimeReleaseSession,
16
29
  loadRecoveryConfig,
17
30
  loadWatchdogState,
18
31
  saveWatchdogState,
@@ -22,12 +35,14 @@ import {
22
35
  repairPublicTunnel,
23
36
  restartPrimaryConnector,
24
37
  restartPrimaryRuntime,
25
- restartRecoveryWatchdog,
26
38
  stageAndActivateConfiguredRuntimeRelease,
39
+ unpinRuntimeRelease,
27
40
  rollbackPrevious,
28
41
  secureEqual,
29
42
  runtimeStatus,
30
43
  verifyStableRuntime,
44
+ verifyConfiguredRuntimeReleaseSessionStaticGates,
45
+ probeReleaseCutoverReadiness,
31
46
  watchdogTick,
32
47
  type WatchdogState,
33
48
  type RecoveryConfig,
@@ -41,11 +56,40 @@ import {
41
56
  } from './watchdog-heartbeat';
42
57
  import {
43
58
  RECOVERY_RELEASE_ROLE_CANARY_ARG,
59
+ readCurrentRecoveryRelease,
60
+ recoveryGatewayIsDraining,
44
61
  writeRecoveryRuntimeIdentity,
45
62
  type RecoveryRuntimeIdentity,
46
63
  type RecoveryRuntimeRole,
47
64
  } from './release';
48
65
  import { RECOVERY_MUTATION_IDENTITY_CONTRACT, RECOVERY_MUTATION_IDENTITY_FIELDS } from './mutation-identity-contract';
66
+ import { readReleaseSession } from '../release/release-session';
67
+ import { migrateReleaseDurableState } from '../release/release-state-migration';
68
+ import {
69
+ advanceConfiguredRuntimeRelease,
70
+ advanceConfiguredRuntimeReleaseStep,
71
+ decideConfiguredRuntimeReleaseReconciliation,
72
+ type RuntimeReleaseProvider,
73
+ } from '../release/release-coordinator';
74
+ import { runBoundedChild } from '../shared/bounded-child-supervisor';
75
+ import { runtimeAuthorityFreeEnvironment } from '../shared/process-environment';
76
+
77
+ const RECOVERY_RUNTIME_RELEASE_PROVIDER: RuntimeReleaseProvider<RecoveryConfig> = {
78
+ prepare: (config, requestId) => prepareConfiguredRuntimeReleaseSession(config, {}, requestId),
79
+ verifyStatic: (config, sessionId, requestId) => verifyConfiguredRuntimeReleaseSessionStaticGates(config, sessionId, requestId),
80
+ verifyCandidate: (config, sessionId, requestId) => bootAndVerifyConfiguredRuntimeReleaseSessionCandidate(config, sessionId, requestId),
81
+ cutover: (config, sessionId, requestId) => cutoverConfiguredRuntimeReleaseSession(config, sessionId, requestId),
82
+ promoteKnownGood: (config, sessionId, requestId) => promoteConfiguredRuntimeReleaseSessionKnownGood(config, sessionId, {}, requestId),
83
+ };
84
+
85
+ const RECOVERY_INTERNAL_RELEASE_RECONCILE_COMMAND = '__reconcile-runtime-release-step';
86
+ const RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS = 15_000;
87
+ /**
88
+ * Upper bound for retrying one non-converging release step. A stuck release must
89
+ * still be retried, but never at a cadence that can starve the daemon that owns it.
90
+ */
91
+ const RECOVERY_AUTOMATIC_RELEASE_FAILURE_BACKOFF_MAX_MS = 15 * 60_000;
92
+ const RECOVERY_AUTOMATIC_RELEASE_STEP_TIMEOUT_MS = 15 * 60_000;
49
93
 
50
94
  function option(name: string): string | undefined {
51
95
  const index = process.argv.indexOf(name);
@@ -64,6 +108,7 @@ import { runRecoveryControllerHomeMigrationWorker, scheduleRecoveryControllerHom
64
108
 
65
109
  export const RECOVERY_CLI_COMMANDS = [
66
110
  'status',
111
+ 'daemon',
67
112
  'verify',
68
113
  'verify-external',
69
114
  'list-releases',
@@ -74,6 +119,15 @@ export const RECOVERY_CLI_COMMANDS = [
74
119
  'recover-primary-runtime',
75
120
  'activate-runtime-release',
76
121
  'stage-and-activate-runtime-release',
122
+ 'release-session-status',
123
+ 'release-session-advance',
124
+ 'release-session-prepare',
125
+ 'release-session-static-verify',
126
+ 'release-session-candidate-verify',
127
+ 'release-session-cutover',
128
+ 'release-session-cancel',
129
+ 'release-session-rollback',
130
+ 'release-session-known-good',
77
131
  'migrate-controller-home-worker',
78
132
  'restart-public-tunnel',
79
133
  'diagnose',
@@ -94,6 +148,10 @@ export function recoveryRuntimeRoleFromExecutable(executable = process.execPath)
94
148
  async function cli(): Promise<void> {
95
149
  const command = process.argv.find((value, index) => index >= 2 && !value.startsWith('-') && process.argv[index - 1] !== '--controller-home') ?? 'status';
96
150
  const config = loadRecoveryConfig(controllerHome(), option('--config'));
151
+ // Internal durable state has one current schema. Every Recovery entrypoint,
152
+ // including gateway/watchdog startup, crosses the same migration boundary as
153
+ // Canonical Runtime before reading or mutating release semantics.
154
+ migrateReleaseDurableState(config.controllerHome);
97
155
  const executableRole = recoveryRuntimeRoleFromExecutable();
98
156
  if (executableRole) {
99
157
  if (command !== executableRole) {
@@ -107,8 +165,52 @@ async function cli(): Promise<void> {
107
165
  else await startWatchdog(config);
108
166
  return;
109
167
  }
168
+ if (command === RECOVERY_INTERNAL_PERFORMANCE_COMMAND) {
169
+ try {
170
+ output({ schemaVersion: 1, ok: true, evidence: await measureConfiguredRuntimePerformance(config) });
171
+ } catch (error) {
172
+ const detail = error instanceof Error ? error.message : String(error);
173
+ output({
174
+ schemaVersion: 1,
175
+ ok: false,
176
+ error: /^RECOVERY_PERFORMANCE_(?:UNKNOWN|REJECTED):/.test(detail)
177
+ ? detail
178
+ : 'RECOVERY_PERFORMANCE_UNKNOWN: isolated sampler failed',
179
+ });
180
+ }
181
+ return;
182
+ }
183
+ if (command === RECOVERY_INTERNAL_RELEASE_RECONCILE_COMMAND) {
184
+ try {
185
+ const decision = decideConfiguredRuntimeReleaseReconciliation(
186
+ config.controllerHome,
187
+ () => configuredRuntimeReleaseSourceState(config),
188
+ );
189
+ if (!decision.required) {
190
+ output({ schemaVersion: 1, ok: true, attempted: false, noOp: true, decision });
191
+ return;
192
+ }
193
+ const result = await advanceConfiguredRuntimeReleaseStep(
194
+ config,
195
+ RECOVERY_RUNTIME_RELEASE_PROVIDER,
196
+ `recovery-auto-release:${process.pid}:${Date.now()}`,
197
+ );
198
+ output({ schemaVersion: 1, ok: result.ok, attempted: result.attempted, decision, result });
199
+ } catch (error) {
200
+ output({ schemaVersion: 1, ok: false, attempted: false, error: error instanceof Error ? error.message : String(error) });
201
+ }
202
+ return;
203
+ }
110
204
  switch (command) {
111
205
  case 'status': output(await runtimeStatus(config)); return;
206
+ case 'daemon': {
207
+ if (process.argv.includes(RECOVERY_RELEASE_ROLE_CANARY_ARG)) {
208
+ output({ status: 'ok', role: 'daemon', executable: basename(process.execPath) });
209
+ return;
210
+ }
211
+ await startRecoveryDaemon(config);
212
+ return;
213
+ }
112
214
  case 'verify': output(await verifyStableRuntime(config)); return;
113
215
  case 'verify-external': {
114
216
  const verified = await verifyStableRuntime(config);
@@ -118,7 +220,7 @@ async function cli(): Promise<void> {
118
220
  case 'list-releases': output(await listReleases(config)); return;
119
221
  case 'attest-known-good': output(await attestKnownGood(config)); return;
120
222
  case 'rollback-previous': output(await rollbackPrevious(config)); return;
121
- case 'restart-primary-runtime': output(await restartPrimaryRuntime(config)); return;
223
+ case 'restart-primary-runtime': output(await restartPrimaryRuntime(config, { forceHealthyRestart: true })); return;
122
224
  case 'restart-primary-connector': output(await restartPrimaryConnector(config)); return;
123
225
  case 'recover-primary-runtime': output(await recoverPrimaryRuntime(config)); return;
124
226
  case 'activate-runtime-release': {
@@ -137,6 +239,50 @@ async function cli(): Promise<void> {
137
239
  return;
138
240
  }
139
241
  case 'stage-and-activate-runtime-release': output(await stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-cli:${process.pid}:${Date.now()}`)); return;
242
+ case 'release-session-status': {
243
+ const sessionId = option('--session-id');
244
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
245
+ output(readReleaseSession(config.controllerHome, sessionId) ?? null);
246
+ return;
247
+ }
248
+ case 'release-session-advance': output(await advanceConfiguredRuntimeRelease(config, RECOVERY_RUNTIME_RELEASE_PROVIDER, `recovery-cli:${process.pid}:${Date.now()}`)); return;
249
+ case 'release-session-prepare': output(await prepareConfiguredRuntimeReleaseSession(config, {}, `recovery-cli:${process.pid}:${Date.now()}`)); return;
250
+ case 'release-session-static-verify': {
251
+ const sessionId = option('--session-id');
252
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
253
+ output(await verifyConfiguredRuntimeReleaseSessionStaticGates(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
254
+ return;
255
+ }
256
+ case 'release-session-candidate-verify': {
257
+ const sessionId = option('--session-id');
258
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
259
+ output(await bootAndVerifyConfiguredRuntimeReleaseSessionCandidate(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
260
+ return;
261
+ }
262
+ case 'release-session-cutover': {
263
+ const sessionId = option('--session-id');
264
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
265
+ output(await cutoverConfiguredRuntimeReleaseSession(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
266
+ return;
267
+ }
268
+ case 'release-session-cancel': {
269
+ const sessionId = option('--session-id');
270
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
271
+ output(await cancelConfiguredRuntimeReleaseSession(config, sessionId, `recovery-cli:${process.pid}:${Date.now()}`));
272
+ return;
273
+ }
274
+ case 'release-session-rollback': {
275
+ const sessionId = option('--session-id');
276
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
277
+ output(await rollbackConfiguredRuntimeReleaseSession(config, sessionId, {}, `recovery-cli:${process.pid}:${Date.now()}`));
278
+ return;
279
+ }
280
+ case 'release-session-known-good': {
281
+ const sessionId = option('--session-id');
282
+ if (!sessionId) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
283
+ output(await promoteConfiguredRuntimeReleaseSessionKnownGood(config, sessionId, {}, `recovery-cli:${process.pid}:${Date.now()}`));
284
+ return;
285
+ }
140
286
  case 'migrate-controller-home-worker': {
141
287
  const canonicalSourceRoot = option('--canonical-source-root');
142
288
  const expectedSourceRevision = option('--expected-source-revision');
@@ -178,8 +324,8 @@ export function resetWatchdogStateForRecoveryRelease(
178
324
  };
179
325
  }
180
326
 
181
- async function startWatchdog(config: RecoveryConfig): Promise<void> {
182
- const runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'watchdog');
327
+ async function startWatchdog(config: RecoveryConfig, daemonIdentity?: RecoveryRuntimeIdentity): Promise<void> {
328
+ const runtimeIdentity = daemonIdentity ?? writeRecoveryRuntimeIdentity(config.controllerHome, 'watchdog');
183
329
  let heartbeat: RecoveryWatchdogHeartbeat = createRecoveryWatchdogHeartbeat(runtimeIdentity);
184
330
  const persistHeartbeat = (patch: Partial<RecoveryWatchdogHeartbeat> = {}) => {
185
331
  heartbeat = writeRecoveryWatchdogHeartbeat(config.controllerHome, { ...heartbeat, ...patch });
@@ -286,16 +432,29 @@ function mutationInputSchema(
286
432
 
287
433
  export const RECOVERY_TOOLS = [
288
434
  { name: 'runtime_status', description: 'Read canonical Runtime ownership, readiness, endpoint, release observation, and exact Recovery machine identity.', inputSchema: { type: 'object', additionalProperties: false } },
435
+ { name: 'list_recovery_history', description: 'Read a bounded sanitized tail of canonical standalone Recovery audit events.', inputSchema: { type: 'object', properties: { limit: { type: 'integer', minimum: 1, maximum: 50 } }, additionalProperties: false } },
289
436
  { name: 'list_releases', description: 'Read active, previous, and known-good whole-Runtime release evidence.', inputSchema: { type: 'object', additionalProperties: false } },
290
437
  { name: 'verify_stable_runtime', description: 'Run independent stable runtime verification.', inputSchema: { type: 'object', additionalProperties: false } },
291
438
  { name: 'verify_external_runtime', description: 'Verify the external primary MCP endpoint.', inputSchema: { type: 'object', additionalProperties: false } },
292
439
  { name: 'attest_known_good', description: 'Record the active release as known-good only after full independent verification succeeds.', inputSchema: mutationInputSchema() },
293
440
  { 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() },
441
+ { name: 'restart_primary_runtime', description: 'Explicitly restart the installed canonical Forge Runtime service, including when currently healthy, then require whole-Runtime verification after exact Recovery machine identity matches.', inputSchema: mutationInputSchema() },
295
442
  { 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
443
  { 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
444
  { 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() },
445
+ { name: 'pin_runtime_release', description: 'Pin one extant legacy/home-bound immutable Runtime release so retention preserves it for explicit Runtime-only recovery activation. Portable source candidates are rejected and must use ReleaseSession.', inputSchema: mutationInputSchema({ release_path: { type: 'string', minLength: 8, maxLength: 1024, description: 'Absolute path to the immutable Runtime release directory or manifest.' } }, ['release_path']) },
446
+ { name: 'unpin_runtime_release', description: 'Remove the explicit stable Runtime retention pin without deleting or activating any release.', inputSchema: mutationInputSchema() },
447
+ { name: 'activate_pinned_runtime_release', description: 'Activate the explicitly pinned legacy/home-bound Runtime release without restoring an older SQLite backup; portable source candidates are rejected and must use ReleaseSession.', inputSchema: mutationInputSchema({ expected_active_release_id: { type: 'string', minLength: 1, maxLength: 256 }, expected_authority_revision: { type: 'integer', minimum: 1 } }, ['expected_active_release_id', 'expected_authority_revision']) },
448
+ { name: 'stage_and_activate_runtime_release', description: 'Compatibility alias: freeze the fixed configured source, create isolated Candidate B, and build one portable immutable Runtime release into a durable ReleaseSession. It no longer activates Stable A.', inputSchema: mutationInputSchema() },
449
+ { name: 'release_session_status', description: 'Read one durable ReleaseSession and its exact Stable A/Candidate B phase and evidence.', inputSchema: { type: 'object', properties: { session_id: { type: 'string', minLength: 8, maxLength: 120 } }, required: ['session_id'], additionalProperties: false } },
450
+ { name: 'advance_runtime_release_session', description: 'Run the single active normal Runtime ReleaseSession autonomously through all immediately executable phases until known-good or a genuine provider/safety boundary. ReleaseSession remains the sole durable progression authority and no Work is created per phase.', inputSchema: mutationInputSchema() },
451
+ { name: 'prepare_runtime_release_session', description: 'Freeze configured source and Stable A authority, create isolated Candidate B, and build a portable byte-identifiable Runtime artifact without stopping Stable A.', inputSchema: mutationInputSchema() },
452
+ { name: 'verify_runtime_release_session_static', description: 'Run canonical static gates on the frozen source revision and advance only that exact ReleaseSession.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
453
+ { name: 'verify_runtime_release_session_candidate', description: 'Boot Candidate B in its isolated ControllerHome/service/port, run whole-Runtime and Recovery restart canaries, and mark the session cutover-eligible while Stable A stays active.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
454
+ { name: 'cutover_runtime_release_session', description: 'Perform the single fenced cutover attempt for a cutover-eligible ReleaseSession using the byte-identical verified Candidate B artifact. Failed cutover restores exact Stable A and terminalizes without retry.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
455
+ { name: 'cancel_runtime_release_session', description: 'Retire a superseded or rejected Candidate B before cutover, terminalizing the ReleaseSession with the existing failed state and leaving Stable A unchanged.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
456
+ { name: 'rollback_runtime_release_session', description: 'Abort the exact in-flight ReleaseSession activation transaction and restore its frozen Stable A whole-Runtime release plus SQLite backup.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
457
+ { name: 'promote_runtime_release_session_known_good', description: 'After committed cutover soak, require full verification and performance observation, create a recoverable release+SQLite+service bundle, and terminalize the ReleaseSession known-good.', inputSchema: mutationInputSchema({ session_id: { type: 'string', minLength: 8, maxLength: 120 } }, ['session_id']) },
299
458
  { 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
459
  { 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() },
301
460
  { name: 'reconnect_primary_connector', description: 'Check canonical Runtime Gateway and primary MCP reconnection readiness without publishing a release.', inputSchema: { type: 'object', additionalProperties: false } },
@@ -591,11 +750,18 @@ function assertRecoveryGatewayMutationIdentity(config: RecoveryConfig, args: Rec
591
750
  export async function dispatchRecoveryTool(config: RecoveryConfig, name: string, args: Record<string, unknown>): Promise<unknown> {
592
751
  switch (name) {
593
752
  case 'runtime_status': return runtimeStatus(config);
753
+ case 'list_recovery_history': return listRecoveryHistory(config, typeof args.limit === 'number' ? args.limit : 20);
594
754
  case 'list_releases': return listReleases(config);
595
755
  case 'verify_stable_runtime': return verifyStableRuntime(config);
596
756
  case 'verify_external_runtime': {
597
757
  const verified = await verifyStableRuntime(config);
598
- return { ok: verified.probes.external_mcp_http?.ok === true, external: verified.probes.external_mcp_http, mcp: verified.probes.mcp_initialize };
758
+ const externalConfigured = Boolean(config.publicMcpUrl);
759
+ return {
760
+ ok: externalConfigured ? verified.probes.external_mcp_http?.ok === true : verified.ok,
761
+ externalConfigured,
762
+ external: verified.probes.external_mcp_http,
763
+ mcp: verified.probes.mcp_initialize,
764
+ };
599
765
  }
600
766
  case 'attest_known_good': {
601
767
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
@@ -610,7 +776,7 @@ export async function dispatchRecoveryTool(config: RecoveryConfig, name: string,
610
776
  case 'restart_primary_runtime': {
611
777
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
612
778
  assertRecoveryGatewayMutationIdentity(config, args);
613
- return mutationResponse(config, await restartPrimaryRuntime(config));
779
+ return mutationResponse(config, await restartPrimaryRuntime(config, { forceHealthyRestart: true }));
614
780
  }
615
781
  case 'restart_primary_connector': {
616
782
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
@@ -636,11 +802,85 @@ export async function dispatchRecoveryTool(config: RecoveryConfig, name: string,
636
802
  expectedAuthorityRevision: Number(args.expected_authority_revision),
637
803
  }));
638
804
  }
805
+ case 'pin_runtime_release': {
806
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
807
+ assertRecoveryGatewayMutationIdentity(config, args);
808
+ if (typeof args.release_path !== 'string' || !args.release_path.trim()) throw new Error('RECOVERY_RELEASE_PATH_REQUIRED');
809
+ const releasePath = args.release_path.trim();
810
+ const manifestPath = basename(releasePath) === 'manifest.json' ? releasePath : join(releasePath, 'manifest.json');
811
+ return mutationResponse(config, await pinRuntimeRelease(config, manifestPath, `recovery-gateway:${args.request_id}`));
812
+ }
813
+ case 'unpin_runtime_release': {
814
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
815
+ assertRecoveryGatewayMutationIdentity(config, args);
816
+ return mutationResponse(config, await unpinRuntimeRelease(config, `recovery-gateway:${args.request_id}`));
817
+ }
818
+ case 'activate_pinned_runtime_release': {
819
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
820
+ assertRecoveryGatewayMutationIdentity(config, args);
821
+ if (typeof args.expected_active_release_id !== 'string' || !args.expected_active_release_id.trim()) throw new Error('RECOVERY_EXPECTED_ACTIVE_RELEASE_REQUIRED');
822
+ if (!Number.isInteger(args.expected_authority_revision) || Number(args.expected_authority_revision) < 1) throw new Error('RECOVERY_EXPECTED_AUTHORITY_REVISION_REQUIRED');
823
+ return mutationResponse(config, await activatePinnedRuntimeRelease(config, {}, {
824
+ requestId: `recovery-gateway:${args.request_id}`,
825
+ expectedActiveReleaseId: args.expected_active_release_id.trim(),
826
+ expectedAuthorityRevision: Number(args.expected_authority_revision),
827
+ }));
828
+ }
639
829
  case 'stage_and_activate_runtime_release': {
640
830
  if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
641
831
  assertRecoveryGatewayMutationIdentity(config, args);
642
832
  return mutationResponse(config, await stageAndActivateConfiguredRuntimeRelease(config, {}, `recovery-gateway:${args.request_id}`));
643
833
  }
834
+ case 'release_session_status': {
835
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
836
+ return readReleaseSession(config.controllerHome, args.session_id.trim()) ?? null;
837
+ }
838
+ case 'advance_runtime_release_session': {
839
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
840
+ assertRecoveryGatewayMutationIdentity(config, args);
841
+ return mutationResponse(config, await advanceConfiguredRuntimeRelease(config, RECOVERY_RUNTIME_RELEASE_PROVIDER, `recovery-gateway:${args.request_id}`));
842
+ }
843
+ case 'prepare_runtime_release_session': {
844
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
845
+ assertRecoveryGatewayMutationIdentity(config, args);
846
+ return mutationResponse(config, await prepareConfiguredRuntimeReleaseSession(config, {}, `recovery-gateway:${args.request_id}`));
847
+ }
848
+ case 'verify_runtime_release_session_static': {
849
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
850
+ assertRecoveryGatewayMutationIdentity(config, args);
851
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
852
+ return mutationResponse(config, await verifyConfiguredRuntimeReleaseSessionStaticGates(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
853
+ }
854
+ case 'verify_runtime_release_session_candidate': {
855
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
856
+ assertRecoveryGatewayMutationIdentity(config, args);
857
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
858
+ return mutationResponse(config, await bootAndVerifyConfiguredRuntimeReleaseSessionCandidate(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
859
+ }
860
+ case 'cutover_runtime_release_session': {
861
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
862
+ assertRecoveryGatewayMutationIdentity(config, args);
863
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
864
+ return mutationResponse(config, await cutoverConfiguredRuntimeReleaseSession(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
865
+ }
866
+ case 'cancel_runtime_release_session': {
867
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
868
+ assertRecoveryGatewayMutationIdentity(config, args);
869
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
870
+ return mutationResponse(config, await cancelConfiguredRuntimeReleaseSession(config, args.session_id.trim(), `recovery-gateway:${args.request_id}`));
871
+ }
872
+ case 'rollback_runtime_release_session': {
873
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
874
+ assertRecoveryGatewayMutationIdentity(config, args);
875
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
876
+ return mutationResponse(config, await rollbackConfiguredRuntimeReleaseSession(config, args.session_id.trim(), {}, `recovery-gateway:${args.request_id}`));
877
+ }
878
+ case 'promote_runtime_release_session_known_good': {
879
+ if (!requestId(args.request_id)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
880
+ assertRecoveryGatewayMutationIdentity(config, args);
881
+ if (typeof args.session_id !== 'string' || !args.session_id.trim()) throw new Error('RECOVERY_RELEASE_SESSION_ID_REQUIRED');
882
+ return mutationResponse(config, await promoteConfiguredRuntimeReleaseSessionKnownGood(config, args.session_id.trim(), {}, `recovery-gateway:${args.request_id}`));
883
+ }
644
884
  case 'migrate_controller_home': {
645
885
  const migrationRequestId = requestId(args.request_id);
646
886
  if (!migrationRequestId) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
@@ -663,13 +903,194 @@ export async function dispatchRecoveryTool(config: RecoveryConfig, name: string,
663
903
  }
664
904
  }
665
905
 
666
- async function startGateway(config: RecoveryConfig): Promise<void> {
906
+ /**
907
+ * Stable identity of the release step the daemon is currently driving. A step
908
+ * that keeps failing without progressing (same session, phase, and revision)
909
+ * must not fork a full Recovery executable every interval forever.
910
+ */
911
+ function releaseReconciliationFingerprint(decision: { action?: unknown; session?: { sessionId?: unknown; phase?: unknown; revision?: unknown } }): string {
912
+ return [
913
+ typeof decision.action === 'string' ? decision.action : 'none',
914
+ typeof decision.session?.sessionId === 'string' ? decision.session.sessionId : 'none',
915
+ typeof decision.session?.phase === 'string' ? decision.session.phase : 'none',
916
+ typeof decision.session?.revision === 'number' ? String(decision.session.revision) : 'none',
917
+ ].join(':');
918
+ }
919
+
920
+ /**
921
+ * Retry schedule for the automatic release driver. A step that fails without
922
+ * progressing (same fingerprint) backs off exponentially up to a bounded cap so a
923
+ * non-converging release degrades into a slow retry instead of a
924
+ * full-executable fork storm that starves this daemon, the Recovery gateway it
925
+ * serves, and every tunnel behind it. Progress, or a new session/step, resets the
926
+ * schedule; a successful step restores the base interval.
927
+ */
928
+ export function nextReleaseReconcileBackoff(
929
+ prior: { fingerprint?: string; consecutiveFailures: number },
930
+ failed: boolean,
931
+ fingerprint: string | undefined,
932
+ ): { fingerprint?: string; consecutiveFailures: number; delayMs: number } {
933
+ if (!failed) {
934
+ return { fingerprint: undefined, consecutiveFailures: 0, delayMs: RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS };
935
+ }
936
+ const nextFingerprint = fingerprint ?? 'unknown';
937
+ const consecutiveFailures = nextFingerprint === prior.fingerprint ? Math.max(1, prior.consecutiveFailures) + 1 : 1;
938
+ return {
939
+ fingerprint: nextFingerprint,
940
+ consecutiveFailures,
941
+ delayMs: Math.min(RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS * 2 ** (consecutiveFailures - 1), RECOVERY_AUTOMATIC_RELEASE_FAILURE_BACKOFF_MAX_MS),
942
+ };
943
+ }
944
+
945
+ async function runAutomaticReleaseReconciliationStep(
946
+ config: RecoveryConfig,
947
+ observed: { fingerprint?: string } = {},
948
+ selectedDecision?: ReturnType<typeof decideConfiguredRuntimeReleaseReconciliation>,
949
+ ): Promise<void> {
950
+ // Most daemon ticks are no-ops. Decide that in the resident process first so
951
+ // a healthy/current source does not fork a complete Recovery executable every
952
+ // fifteen seconds merely to rediscover the same result. The short-lived child
953
+ // remains the mutation boundary whenever a durable release action is needed.
954
+ const decision = selectedDecision ?? decideConfiguredRuntimeReleaseReconciliation(
955
+ config.controllerHome,
956
+ () => configuredRuntimeReleaseSourceState(config),
957
+ );
958
+ observed.fingerprint = decision.required ? releaseReconciliationFingerprint(decision) : undefined;
959
+ if (!decision.required) return;
960
+
961
+ const release = readCurrentRecoveryRelease(config.controllerHome);
962
+ if (!release) throw new Error('RECOVERY_AUTOMATIC_RELEASE_CURRENT_RECOVERY_UNKNOWN');
963
+ const executable = join(release.releasePath, 'forge-recovery');
964
+ if (!existsSync(executable)) throw new Error('RECOVERY_AUTOMATIC_RELEASE_EXECUTABLE_UNAVAILABLE');
965
+ const result = await runBoundedChild(
966
+ executable,
967
+ [RECOVERY_INTERNAL_RELEASE_RECONCILE_COMMAND, '--controller-home', config.controllerHome],
968
+ {
969
+ timeoutMs: RECOVERY_AUTOMATIC_RELEASE_STEP_TIMEOUT_MS,
970
+ maxOutputBytes: 64 * 1024,
971
+ forwardSignals: false,
972
+ env: runtimeAuthorityFreeEnvironment(process.env),
973
+ },
974
+ );
975
+ if (result.status !== 0 || result.failureCode || result.timedOut) {
976
+ const detail = result.failureCode ?? result.error ?? (result.stderr.trim() || `exit=${result.status}`);
977
+ throw new Error(`RECOVERY_AUTOMATIC_RELEASE_STEP_FAILED: ${detail.slice(0, 500)}`);
978
+ }
979
+ let envelope: unknown;
980
+ try { envelope = JSON.parse(result.stdout); }
981
+ catch { throw new Error('RECOVERY_AUTOMATIC_RELEASE_PROTOCOL_INVALID'); }
982
+ if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)) throw new Error('RECOVERY_AUTOMATIC_RELEASE_PROTOCOL_INVALID');
983
+ const parsed = envelope as {
984
+ ok?: unknown;
985
+ attempted?: unknown;
986
+ error?: unknown;
987
+ decision?: { reason?: unknown; action?: unknown };
988
+ result?: { detail?: unknown };
989
+ };
990
+ if (parsed.ok !== true) {
991
+ const detail = typeof parsed.error === 'string'
992
+ ? parsed.error
993
+ : typeof parsed.result?.detail === 'string'
994
+ ? parsed.result.detail
995
+ : 'automatic release reconciliation failed';
996
+ throw new Error(`RECOVERY_AUTOMATIC_RELEASE_STEP_FAILED: ${detail.slice(0, 500)}`);
997
+ }
998
+ if (parsed.decision?.action) {
999
+ process.stdout.write(JSON.stringify({
1000
+ at: new Date().toISOString(),
1001
+ action: 'automatic_release_reconcile',
1002
+ reason: parsed.decision?.reason,
1003
+ releaseAction: parsed.decision?.action,
1004
+ }) + '\n');
1005
+ }
1006
+ }
1007
+
1008
+ async function startAutomaticReleaseReconciliation(config: RecoveryConfig): Promise<never> {
1009
+ let lastFailedFingerprint: string | undefined;
1010
+ let consecutiveFailures = 0;
1011
+ for (;;) {
1012
+ const observed: { fingerprint?: string } = {};
1013
+ let failure: string | undefined;
1014
+ try {
1015
+ const decision = decideConfiguredRuntimeReleaseReconciliation(
1016
+ config.controllerHome,
1017
+ () => configuredRuntimeReleaseSourceState(config),
1018
+ );
1019
+ observed.fingerprint = decision.required ? releaseReconciliationFingerprint(decision) : undefined;
1020
+ if (decision.required && decision.action === 'cutover') {
1021
+ const gate = await probeReleaseCutoverReadiness(config);
1022
+ if (!gate.ready) {
1023
+ process.stdout.write(JSON.stringify({
1024
+ at: new Date().toISOString(),
1025
+ action: 'automatic_release_cutover_deferred',
1026
+ reason: gate.detail,
1027
+ sessionId: decision.session?.sessionId,
1028
+ phase: decision.session?.phase,
1029
+ activeSessions: gate.activeSessions,
1030
+ activePosts: gate.activePosts,
1031
+ activeStreams: gate.activeStreams,
1032
+ initializing: gate.initializing,
1033
+ latestActivityAgeMs: gate.latestActivityAgeMs,
1034
+ }) + '\n');
1035
+ } else {
1036
+ await runAutomaticReleaseReconciliationStep(config, observed, decision);
1037
+ }
1038
+ } else {
1039
+ await runAutomaticReleaseReconciliationStep(config, observed, decision);
1040
+ }
1041
+ } catch (error) {
1042
+ failure = error instanceof Error ? error.message : String(error);
1043
+ process.stderr.write(`automatic release reconciliation failed: ${failure}\n`);
1044
+ }
1045
+ const backoff = nextReleaseReconcileBackoff(
1046
+ { fingerprint: lastFailedFingerprint, consecutiveFailures },
1047
+ Boolean(failure),
1048
+ observed.fingerprint,
1049
+ );
1050
+ lastFailedFingerprint = backoff.fingerprint;
1051
+ consecutiveFailures = backoff.consecutiveFailures;
1052
+ if (failure && backoff.delayMs > RECOVERY_AUTOMATIC_RELEASE_INTERVAL_MS) {
1053
+ process.stderr.write(
1054
+ `automatic release reconciliation backing off ${Math.round(backoff.delayMs / 1000)}s after ${backoff.consecutiveFailures} consecutive failures of ${backoff.fingerprint}\n`,
1055
+ );
1056
+ }
1057
+ await new Promise((resolveSleep) => setTimeout(resolveSleep, backoff.delayMs));
1058
+ }
1059
+ }
1060
+
1061
+ async function startRecoveryDaemon(config: RecoveryConfig): Promise<void> {
1062
+ const runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'daemon');
1063
+
1064
+ // The Recovery gateway is the control plane used to repair every other
1065
+ // Recovery subsystem. Bind it before starting background work. A reconcile
1066
+ // or watchdog tick may perform expensive synchronous setup before its first
1067
+ // await, so invoking those loops first can leave a live daemon process with
1068
+ // no listening gateway.
1069
+ const backgroundTimer = setTimeout(() => {
1070
+ void startAutomaticReleaseReconciliation(config).catch((error) => {
1071
+ process.stderr.write(`Recovery release driver failed: ${error instanceof Error ? error.message : String(error)}\n`);
1072
+ process.exit(1);
1073
+ });
1074
+ if (config.installProfile === 'self-healing') {
1075
+ void startWatchdog(config, runtimeIdentity).catch((error) => {
1076
+ process.stderr.write(`Recovery monitor failed: ${error instanceof Error ? error.message : String(error)}\n`);
1077
+ process.exit(1);
1078
+ });
1079
+ }
1080
+ }, 0);
1081
+ backgroundTimer.unref?.();
1082
+
1083
+ await startGateway(config, runtimeIdentity);
1084
+ }
1085
+
1086
+ async function startGateway(config: RecoveryConfig, daemonIdentity?: RecoveryRuntimeIdentity): Promise<void> {
667
1087
  const gateway = config.gateway;
668
1088
  if (!gateway || gateway.host !== '127.0.0.1' || !Number.isInteger(gateway.port) || gateway.port < 1024 || gateway.port > 65535) {
669
1089
  throw new Error('RECOVERY_GATEWAY_CONFIG_INVALID');
670
1090
  }
671
- let runtimeIdentity: RecoveryRuntimeIdentity | undefined;
672
- let watchdogRestartInFlight = false;
1091
+ let runtimeIdentity: RecoveryRuntimeIdentity | undefined = daemonIdentity;
1092
+ let activeMcpRequests = 0;
1093
+ let latestMcpActivityAt: number | undefined;
673
1094
  const recentMutations = new Map<string, number[]>();
674
1095
  const oauthCodes = new Map<string, PendingOAuthCode>();
675
1096
  const oauthClients = new Map<string, OAuthClient>();
@@ -678,10 +1099,10 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
678
1099
  securitySchemes: TOOL_SECURITY_SCHEMES,
679
1100
  _meta: { securitySchemes: TOOL_SECURITY_SCHEMES },
680
1101
  })) as unknown as Tool[];
681
- const recoveryMcp = new RecoveryMcpSessionServer({
1102
+ const recoveryMcp = new RecoveryMcpServer({
682
1103
  tools: recoveryTools,
683
1104
  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') {
1105
+ 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 === 'pin_runtime_release' || name === 'unpin_runtime_release' || name === 'activate_pinned_runtime_release' || name === 'stage_and_activate_runtime_release' || name === 'prepare_runtime_release_session' || name === 'verify_runtime_release_session_static' || name === 'verify_runtime_release_session_candidate' || name === 'cutover_runtime_release_session' || name === 'promote_runtime_release_session_known_good' || name === 'migrate_controller_home' || name === 'restart_public_tunnel') {
685
1106
  const now = Date.now();
686
1107
  const window = (recentMutations.get(context.remoteAddress) ?? []).filter((at) => now - at < 60_000);
687
1108
  if (window.length >= 3) throw new Error('Recovery mutation rate limit exceeded.');
@@ -699,17 +1120,20 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
699
1120
  return;
700
1121
  }
701
1122
  if (request.method === 'GET' && matchesAnyPath(request.url, ['/health', '/recovery/health'])) {
702
- const watchdog = observeRecoveryWatchdogHealth(config.controllerHome);
1123
+ const watchdog = observeRecoveryWatchdogHealth(
1124
+ config.controllerHome,
1125
+ config.installProfile === 'self-healing',
1126
+ );
703
1127
  json(response, 200, {
704
1128
  status: watchdog.ok ? 'ok' : 'degraded',
705
1129
  service: 'forge-standalone-recovery',
706
1130
  watchdog: {
707
1131
  ok: watchdog.ok,
708
1132
  detail: watchdog.detail,
709
- pulseAgeMs: watchdog.pulseAgeMs,
710
- tickAgeMs: watchdog.tickAgeMs,
711
- releaseRevision: watchdog.runtimeIdentity?.releaseRevision,
712
- pid: watchdog.runtimeIdentity?.pid,
1133
+ pulseAgeMs: 'pulseAgeMs' in watchdog ? watchdog.pulseAgeMs : undefined,
1134
+ tickAgeMs: 'tickAgeMs' in watchdog ? watchdog.tickAgeMs : undefined,
1135
+ releaseRevision: 'runtimeIdentity' in watchdog ? watchdog.runtimeIdentity?.releaseRevision : runtimeIdentity?.releaseRevision,
1136
+ pid: 'runtimeIdentity' in watchdog ? watchdog.runtimeIdentity?.pid : runtimeIdentity?.pid,
713
1137
  },
714
1138
  version: FORGE_VERSION,
715
1139
  ...(runtimeIdentity ? {
@@ -718,6 +1142,11 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
718
1142
  sourceCommit: runtimeIdentity.sourceCommit,
719
1143
  manifestSha256: runtimeIdentity.manifestSha256,
720
1144
  } : {}),
1145
+ requests: {
1146
+ draining: recoveryGatewayIsDraining(config.controllerHome),
1147
+ activeMcpRequests,
1148
+ ...(latestMcpActivityAt !== undefined ? { latestMcpActivityAgeMs: Math.max(0, Date.now() - latestMcpActivityAt) } : {}),
1149
+ },
721
1150
  });
722
1151
  return;
723
1152
  }
@@ -854,34 +1283,35 @@ async function startGateway(config: RecoveryConfig): Promise<void> {
854
1283
  const mcpRequest = classifyRecoveryMcpRequest(request, gatewayToken(config));
855
1284
  if (mcpRequest === 'not_mcp' || mcpRequest === 'method_not_supported') { json(response, 404, { error: 'NOT_FOUND' }); return; }
856
1285
  if (mcpRequest === 'auth_required') { response.setHeader('www-authenticate', recoveryWwwAuthenticate(request, config)); json(response, 401, recoveryUnauthorizedBody()); return; }
1286
+ if (recoveryGatewayIsDraining(config.controllerHome)) {
1287
+ response.setHeader('retry-after', '1');
1288
+ json(response, 503, { error: 'RECOVERY_DRAINING' });
1289
+ return;
1290
+ }
857
1291
  let body: unknown;
858
1292
  if (request.method === 'POST') {
859
1293
  if (!/^application\/json(?:\s*;|$)/i.test(String(request.headers['content-type'] ?? ''))) { json(response, 415, { error: 'RECOVERY_CONTENT_TYPE_REQUIRED' }); return; }
860
1294
  try { body = JSON.parse(await readBody(request)); } catch { json(response, 400, rpcError(null, -32700, 'Invalid JSON.')); return; }
861
1295
  }
862
- await recoveryMcp.handle(request, response, body);
863
- });
864
- await new Promise<void>((resolveListen, reject) => { server.once('error', reject); server.listen(gateway.port, gateway.host, () => resolveListen()); });
865
- runtimeIdentity = writeRecoveryRuntimeIdentity(config.controllerHome, 'gateway');
866
- const superviseWatchdog = async () => {
867
- if (!runtimeIdentity || watchdogRestartInFlight) return;
868
- const watchdog = observeRecoveryWatchdogHealth(config.controllerHome);
869
- if (watchdog.ok) return;
870
- watchdogRestartInFlight = true;
1296
+ activeMcpRequests += 1;
1297
+ latestMcpActivityAt = Date.now();
1298
+ let settled = false;
1299
+ const settle = () => {
1300
+ if (settled) return;
1301
+ settled = true;
1302
+ activeMcpRequests = Math.max(0, activeMcpRequests - 1);
1303
+ latestMcpActivityAt = Date.now();
1304
+ };
1305
+ response.once('finish', settle);
1306
+ response.once('close', settle);
871
1307
  try {
872
- const recovery = await restartRecoveryWatchdog(config);
873
- auditGateway({
874
- watchdog_supervision: recovery.ok ? 'recovered' : 'failed',
875
- detail: recovery.detail,
876
- attempted: recovery.attempted,
877
- serviceTarget: recovery.serviceTarget,
878
- });
1308
+ await recoveryMcp.handle(request, response, body);
879
1309
  } finally {
880
- watchdogRestartInFlight = false;
1310
+ if (response.writableEnded || response.destroyed) settle();
881
1311
  }
882
- };
883
- const watchdogSupervisor = setInterval(() => { void superviseWatchdog(); }, 15_000);
884
- watchdogSupervisor.unref?.();
1312
+ });
1313
+ await new Promise<void>((resolveListen, reject) => { server.once('error', reject); server.listen(gateway.port, gateway.host, () => resolveListen()); });
1314
+ runtimeIdentity ??= writeRecoveryRuntimeIdentity(config.controllerHome, 'gateway');
885
1315
  process.stdout.write(JSON.stringify({ status: 'ready', host: gateway.host, port: gateway.port, runtimeIdentity }) + '\n');
886
1316
  }
887
1317