@moretea-labs/forge 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/AGENTS.md +70 -7
  2. package/CHANGELOG.md +14 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/THIRD_PARTY_NOTICES.md +1 -1
  6. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
  8. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  9. package/adapters/chatgpt/controller-round-host.ts +15 -25
  10. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  11. package/adapters/chatgpt/provider-delivery.ts +228 -1
  12. package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
  13. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  14. package/adapters/computer/chatgpt-target-router.ts +103 -0
  15. package/adapters/computer/chrome-extension/background.js +385 -0
  16. package/adapters/computer/chrome-extension/content.js +378 -0
  17. package/adapters/computer/chrome-extension/core.js +24 -0
  18. package/adapters/computer/chrome-extension/manifest.json +30 -0
  19. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  20. package/adapters/computer/interaction-target-authority.ts +142 -18
  21. package/adapters/mcp/auth.ts +0 -1
  22. package/adapters/mcp/instructions.ts +5 -4
  23. package/adapters/mcp/multi-repository.ts +3 -19
  24. package/adapters/mcp/oauth.ts +73 -0
  25. package/adapters/mcp/paths.ts +4 -1
  26. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  27. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  28. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  29. package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
  30. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  31. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  32. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  33. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  34. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  35. package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
  36. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  37. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
  38. package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
  39. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  40. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  41. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  42. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  43. package/adapters/mcp/runtime-gateway/router.ts +18 -504
  44. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  45. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
  46. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  47. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
  48. package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
  49. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
  50. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  51. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
  52. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  53. package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
  54. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  55. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  56. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  57. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  58. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  59. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  60. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  61. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  62. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  63. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  64. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  65. package/adapters/mcp/server.ts +67 -23
  66. package/adapters/mcp/setup.ts +1 -3
  67. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
  68. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
  69. package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
  70. package/adapters/mcp/toolset-names.ts +5 -0
  71. package/adapters/mcp/toolset.ts +6 -5
  72. package/adapters/mcp/transports/http-observation.ts +365 -0
  73. package/adapters/mcp/transports/http.ts +154 -677
  74. package/adapters/mcp/transports/oauth-http.ts +322 -0
  75. package/adapters/mcp/transports/session-registry.ts +6 -0
  76. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  77. package/assets/plugin-registry.v1.json +6 -4
  78. package/assets/reference-configs/agentic-development-flow.md +25 -0
  79. package/assets/reference-configs/global-working-rules.md +19 -0
  80. package/assets/skill-version.json +2 -2
  81. package/docs/architecture/CURRENT.md +111 -41
  82. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  83. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  84. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  85. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  86. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  87. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  88. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  89. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  90. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  91. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  92. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  93. package/docs/architecture/domains/verification.md +1 -0
  94. package/docs/architecture/domains/workflow-engine.md +6 -0
  95. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  96. package/docs/forge-plugin-management.md +2 -2
  97. package/docs/operations/controller-performance-and-502.md +17 -0
  98. package/docs/operations/controller-reliability-runbook.md +2 -2
  99. package/docs/operations/features.md +1 -1
  100. package/docs/operations/features.zh-CN.md +1 -1
  101. package/docs/operations/homebrew.md +2 -2
  102. package/docs/operations/mcp-tool-exposure.md +2 -0
  103. package/docs/operations/releasing.md +2 -0
  104. package/docs/operations/standalone-disaster-recovery.md +14 -6
  105. package/docs/wiki/Implementation.md +1 -1
  106. package/package.json +25 -14
  107. package/packages/kernel/cognition/api/index.ts +6 -0
  108. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  109. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  110. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  111. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  112. package/packages/kernel/cognition/domain/learning.ts +106 -0
  113. package/packages/kernel/cognition/domain/memory.ts +254 -0
  114. package/packages/kernel/controller/api/index.ts +1 -0
  115. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  116. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  117. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  118. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  119. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  120. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  121. package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
  122. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  123. package/packages/kernel/identity/api/index.ts +10 -0
  124. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  125. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  126. package/packages/kernel/identity/domain/grant.ts +72 -0
  127. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  128. package/packages/kernel/identity/domain/user-request.ts +93 -0
  129. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  130. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  131. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  132. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  133. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  134. package/packages/kernel/scheduler/api/index.ts +0 -2
  135. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  136. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  137. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  138. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
  139. package/packages/kernel/work/api/index.ts +3 -2
  140. package/packages/kernel/work/application/work-service.ts +17 -42
  141. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  142. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  143. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  144. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  145. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  146. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  147. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  148. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  149. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  150. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  151. package/packages/kernel/work/domain/state-machine.ts +25 -63
  152. package/packages/kernel/work/domain/types.ts +57 -51
  153. package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
  154. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  155. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  156. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  157. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  158. package/packages/plugin-runtime/computer/index.ts +2 -0
  159. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  160. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  161. package/packages/protocols/computer/contract.ts +27 -1
  162. package/scripts/activate-source-baseline.ts +7 -9
  163. package/scripts/canary-controller-semantic-wait.ts +12 -24
  164. package/scripts/check-mcp-compatibility.ts +48 -136
  165. package/scripts/check-runtime-architecture.mjs +622 -217
  166. package/scripts/forge-local-recovery-helper.mjs +93 -15
  167. package/scripts/lib/project-init-lib.sh +3 -0
  168. package/scripts/open-source-audit-allowlist.txt +1 -2
  169. package/scripts/run-bounded-command.mjs +5 -0
  170. package/scripts/run-governed-gate.ts +0 -1
  171. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  172. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  173. package/scripts/smoke-mcp-http-runtime.ts +5 -0
  174. package/scripts/smoke-runtime-recovery.ts +12 -43
  175. package/scripts/smoke-schedule-engine.ts +23 -15
  176. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  177. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  178. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  179. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  180. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  181. package/scripts/smoke-workflow-supervisor.ts +103 -0
  182. package/scripts/verify-forge-runtime.sh +2 -1
  183. package/src/cli/agent-jobs/executor-health.ts +11 -34
  184. package/src/cli/agent-jobs/integration.ts +18 -49
  185. package/src/cli/agent-jobs/job-manager.ts +7 -3
  186. package/src/cli/commands/brain-assistant.ts +27 -13
  187. package/src/cli/commands/brain.ts +13 -15
  188. package/src/cli/commands/chatgpt.ts +1 -163
  189. package/src/cli/commands/recovery.ts +135 -121
  190. package/src/cli/commands/runtime.ts +3 -3
  191. package/src/cli/commands/setup-profile.ts +8 -0
  192. package/src/cli/controller/check-runner.ts +13 -2
  193. package/src/cli/controller/completion-backlog.ts +3 -3
  194. package/src/cli/controller/completion-orchestrator.ts +4 -43
  195. package/src/cli/controller/completion-target.ts +3 -72
  196. package/src/cli/controller/context/source-materializer.ts +81 -100
  197. package/src/cli/controller/context-pack.ts +10 -8
  198. package/src/cli/controller/execution-completion.ts +4 -7
  199. package/src/cli/controller/execution-policy.ts +36 -153
  200. package/src/cli/controller/governance.ts +2 -3
  201. package/src/cli/controller/issue-store.ts +4 -99
  202. package/src/cli/controller/launch-agents.ts +59 -18
  203. package/src/cli/controller/operational-plan.ts +24 -51
  204. package/src/cli/controller/runtime-config.ts +5 -1
  205. package/src/cli/controller/stuck-state-migration.ts +2 -3
  206. package/src/cli/controller/systemd-user.ts +39 -1
  207. package/src/cli/editing/edit-session.ts +1 -7
  208. package/src/cli/local-bridge/console-automations.ts +18 -13
  209. package/src/cli/local-bridge/console-view-models.ts +3 -12
  210. package/src/cli/local-bridge/facade-api.ts +64 -191
  211. package/src/cli/local-bridge/server.ts +24 -147
  212. package/src/cli/local-bridge/types.ts +0 -1
  213. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  214. package/src/cli/repositories/command-executor.ts +29 -23
  215. package/src/cli/repositories/command-scope.ts +35 -16
  216. package/src/cli/repositories/controller-home.ts +48 -0
  217. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  218. package/src/cli/repositories/git-command-executor.ts +2 -2
  219. package/src/cli/repositories/registry.ts +77 -11
  220. package/src/cli/repositories/structured-git.ts +62 -2
  221. package/src/cli/repository/session-cache.ts +92 -6
  222. package/src/runtime/context/assistant-context.ts +20 -7
  223. package/src/runtime/context/assistant-work-context.ts +89 -27
  224. package/src/runtime/context/automatic-learning.ts +565 -0
  225. package/src/runtime/context/cognitive-attention.ts +173 -0
  226. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  227. package/src/runtime/context/context-closure.ts +46 -9
  228. package/src/runtime/context/context-pack-process.ts +103 -0
  229. package/src/runtime/context/context-plane.ts +1 -9
  230. package/src/runtime/context/direct-controller-learning.ts +298 -0
  231. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  232. package/src/runtime/context/semantic-navigation.ts +6 -34
  233. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  234. package/src/runtime/context/typescript-navigation.ts +89 -3
  235. package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
  236. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  237. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  238. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  239. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  240. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  241. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  242. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  243. package/src/runtime/control-plane/execution/validation.ts +6 -8
  244. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  245. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  246. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  247. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  248. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  249. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  250. package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
  251. package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
  252. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  253. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  254. package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
  255. package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
  256. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  257. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  258. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
  259. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
  260. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  261. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  262. package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
  263. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  264. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  265. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  266. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  267. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  268. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  269. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  270. package/src/runtime/control-plane/facade/index.ts +0 -1
  271. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  272. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  273. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  274. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  275. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  276. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  277. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  278. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  279. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  280. package/src/runtime/control-plane/facade/types.ts +49 -108
  281. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  282. package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
  283. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  284. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  285. package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
  286. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
  287. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  288. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  289. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  290. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  291. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  292. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
  293. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  294. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  295. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  296. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  297. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  298. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  299. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  300. package/src/runtime/control-plane/release-retention.ts +135 -47
  301. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  302. package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
  303. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  304. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  305. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  306. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  307. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  308. package/src/runtime/evidence/artifact-store.ts +158 -40
  309. package/src/runtime/evidence/evidence-store.ts +112 -8
  310. package/src/runtime/evidence/output-handle-index.ts +225 -0
  311. package/src/runtime/evidence/result-store.ts +158 -44
  312. package/src/runtime/execution/jobs/store.ts +3 -4
  313. package/src/runtime/execution/managed-workspace.ts +57 -0
  314. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  315. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  316. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  317. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  318. package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
  319. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  320. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  321. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  322. package/src/runtime/execution/process-runtime/index.ts +3 -0
  323. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  324. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  325. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  326. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  327. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  328. package/src/runtime/execution/process-runtime/runtime.ts +35 -2
  329. package/src/runtime/execution/process-runtime/store.ts +30 -2
  330. package/src/runtime/execution/process-runtime/types.ts +6 -1
  331. package/src/runtime/execution/workers/executor.ts +2 -6
  332. package/src/runtime/platform/service-inventory.ts +130 -0
  333. package/src/runtime/platform/service-manager.ts +8 -0
  334. package/src/runtime/plugins/action-application.ts +85 -28
  335. package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
  336. package/src/runtime/plugins/browser-adapter.ts +757 -34
  337. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  338. package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
  339. package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
  340. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  341. package/src/runtime/plugins/browser-registration.ts +16 -0
  342. package/src/runtime/plugins/browser-runtime.ts +14 -1
  343. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  344. package/src/runtime/plugins/browser-session-store.ts +46 -76
  345. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  346. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  347. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  348. package/src/runtime/plugins/computer-registration.ts +222 -11
  349. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  350. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  351. package/src/runtime/plugins/external-adapter.ts +42 -6
  352. package/src/runtime/plugins/external-registration.ts +21 -1
  353. package/src/runtime/plugins/gmail-adapter.ts +23 -2
  354. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  355. package/src/runtime/plugins/google-shared.ts +14 -7
  356. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  357. package/src/runtime/plugins/ios-adapter.ts +1 -1
  358. package/src/runtime/plugins/lightweight-action.ts +208 -2
  359. package/src/runtime/plugins/local-system-adapter.ts +12 -2
  360. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  361. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  362. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  363. package/src/runtime/plugins/store.ts +602 -428
  364. package/src/runtime/plugins/types.ts +20 -0
  365. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  366. package/src/runtime/projections/invalidation.ts +7 -3
  367. package/src/runtime/projections/materialized-view.ts +80 -36
  368. package/src/runtime/recovery/actions.ts +3 -3
  369. package/src/runtime/recovery/capability-action-application.ts +46 -45
  370. package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
  371. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  372. package/src/runtime/recovery/store.ts +29 -11
  373. package/src/runtime/release/release-coordinator.ts +296 -0
  374. package/src/runtime/release/release-session.ts +716 -0
  375. package/src/runtime/release/release-state-migration.ts +25 -0
  376. package/src/runtime/resources/leases/store.ts +56 -25
  377. package/src/runtime/root/computer-composition.ts +191 -6
  378. package/src/runtime/root/controller-progression-composition.ts +192 -0
  379. package/src/runtime/root/controller-round-composition.ts +20 -1
  380. package/src/runtime/root/deployment-topology.ts +106 -0
  381. package/src/runtime/root/entry.ts +4 -3
  382. package/src/runtime/root/known-good-recovery.ts +184 -0
  383. package/src/runtime/root/local-bridge.ts +0 -1
  384. package/src/runtime/root/mcp-transport.ts +111 -18
  385. package/src/runtime/root/ownership.ts +261 -9
  386. package/src/runtime/root/package-connector-service.ts +105 -19
  387. package/src/runtime/root/package-runtime-release.ts +117 -18
  388. package/src/runtime/root/package-runtime-service.ts +17 -11
  389. package/src/runtime/root/release-execution-canary.ts +44 -12
  390. package/src/runtime/root/release-loader.ts +90 -8
  391. package/src/runtime/root/release-manifest.ts +80 -6
  392. package/src/runtime/root/release-materialize.ts +372 -28
  393. package/src/runtime/root/release-store.ts +395 -36
  394. package/src/runtime/root/runtime-lane.ts +191 -0
  395. package/src/runtime/root/runtime.ts +159 -27
  396. package/src/runtime/root/scheduled-controller-composition.ts +28 -3
  397. package/src/runtime/root/service-runner.ts +2 -2
  398. package/src/runtime/root/service.ts +36 -33
  399. package/src/runtime/root/status.ts +48 -0
  400. package/src/runtime/root/types.ts +32 -3
  401. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  402. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  403. package/src/runtime/root/write-fence.ts +50 -2
  404. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  405. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  406. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  407. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  408. package/src/runtime/shared/process-environment.ts +20 -9
  409. package/src/runtime/shared/response-budget.ts +1 -3
  410. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  411. package/src/runtime/standalone-recovery/core.ts +3702 -342
  412. package/src/runtime/standalone-recovery/entry.ts +469 -39
  413. package/src/runtime/standalone-recovery/installer.ts +262 -148
  414. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  415. package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
  416. package/src/runtime/standalone-recovery/performance.ts +54 -16
  417. package/src/runtime/standalone-recovery/release.ts +53 -5
  418. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  419. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  420. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  421. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
  422. package/src/runtime/workflow/schedules/engine.ts +138 -172
  423. package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
  424. package/src/runtime/workflows/runtime.ts +15 -19
  425. package/src/testing/test-governance.ts +5 -1
  426. package/supervisor/chatgpt-conversation.ts +23 -0
  427. package/supervisor/client.ts +201 -0
  428. package/supervisor/control-plane.ts +868 -0
  429. package/supervisor/entry.ts +15 -0
  430. package/supervisor/forge-validators.ts +80 -0
  431. package/supervisor/index.ts +5 -0
  432. package/supervisor/native-browser-adapter.ts +786 -0
  433. package/supervisor/native-messaging/host.ts +39 -0
  434. package/supervisor/native-messaging/manifest.ts +26 -0
  435. package/supervisor/native-messaging/protocol.ts +27 -0
  436. package/supervisor/paths.ts +18 -0
  437. package/supervisor/protocol.ts +202 -0
  438. package/supervisor/server.ts +404 -0
  439. package/supervisor/store.ts +1451 -0
  440. package/supervisor/tsconfig.json +8 -0
  441. package/supervisor/types.ts +255 -0
  442. package/adapters/chatgpt/controller-host.ts +0 -119
  443. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  444. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  445. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  446. package/packages/kernel/progression/api/index.ts +0 -2
  447. package/packages/kernel/progression/application/projector.ts +0 -211
  448. package/packages/kernel/progression/domain/types.ts +0 -116
  449. package/packages/kernel/progression/index.ts +0 -1
  450. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  451. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  452. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  453. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  454. package/scripts/benchmark-background-check-overlap.ts +0 -228
  455. package/scripts/benchmark-computer-hotpath.ts +0 -255
  456. package/scripts/benchmark-controller-hotpath.ts +0 -212
  457. package/scripts/benchmark-controller-round2.ts +0 -191
  458. package/scripts/benchmark-ios-agent-device.ts +0 -511
  459. package/scripts/benchmark-mcp-host.sh +0 -117
  460. package/scripts/benchmark-mcp-transport.ts +0 -169
  461. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  462. package/scripts/benchmark-operational-shadow.ts +0 -28
  463. package/scripts/benchmark-quality-harness.ts +0 -146
  464. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  465. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  466. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  467. package/scripts/benchmark-thin-harness.ts +0 -251
  468. package/scripts/benchmark-typescript-navigation.ts +0 -186
  469. package/scripts/benchmark-work-efficiency.ts +0 -334
  470. package/scripts/build-controller-ui.ts +0 -28
  471. package/scripts/check-npm-release.sh +0 -32
  472. package/scripts/check-public-release-surface.sh +0 -19
  473. package/scripts/check-release-published.sh +0 -57
  474. package/scripts/check-release-readiness.sh +0 -37
  475. package/scripts/check-release-version.mjs +0 -74
  476. package/scripts/check-tarball-install-smoke.sh +0 -150
  477. package/scripts/package-source-archive.sh +0 -147
  478. package/scripts/public-release-files.txt +0 -39
  479. package/scripts/public-release.ts +0 -174
  480. package/scripts/publish-release-tarball.sh +0 -27
  481. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  482. package/scripts/stage-runtime-release.ts +0 -44
  483. package/src/cli/controller/progress.ts +0 -656
  484. package/src/cli/controller/work-mode.ts +0 -181
  485. package/src/cli/local-bridge/console-assets.ts +0 -17
  486. package/src/cli/local-bridge/dashboard.ts +0 -17
  487. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  488. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  489. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  490. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  491. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  492. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  493. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  494. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  495. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  496. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  497. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  498. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  499. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  500. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  501. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  502. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  503. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  504. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  505. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  506. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  507. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  508. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  509. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  510. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  511. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  512. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  513. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  514. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  515. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  516. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  517. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  518. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  519. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  520. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  521. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  522. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  523. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  524. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  525. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  526. package/src/runtime/control-plane/routing/index.ts +0 -1
  527. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  528. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -0,0 +1,716 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'fs';
3
+ import { dirname, join, resolve } from 'path';
4
+ import { assertStorageHeadroom } from '../shared/storage-capacity';
5
+ import type { CandidateExecutionLane, StableExecutionLane } from '../root/runtime-lane';
6
+ import type { RuntimePublishedRelease, RuntimeReleaseAuthority } from '../root/release-store';
7
+
8
+ export const RELEASE_SESSION_PHASES = [
9
+ 'source_frozen',
10
+ 'built',
11
+ 'static_verified',
12
+ 'candidate_booted',
13
+ 'candidate_verified',
14
+ 'cutover_eligible',
15
+ 'cutover_attempting',
16
+ 'cutover_committed',
17
+ 'soaking',
18
+ 'known_good',
19
+ 'rolled_back',
20
+ 'failed',
21
+ ] as const;
22
+
23
+ export type ReleaseSessionPhase = (typeof RELEASE_SESSION_PHASES)[number];
24
+
25
+ export interface ReleaseSessionStableRelease {
26
+ authorityRevision: number;
27
+ releaseId: string;
28
+ artifactIdentity: string;
29
+ manifestSha256: string;
30
+ workerProtocolVersion: number;
31
+ releaseFencingTokenSha256: string;
32
+ }
33
+
34
+ export interface ReleaseSessionCandidateRelease {
35
+ releaseId: string;
36
+ manifestPath: string;
37
+ artifactIdentity: string;
38
+ manifestSha256: string;
39
+ treeSha256: string;
40
+ sourceCommit: string;
41
+ sourceRepositoryId?: string;
42
+ }
43
+
44
+ export interface ReleaseSessionReceipt {
45
+ id: string;
46
+ kind: 'source' | 'build' | 'static_gate' | 'candidate_canary' | 'cutover' | 'soak' | 'known_good' | 'rollback';
47
+ recordedAt: string;
48
+ summary: string;
49
+ }
50
+
51
+ /**
52
+ * Durable semantic rollback authority for one ReleaseSession after physical
53
+ * cutover has published Candidate B. RuntimeReleaseAuthority stores only
54
+ * physical active/previous release identity and backing artifacts; rollback
55
+ * eligibility and transaction semantics live only in this ReleaseSession.
56
+ */
57
+ export interface ReleaseSessionTransaction {
58
+ schemaVersion: 1;
59
+ operationId: string;
60
+ candidateReleaseId: string;
61
+ cutoverAuthorityRevision: number;
62
+ rollbackRelease: RuntimePublishedRelease;
63
+ startedAt: string;
64
+ }
65
+
66
+ /**
67
+ * Runtime startup migration may persist the exact cutover transaction between
68
+ * activation and Recovery's post-start receipt. Operation ids and timestamps
69
+ * are writer-local evidence, so the semantic transaction identity excludes
70
+ * those fields while retaining every release/authority identity that fences a
71
+ * different cutover.
72
+ */
73
+ function sameReleaseSessionTransactionIdentity(
74
+ left: ReleaseSessionTransaction,
75
+ right: ReleaseSessionTransaction,
76
+ ): boolean {
77
+ return left.schemaVersion === right.schemaVersion
78
+ && left.candidateReleaseId === right.candidateReleaseId
79
+ && left.cutoverAuthorityRevision === right.cutoverAuthorityRevision
80
+ && left.rollbackRelease.releaseId === right.rollbackRelease.releaseId
81
+ && left.rollbackRelease.artifactIdentity === right.rollbackRelease.artifactIdentity
82
+ && left.rollbackRelease.manifestPath === right.rollbackRelease.manifestPath
83
+ && left.rollbackRelease.manifestSha256 === right.rollbackRelease.manifestSha256
84
+ && left.rollbackRelease.workerProtocolVersion === right.rollbackRelease.workerProtocolVersion
85
+ && left.rollbackRelease.publishedAt === right.rollbackRelease.publishedAt
86
+ && left.rollbackRelease.databaseBackup?.path === right.rollbackRelease.databaseBackup?.path
87
+ && left.rollbackRelease.databaseBackup?.schemaVersion === right.rollbackRelease.databaseBackup?.schemaVersion
88
+ && left.rollbackRelease.databaseBackup?.createdAt === right.rollbackRelease.databaseBackup?.createdAt
89
+ && left.rollbackRelease.databaseBackup?.auditEventCount === right.rollbackRelease.databaseBackup?.auditEventCount
90
+ && left.rollbackRelease.databaseBackup?.recordCount === right.rollbackRelease.databaseBackup?.recordCount;
91
+ }
92
+
93
+ export interface ReleaseSession {
94
+ /** Wire/storage schema remains readable by the previous Recovery release. */
95
+ schemaVersion: 1;
96
+ /** Current semantic model. Legacy records omit this field (or carry epoch 1). */
97
+ semanticEpoch: 2;
98
+ sessionId: string;
99
+ stable: StableExecutionLane;
100
+ stableRelease: ReleaseSessionStableRelease;
101
+ candidate: CandidateExecutionLane;
102
+ candidateRelease?: ReleaseSessionCandidateRelease;
103
+ transaction?: ReleaseSessionTransaction;
104
+ sourceRevision: string;
105
+ phase: ReleaseSessionPhase;
106
+ revision: number;
107
+ receipts: ReleaseSessionReceipt[];
108
+ createdAt: string;
109
+ updatedAt: string;
110
+ }
111
+
112
+ const REQUIRED_STATIC_GATES = new Set(['type', 'runtime_architecture', 'architecture_sync', 'bootstrap']);
113
+ const REQUIRED_CANDIDATE_CANARIES = new Set(['recovery', 'mcp', 'scheduler', 'supervisor', 'controller']);
114
+ const REQUIRED_KNOWN_GOOD_SOAK_GATES = new Set(['supervisor_continuation_proof']);
115
+
116
+ function sessionPath(controllerHome: string, sessionId: string): string {
117
+ return join(resolve(controllerHome), 'recovery', 'state', 'release-sessions', `${sessionId}.json`);
118
+ }
119
+
120
+ function validSessionId(value: string): string {
121
+ const sessionId = value.trim();
122
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{7,119}$/.test(sessionId)) throw new Error('RELEASE_SESSION_ID_INVALID');
123
+ return sessionId;
124
+ }
125
+
126
+ function writeSession(path: string, session: ReleaseSession): void {
127
+ const content = `${JSON.stringify(session, null, 2)}\n`;
128
+ assertStorageHeadroom(path, { operation: 'release_session_write', requiredBytes: Buffer.byteLength(content), reserveBytes: 16 * 1024 * 1024 });
129
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
130
+ const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
131
+ writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o600 });
132
+ renameSync(temporary, path);
133
+ invalidateCompleteReleaseSessionInventory(dirname(path));
134
+ }
135
+
136
+ function sameStableRelease(session: Pick<ReleaseSession, 'stableRelease'>, release: RuntimePublishedRelease): boolean {
137
+ return release.releaseId === session.stableRelease.releaseId
138
+ && release.artifactIdentity === session.stableRelease.artifactIdentity
139
+ && release.manifestSha256 === session.stableRelease.manifestSha256
140
+ && release.workerProtocolVersion === session.stableRelease.workerProtocolVersion;
141
+ }
142
+
143
+ function validTransaction(session: ReleaseSession): boolean {
144
+ const transaction = session.transaction;
145
+ if (!transaction) return !['cutover_committed', 'soaking'].includes(session.phase);
146
+ const rollback = transaction.rollbackRelease;
147
+ return transaction.schemaVersion === 1
148
+ && Boolean(transaction.operationId?.trim())
149
+ && Boolean(session.candidateRelease)
150
+ && transaction.candidateReleaseId === session.candidateRelease!.releaseId
151
+ && Number.isInteger(transaction.cutoverAuthorityRevision)
152
+ && transaction.cutoverAuthorityRevision > session.stableRelease.authorityRevision
153
+ && Number.isFinite(Date.parse(transaction.startedAt))
154
+ && sameStableRelease(session, rollback)
155
+ && Boolean(rollback.manifestPath?.trim())
156
+ && Number.isFinite(Date.parse(rollback.publishedAt))
157
+ && Boolean(rollback.databaseBackup)
158
+ && resolve(rollback.databaseBackup!.path) === rollback.databaseBackup!.path
159
+ && Number.isInteger(rollback.databaseBackup!.schemaVersion)
160
+ && rollback.databaseBackup!.schemaVersion > 0
161
+ && Number.isFinite(Date.parse(rollback.databaseBackup!.createdAt));
162
+ }
163
+
164
+ function assertCurrentSession(value: ReleaseSession, sessionId: string): ReleaseSession {
165
+ if (
166
+ value.schemaVersion !== 1
167
+ || value.semanticEpoch !== 2
168
+ || value.sessionId !== sessionId
169
+ || !RELEASE_SESSION_PHASES.includes(value.phase)
170
+ || !Number.isInteger(value.revision)
171
+ || value.revision < 1
172
+ || !validTransaction(value)
173
+ ) throw new Error('RELEASE_SESSION_INVALID');
174
+ return value;
175
+ }
176
+
177
+ export interface ReleaseSessionStateMigration {
178
+ migratedSessionIds: string[];
179
+ currentSessionIds: string[];
180
+ inspected: number;
181
+ }
182
+
183
+ type LegacyReleaseSessionV1 = Omit<ReleaseSession, 'semanticEpoch' | 'transaction'> & {
184
+ schemaVersion: 1;
185
+ semanticEpoch?: 1;
186
+ };
187
+
188
+ type TransitionalReleaseSessionV2 = Omit<ReleaseSession, 'schemaVersion' | 'semanticEpoch'> & {
189
+ schemaVersion: 2;
190
+ semanticEpoch?: never;
191
+ };
192
+
193
+ type MigratableReleaseSession = ReleaseSession | LegacyReleaseSessionV1 | TransitionalReleaseSessionV2;
194
+
195
+ function releaseIdentityKey(release: {
196
+ releaseId?: string;
197
+ artifactIdentity?: string;
198
+ manifestSha256?: string;
199
+ } | undefined): string | undefined {
200
+ const releaseId = release?.releaseId?.trim() || '';
201
+ const artifactIdentity = release?.artifactIdentity?.trim() || '';
202
+ const manifestSha256 = release?.manifestSha256?.trim() || '';
203
+ if (!releaseId || !artifactIdentity || !manifestSha256) return undefined;
204
+ return `${releaseId}\u0000${artifactIdentity}\u0000${manifestSha256}`;
205
+ }
206
+
207
+ function basicMigratableSessionShape(raw: MigratableReleaseSession, id: string): boolean {
208
+ const schemaSupported = (raw.schemaVersion === 1 && (raw.semanticEpoch === undefined || raw.semanticEpoch === 1 || raw.semanticEpoch === 2))
209
+ || (raw.schemaVersion === 2 && raw.semanticEpoch === undefined);
210
+ return schemaSupported
211
+ && raw.sessionId === id
212
+ && RELEASE_SESSION_PHASES.includes(raw.phase)
213
+ && Number.isFinite(Date.parse(raw.createdAt))
214
+ && Number.isFinite(Date.parse(raw.updatedAt));
215
+ }
216
+
217
+ function historicalAcceptanceReceipt(
218
+ sessionId: string,
219
+ recordedAt: string,
220
+ proof: string,
221
+ ): ReleaseSessionReceipt {
222
+ return {
223
+ id: `migration:historical-known-good:${sessionId}`,
224
+ kind: 'known_good',
225
+ recordedAt,
226
+ summary: `Legacy soaking state reconciled as known-good from exact durable release lineage: ${proof}.`,
227
+ };
228
+ }
229
+
230
+ export function migrateReleaseSessionState(
231
+ controllerHome: string,
232
+ dependencies: { readAuthority?: () => RuntimeReleaseAuthority | undefined } = {},
233
+ ): ReleaseSessionStateMigration {
234
+ const root = dirname(sessionPath(controllerHome, 'release-session-migration'));
235
+ if (!existsSync(root)) return { migratedSessionIds: [], currentSessionIds: [], inspected: 0 };
236
+ const authority = dependencies.readAuthority?.();
237
+ const migratedSessionIds: string[] = [];
238
+ const currentSessionIds: string[] = [];
239
+ const names = readdirSync(root).filter((name) => name.endsWith('.json')).sort();
240
+ const parsed = names.map((name) => {
241
+ const id = name.slice(0, -'.json'.length);
242
+ const path = sessionPath(controllerHome, validSessionId(id));
243
+ try {
244
+ return { id, path, raw: JSON.parse(readFileSync(path, 'utf8')) as MigratableReleaseSession };
245
+ } catch {
246
+ throw new Error(`RELEASE_SESSION_MIGRATION_INVALID_JSON: ${id}`);
247
+ }
248
+ });
249
+ const stableSuccessors = new Map<string, Array<{ sessionId: string; createdAt: string }>>();
250
+ for (const entry of parsed) {
251
+ if (!basicMigratableSessionShape(entry.raw, entry.id)) continue;
252
+ const stableKey = releaseIdentityKey(entry.raw.stableRelease);
253
+ if (!stableKey) continue;
254
+ const existing = stableSuccessors.get(stableKey) ?? [];
255
+ existing.push({ sessionId: entry.id, createdAt: entry.raw.createdAt });
256
+ stableSuccessors.set(stableKey, existing);
257
+ }
258
+ const activeKey = releaseIdentityKey(authority?.active);
259
+ const previousKey = releaseIdentityKey(authority?.previous);
260
+
261
+ for (const { id, path, raw } of parsed) {
262
+ if (raw.schemaVersion === 1 && raw.semanticEpoch === 2) {
263
+ let current = raw as ReleaseSession;
264
+ if (
265
+ !current.transaction
266
+ && current.phase === 'cutover_attempting'
267
+ && current.candidateRelease
268
+ && authority?.active.releaseId === current.candidateRelease.releaseId
269
+ && authority.active.artifactIdentity === current.candidateRelease.artifactIdentity
270
+ && authority.previous?.databaseBackup
271
+ && authority.previous.releaseId === current.stableRelease.releaseId
272
+ && authority.previous.artifactIdentity === current.stableRelease.artifactIdentity
273
+ && authority.previous.manifestSha256 === current.stableRelease.manifestSha256
274
+ ) {
275
+ current = {
276
+ ...current,
277
+ transaction: {
278
+ schemaVersion: 1,
279
+ operationId: `reconcile:${id}:${authority.revision}`,
280
+ candidateReleaseId: current.candidateRelease.releaseId,
281
+ cutoverAuthorityRevision: authority.revision,
282
+ rollbackRelease: authority.previous,
283
+ startedAt: authority.committedAt,
284
+ },
285
+ revision: current.revision + 1,
286
+ updatedAt: new Date().toISOString(),
287
+ };
288
+ assertCurrentSession(current, id);
289
+ writeSession(path, current);
290
+ migratedSessionIds.push(id);
291
+ } else {
292
+ assertCurrentSession(current, id);
293
+ currentSessionIds.push(id);
294
+ }
295
+ continue;
296
+ }
297
+ const legacyV1 = raw.schemaVersion === 1
298
+ && (raw.semanticEpoch === undefined || raw.semanticEpoch === 1);
299
+ const transitionalV2 = raw.schemaVersion === 2
300
+ && raw.semanticEpoch === undefined;
301
+ if ((!legacyV1 && !transitionalV2) || !basicMigratableSessionShape(raw, id)) {
302
+ throw new Error(`RELEASE_SESSION_MIGRATION_UNSUPPORTED_SCHEMA: ${id}`);
303
+ }
304
+
305
+ let transaction: ReleaseSessionTransaction | undefined = 'transaction' in raw
306
+ ? raw.transaction
307
+ : undefined;
308
+ let migratedPhase = raw.phase;
309
+ let migratedReceipts = raw.receipts;
310
+ const candidateKey = releaseIdentityKey(raw.candidateRelease);
311
+ const candidateIsActive = Boolean(candidateKey && activeKey && candidateKey === activeKey);
312
+ if (!transaction && ['cutover_attempting', 'cutover_committed', 'soaking', 'known_good'].includes(raw.phase)) {
313
+ const candidate = raw.candidateRelease;
314
+ const previous = authority?.previous;
315
+ if (
316
+ candidateIsActive
317
+ && candidate
318
+ && authority
319
+ && previous?.databaseBackup
320
+ && previous.releaseId === raw.stableRelease.releaseId
321
+ && previous.artifactIdentity === raw.stableRelease.artifactIdentity
322
+ && previous.manifestSha256 === raw.stableRelease.manifestSha256
323
+ ) {
324
+ transaction = {
325
+ schemaVersion: 1,
326
+ operationId: `migration:${id}:${authority.revision}`,
327
+ candidateReleaseId: candidate.releaseId,
328
+ cutoverAuthorityRevision: authority.revision,
329
+ rollbackRelease: previous,
330
+ startedAt: raw.updatedAt,
331
+ };
332
+ } else if (raw.phase === 'known_good') {
333
+ // Historical terminal acceptance needs no live rollback authority. Keep
334
+ // exact transaction evidence when it is reconstructable above, but do
335
+ // not invent obsolete rollback state for already-terminal sessions.
336
+ } else if (raw.phase === 'soaking' && candidateKey && !candidateIsActive) {
337
+ const updatedAtMs = Date.parse(raw.updatedAt);
338
+ const successors = (stableSuccessors.get(candidateKey) ?? [])
339
+ .filter((entry) => entry.sessionId !== id && Date.parse(entry.createdAt) >= updatedAtMs);
340
+ const previousAuthorityProof = Boolean(previousKey && candidateKey === previousKey);
341
+ if (successors.length === 0 && !previousAuthorityProof) {
342
+ throw new Error(`RELEASE_SESSION_MIGRATION_HISTORICAL_ACCEPTANCE_UNPROVEN: ${id}`);
343
+ }
344
+ migratedPhase = 'known_good';
345
+ const proof = successors.length > 0
346
+ ? `${successors.length} later ReleaseSession attempt(s) use exact Candidate B as Stable A; first=${successors[0]!.sessionId}`
347
+ : 'current RuntimeReleaseAuthority.previous references Candidate B';
348
+ const receiptId = `migration:historical-known-good:${id}`;
349
+ migratedReceipts = raw.receipts.some((receipt) => receipt.id === receiptId)
350
+ ? raw.receipts
351
+ : [...raw.receipts, historicalAcceptanceReceipt(id, raw.updatedAt, proof)];
352
+ } else {
353
+ throw new Error(`RELEASE_SESSION_MIGRATION_AUTHORITY_MISMATCH: ${id}`);
354
+ }
355
+ }
356
+ const migrated = assertCurrentSession({
357
+ ...raw,
358
+ schemaVersion: 1,
359
+ semanticEpoch: 2,
360
+ phase: migratedPhase,
361
+ receipts: migratedReceipts,
362
+ ...(transaction ? { transaction } : {}),
363
+ } as ReleaseSession, id);
364
+ writeSession(path, migrated);
365
+ migratedSessionIds.push(id);
366
+ }
367
+ return { migratedSessionIds, currentSessionIds, inspected: names.length };
368
+ }
369
+
370
+ export function readReleaseSession(controllerHome: string, sessionId: string): ReleaseSession | undefined {
371
+ const path = sessionPath(controllerHome, validSessionId(sessionId));
372
+ if (!existsSync(path)) return undefined;
373
+ try {
374
+ const value = JSON.parse(readFileSync(path, 'utf8')) as ReleaseSession;
375
+ return assertCurrentSession(value, sessionId);
376
+ } catch (error) {
377
+ if (error instanceof Error && error.message === 'RELEASE_SESSION_INVALID') throw error;
378
+ throw new Error('RELEASE_SESSION_INVALID');
379
+ }
380
+ }
381
+
382
+ export interface ReleaseSessionInventory {
383
+ sessions: ReleaseSession[];
384
+ invalidSessionFiles: string[];
385
+ inspected: number;
386
+ truncated: boolean;
387
+ }
388
+
389
+ interface CompleteReleaseSessionInventoryCacheEntry {
390
+ revision: string;
391
+ inventory: ReleaseSessionInventory;
392
+ }
393
+
394
+ const completeReleaseSessionInventoryCache = new Map<string, CompleteReleaseSessionInventoryCacheEntry>();
395
+
396
+ function releaseSessionInventoryRoot(controllerHome: string): string {
397
+ return dirname(sessionPath(controllerHome, 'release-session-inventory'));
398
+ }
399
+
400
+ function invalidateCompleteReleaseSessionInventory(root: string): void {
401
+ completeReleaseSessionInventoryCache.delete(resolve(root));
402
+ }
403
+
404
+ function copyReleaseSessionInventory(inventory: ReleaseSessionInventory): ReleaseSessionInventory {
405
+ return {
406
+ sessions: [...inventory.sessions],
407
+ invalidSessionFiles: [...inventory.invalidSessionFiles],
408
+ inspected: inventory.inspected,
409
+ truncated: inventory.truncated,
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Complete ReleaseSession authority inventory. The JSON files remain the single
415
+ * durable authority; this cache is process-local and is invalidated by every
416
+ * atomic session write. Directory metadata also invalidates cache entries when
417
+ * another Recovery process replaces a session file.
418
+ */
419
+ export function listCompleteReleaseSessions(controllerHome: string): ReleaseSessionInventory {
420
+ const root = releaseSessionInventoryRoot(controllerHome);
421
+ if (!existsSync(root)) {
422
+ invalidateCompleteReleaseSessionInventory(root);
423
+ return { sessions: [], invalidSessionFiles: [], inspected: 0, truncated: false };
424
+ }
425
+ const names = readdirSync(root)
426
+ .filter((name) => name.endsWith('.json'))
427
+ .sort();
428
+ const directory = statSync(root);
429
+ const revision = [directory.mtimeMs, directory.ctimeMs, names.length, names[0] ?? '', names[names.length - 1] ?? ''].join(':');
430
+ const cacheKey = resolve(root);
431
+ const cached = completeReleaseSessionInventoryCache.get(cacheKey);
432
+ if (cached?.revision === revision) return copyReleaseSessionInventory(cached.inventory);
433
+
434
+ const sessions: ReleaseSession[] = [];
435
+ const invalidSessionFiles: string[] = [];
436
+ for (const name of names) {
437
+ const sessionId = name.slice(0, -'.json'.length);
438
+ try {
439
+ const session = readReleaseSession(controllerHome, sessionId);
440
+ if (session) sessions.push(session);
441
+ else invalidSessionFiles.push(name);
442
+ } catch {
443
+ invalidSessionFiles.push(name);
444
+ }
445
+ }
446
+ const inventory: ReleaseSessionInventory = {
447
+ sessions,
448
+ invalidSessionFiles,
449
+ inspected: names.length,
450
+ truncated: false,
451
+ };
452
+ completeReleaseSessionInventoryCache.set(cacheKey, { revision, inventory });
453
+ return copyReleaseSessionInventory(inventory);
454
+ }
455
+
456
+ /**
457
+ * Bounded read-only inventory for diagnostic/projection consumers.
458
+ * The release domain owns phase progression. Recovery may execute fenced
459
+ * Runtime mutations, but it is not the semantic owner of normal release intent.
460
+ */
461
+ export function listReleaseSessions(
462
+ controllerHome: string,
463
+ options: { maxEntries?: number } = {},
464
+ ): ReleaseSessionInventory {
465
+ const root = releaseSessionInventoryRoot(controllerHome);
466
+ if (!existsSync(root)) return { sessions: [], invalidSessionFiles: [], inspected: 0, truncated: false };
467
+ const maxEntries = Math.max(1, Math.floor(options.maxEntries ?? 512));
468
+ const names = readdirSync(root)
469
+ .filter((name) => name.endsWith('.json'))
470
+ .sort();
471
+ const selected = names.slice(0, maxEntries);
472
+ const sessions: ReleaseSession[] = [];
473
+ const invalidSessionFiles: string[] = [];
474
+ for (const name of selected) {
475
+ const sessionId = name.slice(0, -'.json'.length);
476
+ try {
477
+ const session = readReleaseSession(controllerHome, sessionId);
478
+ if (session) sessions.push(session);
479
+ else invalidSessionFiles.push(name);
480
+ } catch {
481
+ invalidSessionFiles.push(name);
482
+ }
483
+ }
484
+ return {
485
+ sessions,
486
+ invalidSessionFiles,
487
+ inspected: selected.length,
488
+ truncated: names.length > selected.length,
489
+ };
490
+ }
491
+
492
+ const RELEASE_SESSION_TERMINAL_PHASES = new Set<ReleaseSessionPhase>([
493
+ 'known_good',
494
+ 'rolled_back',
495
+ 'failed',
496
+ ]);
497
+
498
+ const RELEASE_SESSION_CANDIDATE_RETIRED_PHASES = new Set<ReleaseSessionPhase>([
499
+ 'soaking',
500
+ ...RELEASE_SESSION_TERMINAL_PHASES,
501
+ ]);
502
+
503
+ export function releaseSessionIsTerminal(session: ReleaseSession): boolean {
504
+ return RELEASE_SESSION_TERMINAL_PHASES.has(session.phase);
505
+ }
506
+
507
+ /**
508
+ * A soaking ReleaseSession may temporarily remain non-terminal while a repair
509
+ * successor is prepared. This relation is deliberately stricter than generic
510
+ * release equality: the predecessor must have durably cut over the exact
511
+ * release/authority revision that the successor freezes as Stable A. It
512
+ * therefore preserves the predecessor's rollback authority until the successor
513
+ * itself has committed a replacement cutover without permitting arbitrary
514
+ * multiple active ReleaseSessions.
515
+ */
516
+ export function releaseSessionIsSoakingPredecessorOfStable(
517
+ session: ReleaseSession,
518
+ stableRelease: ReleaseSessionStableRelease,
519
+ ): boolean {
520
+ const candidate = session.candidateRelease;
521
+ const transaction = session.transaction;
522
+ return session.phase === 'soaking'
523
+ && Boolean(candidate)
524
+ && Boolean(transaction)
525
+ && transaction!.candidateReleaseId === candidate!.releaseId
526
+ && transaction!.cutoverAuthorityRevision === stableRelease.authorityRevision
527
+ && candidate!.releaseId === stableRelease.releaseId
528
+ && candidate!.artifactIdentity === stableRelease.artifactIdentity
529
+ && candidate!.manifestSha256 === stableRelease.manifestSha256;
530
+ }
531
+
532
+ /**
533
+ * Emergency Recovery may publish a later whole-Runtime authority outside the
534
+ * original soaking ReleaseSession. The old session is superseded only when the
535
+ * active authority revision has strictly advanced beyond its cutover and the
536
+ * active stable identity is no longer that exact candidate. Equal/older or
537
+ * partially-matching identities remain ambiguous and must stay fail-closed.
538
+ */
539
+ export function releaseSessionIsSoakingSupersededByStableAuthority(
540
+ session: ReleaseSession,
541
+ stableRelease: ReleaseSessionStableRelease,
542
+ ): boolean {
543
+ const candidate = session.candidateRelease;
544
+ const transaction = session.transaction;
545
+ if (
546
+ session.phase !== 'soaking'
547
+ || !candidate
548
+ || !transaction
549
+ || transaction.candidateReleaseId !== candidate.releaseId
550
+ || stableRelease.authorityRevision <= transaction.cutoverAuthorityRevision
551
+ ) return false;
552
+ return candidate.releaseId !== stableRelease.releaseId
553
+ || candidate.artifactIdentity !== stableRelease.artifactIdentity
554
+ || candidate.manifestSha256 !== stableRelease.manifestSha256;
555
+ }
556
+
557
+ /**
558
+ * Durable semantic proof that Candidate B is no longer required as a mutable
559
+ * Controller Home. This does not itself authorize deletion: cleanup must still
560
+ * prove the exact fenced path and absence of a live Runtime owner.
561
+ */
562
+ export function releaseSessionCandidateIsRetired(session: ReleaseSession): boolean {
563
+ return RELEASE_SESSION_CANDIDATE_RETIRED_PHASES.has(session.phase);
564
+ }
565
+
566
+ export function createReleaseSession(input: {
567
+ controllerHome: string;
568
+ sessionId: string;
569
+ stable: StableExecutionLane;
570
+ stableRelease: ReleaseSessionStableRelease;
571
+ candidate: CandidateExecutionLane;
572
+ sourceRevision: string;
573
+ }): ReleaseSession {
574
+ const sessionId = validSessionId(input.sessionId);
575
+ const path = sessionPath(input.controllerHome, sessionId);
576
+ if (existsSync(path)) throw new Error('RELEASE_SESSION_ALREADY_EXISTS');
577
+ const inventory = listCompleteReleaseSessions(input.controllerHome);
578
+ if (inventory.invalidSessionFiles.length > 0) {
579
+ throw new Error(`RELEASE_SESSION_INVENTORY_INCOMPLETE: invalid=${inventory.invalidSessionFiles.join(',')}`);
580
+ }
581
+ const active = inventory.sessions.filter((session) => !releaseSessionIsTerminal(session));
582
+ const sourceRevision = input.sourceRevision.trim();
583
+ const allowedRepairPredecessor = active.length === 1
584
+ && active[0] !== undefined
585
+ && releaseSessionIsSoakingPredecessorOfStable(active[0], input.stableRelease)
586
+ && active[0].sourceRevision !== sourceRevision;
587
+ if (active.length > 0 && !allowedRepairPredecessor) {
588
+ throw new Error(`RELEASE_SESSION_ACTIVE_EXISTS: ${active.map((session) => `${session.sessionId}:${session.phase}`).join(',')}`);
589
+ }
590
+ if (resolve(input.stable.controllerHome) === resolve(input.candidate.controllerHome)) throw new Error('RELEASE_SESSION_LANE_COLLISION');
591
+ const timestamp = new Date().toISOString();
592
+ const session: ReleaseSession = {
593
+ schemaVersion: 1,
594
+ semanticEpoch: 2,
595
+ sessionId,
596
+ stable: input.stable,
597
+ stableRelease: input.stableRelease,
598
+ candidate: input.candidate,
599
+ sourceRevision,
600
+ phase: 'source_frozen',
601
+ revision: 1,
602
+ receipts: [{ id: `source:${sessionId}`, kind: 'source', recordedAt: timestamp, summary: `source frozen at ${input.sourceRevision.trim()}` }],
603
+ createdAt: timestamp,
604
+ updatedAt: timestamp,
605
+ };
606
+ if (!session.sourceRevision) throw new Error('RELEASE_SESSION_SOURCE_REVISION_REQUIRED');
607
+ if (
608
+ !Number.isInteger(session.stableRelease.authorityRevision)
609
+ || session.stableRelease.authorityRevision < 1
610
+ || !session.stableRelease.releaseId
611
+ || !session.stableRelease.artifactIdentity
612
+ || !session.stableRelease.manifestSha256
613
+ || !Number.isInteger(session.stableRelease.workerProtocolVersion)
614
+ || session.stableRelease.workerProtocolVersion < 1
615
+ || !session.stableRelease.releaseFencingTokenSha256
616
+ ) throw new Error('RELEASE_SESSION_STABLE_RELEASE_IDENTITY_INVALID');
617
+ writeSession(path, session);
618
+ return session;
619
+ }
620
+
621
+ function receiptIds(session: ReleaseSession, kind: ReleaseSessionReceipt['kind']): Set<string> {
622
+ return new Set(session.receipts.filter((receipt) => receipt.kind === kind).map((receipt) => receipt.id));
623
+ }
624
+
625
+ function assertTransition(session: ReleaseSession, phase: ReleaseSessionPhase): void {
626
+ const order: ReleaseSessionPhase[] = ['source_frozen', 'built', 'static_verified', 'candidate_booted', 'candidate_verified', 'cutover_eligible', 'cutover_attempting', 'cutover_committed', 'soaking', 'known_good'];
627
+ if (session.phase === 'failed' || session.phase === 'rolled_back' || session.phase === 'known_good') {
628
+ throw new Error('RELEASE_SESSION_TERMINAL');
629
+ }
630
+ if (phase === 'failed') return;
631
+ if (phase === 'rolled_back') {
632
+ if (order.indexOf(session.phase) < order.indexOf('cutover_attempting')) {
633
+ throw new Error('RELEASE_SESSION_ROLLBACK_BEFORE_CUTOVER_ATTEMPT');
634
+ }
635
+ return;
636
+ }
637
+ if (order.indexOf(phase) !== order.indexOf(session.phase) + 1) throw new Error('RELEASE_SESSION_TRANSITION_INVALID');
638
+ if ((phase === 'built' || phase === 'static_verified') && !session.candidateRelease) {
639
+ throw new Error('RELEASE_SESSION_CANDIDATE_RELEASE_REQUIRED');
640
+ }
641
+ if (phase === 'static_verified' && [...REQUIRED_STATIC_GATES].some((id) => !receiptIds(session, 'static_gate').has(id))) {
642
+ throw new Error('RELEASE_SESSION_STATIC_GATES_INCOMPLETE');
643
+ }
644
+ if (phase === 'candidate_verified' && [...REQUIRED_CANDIDATE_CANARIES].some((id) => !receiptIds(session, 'candidate_canary').has(id))) {
645
+ throw new Error('RELEASE_SESSION_CANDIDATE_CANARIES_INCOMPLETE');
646
+ }
647
+ if (phase === 'known_good' && [...REQUIRED_KNOWN_GOOD_SOAK_GATES].some((id) => !receiptIds(session, 'soak').has(id))) {
648
+ throw new Error('RELEASE_SESSION_SOAK_GATES_INCOMPLETE');
649
+ }
650
+ if (phase === 'cutover_eligible' && (session.stable.controllerHome === session.candidate.controllerHome || session.stable.port === session.candidate.port)) {
651
+ throw new Error('RELEASE_SESSION_LANE_COLLISION');
652
+ }
653
+ }
654
+
655
+ /**
656
+ * The release coordinator calls this while the executing provider holds the
657
+ * appropriate mutation lock. expectedRevision is a CAS fence, so a stale
658
+ * observer cannot advance a newer session.
659
+ */
660
+ export function recordReleaseSessionTransaction(input: {
661
+ controllerHome: string;
662
+ sessionId: string;
663
+ expectedRevision: number;
664
+ transaction: ReleaseSessionTransaction;
665
+ }): ReleaseSession {
666
+ const current = readReleaseSession(input.controllerHome, input.sessionId);
667
+ if (!current) throw new Error('RELEASE_SESSION_MISSING');
668
+ if (current.phase !== 'cutover_attempting') throw new Error(`RELEASE_SESSION_TRANSACTION_REQUIRES_CUTOVER_ATTEMPTING: ${current.phase}`);
669
+ // Runtime startup migration and the Recovery activation writer are two
670
+ // legitimate physical writers for this one bounded transaction. If the
671
+ // migration already persisted the exact transaction, accept its durable
672
+ // result instead of treating the activation's stale local revision as a
673
+ // competing semantic update. Any different transaction remains fenced.
674
+ if (current.transaction && sameReleaseSessionTransactionIdentity(current.transaction, input.transaction)) {
675
+ return current;
676
+ }
677
+ if (current.revision !== input.expectedRevision) throw new Error('RELEASE_SESSION_REVISION_FENCED');
678
+ const next = {
679
+ ...current,
680
+ transaction: input.transaction,
681
+ revision: current.revision + 1,
682
+ updatedAt: new Date().toISOString(),
683
+ } satisfies ReleaseSession;
684
+ if (!validTransaction(next)) throw new Error('RELEASE_SESSION_TRANSACTION_INVALID');
685
+ writeSession(sessionPath(input.controllerHome, current.sessionId), next);
686
+ return next;
687
+ }
688
+
689
+ export function advanceReleaseSession(input: {
690
+ controllerHome: string;
691
+ sessionId: string;
692
+ expectedRevision: number;
693
+ phase: ReleaseSessionPhase;
694
+ candidateRelease?: ReleaseSessionCandidateRelease;
695
+ receipts?: Array<Omit<ReleaseSessionReceipt, 'recordedAt'>>;
696
+ }): ReleaseSession {
697
+ const current = readReleaseSession(input.controllerHome, input.sessionId);
698
+ if (!current) throw new Error('RELEASE_SESSION_MISSING');
699
+ if (current.revision !== input.expectedRevision) throw new Error('RELEASE_SESSION_REVISION_FENCED');
700
+ const recordedAt = new Date().toISOString();
701
+ const receipts = [...current.receipts, ...(input.receipts ?? []).map((receipt) => ({ ...receipt, recordedAt }))].slice(-64);
702
+ const candidateRelease = input.candidateRelease ?? current.candidateRelease;
703
+ if (candidateRelease && (
704
+ !candidateRelease.releaseId
705
+ || !candidateRelease.manifestPath
706
+ || !candidateRelease.artifactIdentity
707
+ || !candidateRelease.manifestSha256
708
+ || !/^[a-f0-9]{64}$/i.test(candidateRelease.treeSha256)
709
+ || candidateRelease.sourceCommit !== current.sourceRevision
710
+ )) throw new Error('RELEASE_SESSION_CANDIDATE_RELEASE_IDENTITY_INVALID');
711
+ const proposed: ReleaseSession = { ...current, ...(candidateRelease ? { candidateRelease } : {}), receipts };
712
+ assertTransition(proposed, input.phase);
713
+ const next: ReleaseSession = { ...proposed, phase: input.phase, revision: current.revision + 1, updatedAt: recordedAt };
714
+ writeSession(sessionPath(input.controllerHome, current.sessionId), next);
715
+ return next;
716
+ }