@moretea-labs/forge 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (685) hide show
  1. package/AGENTS.md +58 -62
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +42 -62
  4. package/README.md +2 -2
  5. package/SKILL.md +0 -1
  6. package/THIRD_PARTY_NOTICES.md +5 -2
  7. package/adapters/browser/index.ts +2 -0
  8. package/adapters/chatgpt/browser-delivery-host.ts +71 -0
  9. package/adapters/chatgpt/browser-delivery-runtime.ts +783 -0
  10. package/adapters/chatgpt/controller-binding-store.ts +55 -0
  11. package/adapters/chatgpt/controller-host.ts +119 -0
  12. package/adapters/chatgpt/controller-round-host.ts +78 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +45 -0
  14. package/adapters/chatgpt/provider-delivery.ts +113 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +192 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/desktop-operator-contract.ts +7 -0
  18. package/adapters/computer/desktop-operator-discovery.ts +201 -0
  19. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  20. package/adapters/computer/desktop-operator-provider.ts +312 -0
  21. package/adapters/computer/index.ts +1 -0
  22. package/adapters/computer/interaction-target-authority.ts +944 -0
  23. package/adapters/controller-process/binding-store.ts +34 -0
  24. package/adapters/controller-process/controller-host.ts +26 -0
  25. package/adapters/mcp/access-mode.ts +132 -0
  26. package/adapters/mcp/audit.ts +32 -0
  27. package/adapters/mcp/auth.ts +407 -0
  28. package/adapters/mcp/controller-round-compatibility.ts +184 -0
  29. package/adapters/mcp/frozen-client-semantic-compatibility.ts +292 -0
  30. package/adapters/mcp/instructions.ts +36 -0
  31. package/adapters/mcp/legacy-context.ts +18 -0
  32. package/adapters/mcp/multi-repository.ts +437 -0
  33. package/adapters/mcp/oauth.ts +391 -0
  34. package/adapters/mcp/paths.ts +140 -0
  35. package/adapters/mcp/policy.ts +232 -0
  36. package/adapters/mcp/proxy-env.ts +62 -0
  37. package/adapters/mcp/readiness-projection.ts +24 -0
  38. package/adapters/mcp/redaction.ts +21 -0
  39. package/adapters/mcp/repo.ts +24 -0
  40. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  41. package/adapters/mcp/runtime-gateway/context-adapter.ts +829 -0
  42. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +191 -0
  43. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +147 -0
  44. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  45. package/adapters/mcp/runtime-gateway/execution-tools.ts +207 -0
  46. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  47. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +411 -0
  48. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  49. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  50. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  51. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  52. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +308 -0
  53. package/adapters/mcp/runtime-gateway/process-tools.ts +289 -0
  54. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +76 -0
  55. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +268 -0
  56. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  57. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  58. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +110 -0
  59. package/adapters/mcp/runtime-gateway/result-adapter.ts +108 -0
  60. package/adapters/mcp/runtime-gateway/router.ts +728 -0
  61. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1251 -0
  62. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +634 -0
  63. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  64. package/adapters/mcp/runtime-gateway/runtime-tools.ts +68 -0
  65. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +186 -0
  66. package/adapters/mcp/runtime-gateway/shared-adapter.ts +25 -0
  67. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +870 -0
  68. package/adapters/mcp/runtime-gateway/work-adapter.ts +3186 -0
  69. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +502 -0
  70. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  71. package/adapters/mcp/server.ts +1048 -0
  72. package/adapters/mcp/setup.ts +791 -0
  73. package/adapters/mcp/tool-capability-cache.ts +87 -0
  74. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  75. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5265 -0
  76. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +150 -0
  77. package/adapters/mcp/tool-mapping/repository-tools.ts +1691 -0
  78. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  79. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  80. package/adapters/mcp/toolset-names.ts +74 -0
  81. package/adapters/mcp/toolset-selection.ts +41 -0
  82. package/adapters/mcp/toolset.ts +336 -0
  83. package/adapters/mcp/transports/http.ts +1456 -0
  84. package/adapters/mcp/transports/session-registry.ts +513 -0
  85. package/adapters/mcp/transports/stdio.ts +8 -0
  86. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +139 -0
  87. package/adapters/mcp/types.ts +41 -0
  88. package/assets/AGENTS.md +2 -16
  89. package/assets/CLAUDE.md +2 -16
  90. package/assets/hooks/AGENTS.md +3 -14
  91. package/assets/hooks/CLAUDE.md +3 -14
  92. package/assets/hooks/hook-input.sh +9 -2
  93. package/assets/hooks/lib/workflow-state.sh +28 -10
  94. package/assets/hooks/prompt-guard.sh +63 -37
  95. package/assets/hooks/security-sentinel.sh +2 -1
  96. package/assets/hooks/session-start-context.sh +7 -5
  97. package/assets/hooks/stop-orchestrator.sh +13 -3
  98. package/assets/initializer-question-pack.v4.json +11 -7
  99. package/assets/partials/05-workflow.partial.md +18 -73
  100. package/assets/partials/07-footer.partial.md +1 -1
  101. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  102. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  103. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  104. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  105. package/assets/plan-map.json +11 -11
  106. package/assets/plugin-registry.v1.json +9 -3
  107. package/assets/plugin-trust-policy.v1.json +6 -0
  108. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  109. package/assets/recovery/forge-wsl-rescue +271 -0
  110. package/assets/reference-configs/development-protocol.md +11 -15
  111. package/assets/reference-configs/document-generation.md +1 -1
  112. package/assets/reference-configs/global-working-rules.md +9 -1
  113. package/assets/reference-configs/harness-overview.md +22 -112
  114. package/assets/reference-configs/hook-operations.md +1 -1
  115. package/assets/reference-configs/sprint-contracts.md +2 -2
  116. package/assets/skill-version.json +2 -2
  117. package/assets/templates/helpers/architecture-event.ts +3 -34
  118. package/assets/templates/helpers/architecture-queue.sh +61 -10
  119. package/assets/templates/helpers/archive-workflow.sh +90 -180
  120. package/assets/templates/helpers/capability-config.ts +8 -11
  121. package/assets/templates/helpers/capability-resolver.ts +37 -19
  122. package/assets/templates/helpers/capture-plan.sh +2 -2
  123. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  124. package/assets/templates/helpers/check-task-sync.sh +82 -11
  125. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  126. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  127. package/assets/templates/helpers/contract-worktree.sh +26 -24
  128. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  129. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  130. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  131. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  132. package/assets/templates/helpers/new-plan.sh +2 -2
  133. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  134. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  135. package/assets/templates/helpers/switch-plan.sh +1 -1
  136. package/assets/templates/helpers/workflow-contract.ts +1 -0
  137. package/assets/workflow-contract.v1.json +63 -171
  138. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  139. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  140. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  141. package/docs/architecture/CURRENT.md +148 -13
  142. package/docs/architecture/EVOLUTION.md +13 -0
  143. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  144. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  145. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  146. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  147. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +35 -0
  148. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +296 -0
  149. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  150. package/docs/architecture/domains/verification.md +7 -0
  151. package/docs/architecture/domains/workflow-engine.md +1 -1
  152. package/docs/architecture/index.md +3 -3
  153. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  154. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  155. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  156. package/docs/forge-plugin-management.md +20 -4
  157. package/docs/forge-runtime-self-healing-loop.md +2 -0
  158. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  159. package/docs/operations/controller-browser-plugin.md +3 -1
  160. package/docs/operations/homebrew.md +2 -2
  161. package/docs/operations/platform-support.md +16 -8
  162. package/docs/operations/platform-support.zh-CN.md +2 -2
  163. package/docs/operations/standalone-disaster-recovery.md +26 -1
  164. package/docs/operations/uu-remote-rescue.md +41 -0
  165. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  166. package/docs/public-usage-guide.md +1 -1
  167. package/docs/public-usage-guide.zh-CN.md +1 -1
  168. package/docs/tutorials/01-install-and-start.md +1 -1
  169. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  170. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  171. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  172. package/docs/wiki/Implementation.md +1 -1
  173. package/package.json +23 -14
  174. package/packages/kernel/controller/api/index.ts +2 -0
  175. package/packages/kernel/controller/application/controller-round-service.ts +29 -0
  176. package/packages/kernel/controller/application/controller-service.ts +30 -0
  177. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +308 -0
  178. package/packages/kernel/controller/domain/controller-round.ts +107 -0
  179. package/packages/kernel/controller/domain/execution-quality.ts +133 -0
  180. package/packages/kernel/controller/domain/types.ts +56 -0
  181. package/packages/kernel/controller/index.ts +1 -0
  182. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  183. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1384 -0
  184. package/packages/kernel/controller/infrastructure/controller-session-store.ts +578 -0
  185. package/packages/kernel/controller/ports/controller-host.ts +16 -0
  186. package/packages/kernel/goal/api/index.ts +1 -0
  187. package/packages/kernel/goal/domain/types.ts +40 -0
  188. package/packages/kernel/goal/index.ts +1 -0
  189. package/packages/kernel/identity/api/index.ts +5 -0
  190. package/packages/kernel/identity/application/identity-service.ts +67 -0
  191. package/packages/kernel/identity/domain/scope.ts +63 -0
  192. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  193. package/packages/kernel/identity/domain/types.ts +44 -0
  194. package/packages/kernel/identity/domain/workspace.ts +56 -0
  195. package/packages/kernel/identity/index.ts +1 -0
  196. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  197. package/packages/kernel/memory/api/index.ts +5 -0
  198. package/packages/kernel/memory/application/experience-service.ts +118 -0
  199. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  200. package/packages/kernel/memory/domain/experience.ts +103 -0
  201. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  202. package/packages/kernel/memory/index.ts +1 -0
  203. package/packages/kernel/progression/api/index.ts +2 -0
  204. package/packages/kernel/progression/application/projector.ts +211 -0
  205. package/packages/kernel/progression/domain/types.ts +116 -0
  206. package/packages/kernel/progression/index.ts +1 -0
  207. package/packages/kernel/scheduler/api/index.ts +6 -0
  208. package/packages/kernel/scheduler/application/continuation-service.ts +212 -0
  209. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  210. package/packages/kernel/scheduler/application/schedule-service.ts +24 -0
  211. package/packages/kernel/scheduler/application/settlement.ts +197 -0
  212. package/packages/kernel/scheduler/domain/continuation.ts +38 -0
  213. package/packages/kernel/scheduler/domain/schedule.ts +149 -0
  214. package/packages/kernel/scheduler/index.ts +1 -0
  215. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +38 -0
  216. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +785 -0
  217. package/packages/kernel/work/api/index.ts +15 -0
  218. package/packages/kernel/work/application/work-service.ts +148 -0
  219. package/packages/kernel/work/domain/admission-policy.ts +34 -0
  220. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  221. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  222. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  223. package/packages/kernel/work/domain/engineering-design.ts +281 -0
  224. package/packages/kernel/work/domain/engineering-profile.ts +242 -0
  225. package/packages/kernel/work/domain/execution-concurrency.ts +254 -0
  226. package/packages/kernel/work/domain/execution-snapshot.ts +33 -0
  227. package/packages/kernel/work/domain/implementation-review.ts +510 -0
  228. package/packages/kernel/work/domain/project-engineering-contract.ts +202 -0
  229. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  230. package/packages/kernel/work/domain/state-machine.ts +259 -0
  231. package/packages/kernel/work/domain/types.ts +528 -0
  232. package/packages/kernel/work/index.ts +1 -0
  233. package/packages/kernel/work/infrastructure/work-contract-store.ts +1876 -0
  234. package/packages/kernel/work/ports/work-contract-store.ts +18 -0
  235. package/packages/plugin-runtime/browser/index.ts +3 -0
  236. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  237. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  238. package/packages/plugin-runtime/browser/session-authority.ts +61 -0
  239. package/packages/plugin-runtime/computer/index.ts +6 -0
  240. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  241. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  242. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  243. package/packages/plugin-runtime/computer/provider.ts +19 -0
  244. package/packages/plugin-runtime/computer/target-authority.ts +190 -0
  245. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  246. package/packages/plugin-runtime/external/index.ts +1 -0
  247. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +457 -0
  248. package/packages/plugin-runtime/index.ts +3 -0
  249. package/packages/protocols/browser/index.ts +1 -0
  250. package/packages/protocols/browser/session.ts +98 -0
  251. package/packages/protocols/computer/contract.ts +199 -0
  252. package/packages/protocols/computer/index.ts +1 -0
  253. package/packages/protocols/failure/contract.ts +158 -0
  254. package/packages/protocols/failure/index.ts +1 -0
  255. package/packages/protocols/handoff/index.ts +1 -0
  256. package/packages/protocols/handoff/status.ts +22 -0
  257. package/packages/protocols/mcp/execution-context.ts +11 -0
  258. package/packages/protocols/mcp/index.ts +2 -0
  259. package/packages/protocols/mcp/tool-contract.ts +42 -0
  260. package/packages/workflow-runtime/api/index.ts +2 -0
  261. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  262. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  263. package/packages/workflow-runtime/index.ts +1 -0
  264. package/scripts/assemble-template.ts +1 -1
  265. package/scripts/benchmark-computer-hotpath.ts +255 -0
  266. package/scripts/benchmark-controller-round2.ts +54 -2
  267. package/scripts/benchmark-ios-agent-device.ts +3 -6
  268. package/scripts/benchmark-mcp-transport.ts +3 -4
  269. package/scripts/benchmark-operational-memory-activation.ts +235 -0
  270. package/scripts/benchmark-operational-shadow.ts +28 -0
  271. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  272. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  273. package/scripts/canary-controller-semantic-wait.ts +195 -0
  274. package/scripts/canary-global-hook.sh +2 -2
  275. package/scripts/check-mcp-compatibility.ts +173 -7
  276. package/scripts/check-platform-support.mjs +28 -2
  277. package/scripts/check-public-docs.sh +1 -1
  278. package/scripts/check-release-readiness.sh +1 -1
  279. package/scripts/check-repository-hygiene.mjs +92 -0
  280. package/scripts/check-runtime-architecture.mjs +1206 -95
  281. package/scripts/check-source-duplication.mjs +9 -1
  282. package/scripts/check-tarball-install-smoke.sh +98 -15
  283. package/scripts/controller-ngrok-rotation.sh +5 -4
  284. package/scripts/create-project-dirs.sh +10 -74
  285. package/scripts/forge-local-recovery-helper.mjs +204 -0
  286. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  287. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  288. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  289. package/scripts/hook-shim.sh +2 -2
  290. package/scripts/init-project.sh +12 -70
  291. package/scripts/install-desktop-operator-registration.ts +4 -2
  292. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  293. package/scripts/install-standalone-recovery.ts +122 -10
  294. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  295. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  296. package/scripts/lib/controller-home-env.sh +6 -3
  297. package/scripts/lib/project-init-lib.sh +39 -582
  298. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  299. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  300. package/scripts/open-source-audit-allowlist.txt +12 -1
  301. package/scripts/portability-bindings.mjs +70 -0
  302. package/scripts/run-bounded-command.mjs +55 -0
  303. package/scripts/run-bun-test-file.ts +22 -6
  304. package/scripts/run-governed-gate.ts +4 -1
  305. package/scripts/smoke-mcp-http-runtime.ts +1 -1
  306. package/scripts/smoke-runtime-recovery.ts +34 -1
  307. package/scripts/smoke-schedule-engine.ts +1 -1
  308. package/scripts/stage-runtime-release.ts +6 -1
  309. package/scripts/test-governance.ts +51 -2
  310. package/src/cli/agent-jobs/job-manager.ts +10 -1
  311. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  312. package/src/cli/chatgpt-browser/binding.ts +20 -10
  313. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  314. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  315. package/src/cli/chatgpt-browser/engine.ts +10 -11
  316. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  317. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  318. package/src/cli/chatgpt-browser/types.ts +1 -0
  319. package/src/cli/commands/bootstrap-control.ts +334 -0
  320. package/src/cli/commands/brain-assistant.ts +38 -0
  321. package/src/cli/commands/brain.ts +193 -40
  322. package/src/cli/commands/chatgpt.ts +127 -0
  323. package/src/cli/commands/computer.ts +437 -0
  324. package/src/cli/commands/doctor.ts +18 -1
  325. package/src/cli/commands/init-hook.ts +71 -61
  326. package/src/cli/commands/init.ts +3 -1
  327. package/src/cli/commands/mcp.ts +16 -10
  328. package/src/cli/commands/plugin.ts +38 -12
  329. package/src/cli/commands/recovery.ts +493 -80
  330. package/src/cli/commands/runtime.ts +9 -4
  331. package/src/cli/commands/setup-profile.ts +89 -39
  332. package/src/cli/commands/status.ts +2 -1
  333. package/src/cli/controller/check-runner.ts +487 -98
  334. package/src/cli/controller/context/types.ts +7 -1
  335. package/src/cli/controller/context-pack.ts +23 -3
  336. package/src/cli/controller/issue-store.ts +1 -1
  337. package/src/cli/controller/systemd-user.ts +110 -0
  338. package/src/cli/controller/types.ts +10 -70
  339. package/src/cli/controller/worklog.ts +67 -13
  340. package/src/cli/editing/edit-operation-contract.ts +92 -0
  341. package/src/cli/editing/edit-session.ts +289 -13
  342. package/src/cli/github/plugin.ts +17 -12
  343. package/src/cli/hook/route-registry.ts +3 -5
  344. package/src/cli/hook/runtime.ts +49 -3
  345. package/src/cli/index.ts +3 -0
  346. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  347. package/src/cli/local-bridge/console-automations.ts +2 -2
  348. package/src/cli/local-bridge/facade-api.ts +41 -14
  349. package/src/cli/local-bridge/job-store.ts +1 -1
  350. package/src/cli/local-bridge/server.ts +24 -27
  351. package/src/cli/mcp/access-mode.ts +2 -132
  352. package/src/cli/mcp/access-tools.ts +2 -387
  353. package/src/cli/mcp/audit.ts +2 -32
  354. package/src/cli/mcp/auth.ts +2 -404
  355. package/src/cli/mcp/instructions.ts +2 -34
  356. package/src/cli/mcp/legacy-context.ts +2 -16
  357. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  358. package/src/cli/mcp/multi-repository.ts +2 -436
  359. package/src/cli/mcp/oauth.ts +2 -375
  360. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  361. package/src/cli/mcp/paths.ts +2 -140
  362. package/src/cli/mcp/policy.ts +2 -232
  363. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  364. package/src/cli/mcp/proxy-env.ts +2 -62
  365. package/src/cli/mcp/readiness-projection.ts +2 -24
  366. package/src/cli/mcp/redaction.ts +2 -21
  367. package/src/cli/mcp/repo.ts +2 -24
  368. package/src/cli/mcp/repository-tools.ts +2 -1613
  369. package/src/cli/mcp/server.ts +2 -951
  370. package/src/cli/mcp/setup.ts +2 -737
  371. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  372. package/src/cli/mcp/tool-contract.ts +2 -14
  373. package/src/cli/mcp/tools.ts +2 -9
  374. package/src/cli/mcp/toolset-names.ts +2 -70
  375. package/src/cli/mcp/toolset-selection.ts +2 -41
  376. package/src/cli/mcp/toolset.ts +2 -336
  377. package/src/cli/mcp/transports/AGENTS.md +1 -10
  378. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  379. package/src/cli/mcp/transports/http.ts +2 -1414
  380. package/src/cli/mcp/transports/session-registry.ts +2 -490
  381. package/src/cli/mcp/transports/stdio.ts +2 -8
  382. package/src/cli/mcp/types.ts +2 -41
  383. package/src/cli/mcp/umbrella-tools.ts +2 -117
  384. package/src/cli/repositories/command-executor.ts +64 -18
  385. package/src/cli/repositories/command-scope.ts +117 -1
  386. package/src/cli/repositories/controller-home.ts +119 -1
  387. package/src/cli/repositories/identity.ts +13 -0
  388. package/src/cli/repositories/registry.ts +68 -49
  389. package/src/cli/repositories/repository-snapshot.ts +53 -7
  390. package/src/cli/repositories/runtime-storage.ts +27 -2
  391. package/src/cli/repositories/safe-patch.ts +34 -38
  392. package/src/cli/repositories/selected-path-actions.ts +22 -2
  393. package/src/cli/repositories/structured-git.ts +105 -25
  394. package/src/cli/runtime/helper-runner.ts +7 -0
  395. package/src/cli/runtime-invocation.ts +8 -1
  396. package/src/cli/tools/codegraph.ts +44 -25
  397. package/src/core/adoption/manifest-templates.ts +1 -1
  398. package/src/core/adoption/plan.ts +3 -26
  399. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  400. package/src/effects/process-runner.ts +8 -14
  401. package/src/runtime/context/assistant-context.ts +154 -0
  402. package/src/runtime/context/assistant-work-context.ts +147 -0
  403. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  404. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  405. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  406. package/src/runtime/context/context-closure.ts +268 -0
  407. package/src/runtime/context/context-plane.ts +211 -0
  408. package/src/runtime/context/project-engineering-contract.ts +60 -0
  409. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  410. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  411. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  412. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  413. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  414. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  415. package/src/runtime/control-plane/cleanup-artifact-retention.ts +328 -0
  416. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  417. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +320 -0
  418. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +169 -0
  419. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +350 -0
  420. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +85 -0
  421. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +369 -99
  422. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +103 -15
  423. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  424. package/src/runtime/control-plane/execution/repository-work-attribution.ts +2 -2
  425. package/src/runtime/control-plane/execution/retained-work-resume.ts +336 -6
  426. package/src/runtime/control-plane/execution/validation.ts +28 -3
  427. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +116 -0
  428. package/src/runtime/control-plane/execution/work-completion-authority.ts +44 -2
  429. package/src/runtime/control-plane/execution/work-evidence-policy.ts +34 -3
  430. package/src/runtime/control-plane/execution/work-execution-support.ts +42 -22
  431. package/src/runtime/control-plane/execution/work-finalization-service.ts +1103 -166
  432. package/src/runtime/control-plane/execution/work-handle-authority.ts +219 -22
  433. package/src/runtime/control-plane/execution/work-handle-store.ts +77 -0
  434. package/src/runtime/control-plane/execution/work-head-settlement.ts +75 -0
  435. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +145 -23
  436. package/src/runtime/control-plane/execution/work-operation-service.ts +49 -13
  437. package/src/runtime/control-plane/execution/work-preparation-service.ts +31 -15
  438. package/src/runtime/control-plane/execution/work-remote-delivery.ts +1 -1
  439. package/src/runtime/control-plane/execution/work-task-receipt.ts +20 -1
  440. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +407 -76
  441. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +88 -18
  442. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  443. package/src/runtime/control-plane/execution/work-verification-service.ts +258 -4
  444. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +200 -15
  445. package/src/runtime/control-plane/facade/capability-registry.ts +21 -10
  446. package/src/runtime/control-plane/facade/controller-round-relay.ts +3 -1118
  447. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  448. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -3
  449. package/src/runtime/control-plane/facade/goal-workloop.ts +972 -137
  450. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +158 -0
  451. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +10 -0
  452. package/src/runtime/control-plane/facade/index.ts +4 -2
  453. package/src/runtime/control-plane/facade/operation-digest.ts +22 -58
  454. package/src/runtime/control-plane/facade/plan-contract-store.ts +1004 -38
  455. package/src/runtime/control-plane/facade/repository-work-admission.ts +19 -1
  456. package/src/runtime/control-plane/facade/requirement-authority.ts +218 -0
  457. package/src/runtime/control-plane/facade/requirement-board.ts +6 -1
  458. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +48 -0
  459. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  460. package/src/runtime/control-plane/facade/suggested-actions.ts +4 -1
  461. package/src/runtime/control-plane/facade/types.ts +189 -532
  462. package/src/runtime/control-plane/facade/work-admission-policy.ts +84 -35
  463. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  464. package/src/runtime/control-plane/facade/work-implementation-review.ts +2 -0
  465. package/src/runtime/control-plane/facade/work-state-machine.ts +2 -250
  466. package/src/runtime/control-plane/generated-cache-retention.ts +239 -18
  467. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +12 -1
  468. package/src/runtime/control-plane/global-scheduler/maintenance.ts +76 -13
  469. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  470. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  471. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -1
  472. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  473. package/src/runtime/control-plane/global-scheduler/scheduler.ts +173 -81
  474. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  475. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +397 -0
  476. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  477. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +301 -560
  478. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +20 -4
  479. package/src/runtime/control-plane/launcher/thin-launcher.ts +50 -4
  480. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  481. package/src/runtime/control-plane/persistence/experience-store.ts +169 -0
  482. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  483. package/src/runtime/control-plane/persistence/requirement-store.ts +30 -22
  484. package/src/runtime/control-plane/persistence/sqlite-store.ts +545 -76
  485. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  486. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  487. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  488. package/src/runtime/control-plane/release-retention.ts +56 -16
  489. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  490. package/src/runtime/control-plane/routing/route-policy.ts +19 -0
  491. package/src/runtime/control-plane/runtime-cleanup.ts +574 -37
  492. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  493. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +465 -0
  494. package/src/runtime/control-plane/workspace/workspace-store.ts +80 -0
  495. package/src/runtime/diagnostics/incident-repair.ts +406 -0
  496. package/src/runtime/diagnostics/mcp-timing.ts +125 -6
  497. package/src/runtime/diagnostics/performance.ts +6 -14
  498. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  499. package/src/runtime/evidence/artifact-store.ts +65 -2
  500. package/src/runtime/evidence/operational-shadow.ts +38 -0
  501. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  502. package/src/runtime/evidence/sensitive-output.ts +4 -4
  503. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  504. package/src/runtime/execution/jobs/store.ts +121 -2
  505. package/src/runtime/execution/managed-workspace.ts +55 -5
  506. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  507. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  508. package/src/runtime/execution/process-runtime/check-facade.ts +23 -9
  509. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  510. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  511. package/src/runtime/execution/process-runtime/check-result.ts +73 -2
  512. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -3
  513. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  514. package/src/runtime/execution/process-runtime/command-facade.ts +38 -87
  515. package/src/runtime/execution/process-runtime/index.ts +1 -0
  516. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  517. package/src/runtime/execution/process-runtime/lightweight-managed.ts +32 -1
  518. package/src/runtime/execution/process-runtime/persisted-check.ts +128 -21
  519. package/src/runtime/execution/process-runtime/resource-claims.ts +60 -3
  520. package/src/runtime/execution/process-runtime/runtime.ts +132 -15
  521. package/src/runtime/execution/process-runtime/types.ts +22 -0
  522. package/src/runtime/execution/workers/executor.ts +16 -13
  523. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  524. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  525. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  526. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  527. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  528. package/src/runtime/gateway/mcp/router.ts +2 -1125
  529. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  530. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  531. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  532. package/src/runtime/maintenance/cleanup.ts +38 -24
  533. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  534. package/src/runtime/platform/computer-platform.ts +14 -0
  535. package/src/runtime/platform/executable-discovery.ts +54 -0
  536. package/src/runtime/platform/fetch-http-server.ts +90 -0
  537. package/src/runtime/platform/service-manager.ts +138 -0
  538. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  539. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  540. package/src/runtime/plugins/action-application.ts +135 -0
  541. package/src/runtime/plugins/app-store-connect-adapter.ts +9 -21
  542. package/src/runtime/plugins/browser-adapter.ts +140 -100
  543. package/src/runtime/plugins/browser-automation-service.ts +15 -36
  544. package/src/runtime/plugins/browser-macos-bridge.ts +69 -21
  545. package/src/runtime/plugins/browser-manifest-surface.ts +30 -6
  546. package/src/runtime/plugins/browser-native-session-service.ts +1 -1
  547. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  548. package/src/runtime/plugins/browser-registration.ts +16 -0
  549. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  550. package/src/runtime/plugins/browser-runtime.ts +41 -9
  551. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  552. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  553. package/src/runtime/plugins/browser-session-store.ts +229 -36
  554. package/src/runtime/plugins/browser-session-types.ts +2 -98
  555. package/src/runtime/plugins/capability-authorization-grants.ts +17 -0
  556. package/src/runtime/plugins/catalog-trust.ts +82 -0
  557. package/src/runtime/plugins/computer-registration.ts +839 -0
  558. package/src/runtime/plugins/config-store.ts +119 -0
  559. package/src/runtime/plugins/desktop-operator-external-policy.ts +932 -0
  560. package/src/runtime/plugins/desktop-operator-registration.ts +111 -8
  561. package/src/runtime/plugins/errors.ts +8 -1
  562. package/src/runtime/plugins/external-adapter.ts +45 -322
  563. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  564. package/src/runtime/plugins/external-registration.ts +9 -1
  565. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  566. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  567. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  568. package/src/runtime/plugins/first-party-registry.ts +3 -1
  569. package/src/runtime/plugins/github-adapter.ts +1 -2
  570. package/src/runtime/plugins/gmail-adapter.ts +6 -6
  571. package/src/runtime/plugins/google-calendar-adapter.ts +6 -6
  572. package/src/runtime/plugins/google-shared.ts +31 -46
  573. package/src/runtime/plugins/google-tasks-adapter.ts +6 -6
  574. package/src/runtime/plugins/ios-adapter.ts +1 -2
  575. package/src/runtime/plugins/local-system-adapter.ts +180 -0
  576. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  577. package/src/runtime/plugins/resend-adapter.ts +15 -24
  578. package/src/runtime/plugins/store.ts +493 -51
  579. package/src/runtime/plugins/types.ts +8 -1
  580. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  581. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  582. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  583. package/src/runtime/plugins/xiaohongshu-publish.ts +86 -430
  584. package/src/runtime/projections/controller-context.ts +12 -6
  585. package/src/runtime/projections/git-status-sampler.ts +73 -22
  586. package/src/runtime/projections/materialized-view.ts +14 -8
  587. package/src/runtime/recovery/capability-action-application.ts +228 -0
  588. package/src/runtime/recovery/index.ts +3 -0
  589. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  590. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  591. package/src/runtime/recovery/maintenance-executor.ts +358 -76
  592. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  593. package/src/runtime/release/identity.ts +6 -0
  594. package/src/runtime/resources/leases/store.ts +1 -1
  595. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  596. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  597. package/src/runtime/root/browser-session-composition.ts +59 -0
  598. package/src/runtime/root/computer-composition.ts +191 -0
  599. package/src/runtime/root/computer-target-composition.ts +16 -0
  600. package/src/runtime/root/computer-target-persistence.ts +39 -0
  601. package/src/runtime/root/controller-round-composition.ts +107 -0
  602. package/src/runtime/root/controller-services.ts +2 -0
  603. package/src/runtime/root/entry.ts +4 -3
  604. package/src/runtime/root/gateway-adapter.ts +3 -3
  605. package/src/runtime/root/local-bridge.ts +5 -2
  606. package/src/runtime/root/mcp-transport.ts +5 -5
  607. package/src/runtime/root/package-connector-service.ts +108 -56
  608. package/src/runtime/root/package-runtime-release.ts +85 -18
  609. package/src/runtime/root/package-runtime-service.ts +60 -97
  610. package/src/runtime/root/release-execution-canary.ts +71 -0
  611. package/src/runtime/root/release-loader.ts +68 -0
  612. package/src/runtime/root/release-manifest.ts +106 -2
  613. package/src/runtime/root/release-materialize.ts +103 -4
  614. package/src/runtime/root/release-store.ts +31 -2
  615. package/src/runtime/root/runtime.ts +240 -25
  616. package/src/runtime/root/scheduled-controller-composition.ts +51 -0
  617. package/src/runtime/root/scheduler.ts +12 -4
  618. package/src/runtime/root/service-runner.ts +1 -1
  619. package/src/runtime/root/service.ts +41 -16
  620. package/src/runtime/root/status.ts +1 -0
  621. package/src/runtime/root/types.ts +24 -1
  622. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  623. package/src/runtime/shared/process-environment.ts +19 -4
  624. package/src/runtime/shared/storage-capacity.ts +68 -0
  625. package/src/runtime/standalone-recovery/controller-home-migration.ts +1116 -0
  626. package/src/runtime/standalone-recovery/core.ts +984 -230
  627. package/src/runtime/standalone-recovery/entry.ts +182 -51
  628. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  629. package/src/runtime/standalone-recovery/installer.ts +212 -104
  630. package/src/runtime/standalone-recovery/mcp-server.ts +243 -0
  631. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  632. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  633. package/src/runtime/standalone-recovery/performance.ts +135 -0
  634. package/src/runtime/standalone-recovery/release.ts +5 -0
  635. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  636. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -1
  637. package/src/runtime/workflow/schedules/engine.ts +191 -343
  638. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  639. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  640. package/src/runtime/workflow/schedules/store.ts +2 -607
  641. package/src/runtime/workflow/schedules/types.ts +2 -127
  642. package/src/runtime/workflow/schedules/work-continuation.ts +180 -15
  643. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  644. package/src/runtime/workflows/runtime.ts +486 -0
  645. package/src/testing/test-governance.ts +90 -29
  646. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  647. package/docs/architecture/history/README.md +0 -7
  648. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  649. package/docs/architecture/history/global-hook-runtime.md +0 -166
  650. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  651. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  652. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  653. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  654. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  655. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  656. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  657. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  658. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  659. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  660. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  661. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  662. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  663. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  664. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  665. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  666. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  667. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  668. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  669. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  670. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  671. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  672. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  673. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  674. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  675. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  676. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  677. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  678. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  679. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  680. package/docs/architecture/requests/archive/2026/root.md +0 -68
  681. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  682. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  683. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  684. package/scripts/archive-architecture-request.sh +0 -4
  685. package/scripts/run-skill-evals.ts +0 -1022
@@ -0,0 +1,1116 @@
1
+ import { createHash, randomUUID } from 'crypto';
2
+ import { spawnSync } from 'child_process';
3
+ import { homedir } from 'os';
4
+ import {
5
+ closeSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readFileSync,
10
+ readdirSync,
11
+ renameSync,
12
+ rmSync,
13
+ statSync,
14
+ writeFileSync,
15
+ } from 'fs';
16
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'path';
17
+ import {
18
+ relocateStoppedControllerHomeAuthority,
19
+ rollbackStoppedControllerHomeAuthorityRelocation,
20
+ type StoppedControllerHomeAuthorityRelocation,
21
+ } from '../../cli/repositories/controller-home';
22
+ import { inspectControlPlaneDatabaseFile } from '../control-plane/persistence/sqlite-store';
23
+ import {
24
+ activeRuntimeEntrypoint,
25
+ forgeRuntimeServicePaths,
26
+ readForgeRuntimeServiceConfig,
27
+ syncForgeRuntimeActiveEntrypoint,
28
+ writeForgeRuntimeServiceConfig,
29
+ type ForgeRuntimeServiceConfig,
30
+ } from '../root/service';
31
+ import {
32
+ installPackageRuntimeService,
33
+ packageRuntimeSystemdUserUnitInput,
34
+ } from '../root/package-runtime-service';
35
+ import { runtimeReleaseAuthorityPath } from '../root/release-store';
36
+ import {
37
+ ensurePackageConnectorService,
38
+ packageConnectorServicePaths,
39
+ readPackageConnectorServiceAuthority,
40
+ waitForPackageConnectorEndpointReady,
41
+ type PackageConnectorServiceAuthority,
42
+ } from '../root/package-connector-service';
43
+ import {
44
+ loadMcpServiceLocalConfig,
45
+ writeMcpServiceLocalConfig,
46
+ type McpLocalConfig,
47
+ } from '../../../adapters/mcp/auth';
48
+ import {
49
+ installSystemdUserUnit,
50
+ systemdUserAvailable,
51
+ systemdUserServicePid,
52
+ systemdUserUnitPath,
53
+ } from '../../cli/controller/systemd-user';
54
+ import {
55
+ RECOVERY_GATEWAY_LABEL,
56
+ RECOVERY_WATCHDOG_LABEL,
57
+ recoverySystemdUserUnitInput,
58
+ verifyRecoveryReleaseActivation,
59
+ } from './installer';
60
+ import {
61
+ createRecoveryConfig,
62
+ loadRecoveryConfig,
63
+ verifyStableRuntime,
64
+ type RecoveryConfig,
65
+ } from './core';
66
+ import { readCurrentRecoveryRelease } from './release';
67
+
68
+ export interface RecoveryControllerHomeMigrationPreflight {
69
+ sourceHome: string;
70
+ destinationHome: string;
71
+ liveOwners: Array<{ label: string; pid: number }>;
72
+ destinationExists: boolean;
73
+ destinationAuthorityFree: boolean;
74
+ destinationUnexpectedFiles: string[];
75
+ destinationRecordCount?: number;
76
+ destinationAuditEventCount?: number;
77
+ }
78
+
79
+ export interface LinuxControllerHomeMigrationRequest {
80
+ schemaVersion: 1;
81
+ operationId: string;
82
+ sourceHome: string;
83
+ destinationHome: string;
84
+ archiveSuffix: string;
85
+ destinationExisted: boolean;
86
+ runtimeConfig: ForgeRuntimeServiceConfig;
87
+ recoveryConfig: RecoveryConfig;
88
+ mcpConfig?: McpLocalConfig;
89
+ connectorAuthority?: PackageConnectorServiceAuthority;
90
+ /** Interpreter captured from the active immutable Runtime launcher before cutover. */
91
+ packageExecutable: string;
92
+ timeoutMs: number;
93
+ createdAt: string;
94
+ requestPath: string;
95
+ receiptPath: string;
96
+ logPath: string;
97
+ /** Stable launcher outside both Controller Homes so systemd restart survives relocation. */
98
+ workerLauncherPath: string;
99
+ workerUnit: string;
100
+ }
101
+
102
+ export type LinuxControllerHomeMigrationStatus = 'scheduled' | 'running' | 'committed' | 'rolled_back' | 'failed';
103
+ export type LinuxControllerHomeMigrationPhase =
104
+ | 'scheduled'
105
+ | 'stopping_source'
106
+ | 'relocating'
107
+ | 'installing_destination'
108
+ | 'verifying_destination'
109
+ | 'rolling_back'
110
+ | 'restoring_source'
111
+ | 'verifying_source'
112
+ | 'complete';
113
+
114
+ export interface LinuxControllerHomeMigrationReceipt {
115
+ schemaVersion: 1;
116
+ operationId: string;
117
+ status: LinuxControllerHomeMigrationStatus;
118
+ phase: LinuxControllerHomeMigrationPhase;
119
+ sourceHome: string;
120
+ destinationHome: string;
121
+ requestPath: string;
122
+ receiptPath: string;
123
+ logPath: string;
124
+ workerUnit: string;
125
+ createdAt: string;
126
+ updatedAt: string;
127
+ error?: string;
128
+ rollbackError?: string;
129
+ relocation?: StoppedControllerHomeAuthorityRelocation;
130
+ destinationServices?: ControllerHomeMigrationServiceObservation;
131
+ restoredSourceServices?: ControllerHomeMigrationServiceObservation;
132
+ }
133
+
134
+ export interface ControllerHomeMigrationServiceObservation {
135
+ runtimePid: number;
136
+ connectorPid: number;
137
+ recoveryGatewayPid: number;
138
+ recoveryWatchdogPid: number;
139
+ }
140
+
141
+ export interface LinuxControllerHomeMigrationDependencies {
142
+ platform?: NodeJS.Platform;
143
+ env?: NodeJS.ProcessEnv;
144
+ now?: () => number;
145
+ uuid?: () => string;
146
+ packageExecutable?: (controllerHome: string) => string;
147
+ systemdAvailable?: (env: NodeJS.ProcessEnv) => boolean;
148
+ systemdPid?: (label: string) => number | undefined;
149
+ inspectDatabaseFile?: typeof inspectControlPlaneDatabaseFile;
150
+ spawnWorker?: (request: LinuxControllerHomeMigrationRequest) => void;
151
+ stopServices?: (controllerHome: string, request: LinuxControllerHomeMigrationRequest) => Promise<void>;
152
+ installServices?: (controllerHome: string, request: LinuxControllerHomeMigrationRequest) => Promise<void>;
153
+ verifyServices?: (
154
+ controllerHome: string,
155
+ otherHome: string,
156
+ request: LinuxControllerHomeMigrationRequest,
157
+ ) => Promise<ControllerHomeMigrationServiceObservation>;
158
+ relocate?: typeof relocateStoppedControllerHomeAuthority;
159
+ rollbackRelocation?: typeof rollbackStoppedControllerHomeAuthorityRelocation;
160
+ }
161
+
162
+ function atomicJson(path: string, value: unknown): void {
163
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
164
+ const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
165
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
166
+ renameSync(temporary, path);
167
+ }
168
+
169
+ function controllerHomeFiles(root: string, current = root): string[] {
170
+ if (!existsSync(current)) return [];
171
+ const files: string[] = [];
172
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
173
+ const path = join(current, entry.name);
174
+ if (entry.isDirectory()) files.push(...controllerHomeFiles(root, path));
175
+ else files.push(relative(root, path));
176
+ }
177
+ return files.sort();
178
+ }
179
+
180
+ function pathStrictlyContains(root: string, candidate: string): boolean {
181
+ const rel = relative(resolve(root), resolve(candidate));
182
+ return rel !== '' && !rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel);
183
+ }
184
+
185
+ function assertControllerHomePathsDisjoint(sourceHome: string, destinationHome: string): void {
186
+ if (resolve(sourceHome) === resolve(destinationHome)) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SAME_HOME');
187
+ if (pathStrictlyContains(sourceHome, destinationHome) || pathStrictlyContains(destinationHome, sourceHome)) {
188
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_OVERLAPPING_HOMES');
189
+ }
190
+ }
191
+
192
+ export function recoveryControllerHomeMigrationPreflight(
193
+ sourceHomeInput: string,
194
+ destinationHomeInput: string,
195
+ dependencies: {
196
+ systemdPid?: (label: string) => number | undefined;
197
+ inspectDatabaseFile?: typeof inspectControlPlaneDatabaseFile;
198
+ } = {},
199
+ ): RecoveryControllerHomeMigrationPreflight {
200
+ const sourceHome = resolve(sourceHomeInput);
201
+ const destinationHome = resolve(destinationHomeInput);
202
+ assertControllerHomePathsDisjoint(sourceHome, destinationHome);
203
+ if (!existsSync(sourceHome) || !statSync(sourceHome).isDirectory()) {
204
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_MISSING: ${sourceHome}`);
205
+ }
206
+ const pidFor = dependencies.systemdPid ?? systemdUserServicePid;
207
+ const labels = [
208
+ forgeRuntimeServicePaths(sourceHome).label,
209
+ packageConnectorServicePaths(sourceHome).label,
210
+ RECOVERY_GATEWAY_LABEL,
211
+ RECOVERY_WATCHDOG_LABEL,
212
+ ];
213
+ const liveOwners = labels
214
+ .map((label) => ({ label, pid: pidFor(label) }))
215
+ .filter((entry): entry is { label: string; pid: number } => typeof entry.pid === 'number' && entry.pid > 0);
216
+
217
+ const destinationExists = existsSync(destinationHome);
218
+ let destinationRecordCount: number | undefined;
219
+ let destinationAuditEventCount: number | undefined;
220
+ let destinationUnexpectedFiles: string[] = [];
221
+ if (destinationExists) {
222
+ const databasePath = join(destinationHome, 'control-plane.sqlite');
223
+ if (existsSync(databasePath)) {
224
+ const inspection = (dependencies.inspectDatabaseFile ?? inspectControlPlaneDatabaseFile)(databasePath);
225
+ destinationRecordCount = inspection.recordCount;
226
+ destinationAuditEventCount = inspection.auditEventCount;
227
+ }
228
+ destinationUnexpectedFiles = controllerHomeFiles(destinationHome).filter((path) => !(
229
+ path === 'control-plane.sqlite'
230
+ || path === 'control-plane.sqlite-wal'
231
+ || path === 'control-plane.sqlite-shm'
232
+ || path.startsWith('source-baseline/')
233
+ ));
234
+ }
235
+ const destinationAuthorityFree = !destinationExists || (
236
+ (destinationRecordCount ?? 0) === 0
237
+ && (destinationAuditEventCount ?? 0) === 0
238
+ && destinationUnexpectedFiles.length === 0
239
+ );
240
+ return {
241
+ sourceHome,
242
+ destinationHome,
243
+ liveOwners,
244
+ destinationExists,
245
+ destinationAuthorityFree,
246
+ destinationUnexpectedFiles,
247
+ ...(destinationRecordCount !== undefined ? { destinationRecordCount } : {}),
248
+ ...(destinationAuditEventCount !== undefined ? { destinationAuditEventCount } : {}),
249
+ };
250
+ }
251
+
252
+ export function assertRecoveryControllerHomeMigrationDestinationReady(preflight: RecoveryControllerHomeMigrationPreflight): void {
253
+ if (!preflight.destinationAuthorityFree) {
254
+ throw new Error(
255
+ `RECOVERY_CONTROLLER_HOME_MIGRATION_DESTINATION_HAS_AUTHORITY: records=${preflight.destinationRecordCount ?? 0} `
256
+ + `audit=${preflight.destinationAuditEventCount ?? 0} unexpected=${preflight.destinationUnexpectedFiles.join(',') || 'none'}`,
257
+ );
258
+ }
259
+ }
260
+
261
+ export function assertRecoveryControllerHomeMigrationStopped(preflight: RecoveryControllerHomeMigrationPreflight): void {
262
+ if (preflight.liveOwners.length > 0) {
263
+ throw new Error(
264
+ `RECOVERY_CONTROLLER_HOME_MIGRATION_OWNERS_LIVE: ${preflight.liveOwners.map((entry) => `${entry.label}:${entry.pid}`).join(',')}`,
265
+ );
266
+ }
267
+ assertRecoveryControllerHomeMigrationDestinationReady(preflight);
268
+ }
269
+
270
+ function evidenceRoot(destinationHome: string): string {
271
+ return join(dirname(resolve(destinationHome)), 'controller-home-migrations');
272
+ }
273
+
274
+ function operationPaths(destinationHome: string, operationId: string): {
275
+ requestPath: string;
276
+ receiptPath: string;
277
+ logPath: string;
278
+ workerLauncherPath: string;
279
+ } {
280
+ const root = evidenceRoot(destinationHome);
281
+ return {
282
+ requestPath: join(root, `${operationId}.request.json`),
283
+ receiptPath: join(root, `${operationId}.receipt.json`),
284
+ logPath: join(root, `${operationId}.log`),
285
+ workerLauncherPath: join(root, `${operationId}.worker.cjs`),
286
+ };
287
+ }
288
+
289
+ const CONTROLLER_HOME_MIGRATION_WORKER_UNIT = 'forge-controller-home-migration-worker';
290
+
291
+ function workerUnitForOperation(_operationId: string): string {
292
+ // Recovery Gateway/Watchdog ownership is user-global, so Controller Home cutover must also have one transient writer.
293
+ // A fixed systemd unit name makes systemd reject a concurrent migration before either worker stops services.
294
+ return CONTROLLER_HOME_MIGRATION_WORKER_UNIT;
295
+ }
296
+
297
+ function safeOperationId(value: string): string {
298
+ const normalized = value.trim();
299
+ if (!/^[A-Za-z0-9._-]{8,120}$/.test(normalized)) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_OPERATION_ID_INVALID');
300
+ return normalized;
301
+ }
302
+
303
+ function containedPath(path: string, root: string): boolean {
304
+ const rel = relative(resolve(root), resolve(path));
305
+ return rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel));
306
+ }
307
+
308
+ function remapHomeValue<T>(value: T, sourceHome: string, destinationHome: string): T {
309
+ if (typeof value === 'string') {
310
+ // Runtime/tunnel secrets remain references. Remap only an in-Home file: reference, never env: references or secret bytes.
311
+ if (value.startsWith('file:')) {
312
+ const referenced = value.slice('file:'.length);
313
+ if (isAbsolute(referenced) && containedPath(referenced, sourceHome)) {
314
+ const rel = relative(resolve(sourceHome), resolve(referenced));
315
+ return `file:${resolve(destinationHome, rel)}` as T;
316
+ }
317
+ return value;
318
+ }
319
+ if (!isAbsolute(value) || !containedPath(value, sourceHome)) return value;
320
+ const rel = relative(resolve(sourceHome), resolve(value));
321
+ return resolve(destinationHome, rel) as T;
322
+ }
323
+ if (Array.isArray(value)) return value.map((entry) => remapHomeValue(entry, sourceHome, destinationHome)) as T;
324
+ if (value && typeof value === 'object') {
325
+ const mapped = Object.fromEntries(Object.entries(value as Record<string, unknown>)
326
+ .map(([key, entry]) => [key, remapHomeValue(entry, sourceHome, destinationHome)]));
327
+ return mapped as T;
328
+ }
329
+ return value;
330
+ }
331
+
332
+ function activePackageExecutable(controllerHome: string): string {
333
+ const entrypoint = activeRuntimeEntrypoint(controllerHome);
334
+ if (!entrypoint) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_RELEASE_MISSING');
335
+ const firstLine = readFileSync(entrypoint, 'utf8').split(/\r?\n/, 1)[0] ?? '';
336
+ const match = firstLine.match(/^#!(.+)$/);
337
+ if (!match?.[1]?.trim()) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_LAUNCHER_EXECUTABLE_UNAVAILABLE');
338
+ const executable = resolve(match[1].trim());
339
+ if (!existsSync(executable)) throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_LAUNCHER_EXECUTABLE_MISSING: ${executable}`);
340
+ if (containedPath(executable, controllerHome)) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_LAUNCHER_EXECUTABLE_INSIDE_HOME');
341
+ if (/^forge-recovery(?:-(?:gateway|watchdog))?$/i.test(basename(executable))) {
342
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_LAUNCHER_EXECUTABLE_INVALID');
343
+ }
344
+ return executable;
345
+ }
346
+
347
+ function readRequest(requestPath: string): LinuxControllerHomeMigrationRequest {
348
+ const parsed = JSON.parse(readFileSync(resolve(requestPath), 'utf8')) as LinuxControllerHomeMigrationRequest;
349
+ if (parsed.schemaVersion !== 1) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_REQUEST_VERSION_UNSUPPORTED');
350
+ safeOperationId(parsed.operationId);
351
+ assertControllerHomePathsDisjoint(parsed.sourceHome, parsed.destinationHome);
352
+ if (!/^[A-Za-z0-9._-]{1,120}$/.test(parsed.archiveSuffix)) throw new Error('CONTROLLER_HOME_RELOCATION_ARCHIVE_SUFFIX_INVALID');
353
+ if (!Number.isInteger(parsed.timeoutMs) || parsed.timeoutMs < 30_000 || parsed.timeoutMs > 900_000) {
354
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_TIMEOUT_INVALID');
355
+ }
356
+ const expectedPaths = operationPaths(parsed.destinationHome, parsed.operationId);
357
+ if (resolve(parsed.requestPath) !== resolve(requestPath) || resolve(parsed.requestPath) !== resolve(expectedPaths.requestPath)) {
358
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_REQUEST_PATH_MISMATCH');
359
+ }
360
+ if (
361
+ resolve(parsed.receiptPath) !== resolve(expectedPaths.receiptPath)
362
+ || resolve(parsed.logPath) !== resolve(expectedPaths.logPath)
363
+ || typeof parsed.workerLauncherPath !== 'string'
364
+ || resolve(parsed.workerLauncherPath) !== resolve(expectedPaths.workerLauncherPath)
365
+ ) {
366
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_EVIDENCE_PATH_MISMATCH');
367
+ }
368
+ if (!existsSync(parsed.workerLauncherPath)) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_LAUNCHER_MISSING');
369
+ if (containedPath(parsed.workerLauncherPath, parsed.sourceHome) || containedPath(parsed.workerLauncherPath, parsed.destinationHome)) {
370
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_LAUNCHER_INSIDE_HOME');
371
+ }
372
+ if (parsed.workerUnit !== workerUnitForOperation(parsed.operationId)) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_UNIT_MISMATCH');
373
+ if (!isAbsolute(parsed.packageExecutable) || !existsSync(parsed.packageExecutable)) {
374
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_PACKAGE_EXECUTABLE_INVALID');
375
+ }
376
+ if (containedPath(parsed.packageExecutable, parsed.sourceHome) || containedPath(parsed.packageExecutable, parsed.destinationHome)) {
377
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_PACKAGE_EXECUTABLE_INSIDE_HOME');
378
+ }
379
+ if (/^forge-recovery(?:-(?:gateway|watchdog))?$/i.test(basename(parsed.packageExecutable))) {
380
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_PACKAGE_EXECUTABLE_INVALID');
381
+ }
382
+ return parsed;
383
+ }
384
+
385
+ export function readLinuxControllerHomeMigrationReceipt(receiptPath: string): LinuxControllerHomeMigrationReceipt | undefined {
386
+ if (!existsSync(receiptPath)) return undefined;
387
+ return JSON.parse(readFileSync(receiptPath, 'utf8')) as LinuxControllerHomeMigrationReceipt;
388
+ }
389
+
390
+ function writeReceipt(
391
+ request: LinuxControllerHomeMigrationRequest,
392
+ prior: LinuxControllerHomeMigrationReceipt | undefined,
393
+ patch: Partial<Omit<LinuxControllerHomeMigrationReceipt, 'schemaVersion' | 'operationId' | 'sourceHome' | 'destinationHome' | 'requestPath' | 'receiptPath' | 'logPath' | 'workerUnit' | 'createdAt'>>,
394
+ ): LinuxControllerHomeMigrationReceipt {
395
+ const receipt: LinuxControllerHomeMigrationReceipt = {
396
+ schemaVersion: 1,
397
+ operationId: request.operationId,
398
+ status: prior?.status ?? 'scheduled',
399
+ phase: prior?.phase ?? 'scheduled',
400
+ sourceHome: request.sourceHome,
401
+ destinationHome: request.destinationHome,
402
+ requestPath: request.requestPath,
403
+ receiptPath: request.receiptPath,
404
+ logPath: request.logPath,
405
+ workerUnit: request.workerUnit,
406
+ createdAt: prior?.createdAt ?? request.createdAt,
407
+ ...prior,
408
+ ...patch,
409
+ updatedAt: new Date().toISOString(),
410
+ };
411
+ atomicJson(request.receiptPath, receipt);
412
+ return receipt;
413
+ }
414
+
415
+ function linuxControllerHomeMigrationWorkerLauncherSource(): string {
416
+ // This tiny CommonJS bootstrap is stored beside the durable request, outside both Controller Homes.
417
+ // On every systemd restart it resolves the package CLI from whichever Home currently owns the
418
+ // relocated authority, so a RuntimeMaxSec kill can still enter fail-closed rollback.
419
+ return `'use strict';\n`
420
+ + `const { spawn } = require('node:child_process');\n`
421
+ + `const { existsSync, readFileSync } = require('node:fs');\n`
422
+ + `const { basename, isAbsolute, join, relative, resolve, sep } = require('node:path');\n`
423
+ + `const requestPath = process.argv[2] ? resolve(process.argv[2]) : '';\n`
424
+ + `if (!requestPath || !existsSync(requestPath)) { console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_REQUEST_MISSING'); process.exit(78); }\n`
425
+ + `let request; try { request = JSON.parse(readFileSync(requestPath, 'utf8')); } catch (error) { console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_REQUEST_INVALID: '+error.message); process.exit(78); }\n`
426
+ + `const sourceHome=resolve(request.sourceHome), destinationHome=resolve(request.destinationHome);\n`
427
+ + `const activeHome=existsSync(sourceHome)?sourceHome:(existsSync(destinationHome)?destinationHome:undefined);\n`
428
+ + `if(!activeHome){console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_HOME_MISSING');process.exit(78);}\n`
429
+ + `const remap=(value)=>{const absolute=resolve(value);const rel=relative(sourceHome,absolute);const inside=rel===''||(!rel.startsWith('..'+sep)&&rel!=='..'&&!isAbsolute(rel));return inside?resolve(activeHome,rel):absolute;};\n`
430
+ + `const repositoryRoot=request.runtimeConfig&&typeof request.runtimeConfig.repositoryRoot==='string'?request.runtimeConfig.repositoryRoot:'';\n`
431
+ + `if(!repositoryRoot){console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_PACKAGE_ROOT_MISSING');process.exit(78);}\n`
432
+ + `const packageRoot=remap(repositoryRoot), entry=join(packageRoot,'src','cli','index.ts'), loader=join(packageRoot,'src','runtime','shared','node-ts-loader.mjs');\n`
433
+ + `if(!existsSync(entry)){console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_ENTRYPOINT_MISSING: '+entry);process.exit(78);}\n`
434
+ + `const executable=resolve(request.packageExecutable||'');\n`
435
+ + `if(!executable||!existsSync(executable)){console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_EXECUTABLE_MISSING');process.exit(78);}\n`
436
+ + `const isBun=Boolean(process.versions&&process.versions.bun)||/(?:^|[\\/-])bun(?:$|[\\/]|\.exe$)/i.test(basename(executable));\n`
437
+ + `if(!isBun&&!existsSync(loader)){console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_LOADER_MISSING: '+loader);process.exit(78);}\n`
438
+ + `const command=['recovery','migrate-controller-home-worker','--request',requestPath];\n`
439
+ + `const args=isBun?[entry,...command]:['--loader',loader,entry,...command];\n`
440
+ + `const child=spawn(executable,args,{stdio:'inherit',env:{...process.env,FORGE_CONNECTOR_EXECUTABLE:executable}});\n`
441
+ + `for(const signal of ['SIGINT','SIGTERM','SIGHUP']){try{process.on(signal,()=>child.kill(signal));}catch{}}\n`
442
+ + `child.on('error',(error)=>{console.error('RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_LAUNCH_FAILED: '+error.message);process.exit(78);});\n`
443
+ + `child.on('exit',(code)=>process.exit(code??1));\n`;
444
+ }
445
+
446
+ function writeLinuxControllerHomeMigrationWorkerLauncher(request: LinuxControllerHomeMigrationRequest): void {
447
+ writeFileSync(request.workerLauncherPath, linuxControllerHomeMigrationWorkerLauncherSource(), { encoding: 'utf8', mode: 0o700 });
448
+ }
449
+
450
+ function cliWorkerInvocation(request: LinuxControllerHomeMigrationRequest): { executable: string; args: string[] } {
451
+ return { executable: request.packageExecutable, args: [request.workerLauncherPath, request.requestPath] };
452
+ }
453
+
454
+ export function linuxControllerHomeMigrationSystemdRunArgs(request: LinuxControllerHomeMigrationRequest): string[] {
455
+ const invocation = cliWorkerInvocation(request);
456
+ return [
457
+ '--user',
458
+ '--collect',
459
+ `--setenv=PATH=${process.env.PATH?.trim() || '/usr/bin:/bin:/usr/sbin:/sbin'}`,
460
+ `--setenv=FORGE_CONNECTOR_EXECUTABLE=${request.packageExecutable}`,
461
+ `--unit=${request.workerUnit}`,
462
+ '--property=Type=exec',
463
+ '--property=Restart=on-failure',
464
+ '--property=RestartSec=2',
465
+ '--property=TimeoutStopSec=15',
466
+ // If a phase wedges past the transaction budget, systemd kills this worker. Restart=on-failure
467
+ // then launches a fresh worker, which sees status=running and enters fail-closed rollback.
468
+ `--property=RuntimeMaxSec=${Math.ceil(request.timeoutMs / 1_000)}s`,
469
+ `--property=StandardOutput=append:${request.logPath}`,
470
+ `--property=StandardError=append:${request.logPath}`,
471
+ invocation.executable,
472
+ ...invocation.args,
473
+ ];
474
+ }
475
+
476
+ function defaultSpawnWorker(request: LinuxControllerHomeMigrationRequest): void {
477
+ const args = linuxControllerHomeMigrationSystemdRunArgs(request);
478
+ const result = spawnSync('systemd-run', args, { encoding: 'utf8', timeout: 15_000 });
479
+ if (result.status !== 0) {
480
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_WORKER_HANDOFF_FAILED: ${(result.stderr || result.stdout || '').trim()}`);
481
+ }
482
+ }
483
+
484
+ export function scheduleLinuxControllerHomeMigration(input: {
485
+ sourceHome: string;
486
+ destinationHome: string;
487
+ archiveSuffix?: string;
488
+ timeoutMs?: number;
489
+ }, dependencies: LinuxControllerHomeMigrationDependencies = {}): {
490
+ request: LinuxControllerHomeMigrationRequest;
491
+ preflight: RecoveryControllerHomeMigrationPreflight;
492
+ receipt: LinuxControllerHomeMigrationReceipt;
493
+ } {
494
+ const platform = dependencies.platform ?? process.platform;
495
+ if (platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
496
+ const env = dependencies.env ?? process.env;
497
+ if (!(dependencies.systemdAvailable ?? systemdUserAvailable)(env)) {
498
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SYSTEMD_USER_REQUIRED');
499
+ }
500
+ const sourceHome = resolve(input.sourceHome);
501
+ const destinationHome = resolve(input.destinationHome);
502
+ const preflight = recoveryControllerHomeMigrationPreflight(sourceHome, destinationHome, {
503
+ systemdPid: dependencies.systemdPid,
504
+ inspectDatabaseFile: dependencies.inspectDatabaseFile,
505
+ });
506
+ assertRecoveryControllerHomeMigrationDestinationReady(preflight);
507
+
508
+ const runtimeConfig = readForgeRuntimeServiceConfig(forgeRuntimeServicePaths(sourceHome).configPath);
509
+ const recoveryConfig = loadRecoveryConfig(sourceHome);
510
+ const mcpConfig = loadMcpServiceLocalConfig(sourceHome) ?? undefined;
511
+ const connectorAuthority = readPackageConnectorServiceAuthority(sourceHome);
512
+ const packageExecutable = (dependencies.packageExecutable ?? activePackageExecutable)(sourceHome);
513
+ if (resolve(runtimeConfig.controllerHome) !== sourceHome || resolve(recoveryConfig.controllerHome) !== sourceHome) {
514
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_AUTHORITY_MISMATCH');
515
+ }
516
+ if (mcpConfig?.chatgpt?.localEndpoint && !connectorAuthority) {
517
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_CONNECTOR_AUTHORITY_MISSING');
518
+ }
519
+ const operationId = safeOperationId(`controller-home-${(dependencies.uuid ?? randomUUID)()}`);
520
+ const paths = operationPaths(destinationHome, operationId);
521
+ const archiveSuffix = input.archiveSuffix?.trim() || operationId;
522
+ if (!/^[A-Za-z0-9._-]{1,120}$/.test(archiveSuffix)) throw new Error('CONTROLLER_HOME_RELOCATION_ARCHIVE_SUFFIX_INVALID');
523
+ const request: LinuxControllerHomeMigrationRequest = {
524
+ schemaVersion: 1,
525
+ operationId,
526
+ sourceHome,
527
+ destinationHome,
528
+ archiveSuffix,
529
+ destinationExisted: preflight.destinationExists,
530
+ runtimeConfig,
531
+ recoveryConfig,
532
+ ...(mcpConfig ? { mcpConfig } : {}),
533
+ ...(connectorAuthority ? { connectorAuthority } : {}),
534
+ packageExecutable,
535
+ timeoutMs: Math.max(30_000, input.timeoutMs ?? 240_000),
536
+ createdAt: new Date().toISOString(),
537
+ ...paths,
538
+ workerUnit: workerUnitForOperation(operationId),
539
+ };
540
+ mkdirSync(evidenceRoot(destinationHome), { recursive: true, mode: 0o700 });
541
+ atomicJson(request.requestPath, request);
542
+ writeLinuxControllerHomeMigrationWorkerLauncher(request);
543
+ let receipt = writeReceipt(request, undefined, { status: 'scheduled', phase: 'scheduled' });
544
+ try {
545
+ (dependencies.spawnWorker ?? defaultSpawnWorker)(request);
546
+ } catch (error) {
547
+ receipt = writeReceipt(request, receipt, {
548
+ status: 'failed',
549
+ phase: 'complete',
550
+ error: error instanceof Error ? error.message : String(error),
551
+ });
552
+ throw error;
553
+ }
554
+ return { request, preflight, receipt };
555
+ }
556
+
557
+ function runtimeAuthorityMigrationEvidencePaths(request: LinuxControllerHomeMigrationRequest): {
558
+ root: string;
559
+ source: string;
560
+ destination: string;
561
+ } {
562
+ const root = join(request.destinationHome, 'runtime', 'releases', 'migration-evidence', request.operationId);
563
+ return { root, source: join(root, 'source-authority.json'), destination: join(root, 'destination-authority.json') };
564
+ }
565
+
566
+ function prepareDestinationRuntimeAuthority(request: LinuxControllerHomeMigrationRequest): void {
567
+ const authorityPath = runtimeReleaseAuthorityPath(request.destinationHome);
568
+ const evidence = runtimeAuthorityMigrationEvidencePaths(request);
569
+ if (existsSync(evidence.source)) return;
570
+ if (!existsSync(authorityPath)) return; // Legacy Runtime authority: destination install will initialize the canonical authority.
571
+ mkdirSync(evidence.root, { recursive: true, mode: 0o700 });
572
+ const parsed = JSON.parse(readFileSync(authorityPath, 'utf8')) as { active?: { manifestPath?: unknown } };
573
+ const manifestPath = typeof parsed.active?.manifestPath === 'string' ? resolve(parsed.active.manifestPath) : undefined;
574
+ if (!manifestPath || !containedPath(manifestPath, request.sourceHome)) {
575
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_RUNTIME_AUTHORITY_UNEXPECTED');
576
+ }
577
+ renameSync(authorityPath, evidence.source);
578
+ }
579
+
580
+ function prepareRuntimeAuthorityForSourceRollback(request: LinuxControllerHomeMigrationRequest): void {
581
+ const authorityPath = runtimeReleaseAuthorityPath(request.destinationHome);
582
+ const evidence = runtimeAuthorityMigrationEvidencePaths(request);
583
+ if (!existsSync(evidence.source)) return; // Original authority never needed rebasing; moving the Home back restores it directly.
584
+ mkdirSync(evidence.root, { recursive: true, mode: 0o700 });
585
+ if (existsSync(authorityPath)) {
586
+ if (existsSync(evidence.destination)) {
587
+ const activeBytes = readFileSync(authorityPath);
588
+ const evidenceBytes = readFileSync(evidence.destination);
589
+ if (!activeBytes.equals(evidenceBytes)) {
590
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_DESTINATION_RUNTIME_AUTHORITY_EVIDENCE_CONFLICT');
591
+ }
592
+ rmSync(authorityPath, { force: true });
593
+ } else {
594
+ renameSync(authorityPath, evidence.destination);
595
+ }
596
+ }
597
+ renameSync(evidence.source, authorityPath);
598
+ }
599
+
600
+ function systemctl(env: NodeJS.ProcessEnv, args: string[], tolerateAbsent = false): void {
601
+ const result = spawnSync('systemctl', ['--user', ...args], { encoding: 'utf8', env, timeout: 30_000 });
602
+ if (result.status === 0) return;
603
+ const detail = `${result.stderr || ''}\n${result.stdout || ''}`.trim();
604
+ if (tolerateAbsent && /not loaded|not found|does not exist|no such file|not enabled/i.test(detail)) return;
605
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_SYSTEMCTL_FAILED: systemctl --user ${args.join(' ')}: ${detail}`);
606
+ }
607
+
608
+ function serviceLabels(controllerHome: string): string[] {
609
+ return [
610
+ forgeRuntimeServicePaths(controllerHome).label,
611
+ packageConnectorServicePaths(controllerHome).label,
612
+ RECOVERY_GATEWAY_LABEL,
613
+ RECOVERY_WATCHDOG_LABEL,
614
+ ];
615
+ }
616
+
617
+ async function defaultStopServices(controllerHome: string, request: LinuxControllerHomeMigrationRequest, env = process.env): Promise<void> {
618
+ for (const label of serviceLabels(controllerHome)) {
619
+ systemctl(env, ['stop', label], true);
620
+ systemctl(env, ['disable', label], true);
621
+ rmSync(systemdUserUnitPath(label, env), { force: true });
622
+ }
623
+ systemctl(env, ['daemon-reload']);
624
+ const live = serviceLabels(controllerHome)
625
+ .map((label) => ({ label, pid: systemdUserServicePid(label, env) }))
626
+ .filter((entry) => entry.pid !== undefined);
627
+ if (live.length > 0) {
628
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_STOP_UNVERIFIED: ${live.map((entry) => `${entry.label}:${entry.pid}`).join(',')}`);
629
+ }
630
+ void request;
631
+ }
632
+
633
+ async function defaultInstallServices(controllerHome: string, request: LinuxControllerHomeMigrationRequest, env = process.env): Promise<void> {
634
+ const runtimeConfig = remapHomeValue(request.runtimeConfig, request.sourceHome, controllerHome);
635
+ const recoveryConfig = remapHomeValue(request.recoveryConfig, request.sourceHome, controllerHome);
636
+ const mcpConfig = request.mcpConfig ? remapHomeValue(request.mcpConfig, request.sourceHome, controllerHome) : undefined;
637
+ if (mcpConfig) writeMcpServiceLocalConfig(controllerHome, mcpConfig);
638
+ createRecoveryConfig(controllerHome, recoveryConfig);
639
+
640
+ const restoringSource = resolve(controllerHome) === resolve(request.sourceHome);
641
+ if (restoringSource) {
642
+ // The original immutable Runtime authority was restored before the Home was renamed back.
643
+ // Rebind its service contract directly; rematerializing here would publish a different
644
+ // launcher identity if this worker is a compiled Recovery binary.
645
+ writeForgeRuntimeServiceConfig(runtimeConfig);
646
+ syncForgeRuntimeActiveEntrypoint(controllerHome);
647
+ installSystemdUserUnit({
648
+ unitName: forgeRuntimeServicePaths(controllerHome).label,
649
+ unit: packageRuntimeSystemdUserUnitInput(controllerHome),
650
+ env,
651
+ errorPrefix: 'RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_REBIND_FAILED',
652
+ });
653
+ const endpoint = mcpConfig?.chatgpt?.localEndpoint;
654
+ const connectorAuthority = request.connectorAuthority;
655
+ if (!endpoint || !connectorAuthority) {
656
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_CONNECTOR_REBIND_EVIDENCE_MISSING');
657
+ }
658
+ const connector = await ensurePackageConnectorService({
659
+ release: connectorAuthority,
660
+ controllerHome,
661
+ endpoint,
662
+ executable: request.packageExecutable,
663
+ platform: 'linux',
664
+ env,
665
+ refresh: true,
666
+ });
667
+ if (connector.mode !== 'systemd-user' || !connector.persistent) {
668
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_CONNECTOR_NOT_SYSTEMD: ${connector.mode}`);
669
+ }
670
+ } else {
671
+ const runtime = await installPackageRuntimeService({
672
+ controllerHome,
673
+ packageRoot: runtimeConfig.repositoryRoot,
674
+ host: runtimeConfig.host,
675
+ port: runtimeConfig.port,
676
+ authTokenFile: runtimeConfig.authTokenFile,
677
+ ...(runtimeConfig.exclusiveWorkId ? { exclusiveWorkId: runtimeConfig.exclusiveWorkId } : {}),
678
+ platform: 'linux',
679
+ env,
680
+ refreshConnector: true,
681
+ });
682
+ if (runtime.mode !== 'systemd-user' || !runtime.persistent) {
683
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_RUNTIME_NOT_SYSTEMD: ${runtime.mode}`);
684
+ }
685
+ if (!runtime.connector || runtime.connector.mode !== 'systemd-user' || !runtime.connector.persistent) {
686
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_CONNECTOR_NOT_SYSTEMD');
687
+ }
688
+ }
689
+
690
+ // Recovery release bytes do not bind Controller Home. Preserve the exact immutable
691
+ // current release and only rewrite its systemd unit/config bindings for the new Home.
692
+ const currentRecovery = readCurrentRecoveryRelease(controllerHome);
693
+ if (!currentRecovery) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_RECOVERY_RELEASE_MISSING');
694
+ const recoveryEnv = { ...env, FORGE_CONNECTOR_EXECUTABLE: request.packageExecutable };
695
+ installSystemdUserUnit({
696
+ unitName: RECOVERY_GATEWAY_LABEL,
697
+ unit: recoverySystemdUserUnitInput(controllerHome, 'gateway', recoveryEnv),
698
+ env: recoveryEnv,
699
+ errorPrefix: 'RECOVERY_CONTROLLER_HOME_MIGRATION_RECOVERY_GATEWAY_REBIND_FAILED',
700
+ });
701
+ installSystemdUserUnit({
702
+ unitName: RECOVERY_WATCHDOG_LABEL,
703
+ unit: recoverySystemdUserUnitInput(controllerHome, 'watchdog', recoveryEnv),
704
+ env: recoveryEnv,
705
+ errorPrefix: 'RECOVERY_CONTROLLER_HOME_MIGRATION_RECOVERY_WATCHDOG_REBIND_FAILED',
706
+ });
707
+ const activation = await verifyRecoveryReleaseActivation({
708
+ controllerHome,
709
+ config: loadRecoveryConfig(controllerHome),
710
+ expectedRelease: currentRecovery,
711
+ timeoutMs: 60_000,
712
+ }, { platform: 'linux', systemdEnv: recoveryEnv });
713
+ if (!activation.ok) {
714
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_RECOVERY_REBIND_UNVERIFIED: ${activation.failures.join('; ')}`);
715
+ }
716
+ }
717
+
718
+ async function defaultVerifyServices(
719
+ controllerHome: string,
720
+ otherHome: string,
721
+ request: LinuxControllerHomeMigrationRequest,
722
+ env = process.env,
723
+ ): Promise<ControllerHomeMigrationServiceObservation> {
724
+ const labels = {
725
+ runtime: forgeRuntimeServicePaths(controllerHome).label,
726
+ connector: packageConnectorServicePaths(controllerHome).label,
727
+ gateway: RECOVERY_GATEWAY_LABEL,
728
+ watchdog: RECOVERY_WATCHDOG_LABEL,
729
+ };
730
+ const observation: ControllerHomeMigrationServiceObservation = {
731
+ runtimePid: systemdUserServicePid(labels.runtime, env) ?? 0,
732
+ connectorPid: systemdUserServicePid(labels.connector, env) ?? 0,
733
+ recoveryGatewayPid: systemdUserServicePid(labels.gateway, env) ?? 0,
734
+ recoveryWatchdogPid: systemdUserServicePid(labels.watchdog, env) ?? 0,
735
+ };
736
+ if (Object.values(observation).some((pid) => pid <= 0) || new Set(Object.values(observation)).size !== 4) {
737
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_SERVICE_SET_INVALID: ${JSON.stringify(observation)}`);
738
+ }
739
+ for (const label of Object.values(labels)) {
740
+ const unitPath = systemdUserUnitPath(label, env);
741
+ const unit = existsSync(unitPath) ? readFileSync(unitPath, 'utf8') : '';
742
+ if (!unit.includes(resolve(controllerHome)) || unit.includes(resolve(otherHome))) {
743
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_SERVICE_BINDING_INVALID: ${label}`);
744
+ }
745
+ }
746
+ for (const label of [forgeRuntimeServicePaths(otherHome).label, packageConnectorServicePaths(otherHome).label]) {
747
+ if (label !== labels.runtime && label !== labels.connector && systemdUserServicePid(label, env)) {
748
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_OLD_SERVICE_STILL_LIVE: ${label}`);
749
+ }
750
+ }
751
+ const localEndpoint = loadMcpServiceLocalConfig(controllerHome)?.chatgpt?.localEndpoint;
752
+ if (!localEndpoint || !(await waitForPackageConnectorEndpointReady(localEndpoint, { timeoutMs: 20_000 }))) {
753
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_CONNECTOR_UNHEALTHY: ${localEndpoint ?? 'missing endpoint'}`);
754
+ }
755
+ const verified = await verifyStableRuntime(loadRecoveryConfig(controllerHome));
756
+ if (!verified.ok) {
757
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_DESTINATION_UNHEALTHY: ${JSON.stringify(verified).slice(0, 2_000)}`);
758
+ }
759
+ void request;
760
+ return observation;
761
+ }
762
+
763
+ function relocationForRequest(request: LinuxControllerHomeMigrationRequest): StoppedControllerHomeAuthorityRelocation {
764
+ const archivedDestinationHome = request.destinationExisted
765
+ ? `${request.destinationHome}.pre-migration-${request.archiveSuffix}`
766
+ : undefined;
767
+ return {
768
+ migrated: true,
769
+ sourceHome: request.sourceHome,
770
+ destinationHome: request.destinationHome,
771
+ ...(archivedDestinationHome && existsSync(archivedDestinationHome) ? { archivedDestinationHome } : {}),
772
+ };
773
+ }
774
+
775
+ function restoreArchivedDestinationShell(request: LinuxControllerHomeMigrationRequest): void {
776
+ if (!request.destinationExisted || existsSync(request.destinationHome)) return;
777
+ const archived = `${request.destinationHome}.pre-migration-${request.archiveSuffix}`;
778
+ if (existsSync(archived)) renameSync(archived, request.destinationHome);
779
+ }
780
+
781
+ function deadlineGuard(deadline: number, now: () => number, phase: string): void {
782
+ if (now() > deadline) throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_TIMEOUT: ${phase}`);
783
+ }
784
+
785
+ async function recoverInterruptedTransaction(
786
+ request: LinuxControllerHomeMigrationRequest,
787
+ receipt: LinuxControllerHomeMigrationReceipt,
788
+ dependencies: LinuxControllerHomeMigrationDependencies,
789
+ ): Promise<LinuxControllerHomeMigrationReceipt> {
790
+ const env = dependencies.env ?? process.env;
791
+ const stopServices = dependencies.stopServices ?? ((home, req) => defaultStopServices(home, req, env));
792
+ const installServices = dependencies.installServices ?? ((home, req) => defaultInstallServices(home, req, env));
793
+ const verifyServices = dependencies.verifyServices ?? ((home, other, req) => defaultVerifyServices(home, other, req, env));
794
+ const rollback = dependencies.rollbackRelocation ?? rollbackStoppedControllerHomeAuthorityRelocation;
795
+ let current = writeReceipt(request, receipt, {
796
+ status: 'running',
797
+ phase: 'rolling_back',
798
+ error: receipt.error ?? 'previous migration worker exited before a terminal receipt; fail-closed recovery started',
799
+ });
800
+ try {
801
+ if (!existsSync(request.sourceHome) && existsSync(request.destinationHome)) {
802
+ await stopServices(request.destinationHome, request);
803
+ prepareRuntimeAuthorityForSourceRollback(request);
804
+ rollback(relocationForRequest(request));
805
+ }
806
+ restoreArchivedDestinationShell(request);
807
+ current = writeReceipt(request, current, { phase: 'restoring_source' });
808
+ await stopServices(request.sourceHome, request);
809
+ await installServices(request.sourceHome, request);
810
+ current = writeReceipt(request, current, { phase: 'verifying_source' });
811
+ const restored = await verifyServices(request.sourceHome, request.destinationHome, request);
812
+ return writeReceipt(request, current, {
813
+ status: 'rolled_back',
814
+ phase: 'complete',
815
+ restoredSourceServices: restored,
816
+ });
817
+ } catch (error) {
818
+ return writeReceipt(request, current, {
819
+ status: 'failed',
820
+ phase: 'complete',
821
+ rollbackError: error instanceof Error ? error.message : String(error),
822
+ });
823
+ }
824
+ }
825
+
826
+ export async function runLinuxControllerHomeMigrationRequest(
827
+ requestPath: string,
828
+ dependencies: LinuxControllerHomeMigrationDependencies = {},
829
+ ): Promise<LinuxControllerHomeMigrationReceipt> {
830
+ const platform = dependencies.platform ?? process.platform;
831
+ if (platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
832
+ const request = readRequest(requestPath);
833
+ const prior = readLinuxControllerHomeMigrationReceipt(request.receiptPath);
834
+ if (prior && ['committed', 'rolled_back', 'failed'].includes(prior.status)) return prior;
835
+ if (prior?.status === 'running') return recoverInterruptedTransaction(request, prior, dependencies);
836
+
837
+ const env = dependencies.env ?? process.env;
838
+ if (!(dependencies.systemdAvailable ?? systemdUserAvailable)(env)) {
839
+ return writeReceipt(request, prior, {
840
+ status: 'failed',
841
+ phase: 'complete',
842
+ error: 'RECOVERY_CONTROLLER_HOME_MIGRATION_SYSTEMD_USER_REQUIRED',
843
+ });
844
+ }
845
+ const now = dependencies.now ?? Date.now;
846
+ const deadline = now() + request.timeoutMs;
847
+ const stopServices = dependencies.stopServices ?? ((home, req) => defaultStopServices(home, req, env));
848
+ const installServices = dependencies.installServices ?? ((home, req) => defaultInstallServices(home, req, env));
849
+ const verifyServices = dependencies.verifyServices ?? ((home, other, req) => defaultVerifyServices(home, other, req, env));
850
+ const relocate = dependencies.relocate ?? relocateStoppedControllerHomeAuthority;
851
+ const rollback = dependencies.rollbackRelocation ?? rollbackStoppedControllerHomeAuthorityRelocation;
852
+
853
+ let receipt = writeReceipt(request, prior, { status: 'running', phase: 'stopping_source' });
854
+ let relocation: StoppedControllerHomeAuthorityRelocation | undefined;
855
+ try {
856
+ await stopServices(request.sourceHome, request);
857
+ deadlineGuard(deadline, now, 'stop source services');
858
+ receipt = writeReceipt(request, receipt, { phase: 'relocating' });
859
+ const stoppedPreflight = recoveryControllerHomeMigrationPreflight(request.sourceHome, request.destinationHome, {
860
+ systemdPid: dependencies.systemdPid,
861
+ inspectDatabaseFile: dependencies.inspectDatabaseFile,
862
+ });
863
+ assertRecoveryControllerHomeMigrationStopped(stoppedPreflight);
864
+ relocation = relocate({
865
+ sourceHome: request.sourceHome,
866
+ destinationHome: request.destinationHome,
867
+ archiveExistingDestination: request.destinationExisted,
868
+ archiveSuffix: request.archiveSuffix,
869
+ });
870
+ receipt = writeReceipt(request, receipt, { relocation, phase: 'installing_destination' });
871
+ deadlineGuard(deadline, now, 'relocate authority');
872
+ prepareDestinationRuntimeAuthority(request);
873
+ await installServices(request.destinationHome, request);
874
+ deadlineGuard(deadline, now, 'install destination services');
875
+ receipt = writeReceipt(request, receipt, { phase: 'verifying_destination' });
876
+ const destinationServices = await verifyServices(request.destinationHome, request.sourceHome, request);
877
+ deadlineGuard(deadline, now, 'verify destination services');
878
+ return writeReceipt(request, receipt, {
879
+ status: 'committed',
880
+ phase: 'complete',
881
+ destinationServices,
882
+ });
883
+ } catch (error) {
884
+ const detail = error instanceof Error ? error.message : String(error);
885
+ receipt = writeReceipt(request, receipt, { phase: 'rolling_back', error: detail, ...(relocation ? { relocation } : {}) });
886
+ try {
887
+ if (!existsSync(request.sourceHome) && existsSync(request.destinationHome)) {
888
+ await stopServices(request.destinationHome, request);
889
+ prepareRuntimeAuthorityForSourceRollback(request);
890
+ rollback(relocation ?? relocationForRequest(request));
891
+ }
892
+ restoreArchivedDestinationShell(request);
893
+ receipt = writeReceipt(request, receipt, { phase: 'restoring_source' });
894
+ await stopServices(request.sourceHome, request);
895
+ await installServices(request.sourceHome, request);
896
+ receipt = writeReceipt(request, receipt, { phase: 'verifying_source' });
897
+ const restoredSourceServices = await verifyServices(request.sourceHome, request.destinationHome, request);
898
+ return writeReceipt(request, receipt, {
899
+ status: 'rolled_back',
900
+ phase: 'complete',
901
+ restoredSourceServices,
902
+ });
903
+ } catch (rollbackError) {
904
+ return writeReceipt(request, receipt, {
905
+ status: 'failed',
906
+ phase: 'complete',
907
+ rollbackError: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
908
+ });
909
+ }
910
+ }
911
+ }
912
+
913
+ // Compatibility facade for the pre-transaction Recovery API. These functions
914
+ // deliberately delegate to the transactional Linux migration engine above;
915
+ // they do not own a second cutover/rollback implementation.
916
+ export function defaultUserControllerHomeForMigration(
917
+ env: NodeJS.ProcessEnv = process.env,
918
+ userHome = homedir(),
919
+ ): string {
920
+ const xdgStateHome = env.XDG_STATE_HOME?.trim();
921
+ return resolve(xdgStateHome ? join(xdgStateHome, 'forge', 'controller') : join(userHome, '.forge', 'controller'));
922
+ }
923
+
924
+ export function recoveryControllerHomeOwnerLabels(sourceHomeInput: string): string[] {
925
+ return serviceLabels(resolve(sourceHomeInput));
926
+ }
927
+
928
+ export function assertRecoveryControllerHomeDestinationAuthorityFree(
929
+ preflight: RecoveryControllerHomeMigrationPreflight,
930
+ ): void {
931
+ assertRecoveryControllerHomeMigrationDestinationReady(preflight);
932
+ }
933
+
934
+ export function assertRecoveryControllerHomeMigrationReady(
935
+ preflight: RecoveryControllerHomeMigrationPreflight,
936
+ ): void {
937
+ assertRecoveryControllerHomeMigrationStopped(preflight);
938
+ }
939
+
940
+ export interface RecoveryControllerHomeMigrationScheduleInput {
941
+ requestId: string;
942
+ canonicalSourceRoot: string;
943
+ expectedSourceRevision: string;
944
+ }
945
+
946
+ export interface RecoveryControllerHomeMigrationScheduleResult {
947
+ status: 'scheduled' | 'already_migrated';
948
+ requestId: string;
949
+ sourceHome: string;
950
+ destinationHome: string;
951
+ unitName?: string;
952
+ canonicalSourceRoot: string;
953
+ expectedSourceRevision: string;
954
+ }
955
+
956
+ export interface RecoveryControllerHomeMigrationWorkerResult {
957
+ ok: boolean;
958
+ requestId: string;
959
+ sourceHome: string;
960
+ destinationHome: string;
961
+ canonicalSourceRoot: string;
962
+ expectedSourceRevision: string;
963
+ relocation?: StoppedControllerHomeAuthorityRelocation;
964
+ verification?: Awaited<ReturnType<typeof verifyStableRuntime>>;
965
+ rollback?: { attempted: boolean; succeeded: boolean; errors: string[] };
966
+ transaction?: LinuxControllerHomeMigrationReceipt;
967
+ }
968
+
969
+ function boundedCompatibilityRequestId(value: string): string {
970
+ const requestId = value.trim();
971
+ if (!/^[A-Za-z0-9._:-]{8,120}$/.test(requestId)) throw new Error('RECOVERY_REQUEST_ID_REQUIRED');
972
+ return requestId;
973
+ }
974
+
975
+ function compatibilityGitOutput(root: string, args: string[]): string {
976
+ const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8', timeout: 30_000, maxBuffer: 512 * 1024 });
977
+ if (result.status !== 0) throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_GIT_FAILED: ${(result.stderr || result.stdout || '').trim()}`);
978
+ return result.stdout.trim();
979
+ }
980
+
981
+ export function validateRecoveryControllerHomeMigrationSource(
982
+ canonicalSourceRootInput: string,
983
+ expectedSourceRevisionInput: string,
984
+ ): { canonicalSourceRoot: string; sourceRevision: string } {
985
+ const canonicalSourceRoot = resolve(canonicalSourceRootInput);
986
+ const expectedSourceRevision = expectedSourceRevisionInput.trim();
987
+ if (!expectedSourceRevision) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REVISION_REQUIRED');
988
+ const managedRoot = resolve(homedir(), '.forge', 'managed-worktrees');
989
+ if (containedPath(canonicalSourceRoot, managedRoot) && canonicalSourceRoot !== managedRoot) {
990
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_MANAGED_WORKTREE_SOURCE_FORBIDDEN');
991
+ }
992
+ const topLevel = resolve(compatibilityGitOutput(canonicalSourceRoot, ['rev-parse', '--show-toplevel']));
993
+ if (topLevel !== canonicalSourceRoot) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_CANONICAL_SOURCE_REQUIRED');
994
+ const sourceRevision = compatibilityGitOutput(canonicalSourceRoot, ['rev-parse', 'HEAD']);
995
+ if (sourceRevision !== expectedSourceRevision) {
996
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_REVISION_MISMATCH: expected=${expectedSourceRevision} actual=${sourceRevision}`);
997
+ }
998
+ if (compatibilityGitOutput(canonicalSourceRoot, ['status', '--porcelain=v1'])) {
999
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_DIRTY');
1000
+ }
1001
+ const packagePath = join(canonicalSourceRoot, 'package.json');
1002
+ if (!existsSync(packagePath)) throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_FORGE_SOURCE_REQUIRED');
1003
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) as { name?: unknown };
1004
+ if (packageJson.name !== '@moretea-labs/forge') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_FORGE_SOURCE_REQUIRED');
1005
+ return { canonicalSourceRoot, sourceRevision };
1006
+ }
1007
+
1008
+ function compatibilityMigrationToken(requestId: string): string {
1009
+ return createHash('sha256').update(requestId).digest('hex').slice(0, 16);
1010
+ }
1011
+
1012
+ function compatibilityOperationId(requestId: string): string {
1013
+ return `controller-home-${compatibilityMigrationToken(requestId)}`;
1014
+ }
1015
+
1016
+ function compatibilityTransactionPaths(destinationHome: string, requestId: string) {
1017
+ return operationPaths(destinationHome, compatibilityOperationId(requestId));
1018
+ }
1019
+
1020
+ function assertCompatibilityTransactionSource(
1021
+ request: LinuxControllerHomeMigrationRequest,
1022
+ source: { canonicalSourceRoot: string; sourceRevision: string },
1023
+ ): void {
1024
+ const repositoryRoot = request.runtimeConfig.repositoryRoot?.trim();
1025
+ if (!repositoryRoot || resolve(repositoryRoot) !== source.canonicalSourceRoot) {
1026
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_SOURCE_AUTHORITY_MISMATCH');
1027
+ }
1028
+ }
1029
+
1030
+ export function scheduleRecoveryControllerHomeMigration(
1031
+ config: RecoveryConfig,
1032
+ input: RecoveryControllerHomeMigrationScheduleInput,
1033
+ dependencies: { platform?: NodeJS.Platform; recoveryExecutable?: string } = {},
1034
+ ): RecoveryControllerHomeMigrationScheduleResult {
1035
+ const platform = dependencies.platform ?? process.platform;
1036
+ if (platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
1037
+ const requestId = boundedCompatibilityRequestId(input.requestId);
1038
+ const sourceHome = resolve(config.controllerHome);
1039
+ const destinationHome = defaultUserControllerHomeForMigration();
1040
+ const source = validateRecoveryControllerHomeMigrationSource(input.canonicalSourceRoot, input.expectedSourceRevision);
1041
+ if (sourceHome === destinationHome) {
1042
+ return { status: 'already_migrated', requestId, sourceHome, destinationHome, canonicalSourceRoot: source.canonicalSourceRoot, expectedSourceRevision: source.sourceRevision };
1043
+ }
1044
+ const existingPath = compatibilityTransactionPaths(destinationHome, requestId).requestPath;
1045
+ if (existsSync(existingPath)) {
1046
+ const existing = readRequest(existingPath);
1047
+ assertCompatibilityTransactionSource(existing, source);
1048
+ return {
1049
+ status: 'scheduled', requestId, sourceHome, destinationHome,
1050
+ unitName: existing.workerUnit,
1051
+ canonicalSourceRoot: source.canonicalSourceRoot,
1052
+ expectedSourceRevision: source.sourceRevision,
1053
+ };
1054
+ }
1055
+ const token = compatibilityMigrationToken(requestId);
1056
+ const scheduled = scheduleLinuxControllerHomeMigration(
1057
+ { sourceHome, destinationHome, archiveSuffix: `recovery-${token}` },
1058
+ { platform, uuid: () => token },
1059
+ );
1060
+ assertCompatibilityTransactionSource(scheduled.request, source);
1061
+ return {
1062
+ status: 'scheduled', requestId, sourceHome, destinationHome,
1063
+ unitName: scheduled.request.workerUnit,
1064
+ canonicalSourceRoot: source.canonicalSourceRoot,
1065
+ expectedSourceRevision: source.sourceRevision,
1066
+ };
1067
+ }
1068
+
1069
+ export async function runRecoveryControllerHomeMigrationWorker(
1070
+ config: RecoveryConfig,
1071
+ input: RecoveryControllerHomeMigrationScheduleInput,
1072
+ dependencies: { platform?: NodeJS.Platform; startupDelayMs?: number } = {},
1073
+ ): Promise<RecoveryControllerHomeMigrationWorkerResult> {
1074
+ const platform = dependencies.platform ?? process.platform;
1075
+ if (platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
1076
+ const requestId = boundedCompatibilityRequestId(input.requestId);
1077
+ const sourceHome = resolve(config.controllerHome);
1078
+ const destinationHome = defaultUserControllerHomeForMigration();
1079
+ const source = validateRecoveryControllerHomeMigrationSource(input.canonicalSourceRoot, input.expectedSourceRevision);
1080
+ if (sourceHome === destinationHome) {
1081
+ const verification = await verifyStableRuntime(config);
1082
+ return { ok: verification.ok, requestId, sourceHome, destinationHome, canonicalSourceRoot: source.canonicalSourceRoot, expectedSourceRevision: source.sourceRevision, verification };
1083
+ }
1084
+ const paths = compatibilityTransactionPaths(destinationHome, requestId);
1085
+ if (!existsSync(paths.requestPath)) {
1086
+ const token = compatibilityMigrationToken(requestId);
1087
+ const prepared = scheduleLinuxControllerHomeMigration(
1088
+ { sourceHome, destinationHome, archiveSuffix: `recovery-${token}` },
1089
+ { platform, uuid: () => token, spawnWorker: () => undefined },
1090
+ );
1091
+ assertCompatibilityTransactionSource(prepared.request, source);
1092
+ } else {
1093
+ assertCompatibilityTransactionSource(readRequest(paths.requestPath), source);
1094
+ }
1095
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, dependencies.startupDelayMs ?? 1_500));
1096
+ const transaction = await runLinuxControllerHomeMigrationRequest(paths.requestPath, { platform });
1097
+ const ok = transaction.status === 'committed';
1098
+ const verificationHome = ok ? destinationHome : transaction.status === 'rolled_back' ? sourceHome : undefined;
1099
+ const verification = verificationHome ? await verifyStableRuntime(loadRecoveryConfig(verificationHome)) : undefined;
1100
+ return {
1101
+ ok,
1102
+ requestId,
1103
+ sourceHome,
1104
+ destinationHome,
1105
+ canonicalSourceRoot: source.canonicalSourceRoot,
1106
+ expectedSourceRevision: source.sourceRevision,
1107
+ ...(transaction.relocation ? { relocation: transaction.relocation } : {}),
1108
+ ...(verification ? { verification } : {}),
1109
+ rollback: {
1110
+ attempted: transaction.status === 'rolled_back' || Boolean(transaction.rollbackError),
1111
+ succeeded: transaction.status === 'rolled_back',
1112
+ errors: transaction.rollbackError ? [transaction.rollbackError] : [],
1113
+ },
1114
+ transaction,
1115
+ };
1116
+ }