@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
@@ -26,13 +26,15 @@ import {
26
26
  installSystemdUserUnit,
27
27
  systemdUserAvailable,
28
28
  systemdUserServicePid,
29
+ systemdUserUnitName,
30
+ systemdUserUnitPath,
29
31
  type SystemdUserUnitInput,
30
32
  } from '../../cli/controller/systemd-user';
31
33
  import { FORGE_VERSION } from '../../version';
32
- import { initializeStandaloneRecovery, loadRecoveryConfig, type LaunchdPrimaryConnectorServiceConfig, type LaunchdPublicTunnelServiceConfig, type PrimaryConnectorServiceConfig, type PrimaryRuntimeServiceConfig, type PublicTunnelServiceConfig, type RecoveryConfig, type RecoveryTunnelServiceConfig } from './core';
33
- import { RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
34
+ import { initializeStandaloneRecovery, loadRecoveryConfig, normalizeRecoveryInstallProfile, recoveryInstallProfileRoles, type LaunchdPrimaryConnectorServiceConfig, type LaunchdPublicTunnelServiceConfig, type PrimaryConnectorServiceConfig, type PrimaryRuntimeServiceConfig, type PublicTunnelServiceConfig, type RecoveryConfig, type RecoveryInstallProfile, type RecoveryTunnelServiceConfig } from './core';
35
+ import { RECOVERY_DAEMON_LABEL, RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
34
36
  import { acquireRecoveryOperationLock } from './operation-lock';
35
- export { RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
37
+ export { RECOVERY_DAEMON_LABEL, RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
36
38
  import {
37
39
  RECOVERY_RELEASE_BINARIES,
38
40
  RECOVERY_RELEASE_ROLE_CANARY_ARG,
@@ -46,6 +48,7 @@ import {
46
48
  recoveryPreviousPath,
47
49
  recoveryReleasesRoot,
48
50
  recoveryRoot,
51
+ setRecoveryGatewayDraining,
49
52
  writeRecoveryReleaseManifest,
50
53
  type RecoveryReleaseDescriptor,
51
54
  type RecoveryReleaseManifest,
@@ -77,7 +80,7 @@ export function retireStaleRecoveryLaunchAgents(
77
80
  ): string[] {
78
81
  const generatedRoot = join(recoveryRoot(controllerHome), 'launchd');
79
82
  if (!existsSync(generatedRoot)) return [];
80
- const activeLabels = new Set([RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL]);
83
+ const activeLabels = new Set([RECOVERY_DAEMON_LABEL]);
81
84
  const retired: string[] = [];
82
85
  for (const name of readdirSync(generatedRoot)) {
83
86
  if (!name.endsWith('.plist')) continue;
@@ -130,6 +133,7 @@ export interface RecoveryActivationVerification {
130
133
  ok: boolean;
131
134
  expectedReleaseRevision: string;
132
135
  failures: string[];
136
+ daemonPid?: number;
133
137
  gatewayPid?: number;
134
138
  watchdogPid?: number;
135
139
  healthStatus?: number;
@@ -150,10 +154,7 @@ export interface RecoveryActivationResult {
150
154
  previous?: RecoveryReleaseDescriptor;
151
155
  migratedLegacy?: RecoveryReleaseDescriptor;
152
156
  noOp?: boolean;
153
- handoff?: {
154
- gateway: RecoveryServiceHandoffResult;
155
- watchdog: RecoveryServiceHandoffResult;
156
- };
157
+ handoff?: Partial<Record<RecoveryRuntimeRole, RecoveryServiceHandoffResult>>;
157
158
  verification: RecoveryActivationVerification;
158
159
  rollback?: {
159
160
  release: RecoveryReleaseDescriptor;
@@ -163,6 +164,7 @@ export interface RecoveryActivationResult {
163
164
 
164
165
  export interface RecoveryInstallResult {
165
166
  controllerHome: string;
167
+ profile: RecoveryInstallProfile;
166
168
  staged: StagedRecoveryRelease;
167
169
  activated?: RecoveryActivationResult;
168
170
  config: RecoveryConfig;
@@ -183,6 +185,7 @@ export interface RecoveryInstallerDependencies {
183
185
  controllerHome: string;
184
186
  config: RecoveryConfig;
185
187
  expectedRelease: RecoveryReleaseDescriptor;
188
+ profile?: RecoveryInstallProfile;
186
189
  timeoutMs?: number;
187
190
  }) => Promise<RecoveryActivationVerification>;
188
191
  currentPid?: (controllerHome: string, role: RecoveryRuntimeRole) => number | undefined;
@@ -307,8 +310,7 @@ export function stageRecoveryRelease(input: {
307
310
  const runCanary = dependencies.runCanary ?? defaultRunCanary;
308
311
  const canaries: Array<{ label: string; binaryPath: string; role?: RecoveryRuntimeRole }> = [
309
312
  { label: 'primary', binaryPath: primary },
310
- { label: 'gateway', binaryPath: join(staging, 'forge-recovery-gateway'), role: 'gateway' },
311
- { label: 'watchdog', binaryPath: join(staging, 'forge-recovery-watchdog'), role: 'watchdog' },
313
+ { label: 'daemon', binaryPath: primary, role: 'daemon' },
312
314
  ];
313
315
  const canaryDetails: string[] = [];
314
316
  for (const canary of canaries) {
@@ -393,9 +395,9 @@ export function recoverySystemdUserUnitInput(
393
395
  env: NodeJS.ProcessEnv = process.env,
394
396
  productVersion: string = FORGE_VERSION,
395
397
  ): SystemdUserUnitInput {
396
- const binary = role === 'gateway' ? 'forge-recovery-gateway' : 'forge-recovery-watchdog';
398
+ const binary = role === 'daemon' ? 'forge-recovery' : role === 'gateway' ? 'forge-recovery-gateway' : 'forge-recovery-watchdog';
397
399
  return {
398
- description: role === 'gateway' ? 'Forge Standalone Recovery Gateway' : 'Forge Standalone Recovery Watchdog',
400
+ description: role === 'daemon' ? 'Forge Standalone Recovery' : role === 'gateway' ? 'Forge Standalone Recovery Gateway (legacy)' : 'Forge Standalone Recovery Watchdog (legacy)',
399
401
  executable: join(recoveryCurrentPath(controllerHome), binary),
400
402
  args: [role, '--controller-home', resolve(controllerHome)],
401
403
  environment: {
@@ -412,7 +414,11 @@ export function recoverySystemdUserUnitInput(
412
414
  }
413
415
 
414
416
  function recoverySystemdLabel(role: RecoveryRuntimeRole): string {
415
- return role === 'gateway' ? RECOVERY_GATEWAY_LABEL : RECOVERY_WATCHDOG_LABEL;
417
+ return role === 'daemon' ? RECOVERY_DAEMON_LABEL : role === 'gateway' ? RECOVERY_GATEWAY_LABEL : RECOVERY_WATCHDOG_LABEL;
418
+ }
419
+
420
+ function recoveryRoleServesGateway(role: RecoveryRuntimeRole): boolean {
421
+ return role === 'daemon' || role === 'gateway';
416
422
  }
417
423
 
418
424
  function recoveryPlist(input: {
@@ -446,7 +452,7 @@ export function recoveryLaunchdServicePid(label: string): number | undefined {
446
452
  }
447
453
 
448
454
  export function recoveryLaunchdPid(role: RecoveryRuntimeRole): number | undefined {
449
- return recoveryLaunchdServicePid(role === 'gateway' ? RECOVERY_GATEWAY_LABEL : RECOVERY_WATCHDOG_LABEL);
455
+ return recoveryLaunchdServicePid(recoverySystemdLabel(role));
450
456
  }
451
457
 
452
458
  function defaultCurrentPid(controllerHome: string, role: RecoveryRuntimeRole): number | undefined {
@@ -459,8 +465,11 @@ export async function verifyRecoveryReleaseActivation(input: {
459
465
  controllerHome: string;
460
466
  config: RecoveryConfig;
461
467
  expectedRelease: RecoveryReleaseDescriptor;
468
+ profile?: RecoveryInstallProfile;
462
469
  timeoutMs?: number;
463
470
  }, dependencies: Pick<RecoveryInstallerDependencies, 'servicePid' | 'platform' | 'systemdEnv'> = {}): Promise<RecoveryActivationVerification> {
471
+ const profile = normalizeRecoveryInstallProfile(input.profile ?? input.config.installProfile, 'self-healing');
472
+ const selectedRoles = new Set(recoveryInstallProfileRoles(profile));
464
473
  const deadline = Date.now() + (input.timeoutMs ?? 60_000);
465
474
  let last: RecoveryActivationVerification = {
466
475
  ok: false,
@@ -473,8 +482,6 @@ export async function verifyRecoveryReleaseActivation(input: {
473
482
  if (!current || current.releasePath !== input.expectedRelease.releasePath || current.manifestSha256 !== input.expectedRelease.manifestSha256) {
474
483
  failures.push('Recovery current authority does not match expected release');
475
484
  }
476
- const gateway = readRecoveryRuntimeIdentity(input.controllerHome, 'gateway');
477
- const watchdog = readRecoveryRuntimeIdentity(input.controllerHome, 'watchdog');
478
485
  const observedPids: Partial<Record<RecoveryRuntimeRole, number>> = {};
479
486
  const platform = dependencies.platform ?? process.platform;
480
487
  const servicePid = dependencies.servicePid ?? ((_controllerHome: string, role: RecoveryRuntimeRole) => (
@@ -482,8 +489,13 @@ export async function verifyRecoveryReleaseActivation(input: {
482
489
  ? systemdUserServicePid(recoverySystemdLabel(role), dependencies.systemdEnv ?? process.env)
483
490
  : recoveryLaunchdPid(role)
484
491
  ));
485
- for (const [role, identity] of [['gateway', gateway], ['watchdog', watchdog]] as const) {
492
+ for (const role of ['daemon', 'gateway', 'watchdog'] as const) {
486
493
  const managedPid = servicePid(input.controllerHome, role);
494
+ if (!selectedRoles.has(role)) {
495
+ if (managedPid) failures.push(`${role} service remains active outside Recovery profile ${profile}`);
496
+ continue;
497
+ }
498
+ const identity = readRecoveryRuntimeIdentity(input.controllerHome, role);
487
499
  if (!managedPid) failures.push(`${role} service-owner PID is unavailable`);
488
500
  else observedPids[role] = managedPid;
489
501
  if (input.expectedRelease.legacy) continue;
@@ -497,27 +509,30 @@ export async function verifyRecoveryReleaseActivation(input: {
497
509
  }
498
510
  }
499
511
  let healthStatus: number | undefined;
500
- try {
501
- const response = await fetch(`http://${input.config.gateway?.host ?? '127.0.0.1'}:${input.config.gateway?.port ?? 8787}/health`, {
502
- signal: AbortSignal.timeout(3_000),
503
- });
504
- healthStatus = response.status;
505
- const health = await response.json() as { status?: unknown; version?: unknown; releaseRevision?: unknown; manifestSha256?: unknown };
506
- if (response.status !== 200 || health.status !== 'ok') failures.push(`gateway health HTTP ${response.status}`);
507
- if (input.expectedRelease.productVersion && health.version !== input.expectedRelease.productVersion) failures.push('gateway health product version mismatch');
508
- if (!input.expectedRelease.legacy || health.releaseRevision !== undefined) {
509
- if (health.releaseRevision !== input.expectedRelease.releaseRevision) failures.push('gateway health release revision mismatch');
510
- }
511
- if (!input.expectedRelease.legacy || health.manifestSha256 !== undefined) {
512
- if (health.manifestSha256 !== input.expectedRelease.manifestSha256) failures.push('gateway health manifest hash mismatch');
512
+ if ([...selectedRoles].some(recoveryRoleServesGateway)) {
513
+ try {
514
+ const response = await fetch(`http://${input.config.gateway?.host ?? '127.0.0.1'}:${input.config.gateway?.port ?? 8787}/health`, {
515
+ signal: AbortSignal.timeout(3_000),
516
+ });
517
+ healthStatus = response.status;
518
+ const health = await response.json() as { status?: unknown; version?: unknown; releaseRevision?: unknown; manifestSha256?: unknown };
519
+ if (response.status !== 200 || health.status !== 'ok') failures.push(`gateway health HTTP ${response.status}`);
520
+ if (input.expectedRelease.productVersion && health.version !== input.expectedRelease.productVersion) failures.push('gateway health product version mismatch');
521
+ if (!input.expectedRelease.legacy || health.releaseRevision !== undefined) {
522
+ if (health.releaseRevision !== input.expectedRelease.releaseRevision) failures.push('gateway health release revision mismatch');
523
+ }
524
+ if (!input.expectedRelease.legacy || health.manifestSha256 !== undefined) {
525
+ if (health.manifestSha256 !== input.expectedRelease.manifestSha256) failures.push('gateway health manifest hash mismatch');
526
+ }
527
+ } catch (error) {
528
+ failures.push(`gateway health unavailable: ${error instanceof Error ? error.message : String(error)}`);
513
529
  }
514
- } catch (error) {
515
- failures.push(`gateway health unavailable: ${error instanceof Error ? error.message : String(error)}`);
516
530
  }
517
531
  last = {
518
532
  ok: failures.length === 0,
519
533
  expectedReleaseRevision: input.expectedRelease.releaseRevision,
520
534
  failures,
535
+ daemonPid: observedPids.daemon,
521
536
  gatewayPid: observedPids.gateway,
522
537
  watchdogPid: observedPids.watchdog,
523
538
  healthStatus,
@@ -528,128 +543,208 @@ export async function verifyRecoveryReleaseActivation(input: {
528
543
  return last;
529
544
  }
530
545
 
531
- function acquireRecoveryReleaseLock(controllerHome: string): { path: string; instanceId: string; close: () => void } {
532
- const attempt = acquireRecoveryOperationLock({
533
- controllerHome,
534
- action: 'install_recovery_release',
535
- instanceIdPrefix: 'recovery-release-',
536
- });
537
- if (!attempt.acquired) throw new Error('RECOVERY_OPERATION_LOCK_BUSY');
538
- return {
539
- path: attempt.handle.path,
540
- instanceId: attempt.handle.record.instanceId,
541
- close: attempt.handle.close,
542
- };
546
+ export const RECOVERY_RELEASE_LOCK_WAIT_MS = 10 * 60_000;
547
+ export const RECOVERY_RELEASE_LOCK_POLL_MS = 2_000;
548
+
549
+ /**
550
+ * An explicit operator release install must not fail merely because the Recovery
551
+ * daemon is inside its own bounded autonomous mutation (release step, tunnel
552
+ * repair, watchdog escalation). Wait for the single writer to finish, then take
553
+ * the same mutation lock; the lock still guarantees exactly one writer, and the
554
+ * bounded wait still fails closed if the holder never releases.
555
+ */
556
+ export async function acquireRecoveryReleaseLock(
557
+ controllerHome: string,
558
+ dependencies: {
559
+ acquire?: typeof acquireRecoveryOperationLock;
560
+ sleep?: (ms: number) => Promise<void>;
561
+ now?: () => number;
562
+ waitMs?: number;
563
+ report?: (detail: string) => void;
564
+ } = {},
565
+ ): Promise<{ path: string; instanceId: string; close: () => void }> {
566
+ const acquire = dependencies.acquire ?? acquireRecoveryOperationLock;
567
+ const sleep = dependencies.sleep ?? ((ms: number) => new Promise<void>((resolveSleep) => setTimeout(resolveSleep, ms)));
568
+ const now = dependencies.now ?? Date.now;
569
+ const report = dependencies.report ?? ((detail: string) => process.stderr.write(detail));
570
+ const deadline = now() + (dependencies.waitMs ?? RECOVERY_RELEASE_LOCK_WAIT_MS);
571
+ let reported = false;
572
+ for (;;) {
573
+ const attempt = acquire({
574
+ controllerHome,
575
+ action: 'install_recovery_release',
576
+ instanceIdPrefix: 'recovery-release-',
577
+ });
578
+ if (attempt.acquired) {
579
+ return {
580
+ path: attempt.handle.path,
581
+ instanceId: attempt.handle.record.instanceId,
582
+ close: attempt.handle.close,
583
+ };
584
+ }
585
+ if (now() >= deadline) {
586
+ throw new Error(`RECOVERY_OPERATION_LOCK_BUSY: ${attempt.owner.action} pid=${attempt.owner.pid} acquiredAt=${attempt.owner.acquiredAt}`);
587
+ }
588
+ if (!reported) {
589
+ report(
590
+ `recovery release install waiting for in-flight Recovery mutation: action=${attempt.owner.action} pid=${attempt.owner.pid} acquiredAt=${attempt.owner.acquiredAt}\n`,
591
+ );
592
+ reported = true;
593
+ }
594
+ await sleep(RECOVERY_RELEASE_LOCK_POLL_MS);
595
+ }
543
596
  }
544
597
 
545
- async function handoffRecoveryServices(input: {
598
+ function recoveryRoleLabel(role: RecoveryRuntimeRole): string {
599
+ return recoverySystemdLabel(role);
600
+ }
601
+
602
+ function retireRecoveryRoleService(input: {
546
603
  controllerHome: string;
547
- config: RecoveryConfig;
548
- expectedRelease: RecoveryReleaseDescriptor;
604
+ role: RecoveryRuntimeRole;
549
605
  dependencies: RecoveryInstallerDependencies;
550
- }): Promise<{ gateway: RecoveryServiceHandoffResult; watchdog: RecoveryServiceHandoffResult; verification: RecoveryActivationVerification }> {
606
+ }): void {
551
607
  const platform = input.dependencies.platform ?? process.platform;
608
+ const label = recoveryRoleLabel(input.role);
552
609
  if (platform === 'linux' && !input.dependencies.handoff) {
553
610
  const env = input.dependencies.systemdEnv ?? process.env;
554
- const available = input.dependencies.systemdAvailable ?? systemdUserAvailable;
555
- if (!available(env)) throw new Error('RECOVERY_SYSTEMD_USER_UNAVAILABLE');
556
- const installUnit = input.dependencies.installSystemdUnit ?? installSystemdUserUnit;
557
- const gatewayPath = installUnit({
558
- unitName: RECOVERY_GATEWAY_LABEL,
559
- unit: recoverySystemdUserUnitInput(input.controllerHome, 'gateway', env, recoveryReleaseProductVersion(input.expectedRelease)),
611
+ const unit = systemdUserUnitName(label);
612
+ const unitPath = systemdUserUnitPath(unit, env);
613
+ const disabled = runProcess('systemctl', ['--user', 'disable', '--now', unit], {
560
614
  env,
561
- errorPrefix: 'RECOVERY_SYSTEMD_INSTALL_FAILED',
615
+ timeoutMs: 30_000,
616
+ maxOutputBytes: 64 * 1024,
562
617
  });
563
- const watchdogPath = installUnit({
564
- unitName: RECOVERY_WATCHDOG_LABEL,
565
- unit: recoverySystemdUserUnitInput(input.controllerHome, 'watchdog', env, recoveryReleaseProductVersion(input.expectedRelease)),
618
+ const detail = `${disabled.stderr}\n${disabled.stdout}`;
619
+ if (!disabled.ok && !/not loaded|not found|does not exist|not enabled|no files found/i.test(detail)) {
620
+ throw new Error(`RECOVERY_SYSTEMD_RETIRE_FAILED: ${unit}: ${detail.trim()}`);
621
+ }
622
+ rmSync(unitPath, { force: true });
623
+ const reload = runProcess('systemctl', ['--user', 'daemon-reload'], {
566
624
  env,
567
- errorPrefix: 'RECOVERY_SYSTEMD_INSTALL_FAILED',
625
+ timeoutMs: 30_000,
626
+ maxOutputBytes: 64 * 1024,
568
627
  });
569
- const verification = await (input.dependencies.verify
570
- ?? ((verifyInput) => verifyRecoveryReleaseActivation(verifyInput, input.dependencies)))({
571
- controllerHome: input.controllerHome,
572
- config: input.config,
573
- expectedRelease: input.expectedRelease,
574
- });
575
- if (!verification.ok) throw new Error(`RECOVERY_RELEASE_VERIFICATION_FAILED: ${verification.failures.join('; ')}`);
576
- return {
577
- gateway: {
578
- platform: 'systemd-user',
579
- serviceRegistered: true,
580
- pidReady: Boolean(verification.gatewayPid),
581
- portReady: verification.healthStatus === 200,
582
- servicePath: gatewayPath,
583
- pid: verification.gatewayPid,
584
- },
585
- watchdog: {
586
- platform: 'systemd-user',
587
- serviceRegistered: true,
588
- pidReady: Boolean(verification.watchdogPid),
589
- portReady: true,
590
- servicePath: watchdogPath,
591
- pid: verification.watchdogPid,
592
- },
593
- verification,
594
- };
628
+ if (!reload.ok) throw new Error(`RECOVERY_SYSTEMD_DAEMON_RELOAD_FAILED: ${reload.stderr || reload.stdout}`);
629
+ return;
630
+ }
631
+ if (platform !== 'darwin' && !input.dependencies.handoff) {
632
+ throw new Error(`RECOVERY_RELEASE_ACTIVATION_UNSUPPORTED_PLATFORM: ${platform}`);
595
633
  }
596
- if (platform !== 'darwin' && !input.dependencies.handoff) throw new Error(`RECOVERY_RELEASE_ACTIVATION_UNSUPPORTED_PLATFORM: ${platform}`);
597
634
  const uid = (input.dependencies.uid ?? (() => typeof process.getuid === 'function' ? process.getuid() : Number.NaN))();
598
635
  if (!Number.isInteger(uid)) throw new Error('RECOVERY_LAUNCHD_UID_UNAVAILABLE');
599
- const domain = `gui/${uid}`;
600
- retireStaleRecoveryLaunchAgents(input.controllerHome, uid, input.dependencies.launchctlRunner);
601
- const installAgent = input.dependencies.installAgent ?? installLaunchAgent;
602
- const generated = (() => {
603
- const root = recoveryRoot(input.controllerHome);
604
- const generatedRoot = join(root, 'launchd');
605
- const auditRoot = join(root, 'audit');
636
+ const runner = input.dependencies.launchctlRunner ?? defaultRecoveryLaunchctl;
637
+ const target = `gui/${uid}/${label}`;
638
+ const observed = runner(['print', target]);
639
+ if (observed.ok) {
640
+ const bootout = runner(['bootout', target]);
641
+ if (!bootout.ok && !bootoutAlreadyGone(`${bootout.stderr}\n${bootout.stdout}`)) {
642
+ throw new Error(`RECOVERY_SERVICE_RETIRE_FAILED: ${label}`);
643
+ }
644
+ }
645
+ rmSync(join(recoveryRoot(input.controllerHome), 'launchd', `${label}.plist`), { force: true });
646
+ rmSync(launchAgentPath(label), { force: true });
647
+ }
648
+
649
+ async function handoffRecoveryServices(input: {
650
+ controllerHome: string;
651
+ config: RecoveryConfig;
652
+ expectedRelease: RecoveryReleaseDescriptor;
653
+ profile: RecoveryInstallProfile;
654
+ dependencies: RecoveryInstallerDependencies;
655
+ }): Promise<{ services: Partial<Record<RecoveryRuntimeRole, RecoveryServiceHandoffResult>>; verification: RecoveryActivationVerification }> {
656
+ const platform = input.dependencies.platform ?? process.platform;
657
+ const selectedRoles = new Set(recoveryInstallProfileRoles(input.profile));
658
+ for (const role of ['gateway', 'watchdog'] as const) {
659
+ if (!selectedRoles.has(role)) retireRecoveryRoleService({ controllerHome: input.controllerHome, role, dependencies: input.dependencies });
660
+ }
661
+
662
+ const services: Partial<Record<RecoveryRuntimeRole, RecoveryServiceHandoffResult>> = {};
663
+ if (platform === 'linux' && !input.dependencies.handoff) {
664
+ const env = input.dependencies.systemdEnv ?? process.env;
665
+ const available = input.dependencies.systemdAvailable ?? systemdUserAvailable;
666
+ if (selectedRoles.size > 0 && !available(env)) throw new Error('RECOVERY_SYSTEMD_USER_UNAVAILABLE');
667
+ const installUnit = input.dependencies.installSystemdUnit ?? installSystemdUserUnit;
668
+ for (const role of recoveryInstallProfileRoles(input.profile)) {
669
+ const servicePath = installUnit({
670
+ unitName: recoveryRoleLabel(role),
671
+ unit: recoverySystemdUserUnitInput(input.controllerHome, role, env, recoveryReleaseProductVersion(input.expectedRelease)),
672
+ env,
673
+ errorPrefix: 'RECOVERY_SYSTEMD_INSTALL_FAILED',
674
+ });
675
+ services[role] = {
676
+ platform: 'systemd-user',
677
+ serviceRegistered: true,
678
+ pidReady: false,
679
+ portReady: !recoveryRoleServesGateway(role),
680
+ servicePath,
681
+ };
682
+ }
683
+ } else {
684
+ if (platform !== 'darwin' && !input.dependencies.handoff) throw new Error(`RECOVERY_RELEASE_ACTIVATION_UNSUPPORTED_PLATFORM: ${platform}`);
685
+ const uid = (input.dependencies.uid ?? (() => typeof process.getuid === 'function' ? process.getuid() : Number.NaN))();
686
+ if (!Number.isInteger(uid)) throw new Error('RECOVERY_LAUNCHD_UID_UNAVAILABLE');
687
+ const domain = `gui/${uid}`;
688
+ retireStaleRecoveryLaunchAgents(input.controllerHome, uid, input.dependencies.launchctlRunner);
689
+ const installAgent = input.dependencies.installAgent ?? installLaunchAgent;
690
+ const currentPid = input.dependencies.currentPid ?? defaultCurrentPid;
691
+ const handoff = input.dependencies.handoff ?? safeLaunchdHandoff;
692
+ const generatedRoot = join(recoveryRoot(input.controllerHome), 'launchd');
693
+ const auditRoot = join(recoveryRoot(input.controllerHome), 'audit');
606
694
  mkdirSync(generatedRoot, { recursive: true, mode: 0o700 });
607
695
  mkdirSync(auditRoot, { recursive: true, mode: 0o700 });
608
- const gatewayPath = join(generatedRoot, `${RECOVERY_GATEWAY_LABEL}.plist`);
609
- const watchdogPath = join(generatedRoot, `${RECOVERY_WATCHDOG_LABEL}.plist`);
610
696
  const productVersion = recoveryReleaseProductVersion(input.expectedRelease);
611
- writeFileSync(gatewayPath, recoveryPlist({ label: RECOVERY_GATEWAY_LABEL, executable: join(recoveryCurrentPath(input.controllerHome), 'forge-recovery-gateway'), command: 'gateway', controllerHome: resolve(input.controllerHome), logPath: join(auditRoot, 'gateway.log'), productVersion }), { mode: 0o600 });
612
- writeFileSync(watchdogPath, recoveryPlist({ label: RECOVERY_WATCHDOG_LABEL, executable: join(recoveryCurrentPath(input.controllerHome), 'forge-recovery-watchdog'), command: 'watchdog', controllerHome: resolve(input.controllerHome), logPath: join(auditRoot, 'watchdog.log'), productVersion }), { mode: 0o600 });
613
- return {
614
- gateway: installAgent(gatewayPath, RECOVERY_GATEWAY_LABEL).path,
615
- watchdog: installAgent(watchdogPath, RECOVERY_WATCHDOG_LABEL).path,
616
- };
617
- })();
618
- const currentPid = input.dependencies.currentPid ?? defaultCurrentPid;
619
- const handoff = input.dependencies.handoff ?? safeLaunchdHandoff;
620
- const gatewayLaunchd = await handoff({
621
- label: RECOVERY_GATEWAY_LABEL,
622
- plistPath: generated.gateway,
623
- domain,
624
- oldPid: currentPid(input.controllerHome, 'gateway'),
625
- port: input.config.gateway?.port,
626
- maxBootoutWaitMs: 20_000,
627
- maxBootstrapRetry: 3,
628
- });
629
- if (!gatewayLaunchd.serviceRegistered || !gatewayLaunchd.pidWaitClean || !gatewayLaunchd.portWaitClean) {
630
- throw new Error('RECOVERY_GATEWAY_HANDOFF_FAILED');
697
+ for (const role of recoveryInstallProfileRoles(input.profile)) {
698
+ const label = recoveryRoleLabel(role);
699
+ const generatedPath = join(generatedRoot, `${label}.plist`);
700
+ writeFileSync(generatedPath, recoveryPlist({
701
+ label,
702
+ executable: join(recoveryCurrentPath(input.controllerHome), role === 'daemon' ? 'forge-recovery' : role === 'gateway' ? 'forge-recovery-gateway' : 'forge-recovery-watchdog'),
703
+ command: role,
704
+ controllerHome: resolve(input.controllerHome),
705
+ logPath: join(auditRoot, `${role}.log`),
706
+ productVersion,
707
+ }), { mode: 0o600 });
708
+ const installedPath = installAgent(generatedPath, label).path;
709
+ const launched = await handoff({
710
+ label,
711
+ plistPath: installedPath,
712
+ domain,
713
+ oldPid: currentPid(input.controllerHome, role),
714
+ ...(recoveryRoleServesGateway(role) ? { port: input.config.gateway?.port } : {}),
715
+ maxBootoutWaitMs: 20_000,
716
+ maxBootstrapRetry: 3,
717
+ });
718
+ if (!launched.serviceRegistered || !launched.pidWaitClean || (recoveryRoleServesGateway(role) && !launched.portWaitClean)) {
719
+ throw new Error(role === 'daemon' ? 'RECOVERY_DAEMON_HANDOFF_FAILED' : role === 'gateway' ? 'RECOVERY_GATEWAY_HANDOFF_FAILED' : 'RECOVERY_WATCHDOG_HANDOFF_FAILED');
720
+ }
721
+ services[role] = {
722
+ platform: 'launchd',
723
+ serviceRegistered: launched.serviceRegistered,
724
+ pidReady: launched.pidWaitClean,
725
+ portReady: recoveryRoleServesGateway(role) ? launched.portWaitClean : true,
726
+ servicePath: installedPath,
727
+ launchd: launched,
728
+ };
729
+ }
631
730
  }
632
- const watchdogLaunchd = await handoff({
633
- label: RECOVERY_WATCHDOG_LABEL,
634
- plistPath: generated.watchdog,
635
- domain,
636
- oldPid: currentPid(input.controllerHome, 'watchdog'),
637
- maxBootoutWaitMs: 20_000,
638
- maxBootstrapRetry: 3,
639
- });
640
- if (!watchdogLaunchd.serviceRegistered || !watchdogLaunchd.pidWaitClean) throw new Error('RECOVERY_WATCHDOG_HANDOFF_FAILED');
731
+
641
732
  const verification = await (input.dependencies.verify
642
733
  ?? ((verifyInput) => verifyRecoveryReleaseActivation(verifyInput, input.dependencies)))({
643
734
  controllerHome: input.controllerHome,
644
735
  config: input.config,
645
736
  expectedRelease: input.expectedRelease,
737
+ profile: input.profile,
646
738
  });
647
739
  if (!verification.ok) throw new Error(`RECOVERY_RELEASE_VERIFICATION_FAILED: ${verification.failures.join('; ')}`);
648
- return {
649
- gateway: { platform: 'launchd', serviceRegistered: gatewayLaunchd.serviceRegistered, pidReady: gatewayLaunchd.pidWaitClean, portReady: gatewayLaunchd.portWaitClean, servicePath: generated.gateway, launchd: gatewayLaunchd },
650
- watchdog: { platform: 'launchd', serviceRegistered: watchdogLaunchd.serviceRegistered, pidReady: watchdogLaunchd.pidWaitClean, portReady: watchdogLaunchd.portWaitClean, servicePath: generated.watchdog, launchd: watchdogLaunchd },
651
- verification,
652
- };
740
+ for (const role of recoveryInstallProfileRoles(input.profile)) {
741
+ const service = services[role];
742
+ if (!service) continue;
743
+ service.pid = role === 'daemon' ? verification.daemonPid : role === 'gateway' ? verification.gatewayPid : verification.watchdogPid;
744
+ service.pidReady = Boolean(service.pid);
745
+ if (recoveryRoleServesGateway(role)) service.portReady = verification.healthStatus === 200;
746
+ }
747
+ return { services, verification };
653
748
  }
654
749
 
655
750
  function sameRecoveryReleasePayload(left: RecoveryReleaseDescriptor, right: RecoveryReleaseDescriptor): boolean {
@@ -667,7 +762,8 @@ export async function activateRecoveryRelease(input: {
667
762
  }, dependencies: RecoveryInstallerDependencies = {}): Promise<RecoveryActivationResult> {
668
763
  const controllerHome = resolve(input.controllerHome);
669
764
  const config = input.config ?? loadRecoveryConfig(controllerHome);
670
- const lock = acquireRecoveryReleaseLock(controllerHome);
765
+ const profile = normalizeRecoveryInstallProfile(config.installProfile, 'self-healing');
766
+ const lock = await acquireRecoveryReleaseLock(controllerHome);
671
767
  const current = readCurrentRecoveryRelease(controllerHome);
672
768
  try {
673
769
  if (current && sameRecoveryReleasePayload(current, input.candidate)) {
@@ -676,6 +772,7 @@ export async function activateRecoveryRelease(input: {
676
772
  controllerHome,
677
773
  config,
678
774
  expectedRelease: current,
775
+ profile,
679
776
  });
680
777
  if (verification.ok) {
681
778
  return {
@@ -688,28 +785,37 @@ export async function activateRecoveryRelease(input: {
688
785
  }
689
786
  const previous = current ?? captureLegacyRecoveryRelease(controllerHome, dependencies);
690
787
  const migratedLegacy = previous?.legacy ? previous : undefined;
691
- publishRecoveryRelease(controllerHome, input.candidate.releasePath, previous?.releasePath);
692
- publishRecoveryCompatibilityLinks(controllerHome);
788
+ const drainArmed = Boolean(current);
693
789
  try {
694
- const activated = await handoffRecoveryServices({ controllerHome, config, expectedRelease: input.candidate, dependencies });
695
- return { release: input.candidate, previous, migratedLegacy, handoff: { gateway: activated.gateway, watchdog: activated.watchdog }, verification: activated.verification };
696
- } catch (activationError) {
697
- if (!previous) throw activationError;
698
- publishRecoveryRelease(controllerHome, previous.releasePath, input.candidate.releasePath);
790
+ // The shared operation lock already excludes every Recovery mutation.
791
+ // Stop new transport admission during handoff; retained read streams are
792
+ // connections, not effects, and cannot be a second quiescence authority.
793
+ if (drainArmed) setRecoveryGatewayDraining(controllerHome, true);
794
+ publishRecoveryRelease(controllerHome, input.candidate.releasePath, previous?.releasePath);
699
795
  publishRecoveryCompatibilityLinks(controllerHome);
700
- let rollbackResult: Awaited<ReturnType<typeof handoffRecoveryServices>>;
701
796
  try {
702
- rollbackResult = await handoffRecoveryServices({ controllerHome, config, expectedRelease: previous, dependencies });
703
- } catch (rollbackError) {
797
+ const activated = await handoffRecoveryServices({ controllerHome, config, expectedRelease: input.candidate, profile, dependencies });
798
+ return { release: input.candidate, previous, migratedLegacy, handoff: activated.services, verification: activated.verification };
799
+ } catch (activationError) {
800
+ if (!previous) throw activationError;
801
+ publishRecoveryRelease(controllerHome, previous.releasePath, input.candidate.releasePath);
802
+ publishRecoveryCompatibilityLinks(controllerHome);
803
+ let rollbackResult: Awaited<ReturnType<typeof handoffRecoveryServices>>;
804
+ try {
805
+ rollbackResult = await handoffRecoveryServices({ controllerHome, config, expectedRelease: previous, profile, dependencies });
806
+ } catch (rollbackError) {
807
+ throw new Error(
808
+ `RECOVERY_RELEASE_ACTIVATION_FAILED: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
809
+ + `rollback to ${previous.releaseRevision} failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
810
+ );
811
+ }
704
812
  throw new Error(
705
- `RECOVERY_RELEASE_ACTIVATION_FAILED: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
706
- + `rollback to ${previous.releaseRevision} failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
813
+ `RECOVERY_RELEASE_ACTIVATION_FAILED_ROLLED_BACK: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
814
+ + `restored=${previous.releaseRevision}; recovery=${rollbackResult.verification.daemonPid ?? rollbackResult.verification.gatewayPid ?? 'unknown'}`,
707
815
  );
708
816
  }
709
- throw new Error(
710
- `RECOVERY_RELEASE_ACTIVATION_FAILED_ROLLED_BACK: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
711
- + `restored=${previous.releaseRevision}; gateway=${rollbackResult.verification.gatewayPid ?? 'unknown'}; watchdog=${rollbackResult.verification.watchdogPid ?? 'unknown'}`,
712
- );
817
+ } finally {
818
+ if (drainArmed) setRecoveryGatewayDraining(controllerHome, false);
713
819
  }
714
820
  } finally {
715
821
  lock.close();
@@ -797,9 +903,14 @@ export async function installStandaloneRecovery(input: {
797
903
  primaryPublicTunnelService?: PublicTunnelServiceConfig;
798
904
  primaryRuntimeService?: PrimaryRuntimeServiceConfig;
799
905
  primaryConnectorService?: PrimaryConnectorServiceConfig;
906
+ profile?: RecoveryInstallProfile;
907
+ /** @deprecated Compatibility alias for profile=manual without config/service activation. */
800
908
  stageOnly?: boolean;
801
909
  }, dependencies: RecoveryInstallerDependencies = {}): Promise<RecoveryInstallResult> {
802
910
  const controllerHome = resolve(input.controllerHome);
911
+ const profile = input.stageOnly
912
+ ? 'manual'
913
+ : normalizeRecoveryInstallProfile(input.profile, 'self-healing');
803
914
  const sourceRoot = resolve(input.sourceRoot ?? input.repoRoot);
804
915
  const primaryRuntimeSourceRoot = resolve(input.repoRoot);
805
916
  const primaryRuntimeSourceRepositoryId = input.primaryRuntimeSourceRepositoryId?.trim();
@@ -836,9 +947,10 @@ export async function installStandaloneRecovery(input: {
836
947
  }
837
948
  const staged = stageRecoveryRelease({ controllerHome, sourceRoot }, dependencies);
838
949
  if (input.stageOnly) {
839
- return { controllerHome, staged, config: loadRecoveryConfig(controllerHome) };
950
+ return { controllerHome, profile: 'manual', staged, config: loadRecoveryConfig(controllerHome) };
840
951
  }
841
952
  const config = initializeStandaloneRecovery(controllerHome, input.port ?? 8787, {
953
+ installProfile: profile,
842
954
  ...(input.publicMcpUrl ? { publicMcpUrl: input.publicMcpUrl } : {}),
843
955
  ...(input.recoveryPublicUrl ? { recoveryPublicUrl: input.recoveryPublicUrl } : {}),
844
956
  ...(input.recoveryTunnelService ? { recoveryTunnelService: input.recoveryTunnelService } : {}),
@@ -849,7 +961,7 @@ export async function installStandaloneRecovery(input: {
849
961
  ...(input.primaryConnectorService ? { primaryConnectorService: input.primaryConnectorService } : {}),
850
962
  });
851
963
  const activated = await activateRecoveryRelease({ controllerHome, config, candidate: staged.release }, dependencies);
852
- return { controllerHome, staged, activated, config };
964
+ return { controllerHome, profile, staged, activated, config };
853
965
  }
854
966
 
855
967
  export function recoveryReleaseAuthoritySnapshot(controllerHome: string): {
@@ -857,6 +969,7 @@ export function recoveryReleaseAuthoritySnapshot(controllerHome: string): {
857
969
  previous?: RecoveryReleaseDescriptor;
858
970
  currentPath: string;
859
971
  previousPath: string;
972
+ daemonLaunchAgent: string;
860
973
  gatewayLaunchAgent: string;
861
974
  watchdogLaunchAgent: string;
862
975
  } {
@@ -865,6 +978,7 @@ export function recoveryReleaseAuthoritySnapshot(controllerHome: string): {
865
978
  previous: readPreviousRecoveryRelease(controllerHome),
866
979
  currentPath: recoveryCurrentPath(controllerHome),
867
980
  previousPath: recoveryPreviousPath(controllerHome),
981
+ daemonLaunchAgent: launchAgentPath(RECOVERY_DAEMON_LABEL),
868
982
  gatewayLaunchAgent: launchAgentPath(RECOVERY_GATEWAY_LABEL),
869
983
  watchdogLaunchAgent: launchAgentPath(RECOVERY_WATCHDOG_LABEL),
870
984
  };