@moretea-labs/forge 1.8.0 → 1.8.2

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 (695) hide show
  1. package/AGENTS.md +73 -7
  2. package/CHANGELOG.md +34 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.en.md +24 -3
  6. package/README.md +10 -6
  7. package/README.zh-CN.md +5 -3
  8. package/THIRD_PARTY_NOTICES.md +1 -2
  9. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +531 -56
  11. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  12. package/adapters/chatgpt/controller-round-host.ts +15 -25
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  14. package/adapters/chatgpt/provider-delivery.ts +229 -2
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +99 -54
  16. package/adapters/computer/chatgpt-extension-target.ts +708 -0
  17. package/adapters/computer/chrome-extension/background.js +541 -0
  18. package/adapters/computer/chrome-extension/content.js +402 -0
  19. package/adapters/computer/chrome-extension/core.js +24 -0
  20. package/adapters/computer/chrome-extension/manifest.json +32 -0
  21. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  22. package/adapters/computer/desktop-operator-provider.ts +18 -6
  23. package/adapters/computer/interaction-target-authority.ts +178 -24
  24. package/adapters/mcp/access-mode.ts +18 -39
  25. package/adapters/mcp/auth.ts +2 -7
  26. package/adapters/mcp/instructions.ts +6 -5
  27. package/adapters/mcp/multi-repository.ts +10 -47
  28. package/adapters/mcp/oauth.ts +73 -0
  29. package/adapters/mcp/paths.ts +4 -1
  30. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  31. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  32. package/adapters/mcp/runtime-gateway/context-adapter.ts +490 -113
  33. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +83 -0
  34. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  35. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  36. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  37. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  38. package/adapters/mcp/runtime-gateway/execution-tools.ts +14 -54
  39. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  40. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +132 -47
  41. package/adapters/mcp/runtime-gateway/process-tools.ts +173 -23
  42. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  43. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  44. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  45. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  46. package/adapters/mcp/runtime-gateway/router.ts +19 -507
  47. package/adapters/mcp/runtime-gateway/routing-policy.ts +506 -0
  48. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +15 -66
  49. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  50. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +123 -178
  51. package/adapters/mcp/runtime-gateway/runtime-tools.ts +11 -5
  52. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +37 -1
  53. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  54. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +416 -270
  55. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +140 -0
  56. package/adapters/mcp/runtime-gateway/work-adapter.ts +262 -3123
  57. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  58. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  59. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +229 -0
  60. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  61. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  62. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  63. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  64. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  65. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +320 -0
  66. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  67. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +180 -0
  68. package/adapters/mcp/server.ts +91 -26
  69. package/adapters/mcp/setup.ts +5 -13
  70. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  71. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +703 -3092
  72. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +5 -10
  73. package/adapters/mcp/tool-mapping/repository-tools.ts +170 -213
  74. package/adapters/mcp/toolset-names.ts +11 -37
  75. package/adapters/mcp/toolset.ts +19 -92
  76. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  77. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  78. package/adapters/mcp/transports/http-observation.ts +377 -0
  79. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  80. package/adapters/mcp/transports/http.ts +200 -683
  81. package/adapters/mcp/transports/oauth-http.ts +322 -0
  82. package/adapters/mcp/transports/session-registry.ts +6 -0
  83. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  84. package/adapters/mcp/types.ts +0 -6
  85. package/assets/hooks/lib/session-state.sh +0 -38
  86. package/assets/hooks/post-bash.sh +1 -1
  87. package/assets/hooks/post-tool-observer.sh +0 -5
  88. package/assets/hooks/prompt-guard.sh +0 -55
  89. package/assets/partials/04-project-structure.partial.md +1 -1
  90. package/assets/partials/07-footer.partial.md +1 -1
  91. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  92. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  93. package/assets/plugin-registry.v1.json +6 -4
  94. package/assets/reference-configs/agentic-development-flow.md +26 -1
  95. package/assets/reference-configs/external-tooling.md +0 -76
  96. package/assets/reference-configs/global-working-rules.md +19 -0
  97. package/assets/reference-configs/release-deploy.md +1 -1
  98. package/assets/templates/helpers/architecture-queue.sh +1 -1
  99. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  100. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  101. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  102. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  103. package/assets/workflow-contract.v1.json +0 -2
  104. package/docs/README.md +5 -2
  105. package/docs/architecture/CURRENT.md +205 -51
  106. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  107. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  108. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  109. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  110. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  111. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  112. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  113. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +234 -0
  114. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  115. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +74 -0
  116. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +342 -0
  117. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  118. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  119. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  120. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  121. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  122. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  123. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  124. package/docs/architecture/domains/public-surface.md +3 -5
  125. package/docs/architecture/domains/verification.md +6 -5
  126. package/docs/architecture/domains/workflow-engine.md +6 -0
  127. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +3 -3
  128. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  129. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  130. package/docs/forge-plugin-management.md +16 -4
  131. package/docs/operations/controller-browser-plugin.md +23 -0
  132. package/docs/operations/controller-performance-and-502.md +17 -0
  133. package/docs/operations/controller-reliability-runbook.md +2 -2
  134. package/docs/operations/features.md +6 -7
  135. package/docs/operations/features.zh-CN.md +1 -1
  136. package/docs/operations/github-repository.md +4 -4
  137. package/docs/operations/github-semantic-release-projection.md +20 -0
  138. package/docs/operations/homebrew.md +2 -2
  139. package/docs/operations/mcp-tool-exposure.md +13 -52
  140. package/docs/operations/releasing.md +2 -0
  141. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  142. package/docs/operations/standalone-disaster-recovery.md +15 -7
  143. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  144. package/docs/tutorials/03-first-repository-task.md +2 -0
  145. package/docs/wiki/Computer-and-Plugins.md +78 -0
  146. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  147. package/docs/wiki/Home.md +16 -2
  148. package/docs/wiki/Implementation.md +25 -13
  149. package/docs/wiki/Installation.md +28 -32
  150. package/docs/wiki/Integrations.md +4 -0
  151. package/docs/wiki/Quick-Start.md +35 -22
  152. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  153. package/docs/wiki/_Sidebar.md +5 -1
  154. package/package.json +25 -25
  155. package/packages/kernel/cognition/api/index.ts +6 -0
  156. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  157. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  158. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  159. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  160. package/packages/kernel/cognition/domain/learning.ts +106 -0
  161. package/packages/kernel/cognition/domain/memory.ts +254 -0
  162. package/packages/kernel/controller/api/index.ts +1 -0
  163. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  164. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  165. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  166. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  167. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  168. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  169. package/packages/kernel/controller/infrastructure/controller-session-store.ts +348 -97
  170. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  171. package/packages/kernel/identity/api/index.ts +10 -0
  172. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  173. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  174. package/packages/kernel/identity/domain/grant.ts +72 -0
  175. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  176. package/packages/kernel/identity/domain/user-request.ts +93 -0
  177. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +87 -0
  178. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  179. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  180. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  181. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  182. package/packages/kernel/scheduler/api/index.ts +0 -2
  183. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  184. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  185. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  186. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  187. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +166 -143
  188. package/packages/kernel/work/api/index.ts +3 -2
  189. package/packages/kernel/work/application/work-service.ts +18 -42
  190. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  191. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  192. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  193. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  194. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  195. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  196. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  197. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  198. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  199. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  200. package/packages/kernel/work/domain/state-machine.ts +34 -65
  201. package/packages/kernel/work/domain/types.ts +57 -51
  202. package/packages/kernel/work/infrastructure/work-contract-store.ts +847 -729
  203. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  204. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  205. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  206. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  207. package/packages/plugin-runtime/computer/conversation-target.ts +153 -0
  208. package/packages/plugin-runtime/computer/index.ts +2 -0
  209. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  210. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  211. package/packages/protocols/computer/contract.ts +27 -1
  212. package/scripts/activate-source-baseline.ts +7 -9
  213. package/scripts/assemble-template.ts +11 -72
  214. package/scripts/canary-controller-semantic-wait.ts +12 -24
  215. package/scripts/check-mcp-compatibility.ts +94 -201
  216. package/scripts/check-package-identity.mjs +0 -5
  217. package/scripts/check-runtime-architecture.mjs +921 -239
  218. package/scripts/forge-local-recovery-helper.mjs +93 -15
  219. package/scripts/init-project.sh +0 -46
  220. package/scripts/install-ios-hid-toolchain.sh +60 -0
  221. package/scripts/install-standalone-recovery.ts +1 -1
  222. package/scripts/lib/project-init-lib.sh +6 -3
  223. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  224. package/scripts/open-source-audit-allowlist.txt +1 -3
  225. package/scripts/run-bounded-command.mjs +5 -0
  226. package/scripts/run-governed-gate.ts +2 -1
  227. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  228. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  229. package/scripts/smoke-ios-physical-device.ts +38 -10
  230. package/scripts/smoke-mcp-http-runtime.ts +12 -8
  231. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  232. package/scripts/smoke-runtime-recovery.ts +12 -43
  233. package/scripts/smoke-schedule-engine.ts +23 -15
  234. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  235. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  236. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +95 -0
  237. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  238. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  239. package/scripts/smoke-workflow-supervisor.ts +103 -0
  240. package/scripts/sync-generated-authority.mjs +0 -1
  241. package/scripts/verify-forge-runtime.sh +2 -1
  242. package/src/cli/agent-jobs/executor-health.ts +12 -35
  243. package/src/cli/agent-jobs/integration.ts +20 -51
  244. package/src/cli/agent-jobs/job-manager.ts +7 -3
  245. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  246. package/src/cli/commands/brain-assistant.ts +27 -13
  247. package/src/cli/commands/brain.ts +13 -15
  248. package/src/cli/commands/chatgpt.ts +1 -163
  249. package/src/cli/commands/doctor.ts +1 -143
  250. package/src/cli/commands/global-runtime.ts +3 -196
  251. package/src/cli/commands/init-hook.ts +6 -23
  252. package/src/cli/commands/init.ts +3 -252
  253. package/src/cli/commands/mcp.ts +0 -5
  254. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  255. package/src/cli/commands/recovery.ts +137 -135
  256. package/src/cli/commands/repository.ts +17 -1
  257. package/src/cli/commands/runtime.ts +3 -3
  258. package/src/cli/commands/setup-execution.ts +41 -0
  259. package/src/cli/commands/setup-profile.ts +8 -0
  260. package/src/cli/controller/check-runner.ts +13 -2
  261. package/src/cli/controller/completion-backlog.ts +3 -3
  262. package/src/cli/controller/completion-orchestrator.ts +5 -44
  263. package/src/cli/controller/completion-target.ts +3 -72
  264. package/src/cli/controller/composite-operations.ts +1 -1
  265. package/src/cli/controller/context/instruction-context.ts +1 -1
  266. package/src/cli/controller/context/known-paths.ts +2 -2
  267. package/src/cli/controller/context/query-planning.ts +11 -4
  268. package/src/cli/controller/context/rule-resolution.ts +2 -2
  269. package/src/cli/controller/context/source-materializer.ts +83 -102
  270. package/src/cli/controller/context/structural-provider.ts +84 -0
  271. package/src/cli/controller/context/structural.ts +6 -6
  272. package/src/cli/controller/context/types.ts +8 -9
  273. package/src/cli/controller/context-pack.ts +107 -84
  274. package/src/cli/controller/execution-completion.ts +4 -7
  275. package/src/cli/controller/execution-policy.ts +36 -153
  276. package/src/cli/controller/governance.ts +2 -3
  277. package/src/cli/controller/issue-store.ts +4 -99
  278. package/src/cli/controller/launch-agents.ts +59 -18
  279. package/src/cli/controller/operational-plan.ts +25 -52
  280. package/src/cli/controller/runtime-config.ts +5 -1
  281. package/src/cli/controller/stuck-state-migration.ts +2 -3
  282. package/src/cli/controller/systemd-user.ts +42 -10
  283. package/src/cli/editing/edit-session.ts +18 -12
  284. package/src/cli/github/github.ts +4 -4
  285. package/src/cli/github/semantic-projection.ts +174 -0
  286. package/src/cli/hook/prompt-intents.ts +0 -9
  287. package/src/cli/index.ts +1 -43
  288. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  289. package/src/cli/local-bridge/console-automations.ts +19 -14
  290. package/src/cli/local-bridge/console-view-models.ts +4 -13
  291. package/src/cli/local-bridge/facade-api.ts +64 -193
  292. package/src/cli/local-bridge/server.ts +24 -147
  293. package/src/cli/local-bridge/types.ts +0 -1
  294. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  295. package/src/cli/repositories/command-classifier.ts +0 -9
  296. package/src/cli/repositories/command-executor.ts +29 -23
  297. package/src/cli/repositories/command-scope.ts +37 -18
  298. package/src/cli/repositories/controller-home.ts +48 -0
  299. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  300. package/src/cli/repositories/git-command-executor.ts +2 -2
  301. package/src/cli/repositories/locks.ts +20 -0
  302. package/src/cli/repositories/registry.ts +79 -12
  303. package/src/cli/repositories/safe-patch.ts +2 -12
  304. package/src/cli/repositories/structured-git.ts +101 -5
  305. package/src/cli/repository/inspector.ts +185 -343
  306. package/src/cli/repository/session-cache.ts +92 -6
  307. package/src/effects/process-runner.ts +17 -1
  308. package/src/runtime/context/assistant-context.ts +20 -7
  309. package/src/runtime/context/assistant-work-context.ts +89 -27
  310. package/src/runtime/context/automatic-learning.ts +565 -0
  311. package/src/runtime/context/cognitive-attention.ts +173 -0
  312. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  313. package/src/runtime/context/context-closure.ts +46 -9
  314. package/src/runtime/context/context-pack-process.ts +103 -0
  315. package/src/runtime/context/context-plane.ts +1 -9
  316. package/src/runtime/context/direct-controller-learning.ts +298 -0
  317. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  318. package/src/runtime/context/semantic-navigation-contract.ts +16 -0
  319. package/src/runtime/context/semantic-navigation.ts +7 -35
  320. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  321. package/src/runtime/context/typescript-navigation.ts +89 -3
  322. package/src/runtime/control-plane/cleanup-artifact-retention.ts +215 -12
  323. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  324. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  325. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  326. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  327. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  328. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  329. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  330. package/src/runtime/control-plane/execution/validation.ts +6 -8
  331. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  332. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  333. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  334. package/src/runtime/control-plane/execution/work-delivery-service.ts +616 -0
  335. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  336. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  337. package/src/runtime/control-plane/execution/work-handle-authority.ts +197 -110
  338. package/src/runtime/control-plane/execution/work-handle-store.ts +72 -44
  339. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  340. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  341. package/src/runtime/control-plane/execution/work-operation-service.ts +60 -34
  342. package/src/runtime/control-plane/execution/work-preparation-service.ts +231 -267
  343. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  344. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  345. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  346. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +201 -199
  347. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +33 -85
  348. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  349. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +6 -2
  350. package/src/runtime/control-plane/facade/capability-registry.ts +364 -37
  351. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  352. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  353. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  354. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  355. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  356. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  357. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  358. package/src/runtime/control-plane/facade/index.ts +0 -1
  359. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  360. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  361. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  362. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  363. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  364. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  365. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  366. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  367. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  368. package/src/runtime/control-plane/facade/types.ts +49 -108
  369. package/src/runtime/control-plane/facade/work-continuation.ts +9 -9
  370. package/src/runtime/control-plane/generated-cache-retention.ts +206 -59
  371. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  372. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  373. package/src/runtime/control-plane/global-scheduler/maintenance.ts +256 -117
  374. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -12
  375. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  376. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  377. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  378. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  379. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  380. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  381. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +29 -300
  382. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  383. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  384. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  385. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  386. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  387. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  388. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  389. package/src/runtime/control-plane/release-retention.ts +135 -47
  390. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  391. package/src/runtime/control-plane/runtime-cleanup.ts +295 -107
  392. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  393. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  394. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  395. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  396. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  397. package/src/runtime/evidence/artifact-store.ts +158 -40
  398. package/src/runtime/evidence/evidence-store.ts +112 -8
  399. package/src/runtime/evidence/output-handle-index.ts +225 -0
  400. package/src/runtime/evidence/result-store.ts +158 -44
  401. package/src/runtime/evidence/sensitive-output.ts +22 -2
  402. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  403. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  404. package/src/runtime/execution/jobs/store.ts +3 -4
  405. package/src/runtime/execution/managed-workspace.ts +57 -0
  406. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  407. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  408. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  409. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  410. package/src/runtime/execution/process-runtime/command-facade.ts +82 -30
  411. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  412. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  413. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  414. package/src/runtime/execution/process-runtime/index.ts +3 -0
  415. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  416. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  417. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  418. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  419. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  420. package/src/runtime/execution/process-runtime/runtime.ts +55 -18
  421. package/src/runtime/execution/process-runtime/store.ts +30 -2
  422. package/src/runtime/execution/process-runtime/types.ts +6 -1
  423. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  424. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  425. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  426. package/src/runtime/execution/workers/executor.ts +3 -8
  427. package/src/runtime/maintenance/cleanup.ts +41 -5
  428. package/src/runtime/platform/service-inventory.ts +130 -0
  429. package/src/runtime/platform/service-manager.ts +8 -0
  430. package/src/runtime/plugins/action-application.ts +97 -29
  431. package/src/runtime/plugins/app-store-connect-adapter.ts +290 -18
  432. package/src/runtime/plugins/browser-adapter.ts +1124 -333
  433. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  434. package/src/runtime/plugins/browser-macos-bridge.ts +431 -137
  435. package/src/runtime/plugins/browser-manifest-surface.ts +47 -12
  436. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  437. package/src/runtime/plugins/browser-registration.ts +18 -0
  438. package/src/runtime/plugins/browser-runtime.ts +40 -36
  439. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  440. package/src/runtime/plugins/browser-session-store.ts +46 -76
  441. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  442. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +228 -0
  443. package/src/runtime/plugins/computer-registration.ts +298 -20
  444. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  445. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  446. package/src/runtime/plugins/external-adapter.ts +42 -6
  447. package/src/runtime/plugins/external-registration.ts +21 -1
  448. package/src/runtime/plugins/github-adapter.ts +30 -3
  449. package/src/runtime/plugins/gmail-adapter.ts +189 -3
  450. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  451. package/src/runtime/plugins/google-shared.ts +24 -12
  452. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  453. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  454. package/src/runtime/plugins/ios-adapter.ts +46 -41
  455. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  456. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  457. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  458. package/src/runtime/plugins/lightweight-action.ts +208 -2
  459. package/src/runtime/plugins/local-system-adapter.ts +12 -100
  460. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  461. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  462. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  463. package/src/runtime/plugins/store.ts +682 -435
  464. package/src/runtime/plugins/types.ts +24 -0
  465. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  466. package/src/runtime/projections/git-status-sampler.ts +2 -19
  467. package/src/runtime/projections/invalidation.ts +7 -3
  468. package/src/runtime/projections/materialized-view.ts +80 -36
  469. package/src/runtime/recovery/actions.ts +4 -4
  470. package/src/runtime/recovery/capability-action-application.ts +46 -45
  471. package/src/runtime/recovery/lifecycle-inventory.ts +30 -40
  472. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  473. package/src/runtime/recovery/probe.ts +2 -2
  474. package/src/runtime/recovery/store.ts +29 -11
  475. package/src/runtime/release/release-coordinator.ts +312 -0
  476. package/src/runtime/release/release-session.ts +716 -0
  477. package/src/runtime/release/release-state-migration.ts +25 -0
  478. package/src/runtime/resources/leases/store.ts +56 -25
  479. package/src/runtime/root/computer-composition.ts +191 -6
  480. package/src/runtime/root/controller-progression-composition.ts +254 -0
  481. package/src/runtime/root/controller-round-composition.ts +20 -1
  482. package/src/runtime/root/deployment-topology.ts +106 -0
  483. package/src/runtime/root/entry.ts +4 -3
  484. package/src/runtime/root/known-good-recovery.ts +184 -0
  485. package/src/runtime/root/local-bridge.ts +0 -1
  486. package/src/runtime/root/mcp-transport.ts +111 -18
  487. package/src/runtime/root/ownership.ts +261 -9
  488. package/src/runtime/root/package-connector-service.ts +227 -29
  489. package/src/runtime/root/package-runtime-release.ts +117 -30
  490. package/src/runtime/root/package-runtime-service.ts +24 -24
  491. package/src/runtime/root/release-execution-canary.ts +57 -12
  492. package/src/runtime/root/release-loader.ts +90 -8
  493. package/src/runtime/root/release-manifest.ts +80 -40
  494. package/src/runtime/root/release-materialize.ts +372 -106
  495. package/src/runtime/root/release-store.ts +395 -36
  496. package/src/runtime/root/runtime-lane.ts +191 -0
  497. package/src/runtime/root/runtime.ts +160 -29
  498. package/src/runtime/root/scheduled-controller-composition.ts +29 -3
  499. package/src/runtime/root/service-runner.ts +2 -2
  500. package/src/runtime/root/service.ts +126 -44
  501. package/src/runtime/root/status.ts +48 -0
  502. package/src/runtime/root/types.ts +32 -9
  503. package/src/runtime/root/workflow-supervisor-composition.ts +681 -0
  504. package/src/runtime/root/workflow-supervisor-runtime.ts +564 -0
  505. package/src/runtime/root/write-fence.ts +50 -2
  506. package/src/runtime/safe-tooling/google-credential-store.ts +67 -22
  507. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  508. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  509. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  510. package/src/runtime/safe-tooling/workspace-auth.ts +5 -2
  511. package/src/runtime/shared/atomic-text-file.ts +21 -0
  512. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  513. package/src/runtime/shared/json-files.ts +8 -3
  514. package/src/runtime/shared/process-environment.ts +20 -9
  515. package/src/runtime/shared/response-budget.ts +1 -3
  516. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  517. package/src/runtime/standalone-recovery/core.ts +3792 -417
  518. package/src/runtime/standalone-recovery/entry.ts +507 -45
  519. package/src/runtime/standalone-recovery/installer.ts +262 -148
  520. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  521. package/src/runtime/standalone-recovery/operation-lock.ts +12 -2
  522. package/src/runtime/standalone-recovery/performance.ts +54 -16
  523. package/src/runtime/standalone-recovery/release.ts +53 -5
  524. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  525. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  526. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  527. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -3
  528. package/src/runtime/workflow/schedules/engine.ts +166 -178
  529. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  530. package/src/runtime/workflow/schedules/work-continuation.ts +95 -11
  531. package/src/runtime/workflows/runtime.ts +15 -19
  532. package/src/testing/test-governance.ts +5 -1
  533. package/supervisor/chatgpt-conversation.ts +23 -0
  534. package/supervisor/client.ts +218 -0
  535. package/supervisor/control-plane.ts +963 -0
  536. package/supervisor/entry.ts +15 -0
  537. package/supervisor/forge-validators.ts +147 -0
  538. package/supervisor/index.ts +5 -0
  539. package/supervisor/native-browser-adapter.ts +1151 -0
  540. package/supervisor/native-messaging/host.ts +58 -0
  541. package/supervisor/native-messaging/manifest.ts +26 -0
  542. package/supervisor/native-messaging/protocol.ts +27 -0
  543. package/supervisor/paths.ts +18 -0
  544. package/supervisor/protocol.ts +250 -0
  545. package/supervisor/server.ts +412 -0
  546. package/supervisor/store.ts +1870 -0
  547. package/supervisor/tsconfig.json +8 -0
  548. package/supervisor/types.ts +261 -0
  549. package/SKILL.md +0 -427
  550. package/adapters/chatgpt/controller-host.ts +0 -119
  551. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  552. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  553. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  554. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  555. package/adapters/mcp/toolset-selection.ts +0 -41
  556. package/agents/openai.yaml +0 -4
  557. package/assets/skill-hooks.json +0 -41
  558. package/assets/skill-version.json +0 -189
  559. package/assets/skills/claude-review/SKILL.md +0 -231
  560. package/assets/skills/codex-review/SKILL.md +0 -103
  561. package/assets/templates/helpers/check-skill-version.ts +0 -193
  562. package/packages/kernel/progression/api/index.ts +0 -2
  563. package/packages/kernel/progression/application/projector.ts +0 -211
  564. package/packages/kernel/progression/domain/types.ts +0 -116
  565. package/packages/kernel/progression/index.ts +0 -1
  566. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  567. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  568. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  569. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  570. package/scripts/benchmark-background-check-overlap.ts +0 -228
  571. package/scripts/benchmark-computer-hotpath.ts +0 -255
  572. package/scripts/benchmark-controller-hotpath.ts +0 -212
  573. package/scripts/benchmark-controller-round2.ts +0 -191
  574. package/scripts/benchmark-ios-agent-device.ts +0 -511
  575. package/scripts/benchmark-mcp-host.sh +0 -117
  576. package/scripts/benchmark-mcp-transport.ts +0 -169
  577. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  578. package/scripts/benchmark-operational-shadow.ts +0 -28
  579. package/scripts/benchmark-quality-harness.ts +0 -146
  580. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  581. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  582. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  583. package/scripts/benchmark-thin-harness.ts +0 -251
  584. package/scripts/benchmark-typescript-navigation.ts +0 -186
  585. package/scripts/benchmark-work-efficiency.ts +0 -334
  586. package/scripts/build-controller-ui.ts +0 -28
  587. package/scripts/check-npm-release.sh +0 -32
  588. package/scripts/check-public-release-surface.sh +0 -19
  589. package/scripts/check-release-published.sh +0 -57
  590. package/scripts/check-release-readiness.sh +0 -37
  591. package/scripts/check-release-version.mjs +0 -74
  592. package/scripts/check-skill-version.ts +0 -5
  593. package/scripts/check-tarball-install-smoke.sh +0 -150
  594. package/scripts/ensure-codegraph.sh +0 -16
  595. package/scripts/package-source-archive.sh +0 -147
  596. package/scripts/public-release-files.txt +0 -39
  597. package/scripts/public-release.ts +0 -174
  598. package/scripts/publish-release-tarball.sh +0 -27
  599. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  600. package/scripts/run-skill-hook.ts +0 -244
  601. package/scripts/stage-runtime-release.ts +0 -44
  602. package/scripts/sync-codex-installed-copies.sh +0 -182
  603. package/src/cli/commands/tools.ts +0 -167
  604. package/src/cli/controller/progress.ts +0 -656
  605. package/src/cli/controller/work-mode.ts +0 -181
  606. package/src/cli/local-bridge/console-assets.ts +0 -17
  607. package/src/cli/local-bridge/dashboard.ts +0 -17
  608. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  609. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  610. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  611. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  612. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  613. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  614. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  615. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  616. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  617. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  618. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  619. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  620. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  621. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  622. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  623. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  624. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  625. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  626. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  627. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  628. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  629. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  630. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  631. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  632. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  633. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  634. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  635. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  636. package/src/cli/mcp/access-mode.ts +0 -2
  637. package/src/cli/mcp/access-tools.ts +0 -2
  638. package/src/cli/mcp/audit.ts +0 -2
  639. package/src/cli/mcp/auth.ts +0 -2
  640. package/src/cli/mcp/instructions.ts +0 -2
  641. package/src/cli/mcp/legacy-context.ts +0 -2
  642. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  643. package/src/cli/mcp/multi-repository.ts +0 -2
  644. package/src/cli/mcp/oauth.ts +0 -2
  645. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  646. package/src/cli/mcp/paths.ts +0 -2
  647. package/src/cli/mcp/policy.ts +0 -2
  648. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  649. package/src/cli/mcp/proxy-env.ts +0 -2
  650. package/src/cli/mcp/readiness-projection.ts +0 -2
  651. package/src/cli/mcp/redaction.ts +0 -2
  652. package/src/cli/mcp/repo.ts +0 -2
  653. package/src/cli/mcp/repository-tools.ts +0 -2
  654. package/src/cli/mcp/server.ts +0 -2
  655. package/src/cli/mcp/setup.ts +0 -2
  656. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  657. package/src/cli/mcp/tool-contract.ts +0 -2
  658. package/src/cli/mcp/tools.ts +0 -2
  659. package/src/cli/mcp/toolset-names.ts +0 -2
  660. package/src/cli/mcp/toolset-selection.ts +0 -2
  661. package/src/cli/mcp/toolset.ts +0 -2
  662. package/src/cli/mcp/transports/http.ts +0 -2
  663. package/src/cli/mcp/transports/session-registry.ts +0 -2
  664. package/src/cli/mcp/transports/stdio.ts +0 -2
  665. package/src/cli/mcp/types.ts +0 -2
  666. package/src/cli/mcp/umbrella-tools.ts +0 -2
  667. package/src/cli/tools/codegraph.ts +0 -695
  668. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  669. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  670. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  671. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  672. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  673. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  674. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  675. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  676. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  677. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  678. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  679. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  680. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  681. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  682. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  683. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  684. package/src/runtime/control-plane/routing/index.ts +0 -1
  685. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  686. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
  687. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  688. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  689. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  690. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  691. package/src/runtime/gateway/mcp/router.ts +0 -2
  692. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  693. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  694. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  695. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
@@ -0,0 +1,1870 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { createRequire } from 'node:module';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { resolveWorkflowSupervisorForgeHome, workflowSupervisorDatabasePathValue, workflowSupervisorRootPath } from './paths';
5
+ import { validateSupervisorObjective } from './protocol';
6
+ import type { WorkflowEffectKind, WorkflowEffectOutcome, WorkflowSupervisorCompletion, WorkflowSupervisorContinuationProof, WorkflowSupervisorDiscoverySnapshot, WorkflowSupervisorDiscoveredConversation, WorkflowSupervisorEffect, WorkflowSupervisorTask, WorkflowSupervisorTaskInput, WorkflowSupervisorTaskStall, WorkflowSupervisorTerminalState, WorkflowSupervisorPreflightComposerObservation } from './types';
7
+ export type { WorkflowSupervisorTaskStall } from './types';
8
+
9
+ interface Statement { get(...params: unknown[]): unknown; all(...params: unknown[]): unknown[]; run(...params: unknown[]): unknown; finalize?(): void }
10
+ interface Database { exec(sql: string): void; prepare(sql: string): Statement; close(): void }
11
+ const require = createRequire(import.meta.url);
12
+
13
+ function databaseConstructor(): new (path: string) => Database {
14
+ const module = require(process.versions.bun ? 'bun:sqlite' : 'node:sqlite') as { Database?: new (path: string) => Database; DatabaseSync?: new (path: string) => Database };
15
+ const Constructor = module.Database ?? module.DatabaseSync;
16
+ if (!Constructor) throw new Error('WORKFLOW_SUPERVISOR_SQLITE_UNAVAILABLE');
17
+ return Constructor;
18
+ }
19
+
20
+ function statement<T>(db: Database, sql: string, fn: (statement: Statement) => T): T {
21
+ const prepared = db.prepare(sql);
22
+ try { return fn(prepared); } finally { prepared.finalize?.(); }
23
+ }
24
+ function now(): string { return new Date().toISOString(); }
25
+ function objectiveSha256(value: string): string { return createHash('sha256').update(value).digest('hex'); }
26
+ function projectMetadataFromConversationUrl(value: string, observedTitle?: string, observedUrl?: string): { projectTitle?: string; projectUrl: string } | undefined {
27
+ try {
28
+ const parsed = new URL(value);
29
+ const match = /^\/g\/(g-p-[a-z0-9]+)(?:-([^/]+))?\/c\/[a-z0-9-]+\/?$/i.exec(parsed.pathname);
30
+ const slug = match?.[2]?.trim();
31
+ if (parsed.protocol !== 'https:' || parsed.hostname !== 'chatgpt.com' || !match?.[1]) return undefined;
32
+ let observedProjectId: string | undefined;
33
+ try {
34
+ const observed = new URL(observedUrl ?? '');
35
+ if (observed.origin === parsed.origin) observedProjectId = /^\/g\/(g-p-[a-z0-9]+)(?:-[^/]+)?\/project\/?$/i.exec(observed.pathname)?.[1];
36
+ } catch { /* An unproven title cannot override the canonical route. */ }
37
+ const projectTitle = observedProjectId?.toLowerCase() === match[1].toLowerCase()
38
+ ? observedTitle ?? slug?.replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim()
39
+ : slug?.replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim();
40
+ return {
41
+ ...(projectTitle ? { projectTitle } : {}),
42
+ projectUrl: `https://chatgpt.com/g/${match[1]}/project`,
43
+ };
44
+ } catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ function json(value: unknown): string {
49
+ if (Array.isArray(value)) return `[${value.map((item) => json(item)).join(',')}]`;
50
+ if (value && typeof value === 'object') {
51
+ const entries = Object.entries(value as Record<string, unknown>).sort(([a],[b]) => a.localeCompare(b));
52
+ return `{${entries.map(([key,item]) => `${JSON.stringify(key)}:${json(item)}`).join(',')}}`;
53
+ }
54
+ return JSON.stringify(value);
55
+ }
56
+
57
+ export function workflowSupervisorCompletionContractSha256(contract: Record<string, unknown>): string {
58
+ return createHash('sha256').update(json(contract)).digest('hex');
59
+ }
60
+
61
+ export { resolveWorkflowSupervisorForgeHome } from './paths';
62
+ export function workflowSupervisorRoot(forgeHome?: string): string {
63
+ const root = workflowSupervisorRootPath(forgeHome);
64
+ mkdirSync(root, { recursive: true, mode: 0o700 });
65
+ return root;
66
+ }
67
+ export function workflowSupervisorDatabasePath(forgeHome?: string): string {
68
+ workflowSupervisorRoot(forgeHome);
69
+ return workflowSupervisorDatabasePathValue(forgeHome);
70
+ }
71
+
72
+ function openDatabase(forgeHome?: string): Database {
73
+ const Constructor = databaseConstructor();
74
+ const db = new Constructor(workflowSupervisorDatabasePath(forgeHome));
75
+ db.exec('PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;');
76
+ db.exec(`
77
+ CREATE TABLE IF NOT EXISTS supervisor_schema (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL);
78
+ CREATE TABLE IF NOT EXISTS tasks (
79
+ task_id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL UNIQUE, conversation_url TEXT NOT NULL, objective TEXT NOT NULL,
80
+ completion_contract_json TEXT NOT NULL, continuation_policy_json TEXT NOT NULL, user_blocker_policy_json TEXT NOT NULL, created_at TEXT NOT NULL
81
+ );
82
+ CREATE TABLE IF NOT EXISTS completions (
83
+ completion_fingerprint TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(task_id), source_effect_id TEXT NOT NULL,
84
+ action TEXT NOT NULL, response_sha256 TEXT NOT NULL, control_block_sha256 TEXT NOT NULL, proposal_json TEXT NOT NULL, committed_at TEXT NOT NULL,
85
+ UNIQUE(task_id, source_effect_id, response_sha256, control_block_sha256)
86
+ );
87
+ CREATE UNIQUE INDEX IF NOT EXISTS completions_one_per_source_effect
88
+ ON completions(task_id, source_effect_id);
89
+ CREATE TABLE IF NOT EXISTS effects (
90
+ effect_id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(task_id), kind TEXT NOT NULL, origin_key TEXT NOT NULL UNIQUE,
91
+ source_completion_fingerprint TEXT, prompt_text TEXT NOT NULL, created_at TEXT NOT NULL,
92
+ UNIQUE(task_id, source_completion_fingerprint)
93
+ );
94
+ CREATE TABLE IF NOT EXISTS events (
95
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL REFERENCES tasks(task_id), event_key TEXT NOT NULL UNIQUE,
96
+ kind TEXT NOT NULL, effect_id TEXT, completion_fingerprint TEXT, payload_json TEXT NOT NULL, occurred_at TEXT NOT NULL
97
+ );
98
+ CREATE INDEX IF NOT EXISTS events_task_order ON events(task_id, event_id);
99
+ -- Terminal probes run for every task on the polling/reconciliation paths.
100
+ -- Index only terminal facts so an open task does not rescan its whole log.
101
+ CREATE INDEX IF NOT EXISTS events_terminal_task_order ON events(task_id, event_id)
102
+ WHERE kind IN ('terminal_done','terminal_needs_user','terminal_stopped');
103
+ CREATE INDEX IF NOT EXISTS events_effect_kind_order ON events(effect_id, kind, event_id);
104
+ CREATE TABLE IF NOT EXISTS discovered_conversations (
105
+ source TEXT NOT NULL, conversation_id TEXT NOT NULL, canonical_url TEXT NOT NULL, title TEXT,
106
+ project_title TEXT, project_url TEXT, observed_at TEXT NOT NULL,
107
+ PRIMARY KEY(source, conversation_id)
108
+ );
109
+ CREATE INDEX IF NOT EXISTS discovered_conversations_observed ON discovered_conversations(observed_at, conversation_id);
110
+ INSERT OR IGNORE INTO supervisor_schema(version, applied_at) VALUES (1, datetime('now'));
111
+ `);
112
+ const schema = statement(db, 'SELECT MAX(version) AS version FROM supervisor_schema', (s) => s.get()) as { version?: number } | undefined;
113
+ if (schema?.version !== 1) { db.close(); throw new Error(`WORKFLOW_SUPERVISOR_SCHEMA_UNSUPPORTED:${String(schema?.version ?? 'missing')}`); }
114
+ return db;
115
+ }
116
+
117
+ function taskFromRow(row: Record<string, unknown>): WorkflowSupervisorTask {
118
+ return {
119
+ taskId: String(row.task_id), conversationId: String(row.conversation_id), conversationUrl: String(row.conversation_url), objective: String(row.objective),
120
+ completionContract: JSON.parse(String(row.completion_contract_json)) as Record<string, unknown>, continuationPolicy: JSON.parse(String(row.continuation_policy_json)) as Record<string, unknown>,
121
+ userBlockerPolicy: JSON.parse(String(row.user_blocker_policy_json)) as Record<string, unknown>, createdAt: String(row.created_at),
122
+ };
123
+ }
124
+
125
+ function boundedText(value: unknown): string | undefined {
126
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
127
+ }
128
+
129
+ function explicitRequirementTaskUpgrade(input: WorkflowSupervisorTaskInput): { requirementId: string; repoId: string } | undefined {
130
+ if (input.completionContract.kind !== 'forge_requirement_done'
131
+ || input.continuationPolicy.kind !== 'forge_goal_outer_turn'
132
+ || input.userBlockerPolicy.kind !== 'forge_requirement_waiting_for_user') return undefined;
133
+ const requirementId = boundedText(input.completionContract.requirement_id);
134
+ const blockerRequirementId = boundedText(input.userBlockerPolicy.requirement_id);
135
+ const repoId = boundedText(input.completionContract.repo_id);
136
+ const blockerRepoId = boundedText(input.userBlockerPolicy.repo_id);
137
+ if (!requirementId || blockerRequirementId !== requirementId || !repoId || blockerRepoId !== repoId) return undefined;
138
+ if (boundedText(input.continuationPolicy.exact_conversation_id) !== input.conversationId
139
+ || boundedText(input.continuationPolicy.exact_conversation_url) !== input.conversationUrl) return undefined;
140
+ return { requirementId, repoId };
141
+ }
142
+ function effectFromRow(row: Record<string, unknown>): WorkflowSupervisorEffect {
143
+ return { effectId: String(row.effect_id), taskId: String(row.task_id), kind: String(row.kind) as WorkflowEffectKind,
144
+ ...(row.source_completion_fingerprint ? { sourceCompletionFingerprint: String(row.source_completion_fingerprint) } : {}), prompt: String(row.prompt_text), createdAt: String(row.created_at) };
145
+ }
146
+
147
+ function completionFromRow(row: Record<string, unknown>): WorkflowSupervisorCompletion {
148
+ return {
149
+ completionFingerprint: String(row.completion_fingerprint), taskId: String(row.task_id), sourceEffectId: String(row.source_effect_id),
150
+ action: String(row.action) as WorkflowSupervisorCompletion['action'], responseSha256: String(row.response_sha256), controlBlockSha256: String(row.control_block_sha256),
151
+ proposal: JSON.parse(String(row.proposal_json)) as WorkflowSupervisorCompletion['proposal'], committedAt: String(row.committed_at),
152
+ };
153
+ }
154
+ function parsedObject(value: unknown): Record<string, unknown> {
155
+ if (value && typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>;
156
+ try { const parsed = JSON.parse(String(value)); return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {}; }
157
+ catch { return {}; }
158
+ }
159
+ function storedGeneration(value: unknown): number {
160
+ const generation = Number(parsedObject(value).generation);
161
+ return Number.isInteger(generation) && generation > 0 ? generation : 1;
162
+ }
163
+ function latestNotAppliedProofEventId(db: Database, effectId: string): number {
164
+ // Delayed negative evidence from an older generation must not authorize
165
+ // another Send while the latest generation is outcome-unknown. Apply this
166
+ // read-side fence to journals written by earlier releases as well.
167
+ const dispatch = statement(db, "SELECT event_id,payload_json FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_started' ORDER BY event_id DESC LIMIT 1", (s) => s.get(effectId)) as { event_id?: number; payload_json?: string } | undefined;
168
+ if (!dispatch?.event_id) return 0;
169
+ const notApplied = statement(db, "SELECT event_id FROM events WHERE effect_id = ? AND kind = 'effect_not_applied' AND event_id > ? AND json_extract(payload_json,'$.dispatch_generation') = ? ORDER BY event_id DESC LIMIT 1", (s) => s.get(effectId, dispatch.event_id, storedGeneration(dispatch.payload_json))) as { event_id?: number } | undefined;
170
+ return Number(notApplied?.event_id ?? 0);
171
+ }
172
+
173
+ /** A durable operator supersession retires an effect from the causal obligation; it is never replayed. */
174
+ function effectSuperseded(db: Database, effectId: string): boolean {
175
+ return Boolean(statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_outcome_unknown_superseded' LIMIT 1", (s) => s.get(effectId)));
176
+ }
177
+
178
+ function providerResumeExhaustedWithin(db: Database, effectId: string): boolean {
179
+ return Boolean(statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'assistant_recovery_exhausted' LIMIT 1", (s) => s.get(effectId)));
180
+ }
181
+
182
+ function providerRecoveryDepthWithin(db: Database, effectId: string): number {
183
+ let currentEffectId = effectId;
184
+ let depth = 0;
185
+ const seen = new Set<string>();
186
+ while (true) {
187
+ if (seen.has(currentEffectId)) throw new Error('WORKFLOW_SUPERVISOR_PROVIDER_RECOVERY_CHAIN_CORRUPT');
188
+ seen.add(currentEffectId);
189
+ const row = statement(db, 'SELECT kind,origin_key FROM effects WHERE effect_id = ?', (s) => s.get(currentEffectId)) as { kind?: string; origin_key?: string } | undefined;
190
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_PROVIDER_RECOVERY_CHAIN_CORRUPT');
191
+ const originKey = String(row.origin_key ?? '');
192
+ if (row.kind !== 'recovery' || !originKey.startsWith('provider-recovery:')) return depth;
193
+ depth += 1;
194
+ currentEffectId = originKey.slice('provider-recovery:'.length).trim();
195
+ if (!currentEffectId) throw new Error('WORKFLOW_SUPERVISOR_PROVIDER_RECOVERY_CHAIN_CORRUPT');
196
+ }
197
+ }
198
+
199
+ function operatorAuthorizedRefundCount(db: Database, effectId: string): number {
200
+ const row = statement(db, "SELECT COUNT(*) AS total FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_budget_refunded' AND json_extract(payload_json,'$.reason') = 'operator_authorized_retry'", (s) => s.get(effectId)) as { total?: number } | undefined;
201
+ return Number(row?.total ?? 0);
202
+ }
203
+
204
+ function oldestUnappliedEffect(db: Database, taskId: string): WorkflowSupervisorEffect | undefined {
205
+ const row = statement(db, `SELECT e.* FROM effects e
206
+ WHERE e.task_id = ? AND NOT EXISTS (
207
+ SELECT 1 FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied'
208
+ ) AND NOT EXISTS (
209
+ SELECT 1 FROM events superseded WHERE superseded.effect_id = e.effect_id AND superseded.kind = 'effect_outcome_unknown_superseded'
210
+ ) AND NOT (
211
+ e.kind = 'recovery' AND NOT EXISTS (
212
+ SELECT 1 FROM events started
213
+ WHERE started.effect_id = e.effect_id AND started.kind = 'effect_dispatch_started'
214
+ ) AND EXISTS (
215
+ SELECT 1 FROM completions completed
216
+ WHERE completed.task_id = e.task_id
217
+ AND e.origin_key = 'provider-recovery:' || completed.source_effect_id
218
+ )
219
+ ) ORDER BY e.created_at, e.effect_id LIMIT 1`, (s) => s.get(taskId)) as Record<string, unknown> | undefined;
220
+ return row ? effectFromRow(row) : undefined;
221
+ }
222
+
223
+ /**
224
+ * Mechanical ceiling on how many times one un-applied Supervisor effect may be
225
+ * submitted to the provider. A proven non-application may legitimately be
226
+ * retried, but ChatGPT is one rate-limited shared resource: without a ceiling a
227
+ * single stuck effect minted an unbounded generation chain (live evidence:
228
+ * single effects were re-sent 130-166 times over hours), which is provider
229
+ * request volume that no Work actually asked for. Three total attempts is the
230
+ * hard ceiling: one normal submission plus at most two causally authorized retries.
231
+ */
232
+ export const WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS = 3;
233
+ /** A broken preparation cannot silently poll forever across Runtime restarts. */
234
+ export const WORKFLOW_SUPERVISOR_MAX_PREFLIGHT_RELEASE_ATTEMPTS = 3;
235
+
236
+ /**
237
+ * Once a provider has accepted a Supervisor effect, recovery never replays that
238
+ * source mutation. It may emit at most two separately identified recovery turns,
239
+ * matching the repository-wide three-provider-attempt ceiling (initial turn plus
240
+ * two causal retries). The depth is derived from effect origin links, not stored
241
+ * as another lifecycle state.
242
+ */
243
+ export const WORKFLOW_SUPERVISOR_MAX_PROVIDER_RECOVERY_TURNS = 2;
244
+ export const WORKFLOW_SUPERVISOR_DISPATCH_RETRY_BASE_MS = 30_000;
245
+ export const WORKFLOW_SUPERVISOR_DISPATCH_RETRY_MAX_MS = 10 * 60_000;
246
+ export const WORKFLOW_SUPERVISOR_UNKNOWN_OBSERVATION_BASE_MS = 5_000;
247
+ export const WORKFLOW_SUPERVISOR_UNKNOWN_OBSERVATION_MAX_MS = 60_000;
248
+ /**
249
+ * An explicit operator recovery may refund the mechanical retry budget for an
250
+ * effect whose latest dispatch carries a mechanical pre-send negative proof.
251
+ * That is a safety statement about *this* effect, not a reset of provider
252
+ * budgets, so it stays bounded: exceeding it requires a new explicit decision
253
+ * rather than another silent attempt.
254
+ */
255
+ export const WORKFLOW_SUPERVISOR_MAX_OPERATOR_REFUNDS = 2;
256
+ /**
257
+ * A standalone Supervisor task's conversation is its delivery surface. If that
258
+ * exact conversation becomes permanently unusable, an explicit operator may
259
+ * move the task to a replacement conversation. The budget keeps a transient
260
+ * provider problem from turning into a conversation-hopping loop.
261
+ */
262
+ export const WORKFLOW_SUPERVISOR_MAX_CONVERSATION_MIGRATIONS = 2;
263
+ export const WORKFLOW_SUPERVISOR_MIN_TURN_INTERVAL_MS = 3 * 60_000;
264
+
265
+ /** Spaced retries keep a persistent local obstacle from becoming a request storm. */
266
+ export function workflowSupervisorDispatchRetryDelayMs(generation: number): number {
267
+ const exponent = Math.max(0, Math.min(8, Math.trunc(generation) - 1));
268
+ return Math.min(WORKFLOW_SUPERVISOR_DISPATCH_RETRY_MAX_MS, WORKFLOW_SUPERVISOR_DISPATCH_RETRY_BASE_MS * 2 ** exponent);
269
+ }
270
+
271
+ export interface WorkflowSupervisorEffectDispatchBudget {
272
+ effectId: string;
273
+ generations: number;
274
+ maxGenerations: number;
275
+ lastDispatchedAtMs?: number;
276
+ retryDelayMs?: number;
277
+ exhausted: boolean;
278
+ }
279
+
280
+
281
+ interface EffectDispatchLedger {
282
+ generations: number;
283
+ budgetRefunds: number;
284
+ lastEventId: number;
285
+ lastGeneration: number;
286
+ lastOccurredAtMs: number;
287
+ }
288
+
289
+ interface EffectUnknownObservationLedger {
290
+ sameFingerprintCount: number;
291
+ lastOccurredAtMs: number;
292
+ fingerprint?: string;
293
+ }
294
+
295
+ function unknownObservationFingerprintFromObject(payload: Record<string, unknown>): string | undefined {
296
+ const explicit = boundedText(payload.observation_fingerprint);
297
+ if (explicit) return `explicit:${explicit}`;
298
+ // A transport may fail before a Browser snapshot exists. The canonical
299
+ // ledger still has to collapse repeated identical transport failures from
300
+ // native messaging, extensions, or older adapters. A changed reason/surface
301
+ // is materially new evidence and receives a fresh bounded budget.
302
+ const reason = boundedText(payload.reason) ?? 'unknown';
303
+ const surface = boundedText(payload.surface) ?? '';
304
+ const reconciliation = payload.reconciliation === true ? 'reconcile' : 'observe';
305
+ return `fallback:${surface}:${reconciliation}:${reason}`;
306
+ }
307
+
308
+ function unknownObservationFingerprint(payloadJson: unknown): string | undefined {
309
+ return unknownObservationFingerprintFromObject(parsedObject(payloadJson));
310
+ }
311
+
312
+ function effectUnknownObservationLedger(db: Database, effectId: string, afterEventId: number): EffectUnknownObservationLedger {
313
+ const rows = statement(db, "SELECT event_id,payload_json,occurred_at FROM events WHERE effect_id = ? AND kind = 'effect_unknown' AND event_id > ? ORDER BY event_id DESC LIMIT 32", (s) => s.all(effectId, afterEventId)) as Array<{ event_id?: number; payload_json?: string; occurred_at?: string }>;
314
+ const latest = rows[0];
315
+ if (!latest) return { sameFingerprintCount: 0, lastOccurredAtMs: 0 };
316
+ const fingerprint = unknownObservationFingerprint(latest.payload_json);
317
+ if (!fingerprint) return { sameFingerprintCount: 0, lastOccurredAtMs: 0 };
318
+ let sameFingerprintCount = 0;
319
+ for (const row of rows) {
320
+ if (unknownObservationFingerprint(row.payload_json) !== fingerprint) break;
321
+ sameFingerprintCount += 1;
322
+ }
323
+ const occurredAtMs = Date.parse(String(latest.occurred_at ?? ''));
324
+ return {
325
+ sameFingerprintCount,
326
+ lastOccurredAtMs: Number.isFinite(occurredAtMs) ? occurredAtMs : 0,
327
+ fingerprint,
328
+ };
329
+ }
330
+
331
+ function unknownObservationDelayMs(count: number): number {
332
+ const exponent = Math.max(0, Math.min(8, Math.trunc(count) - 1));
333
+ return Math.min(
334
+ WORKFLOW_SUPERVISOR_UNKNOWN_OBSERVATION_MAX_MS,
335
+ WORKFLOW_SUPERVISOR_UNKNOWN_OBSERVATION_BASE_MS * 2 ** exponent,
336
+ );
337
+ }
338
+
339
+ function unknownObservationDue(db: Database, effectId: string, afterEventId: number, nowMs: number): boolean {
340
+ const observation = effectUnknownObservationLedger(db, effectId, afterEventId);
341
+ return observation.sameFingerprintCount === 0
342
+ || nowMs - observation.lastOccurredAtMs >= unknownObservationDelayMs(observation.sameFingerprintCount);
343
+ }
344
+
345
+ function effectDispatchLedger(db: Database, effectId: string): EffectDispatchLedger {
346
+ const rows = statement(db, "SELECT event_id,payload_json,occurred_at FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_started' ORDER BY event_id", (s) => s.all(effectId)) as Array<{ event_id?: number; payload_json?: string; occurred_at?: string }>;
347
+ const last = rows[rows.length - 1];
348
+ const refundRows = statement(db, "SELECT event_id FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_budget_refunded'", (s) => s.all(effectId)) as Array<{ event_id?: number }>;
349
+ const payload = parsedObject(last?.payload_json);
350
+ const payloadMs = Number(payload.dispatched_at_ms);
351
+ const lastOccurredAtMs = Number.isFinite(payloadMs) && payloadMs > 0
352
+ ? payloadMs
353
+ : Date.parse(String(last?.occurred_at ?? ''));
354
+ return {
355
+ generations: rows.length,
356
+ budgetRefunds: refundRows.length,
357
+ lastEventId: Number(last?.event_id ?? 0),
358
+ lastGeneration: last?.event_id ? storedGeneration(last.payload_json) : 0,
359
+ lastOccurredAtMs: Number.isFinite(lastOccurredAtMs) ? lastOccurredAtMs : 0,
360
+ };
361
+ }
362
+ /** Derived entirely from the Supervisor effect event log; no second retry authority. */
363
+ function effectPreflightFailureReleaseIds(db: Database, effectId: string): Array<{ eventId: number; releaseId: string }> {
364
+ const rows = statement(db, "SELECT event_id,payload_json FROM events WHERE effect_id = ? AND kind = 'effect_preflight_rejected' ORDER BY event_id", (s) => s.all(effectId)) as Array<{ event_id?: number; payload_json?: string }>;
365
+ return rows.map((row) => ({ eventId: Number(row.event_id ?? 0), releaseId: String(parsedObject(row.payload_json).active_release_id ?? 'unidentified-runtime') }));
366
+ }
367
+
368
+ export class WorkflowSupervisorStore {
369
+ private readonly db: Database;
370
+ private closed = false;
371
+ /** Mechanical clock for provider retry spacing. Durable event timestamps keep wall-clock ISO. */
372
+ private readonly clockMs: () => number;
373
+ private readonly activeReleaseId: () => string | undefined;
374
+
375
+ constructor(readonly forgeHome?: string, options: { now?: () => number; activeReleaseId?: () => string | undefined } = {}) {
376
+ this.db = openDatabase(forgeHome);
377
+ this.clockMs = options.now ?? Date.now;
378
+ this.activeReleaseId = options.activeReleaseId ?? (() => undefined);
379
+ }
380
+
381
+ private preflightBlocked(db: Database, effectId: string): boolean {
382
+ const tried = effectPreflightFailureReleaseIds(db, effectId);
383
+ const releaseId = this.activeReleaseId()?.trim() || 'unidentified-runtime';
384
+ const lastFailure = tried.filter((entry) => entry.releaseId === releaseId).at(-1);
385
+ // Three failed releases exhaust automatic re-preparation, not an
386
+ // operator's single explicit post-repair proof. The same durable event
387
+ // ledger still enforces one rearm at most and re-blocks after its failure.
388
+ const rearm = statement(db, "SELECT event_id FROM events WHERE effect_id = ? AND kind = 'effect_preflight_operator_rearmed' ORDER BY event_id DESC LIMIT 1", (s) => s.get(effectId)) as { event_id?: number } | undefined;
389
+ const rearmedEventId = Number(rearm?.event_id ?? 0);
390
+ if (tried.length >= WORKFLOW_SUPERVISOR_MAX_PREFLIGHT_RELEASE_ATTEMPTS) {
391
+ return rearmedEventId <= (tried.at(-1)?.eventId ?? 0);
392
+ }
393
+ if (!lastFailure) return false;
394
+ return rearmedEventId <= lastFailure.eventId;
395
+ }
396
+
397
+ close(): void {
398
+ if (this.closed) return;
399
+ this.closed = true;
400
+ this.db.close();
401
+ }
402
+
403
+ private database(): Database {
404
+ if (this.closed) throw new Error('WORKFLOW_SUPERVISOR_STORE_CLOSED');
405
+ return this.db;
406
+ }
407
+
408
+ private transaction<T>(fn: (db: Database) => T): T {
409
+ const db = this.database();
410
+ db.exec('BEGIN IMMEDIATE');
411
+ try { const result = fn(db); db.exec('COMMIT'); return result; } catch (error) { db.exec('ROLLBACK'); throw error; }
412
+ }
413
+ private read<T>(fn: (db: Database) => T): T { return fn(this.database()); }
414
+
415
+ recordDiscovery(source: string, conversations: readonly WorkflowSupervisorDiscoveredConversation[]): WorkflowSupervisorDiscoverySnapshot {
416
+ const normalizedSource = source.trim();
417
+ if (!/^[a-z0-9][a-z0-9._:-]{0,127}$/i.test(normalizedSource)) throw new Error('WORKFLOW_SUPERVISOR_DISCOVERY_SOURCE_INVALID');
418
+ const observedAt = now();
419
+ this.transaction((db) => {
420
+ for (const conversation of conversations) {
421
+ const project = projectMetadataFromConversationUrl(conversation.canonicalUrl, conversation.projectTitle, conversation.projectUrl)
422
+ ?? { projectTitle: conversation.projectTitle, projectUrl: conversation.projectUrl };
423
+ statement(db, `INSERT INTO discovered_conversations(source,conversation_id,canonical_url,title,project_title,project_url,observed_at)
424
+ VALUES (?,?,?,?,?,?,?)
425
+ ON CONFLICT(source,conversation_id) DO UPDATE SET canonical_url=excluded.canonical_url,title=excluded.title,
426
+ project_title=CASE WHEN excluded.project_url IS NOT NULL AND discovered_conversations.project_url IS NOT NULL
427
+ AND excluded.project_url<>discovered_conversations.project_url THEN excluded.project_title
428
+ ELSE COALESCE(excluded.project_title,discovered_conversations.project_title) END,
429
+ project_url=COALESCE(excluded.project_url,discovered_conversations.project_url),observed_at=excluded.observed_at`,
430
+ (s) => s.run(normalizedSource, conversation.conversationId, conversation.canonicalUrl, conversation.title ?? null, project.projectTitle ?? null, project.projectUrl ?? null, observedAt));
431
+ }
432
+ });
433
+ return this.discoverySnapshot();
434
+ }
435
+
436
+ discoverySnapshot(): WorkflowSupervisorDiscoverySnapshot {
437
+ return this.read((db) => {
438
+ const rows = statement(db, 'SELECT * FROM discovered_conversations ORDER BY observed_at DESC, conversation_id, source', (s) => s.all()) as Record<string, unknown>[];
439
+ const byConversation = new Map<string, WorkflowSupervisorDiscoveredConversation>();
440
+ let observedAt = '';
441
+ for (const row of rows) {
442
+ const conversationId = String(row.conversation_id ?? '');
443
+ const canonicalUrl = String(row.canonical_url ?? '');
444
+ // Validate retained observations too: an older adapter may have tagged
445
+ // every sidebar conversation with the project page it happened to scan.
446
+ const project = projectMetadataFromConversationUrl(canonicalUrl, row.project_title ? String(row.project_title) : undefined, row.project_url ? String(row.project_url) : undefined)
447
+ ?? { projectTitle: row.project_title ? String(row.project_title) : undefined, projectUrl: row.project_url ? String(row.project_url) : undefined };
448
+ const rowObservedAt = String(row.observed_at ?? '');
449
+ if (rowObservedAt > observedAt) observedAt = rowObservedAt;
450
+ const current = byConversation.get(conversationId);
451
+ const candidate: WorkflowSupervisorDiscoveredConversation = {
452
+ conversationId,
453
+ canonicalUrl,
454
+ ...(row.title ? { title: String(row.title) } : {}),
455
+ ...(project.projectTitle ? { projectTitle: project.projectTitle } : {}),
456
+ ...(project.projectUrl ? { projectUrl: project.projectUrl } : {}),
457
+ };
458
+ if (!current) byConversation.set(conversationId, candidate);
459
+ else if (!current.projectTitle && candidate.projectTitle && (!current.projectUrl || current.projectUrl === candidate.projectUrl)) byConversation.set(conversationId, { ...current, projectTitle: candidate.projectTitle, ...(candidate.projectUrl ? { projectUrl: candidate.projectUrl } : {}) });
460
+ }
461
+ return { observedAt, conversations: [...byConversation.values()] };
462
+ });
463
+ }
464
+
465
+ registerTask(input: WorkflowSupervisorTaskInput): WorkflowSupervisorTask {
466
+ validateSupervisorObjective(input.objective);
467
+ return this.transaction((db) => {
468
+ const createdAt = now();
469
+ statement(db, 'INSERT OR IGNORE INTO tasks VALUES (?, ?, ?, ?, ?, ?, ?, ?)', (s) => s.run(input.taskId, input.conversationId, input.conversationUrl, input.objective, json(input.completionContract), json(input.continuationPolicy), json(input.userBlockerPolicy), createdAt));
470
+ let row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
471
+ if (!row) {
472
+ row = statement(db, 'SELECT * FROM tasks WHERE conversation_id = ?', (s) => s.get(input.conversationId)) as Record<string, unknown> | undefined;
473
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
474
+ const existing = taskFromRow(row);
475
+ if (existing.conversationUrl !== input.conversationUrl) throw new Error('WORKFLOW_SUPERVISOR_TASK_CONVERSATION_CONFLICT');
476
+ const existingRepo = typeof existing.completionContract.repo_id === 'string' ? existing.completionContract.repo_id : existing.continuationPolicy.repo_id;
477
+ const incomingRepo = typeof input.completionContract.repo_id === 'string' ? input.completionContract.repo_id : input.continuationPolicy.repo_id;
478
+ if (typeof existingRepo === 'string' && typeof incomingRepo === 'string' && existingRepo !== incomingRepo) throw new Error('WORKFLOW_SUPERVISOR_TASK_REPOSITORY_CONFLICT');
479
+ return existing;
480
+ }
481
+ const task = taskFromRow(row);
482
+ const taskRepo = typeof task.completionContract.repo_id === 'string' ? task.completionContract.repo_id : task.continuationPolicy.repo_id;
483
+ const inputRepo = typeof input.completionContract.repo_id === 'string' ? input.completionContract.repo_id : input.continuationPolicy.repo_id;
484
+ const { checkout_id: incomingCheckout, ...incomingCompletion } = input.completionContract;
485
+ const { checkout_id: selectedCheckout, ...selectedCompletion } = task.completionContract;
486
+ // Registration may fill an initially missing execution hint. It cannot
487
+ // replace a selected checkout or change task identity/semantic obligations.
488
+ const sameExecutionContextOwner = typeof taskRepo === 'string' && taskRepo === inputRepo
489
+ && task.conversationId === input.conversationId && task.conversationUrl === input.conversationUrl
490
+ && task.objective === input.objective
491
+ && json(selectedCompletion) === json(incomingCompletion)
492
+ && json(task.continuationPolicy) === json(input.continuationPolicy)
493
+ && json(task.userBlockerPolicy) === json(input.userBlockerPolicy);
494
+ // A retry of the original registration cannot erase the selected hint.
495
+ if (sameExecutionContextOwner && selectedCheckout !== undefined && incomingCheckout === undefined) return task;
496
+ const initialCheckoutContext = sameExecutionContextOwner && selectedCheckout === undefined
497
+ && task.continuationPolicy.checkout_id === undefined
498
+ && typeof incomingCheckout === 'string' && /^checkout_[a-zA-Z0-9_-]{8,120}$/.test(incomingCheckout);
499
+ if (initialCheckoutContext) {
500
+ statement(db, 'UPDATE tasks SET completion_contract_json = ? WHERE task_id = ?', s => s.run(json(input.completionContract), task.taskId));
501
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', s => s.run(
502
+ task.taskId, `task-checkout-context:${task.taskId}`, 'task_execution_context_enriched', json({ repo_id: taskRepo, checkout_id: incomingCheckout }), createdAt,
503
+ ));
504
+ return { ...task, completionContract: input.completionContract };
505
+ }
506
+ const bootstrapUpgrade = task.continuationPolicy.bootstrap === true
507
+ && input.continuationPolicy.bootstrap !== true
508
+ && taskRepo === inputRepo
509
+ && task.conversationId === input.conversationId
510
+ && task.conversationUrl === input.conversationUrl;
511
+ if (bootstrapUpgrade) {
512
+ const existingWorkId = boundedText(task.completionContract.work_id);
513
+ const existingRequirementId = boundedText(task.completionContract.requirement_id);
514
+ const incomingWorkId = boundedText(input.completionContract.work_id);
515
+ const incomingRequirementId = boundedText(input.completionContract.requirement_id);
516
+ if ((existingWorkId && incomingWorkId !== existingWorkId)
517
+ || (existingRequirementId && incomingRequirementId !== existingRequirementId)) {
518
+ throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_CONFLICT');
519
+ }
520
+ statement(db, `UPDATE tasks
521
+ SET objective = ?, completion_contract_json = ?, continuation_policy_json = ?, user_blocker_policy_json = ?
522
+ WHERE task_id = ?`, (s) => s.run(
523
+ input.objective,
524
+ json(input.completionContract),
525
+ json(input.continuationPolicy),
526
+ json(input.userBlockerPolicy),
527
+ input.taskId,
528
+ ));
529
+ const upgraded = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
530
+ if (!upgraded) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
531
+ return taskFromRow(upgraded);
532
+ }
533
+ const projectBootstrap = task.continuationPolicy.kind === 'forge_project_conversation_outer_turn';
534
+ const sameProjectBootstrapIdentity = projectBootstrap
535
+ && task.conversationId === input.conversationId
536
+ && task.conversationUrl === input.conversationUrl
537
+ && (!taskRepo || !inputRepo || taskRepo === inputRepo);
538
+ if (sameProjectBootstrapIdentity) {
539
+ const explicitRequirementUpgrade = explicitRequirementTaskUpgrade(input);
540
+ if (input.continuationPolicy.kind === 'forge_goal_outer_turn') {
541
+ if (!explicitRequirementUpgrade || !taskRepo || taskRepo !== explicitRequirementUpgrade.repoId) {
542
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_ID_CONFLICT');
543
+ }
544
+ statement(db, `UPDATE tasks
545
+ SET objective = ?, completion_contract_json = ?, continuation_policy_json = ?, user_blocker_policy_json = ?
546
+ WHERE task_id = ?`, (s) => s.run(
547
+ input.objective,
548
+ json(input.completionContract),
549
+ json(input.continuationPolicy),
550
+ json(input.userBlockerPolicy),
551
+ input.taskId,
552
+ ));
553
+ const upgraded = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
554
+ if (!upgraded) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
555
+ return taskFromRow(upgraded);
556
+ }
557
+ return task;
558
+ }
559
+ if (task.conversationId !== input.conversationId
560
+ || task.conversationUrl !== input.conversationUrl
561
+ || task.objective !== input.objective
562
+ || json(task.completionContract) !== json(input.completionContract)
563
+ || json(task.continuationPolicy) !== json(input.continuationPolicy)
564
+ || json(task.userBlockerPolicy) !== json(input.userBlockerPolicy)) {
565
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_ID_CONFLICT');
566
+ }
567
+ return task;
568
+ });
569
+ }
570
+ reviseTaskObjective(input: {
571
+ taskId: string;
572
+ objective: string;
573
+ expectedObjectiveSha256: string;
574
+ requestId: string;
575
+ authorizedBy: string;
576
+ refreshEffect?: { effectId: string; expectedPrompt: string; prompt: string };
577
+ semanticBinding?: { workId?: string; requirementId?: string; expectedCompletionContractSha256: string };
578
+ }): { task: WorkflowSupervisorTask; changed: boolean; deduplicated: boolean; objectiveSha256: string; completionContractSha256: string; semanticBindingChanged: boolean; refreshedEffect?: WorkflowSupervisorEffect } {
579
+ return this.transaction((db) => {
580
+ const objective = validateSupervisorObjective(input.objective);
581
+ if (!/^[0-9a-f]{64}$/i.test(input.expectedObjectiveSha256)) throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_REVISION_EXPECTED_SHA_INVALID');
582
+ if (!input.requestId.trim()) throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_REVISION_REQUEST_REQUIRED');
583
+ const targetSha = objectiveSha256(objective);
584
+ const semanticBinding = input.semanticBinding;
585
+ const workId = boundedText(semanticBinding?.workId);
586
+ const requirementId = boundedText(semanticBinding?.requirementId);
587
+ if (semanticBinding) {
588
+ if (Boolean(workId) === Boolean(requirementId)) throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_TARGET_INVALID');
589
+ if (!/^[0-9a-f]{64}$/i.test(semanticBinding.expectedCompletionContractSha256)) {
590
+ throw new Error('WORKFLOW_SUPERVISOR_COMPLETION_CONTRACT_REVISION_EXPECTED_SHA_INVALID');
591
+ }
592
+ }
593
+ const semanticBindingKind = workId ? 'work' : requirementId ? 'requirement' : undefined;
594
+ const semanticBindingId = workId ?? requirementId;
595
+ const eventKey = `task-objective-revise:${input.taskId}:${input.requestId}`;
596
+ const bindingEventKey = `task-semantic-binding-revise:${input.taskId}:${input.requestId}`;
597
+ const existingEvent = statement(db, 'SELECT payload_json FROM events WHERE event_key = ?', (s) => s.get(eventKey)) as { payload_json?: string } | undefined;
598
+ const existingBindingEvent = statement(db, 'SELECT payload_json FROM events WHERE event_key = ?', (s) => s.get(bindingEventKey)) as { payload_json?: string } | undefined;
599
+ if (existingEvent) {
600
+ const payload = parsedObject(existingEvent.payload_json);
601
+ if (boundedText(payload.objective_sha256) !== targetSha
602
+ || boundedText(payload.semantic_binding_id) !== semanticBindingId
603
+ || boundedText(payload.semantic_binding_kind) !== semanticBindingKind
604
+ || Boolean(existingBindingEvent) !== Boolean(semanticBinding)) {
605
+ throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_REVISION_REQUEST_CONFLICT');
606
+ }
607
+ if (existingBindingEvent) {
608
+ const bindingPayload = parsedObject(existingBindingEvent.payload_json);
609
+ if (boundedText(bindingPayload.completion_contract_sha256) !== boundedText(payload.completion_contract_sha256)
610
+ || boundedText(bindingPayload.semantic_binding_id) !== semanticBindingId
611
+ || boundedText(bindingPayload.semantic_binding_kind) !== semanticBindingKind) {
612
+ throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_REVISION_REQUEST_CONFLICT');
613
+ }
614
+ }
615
+ const taskRow = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
616
+ if (!taskRow) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
617
+ const deduplicatedTask = taskFromRow(taskRow);
618
+ const refreshedEffectId = boundedText(payload.refreshed_effect_id);
619
+ const refreshedRow = refreshedEffectId
620
+ ? statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(refreshedEffectId)) as Record<string, unknown> | undefined
621
+ : undefined;
622
+ return {
623
+ task: deduplicatedTask,
624
+ changed: payload.changed === true,
625
+ deduplicated: true,
626
+ objectiveSha256: targetSha,
627
+ completionContractSha256: workflowSupervisorCompletionContractSha256(deduplicatedTask.completionContract),
628
+ semanticBindingChanged: payload.semantic_binding_changed === true,
629
+ ...(refreshedRow ? { refreshedEffect: effectFromRow(refreshedRow) } : {}),
630
+ };
631
+ }
632
+ if (existingBindingEvent) throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_REVISION_REQUEST_CONFLICT');
633
+ const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
634
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
635
+ const terminal = statement(db, "SELECT kind FROM events WHERE task_id = ? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') ORDER BY event_id DESC LIMIT 1", (s) => s.get(input.taskId)) as { kind?: string } | undefined;
636
+ if (terminal?.kind) throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
637
+ const task = taskFromRow(row);
638
+ const currentSha = objectiveSha256(task.objective);
639
+ if (currentSha !== input.expectedObjectiveSha256.toLowerCase()) throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_REVISION_CONFLICT');
640
+
641
+ const currentContractSha = workflowSupervisorCompletionContractSha256(task.completionContract);
642
+ let nextCompletionContract = task.completionContract;
643
+ let completionContractSha = currentContractSha;
644
+ let semanticBindingChanged = false;
645
+ if (semanticBinding) {
646
+ if (task.completionContract.kind !== 'model_semantic_completion') {
647
+ throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_REVISION_KIND_UNSUPPORTED');
648
+ }
649
+ const currentWorkId = boundedText(task.completionContract.work_id);
650
+ const currentRequirementId = boundedText(task.completionContract.requirement_id);
651
+ if ((currentWorkId || currentRequirementId)
652
+ && (currentWorkId !== workId || currentRequirementId !== requirementId)) {
653
+ throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_CONFLICT');
654
+ }
655
+ if (currentContractSha !== semanticBinding.expectedCompletionContractSha256.toLowerCase()) {
656
+ throw new Error('WORKFLOW_SUPERVISOR_COMPLETION_CONTRACT_REVISION_CONFLICT');
657
+ }
658
+ nextCompletionContract = {
659
+ ...task.completionContract,
660
+ ...(workId ? { work_id: workId } : { requirement_id: requirementId }),
661
+ };
662
+ completionContractSha = workflowSupervisorCompletionContractSha256(nextCompletionContract);
663
+ semanticBindingChanged = completionContractSha !== currentContractSha;
664
+ }
665
+
666
+ let refreshedEffect: WorkflowSupervisorEffect | undefined;
667
+ if (input.refreshEffect) {
668
+ const effectRow = statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(input.refreshEffect!.effectId)) as Record<string, unknown> | undefined;
669
+ if (!effectRow) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
670
+ const effect = effectFromRow(effectRow);
671
+ if (effect.taskId !== input.taskId) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_TASK_CONFLICT');
672
+ if (effect.prompt !== input.refreshEffect.expectedPrompt) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_PROMPT_CONFLICT');
673
+ const dispatched = effectDispatchLedger(db, effect.effectId).generations > 0;
674
+ const applied = Boolean(statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effect.effectId)));
675
+ const superseded = effectSuperseded(db, effect.effectId);
676
+ if (!dispatched && !applied && !superseded) {
677
+ statement(db, 'UPDATE effects SET prompt_text = ? WHERE effect_id = ?', (s) => s.run(input.refreshEffect!.prompt, effect.effectId));
678
+ const updatedEffect = statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(effect.effectId)) as Record<string, unknown> | undefined;
679
+ if (!updatedEffect) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
680
+ refreshedEffect = effectFromRow(updatedEffect);
681
+ }
682
+ }
683
+
684
+ const changed = task.objective !== objective;
685
+ if (changed) statement(db, 'UPDATE tasks SET objective = ? WHERE task_id = ?', (s) => s.run(objective, input.taskId));
686
+ if (semanticBindingChanged) {
687
+ statement(db, 'UPDATE tasks SET completion_contract_json = ? WHERE task_id = ?', (s) => s.run(json(nextCompletionContract), input.taskId));
688
+ }
689
+ if (semanticBinding) {
690
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(
691
+ input.taskId,
692
+ bindingEventKey,
693
+ 'task_semantic_binding_revised',
694
+ json({
695
+ request_id: input.requestId,
696
+ authorized_by: input.authorizedBy,
697
+ semantic_binding_kind: semanticBindingKind,
698
+ semantic_binding_id: semanticBindingId,
699
+ previous_completion_contract_sha256: currentContractSha,
700
+ completion_contract_sha256: completionContractSha,
701
+ changed: semanticBindingChanged,
702
+ }),
703
+ now(),
704
+ ));
705
+ }
706
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(
707
+ input.taskId,
708
+ eventKey,
709
+ 'task_objective_revised',
710
+ json({
711
+ request_id: input.requestId,
712
+ authorized_by: input.authorizedBy,
713
+ previous_objective_sha256: currentSha,
714
+ objective_sha256: targetSha,
715
+ changed,
716
+ completion_contract_sha256: completionContractSha,
717
+ ...(semanticBinding ? {
718
+ semantic_binding_kind: semanticBindingKind,
719
+ semantic_binding_id: semanticBindingId,
720
+ semantic_binding_changed: semanticBindingChanged,
721
+ } : {}),
722
+ ...(refreshedEffect ? { refreshed_effect_id: refreshedEffect.effectId } : {}),
723
+ }),
724
+ now(),
725
+ ));
726
+ const updatedTask = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
727
+ if (!updatedTask) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
728
+ return {
729
+ task: taskFromRow(updatedTask), changed, deduplicated: false, objectiveSha256: targetSha,
730
+ completionContractSha256: completionContractSha, semanticBindingChanged,
731
+ ...(refreshedEffect ? { refreshedEffect } : {}),
732
+ };
733
+ });
734
+ }
735
+ getTask(taskId: string): WorkflowSupervisorTask | undefined { return this.read((db) => { const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(taskId)); return row ? taskFromRow(row as Record<string, unknown>) : undefined; }); }
736
+ bindScheduledWake(input: { taskId: string; repoId: string; workId: string; scheduleId: string }): { task: WorkflowSupervisorTask; changed: boolean; deduplicated: boolean } {
737
+ const scheduleId = boundedText(input.scheduleId);
738
+ if (!scheduleId || scheduleId.length > 512) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_ID_INVALID');
739
+ return this.transaction((db) => {
740
+ const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
741
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
742
+ const task = taskFromRow(row);
743
+ if (statement(db, "SELECT 1 FROM events WHERE task_id=? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') LIMIT 1", (s) => s.get(input.taskId))) {
744
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
745
+ }
746
+ if (task.completionContract.repo_id !== input.repoId || task.completionContract.work_id !== input.workId) {
747
+ throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_WORK_IDENTITY_MISMATCH');
748
+ }
749
+ if (task.continuationPolicy.kind !== 'forge_goal_outer_turn') throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_OUTER_TURN_REQUIRED');
750
+ const existingScheduleId = boundedText(task.continuationPolicy.scheduled_wake_schedule_id);
751
+ if (existingScheduleId) {
752
+ if (existingScheduleId !== scheduleId) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_BINDING_CONFLICT');
753
+ return { task, changed: false, deduplicated: true };
754
+ }
755
+ const continuationPolicy = { ...task.continuationPolicy, scheduled_wake_schedule_id: scheduleId };
756
+ statement(db, 'UPDATE tasks SET continuation_policy_json = ? WHERE task_id = ?', (s) => s.run(json(continuationPolicy), input.taskId));
757
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(
758
+ input.taskId, `scheduled-wake-bound:${input.taskId}:${scheduleId}`, 'scheduled_wake_bound',
759
+ json({ repo_id: input.repoId, work_id: input.workId, schedule_id: scheduleId, attestation: 'exact_work_schedule_binding' }), now()));
760
+ const updated = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
761
+ if (!updated) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
762
+ return { task: taskFromRow(updated), changed: true, deduplicated: false };
763
+ });
764
+ }
765
+ refreshUndispatchedEffectPrompt(input: { taskId: string; effectId: string; expectedPrompt: string; prompt: string }): boolean {
766
+ if (!input.prompt.trim()) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_PROMPT_REQUIRED');
767
+ return this.transaction((db) => {
768
+ const row = statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(input.effectId)) as Record<string, unknown> | undefined;
769
+ if (!row || String(row.task_id) !== input.taskId) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_TASK_MISMATCH');
770
+ if (String(row.prompt_text) !== input.expectedPrompt) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_PROMPT_CHANGED');
771
+ const dispatched = statement(db, "SELECT 1 FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_started' LIMIT 1", (s) => s.get(input.effectId));
772
+ const applied = statement(db, "SELECT 1 FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(input.effectId));
773
+ if (dispatched || applied) return false;
774
+ statement(db, 'UPDATE effects SET prompt_text = ? WHERE effect_id = ? AND prompt_text = ?', (s) => s.run(input.prompt, input.effectId, input.expectedPrompt));
775
+ return true;
776
+ });
777
+ }
778
+ reserveScheduledWake(input: { taskId: string; scheduleId: string; occurrenceId: string; effectId: string; prompt: string }): { effect: WorkflowSupervisorEffect; deduplicated: boolean } {
779
+ const scheduleId = boundedText(input.scheduleId);
780
+ const occurrenceId = boundedText(input.occurrenceId);
781
+ if (!scheduleId || scheduleId.length > 512) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_ID_INVALID');
782
+ if (!occurrenceId || occurrenceId.length > 512) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_OCCURRENCE_ID_INVALID');
783
+ return this.transaction((db) => {
784
+ const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
785
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
786
+ const task = taskFromRow(row);
787
+ if (task.continuationPolicy.kind !== 'forge_goal_outer_turn'
788
+ || task.continuationPolicy.scheduled_wake_schedule_id !== scheduleId) {
789
+ throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_BINDING_MISMATCH');
790
+ }
791
+ const originKey = `scheduled-wake:${scheduleId}:${occurrenceId}`;
792
+ const priorRow = statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(originKey)) as Record<string, unknown> | undefined;
793
+ if (priorRow) {
794
+ const prior = effectFromRow(priorRow);
795
+ if (prior.taskId !== input.taskId || prior.effectId !== input.effectId || prior.kind !== 'continuation' || !prior.sourceCompletionFingerprint) {
796
+ throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_IDENTITY_CONFLICT');
797
+ }
798
+ return { effect: prior, deduplicated: true };
799
+ }
800
+ if (statement(db, "SELECT 1 FROM events WHERE task_id=? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') LIMIT 1", (s) => s.get(input.taskId))) {
801
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
802
+ }
803
+ if (oldestUnappliedEffect(db, input.taskId)) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_CAUSAL_OBLIGATION_PENDING');
804
+ const awaitingReceipt = statement(db, `SELECT 1 AS ok FROM effects e
805
+ WHERE e.task_id = ?
806
+ AND EXISTS (SELECT 1 FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied')
807
+ AND NOT EXISTS (SELECT 1 FROM completions c WHERE c.task_id = e.task_id AND c.source_effect_id = e.effect_id)
808
+ LIMIT 1`, (s) => s.get(input.taskId));
809
+ if (awaitingReceipt) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_CAUSAL_OBLIGATION_PENDING');
810
+ const latestRow = statement(db, "SELECT c.* FROM completions c WHERE c.task_id = ? ORDER BY COALESCE((SELECT MAX(completion_event.event_id) FROM events completion_event WHERE completion_event.task_id = c.task_id AND completion_event.completion_fingerprint = c.completion_fingerprint AND completion_event.kind = 'assistant_completion'), c.rowid) DESC LIMIT 1", (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
811
+ if (!latestRow) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_NOT_WAITING');
812
+ const completion = completionFromRow(latestRow);
813
+ if (completion.action !== 'WAIT') throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_NOT_WAITING');
814
+ const consumed = statement(db, 'SELECT * FROM effects WHERE task_id = ? AND source_completion_fingerprint = ?', (s) => s.get(input.taskId, completion.completionFingerprint)) as Record<string, unknown> | undefined;
815
+ if (consumed) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_ALREADY_CONSUMED');
816
+ const effect = this.reserveEffectWithin(db, {
817
+ taskId: input.taskId,
818
+ effectId: input.effectId,
819
+ kind: 'continuation',
820
+ originKey,
821
+ sourceCompletionFingerprint: completion.completionFingerprint,
822
+ prompt: input.prompt,
823
+ });
824
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,completion_fingerprint,payload_json,occurred_at) VALUES (?,?,?,?,?,?,?)', (s) => s.run(
825
+ input.taskId, `scheduled-wake-committed:${scheduleId}:${occurrenceId}`, 'scheduled_wake_reserved', effect.effectId,
826
+ completion.completionFingerprint, json({ schedule_id: scheduleId, occurrence_id: occurrenceId, source_completion_fingerprint: completion.completionFingerprint }), now()));
827
+ return { effect, deduplicated: false };
828
+ });
829
+ }
830
+ bindBootstrapConversation(taskId: string, conversationId: string, conversationUrl: string): WorkflowSupervisorTask {
831
+ return this.transaction((db) => {
832
+ const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(taskId)) as Record<string, unknown> | undefined;
833
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
834
+ const task = taskFromRow(row);
835
+ if (!task.conversationId.startsWith('bootstrap:')) {
836
+ if (task.conversationId === conversationId && task.conversationUrl === conversationUrl) return task;
837
+ throw new Error('WORKFLOW_SUPERVISOR_BOOTSTRAP_ALREADY_BOUND');
838
+ }
839
+ // The migration event ledger is the sole authority for previously used
840
+ // source conversations. Do not rebind any retired source, including one
841
+ // from an earlier 15-turn rotation (not only the immediately prior chat).
842
+ const retired = statement(db, `SELECT 1 AS ok FROM events
843
+ WHERE task_id=? AND kind='conversation_migrated'
844
+ AND json_extract(payload_json,'$.from_conversation_id')=? LIMIT 1`,
845
+ (s) => s.get(taskId, conversationId));
846
+ if (retired) throw new Error('WORKFLOW_SUPERVISOR_FRESH_CONVERSATION_REBOUND_SOURCE');
847
+ const conflict = statement(db, 'SELECT task_id FROM tasks WHERE conversation_id = ? AND task_id <> ?', (s) => s.get(conversationId, taskId)) as { task_id?: string } | undefined;
848
+ if (conflict) throw new Error('WORKFLOW_SUPERVISOR_BOOTSTRAP_CONVERSATION_CONFLICT');
849
+ statement(db, 'UPDATE tasks SET conversation_id = ?, conversation_url = ? WHERE task_id = ?', (s) => s.run(conversationId, conversationUrl, taskId));
850
+ const bound = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(taskId)) as Record<string, unknown> | undefined;
851
+ if (!bound) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
852
+ return taskFromRow(bound);
853
+ });
854
+ }
855
+ getTaskByConversationId(conversationId: string): WorkflowSupervisorTask | undefined { return this.read((db) => { const row = statement(db, 'SELECT * FROM tasks WHERE conversation_id = ?', (s) => s.get(conversationId)); return row ? taskFromRow(row as Record<string, unknown>) : undefined; }); }
856
+ listTasks(): WorkflowSupervisorTask[] { return this.read((db) => statement(db, 'SELECT * FROM tasks ORDER BY created_at, task_id', (s) => s.all()).map((row) => taskFromRow(row as Record<string, unknown>))); }
857
+ listTasksWithoutCausalObligation(limit = 16): WorkflowSupervisorTask[] {
858
+ const boundedLimit = Math.max(1, Math.min(Math.trunc(limit), 128));
859
+ return this.read((db) => statement(db, `SELECT t.* FROM tasks t
860
+ WHERE NOT EXISTS (
861
+ SELECT 1 FROM events terminal
862
+ WHERE terminal.task_id = t.task_id
863
+ AND terminal.kind IN ('terminal_done','terminal_needs_user','terminal_stopped')
864
+ )
865
+ AND NOT EXISTS (
866
+ SELECT 1 FROM effects e
867
+ WHERE e.task_id = t.task_id
868
+ AND NOT EXISTS (
869
+ SELECT 1 FROM events applied
870
+ WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied'
871
+ )
872
+ )
873
+ AND NOT EXISTS (
874
+ SELECT 1 FROM effects e
875
+ WHERE e.task_id = t.task_id
876
+ AND EXISTS (
877
+ SELECT 1 FROM events applied
878
+ WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied'
879
+ )
880
+ AND NOT EXISTS (
881
+ SELECT 1 FROM completions c
882
+ WHERE c.task_id = e.task_id AND c.source_effect_id = e.effect_id
883
+ )
884
+ )
885
+ ORDER BY t.created_at, t.task_id
886
+ LIMIT ?`, (s) => s.all(boundedLimit))
887
+ .map((row) => taskFromRow(row as Record<string, unknown>)));
888
+ }
889
+ getEffect(effectId: string): WorkflowSupervisorEffect | undefined { return this.read((db) => { const row = statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(effectId)); return row ? effectFromRow(row as Record<string, unknown>) : undefined; }); }
890
+ currentUnappliedEffect(taskId: string): WorkflowSupervisorEffect | undefined { return this.read((db) => oldestUnappliedEffect(db, taskId)); }
891
+ getEffectByOriginKey(originKey: string): WorkflowSupervisorEffect | undefined { return this.read((db) => { const row = statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(originKey)); return row ? effectFromRow(row as Record<string, unknown>) : undefined; }); }
892
+ getCompletion(completionFingerprint: string): WorkflowSupervisorCompletion | undefined { return this.read((db) => { const row = statement(db, 'SELECT * FROM completions WHERE completion_fingerprint = ?', (s) => s.get(completionFingerprint)); return row ? completionFromRow(row as Record<string, unknown>) : undefined; }); }
893
+ getCompletionBySourceEffectId(taskId: string, sourceEffectId: string): WorkflowSupervisorCompletion | undefined {
894
+ return this.read((db) => {
895
+ const row = statement(db, 'SELECT * FROM completions WHERE task_id = ? AND source_effect_id = ? LIMIT 1', (s) => s.get(taskId, sourceEffectId)) as Record<string, unknown> | undefined;
896
+ return row ? completionFromRow(row) : undefined;
897
+ });
898
+ }
899
+ listContinueCompletionsAwaitingSuccessor(limit = 16): WorkflowSupervisorCompletion[] {
900
+ const boundedLimit = Math.max(1, Math.min(Math.trunc(limit), 128));
901
+ return this.read((db) => statement(db, `SELECT c.* FROM completions c
902
+ WHERE c.action = 'CONTINUE'
903
+ AND NOT EXISTS (SELECT 1 FROM effects successor WHERE successor.origin_key = 'completion:' || c.completion_fingerprint)
904
+ AND NOT EXISTS (SELECT 1 FROM events terminal WHERE terminal.task_id = c.task_id AND terminal.kind IN ('terminal_done','terminal_needs_user','terminal_stopped'))
905
+ ORDER BY COALESCE((SELECT MAX(completion_event.event_id) FROM events completion_event WHERE completion_event.task_id = c.task_id AND completion_event.completion_fingerprint = c.completion_fingerprint AND completion_event.kind = 'assistant_completion'), c.rowid) DESC LIMIT ?`, (s) => s.all(boundedLimit))
906
+ .map((row) => completionFromRow(row as Record<string, unknown>)));
907
+ }
908
+ getCompletionByResponseSha256(taskId: string, responseSha256: string): WorkflowSupervisorCompletion | undefined {
909
+ return this.read((db) => {
910
+ const rows = statement(db, 'SELECT * FROM completions WHERE task_id = ? AND response_sha256 = ? ORDER BY committed_at, rowid LIMIT 2', (s) => s.all(taskId, responseSha256)) as Record<string, unknown>[];
911
+ if (rows.length > 1) throw new Error('WORKFLOW_SUPERVISOR_RESPONSE_COMPLETION_AMBIGUOUS');
912
+ return rows[0] ? completionFromRow(rows[0]) : undefined;
913
+ });
914
+ }
915
+ /** The only replay candidate when no applied effect is awaiting a receipt. */
916
+ getLatestCompletion(taskId: string): WorkflowSupervisorCompletion | undefined {
917
+ return this.read((db) => {
918
+ const row = statement(db, "SELECT c.* FROM completions c WHERE c.task_id = ? ORDER BY COALESCE((SELECT MAX(completion_event.event_id) FROM events completion_event WHERE completion_event.task_id = c.task_id AND completion_event.completion_fingerprint = c.completion_fingerprint AND completion_event.kind = 'assistant_completion'), c.rowid) DESC LIMIT 1", (s) => s.get(taskId)) as Record<string, unknown> | undefined;
919
+ return row ? completionFromRow(row) : undefined;
920
+ });
921
+ }
922
+ continuationProof(input: { repoId?: string; activeReleaseId: string; notBefore: string }): WorkflowSupervisorContinuationProof | undefined {
923
+ const notBeforeMs = Date.parse(input.notBefore);
924
+ if (!Number.isFinite(notBeforeMs)) throw new Error('WORKFLOW_SUPERVISOR_PROOF_BOUNDARY_INVALID');
925
+ const activeReleaseId = boundedText(input.activeReleaseId);
926
+ if (!activeReleaseId) throw new Error('WORKFLOW_SUPERVISOR_PROOF_RELEASE_REQUIRED');
927
+ return this.read((db) => {
928
+ const taskRows = statement(db, 'SELECT * FROM tasks ORDER BY created_at DESC, task_id DESC', (s) => s.all()) as Record<string, unknown>[];
929
+ for (const taskRow of taskRows) {
930
+ const task = taskFromRow(taskRow);
931
+ const taskRepoId = boundedText(task.completionContract.repo_id) ?? boundedText(task.continuationPolicy.repo_id);
932
+ if (input.repoId?.trim() && taskRepoId !== input.repoId.trim()) continue;
933
+ const completionRows = statement(db, "SELECT c.* FROM completions c WHERE c.task_id = ? AND c.committed_at >= ? ORDER BY COALESCE((SELECT MAX(completion_event.event_id) FROM events completion_event WHERE completion_event.task_id = c.task_id AND completion_event.completion_fingerprint = c.completion_fingerprint AND completion_event.kind = 'assistant_completion'), c.rowid) ASC", (s) => s.all(task.taskId, input.notBefore)) as Record<string, unknown>[];
934
+ const completions = completionRows.map(completionFromRow);
935
+ // The bounded release canary task must prove C -> C -> accepted D.
936
+ // Long-running production tasks may remain open; any disposable task can supply this proof.
937
+ for (let index = 0; index + 2 < completions.length; index += 1) {
938
+ if (index + 3 !== completions.length) continue;
939
+ const first = completions[index]!;
940
+ const second = completions[index + 1]!;
941
+ const third = completions[index + 2]!;
942
+ if (first.action !== 'CONTINUE' || second.action !== 'CONTINUE' || third.action !== 'DONE') continue;
943
+ const rows = [first, second, third].map((completion) => statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(completion.sourceEffectId)) as Record<string, unknown> | undefined);
944
+ if (rows.some((row) => !row)) continue;
945
+ const [firstEffect, secondEffect, thirdEffect] = rows.map((row) => effectFromRow(row!)) as [WorkflowSupervisorEffect, WorkflowSupervisorEffect, WorkflowSupervisorEffect];
946
+ if (
947
+ [firstEffect, secondEffect, thirdEffect].some((effect) => effect.taskId !== task.taskId)
948
+ || secondEffect.sourceCompletionFingerprint !== first.completionFingerprint
949
+ || thirdEffect.sourceCompletionFingerprint !== second.completionFingerprint
950
+ ) continue;
951
+ const runtimeInstanceIds: string[] = [];
952
+ let valid = true;
953
+ let thirdCompletionEventId = 0;
954
+ for (const [completionIndex, completion, effect] of [
955
+ [0, first, firstEffect],
956
+ [1, second, secondEffect],
957
+ [2, third, thirdEffect],
958
+ ] as const) {
959
+ const dispatchRows = statement(db, "SELECT event_id,payload_json,occurred_at FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_started' ORDER BY event_id", (s) => s.all(effect.effectId)) as Array<{ event_id?: number; payload_json?: string; occurred_at?: string }>;
960
+ if (
961
+ dispatchRows.length !== 1
962
+ || !Number.isFinite(Date.parse(String(dispatchRows[0]?.occurred_at ?? '')))
963
+ || Date.parse(String(dispatchRows[0]?.occurred_at ?? '')) < notBeforeMs
964
+ ) { valid = false; break; }
965
+ const dispatch = dispatchRows[0]!;
966
+ const evidence = parsedObject(dispatch.payload_json);
967
+ if (boundedText(evidence.active_release_id) !== activeReleaseId) { valid = false; break; }
968
+ const runtimeInstanceId = boundedText(evidence.runtime_instance_id);
969
+ if (!runtimeInstanceId) { valid = false; break; }
970
+ runtimeInstanceIds.push(runtimeInstanceId);
971
+ const appliedRows = statement(db, "SELECT event_id,occurred_at FROM events WHERE effect_id = ? AND kind = 'effect_applied' ORDER BY event_id", (s) => s.all(effect.effectId)) as Array<{ event_id?: number; occurred_at?: string }>;
972
+ const applied = appliedRows.find((row) => {
973
+ const occurredAtMs = Date.parse(String(row.occurred_at ?? ''));
974
+ return Number(row.event_id ?? 0) > Number(dispatch.event_id ?? 0)
975
+ && Number.isFinite(occurredAtMs)
976
+ && occurredAtMs >= notBeforeMs;
977
+ });
978
+ if (!applied) { valid = false; break; }
979
+ const completionEvents = statement(db, "SELECT event_id,occurred_at FROM events WHERE task_id = ? AND completion_fingerprint = ? AND kind = 'assistant_completion' ORDER BY event_id", (s) => s.all(task.taskId, completion.completionFingerprint)) as Array<{ event_id?: number; occurred_at?: string }>;
980
+ if (
981
+ completionEvents.length !== 1
982
+ || !Number.isFinite(Date.parse(String(completionEvents[0]?.occurred_at ?? '')))
983
+ || Date.parse(String(completionEvents[0]?.occurred_at ?? '')) < notBeforeMs
984
+ || Number(completionEvents[0]?.event_id ?? 0) <= Number(applied.event_id ?? 0)
985
+ ) { valid = false; break; }
986
+ if (completionIndex === 2) thirdCompletionEventId = Number(completionEvents[0]?.event_id ?? 0);
987
+ }
988
+ const minimumRuntimeInstances = 2;
989
+ if (!valid || new Set(runtimeInstanceIds).size < minimumRuntimeInstances) continue;
990
+ const terminalRows = statement(db, "SELECT event_id,occurred_at FROM events WHERE task_id = ? AND completion_fingerprint = ? AND kind = 'terminal_done' ORDER BY event_id", (s) => s.all(task.taskId, third.completionFingerprint)) as Array<{ event_id?: number; occurred_at?: string }>;
991
+ const latestTerminal = statement(db, "SELECT kind,completion_fingerprint FROM events WHERE task_id = ? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') ORDER BY event_id DESC LIMIT 1", (s) => s.get(task.taskId)) as { kind?: string; completion_fingerprint?: string } | undefined;
992
+ if (
993
+ terminalRows.length !== 1
994
+ || !Number.isFinite(Date.parse(String(terminalRows[0]?.occurred_at ?? '')))
995
+ || Date.parse(String(terminalRows[0]?.occurred_at ?? '')) < notBeforeMs
996
+ || Number(terminalRows[0]?.event_id ?? 0) <= thirdCompletionEventId
997
+ || latestTerminal?.kind !== 'terminal_done'
998
+ || latestTerminal.completion_fingerprint !== third.completionFingerprint
999
+ ) continue;
1000
+ return {
1001
+ taskId: task.taskId, conversationId: task.conversationId, activeReleaseId,
1002
+ actions: ['CONTINUE', 'CONTINUE', 'DONE'],
1003
+ completionFingerprints: [first.completionFingerprint, second.completionFingerprint, third.completionFingerprint],
1004
+ sourceEffectIds: [first.sourceEffectId, second.sourceEffectId, third.sourceEffectId],
1005
+ runtimeInstanceIds: [...new Set(runtimeInstanceIds)],
1006
+ firstCommittedAt: first.committedAt, lastCommittedAt: third.committedAt,
1007
+ };
1008
+ }
1009
+ }
1010
+ return undefined;
1011
+ });
1012
+ }
1013
+ /**
1014
+ * Exact persisted pre-Send editor evidence. ProseMirror may introduce a
1015
+ * mid-body line break; only the observed prior exact digest grants recovery.
1016
+ */
1017
+ preSendComposerObservation(effectId: string): WorkflowSupervisorPreflightComposerObservation | undefined {
1018
+ return this.read((db) => {
1019
+ const sql = "SELECT e.payload_json FROM events e JOIN effects fx ON fx.effect_id = e.effect_id "
1020
+ + "WHERE e.effect_id = ? AND e.kind = 'effect_preflight_rejected' "
1021
+ + "AND e.event_id < (SELECT MIN(t.event_id) FROM events t WHERE t.task_id = fx.task_id AND t.kind = 'terminal_stopped') "
1022
+ + "ORDER BY e.event_id DESC LIMIT 1";
1023
+ const row = statement(db, sql, (query) => query.get(effectId)) as { payload_json?: string } | undefined;
1024
+ const data = parsedObject(row?.payload_json);
1025
+ const editor = parsedObject(data.composer_observation);
1026
+ if (data.send_clicked !== false || data.attestation !== 'computer_prepare_returned_before_supervisor_send_intent'
1027
+ || editor.classification !== 'current_effect' || editor.selectorVersion !== 'visible-single-v2'
1028
+ || !Number.isInteger(editor.chars) || Number(editor.chars) < 1
1029
+ || typeof editor.fingerprint !== 'string' || !/^[a-f0-9]{24}$/.test(editor.fingerprint)) return undefined;
1030
+ return { chars: Number(editor.chars), fingerprint: editor.fingerprint,
1031
+ classification: 'current_effect', selectorVersion: 'visible-single-v2' };
1032
+ });
1033
+ }
1034
+
1035
+ latestEffectDispatch(effectId: string): { eventId: number; generation: number; evidence: Record<string, unknown> } | undefined {
1036
+ return this.read((db) => {
1037
+ const row = statement(db, "SELECT event_id,payload_json FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_started' ORDER BY event_id DESC LIMIT 1", (s) => s.get(effectId)) as { event_id?: number; payload_json?: string } | undefined;
1038
+ if (!row?.event_id) return undefined;
1039
+ return { eventId: Number(row.event_id), generation: storedGeneration(row.payload_json), evidence: parsedObject(row.payload_json) };
1040
+ });
1041
+ }
1042
+ /**
1043
+ * Select the one browser command for a task's oldest un-applied effect.
1044
+ *
1045
+ * A dispatch generation is only minted from the canonical negative proof that
1046
+ * the previous send never reached the conversation, and only while the effect
1047
+ * stays inside its mechanical retry budget and retry window. Mutation retries
1048
+ * retain a hard ceiling. Outcome-unknown reconciliation is read-only and stays
1049
+ * eligible forever at a capped cadence so late provider evidence can recover
1050
+ * the same effect without user intervention or another send.
1051
+ */
1052
+ nextBrowserEffect(
1053
+ taskId: string,
1054
+ options: { nowMs?: number } = {},
1055
+ ): { effect: WorkflowSupervisorEffect; mode: 'send' | 'reconcile'; generation: number } | undefined {
1056
+ return this.read((db) => {
1057
+ const effect = oldestUnappliedEffect(db, taskId);
1058
+ if (!effect) return undefined;
1059
+ const ledger = effectDispatchLedger(db, effect.effectId);
1060
+ const nowMs = options.nowMs ?? this.clockMs();
1061
+ if (ledger.generations === 0) {
1062
+ // A fresh continuation is one further provider turn of the same task.
1063
+ // Spacing is measured from the completion that authorized it, so a fast
1064
+ // provider reply cannot turn the outer-turn chain into a request storm.
1065
+ const source = effect.sourceCompletionFingerprint
1066
+ ? statement(db, 'SELECT committed_at FROM completions WHERE completion_fingerprint = ?', (s) => s.get(effect.sourceCompletionFingerprint!)) as { committed_at?: string } | undefined
1067
+ : undefined;
1068
+ const committedAtMs = Date.parse(String(source?.committed_at ?? ''));
1069
+ if (Number.isFinite(committedAtMs) && nowMs - committedAtMs < WORKFLOW_SUPERVISOR_MIN_TURN_INTERVAL_MS) return undefined;
1070
+ if (this.preflightBlocked(db, effect.effectId)) return undefined;
1071
+ return { effect, mode: 'send', generation: 1 };
1072
+ }
1073
+ const retryAuthorized = latestNotAppliedProofEventId(db, effect.effectId) > ledger.lastEventId;
1074
+ if (!retryAuthorized) {
1075
+ if (!unknownObservationDue(db, effect.effectId, ledger.lastEventId, nowMs)) return undefined;
1076
+ return { effect, mode: 'reconcile', generation: ledger.lastGeneration };
1077
+ }
1078
+ if (ledger.generations >= WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS + ledger.budgetRefunds) return undefined;
1079
+ if (this.preflightBlocked(db, effect.effectId)) return undefined;
1080
+ if (nowMs - ledger.lastOccurredAtMs < workflowSupervisorDispatchRetryDelayMs(ledger.lastGeneration)) {
1081
+ return undefined;
1082
+ }
1083
+ return { effect, mode: 'send', generation: ledger.lastGeneration + 1 };
1084
+ });
1085
+ }
1086
+ effectDispatchBudget(effectId: string): WorkflowSupervisorEffectDispatchBudget {
1087
+ return this.read((db) => {
1088
+ const ledger = effectDispatchLedger(db, effectId);
1089
+ const exhausted = ledger.generations >= WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS + ledger.budgetRefunds
1090
+ && !statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effectId));
1091
+ return {
1092
+ effectId,
1093
+ generations: ledger.generations,
1094
+ maxGenerations: WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS,
1095
+ ...(ledger.lastOccurredAtMs ? { lastDispatchedAtMs: ledger.lastOccurredAtMs } : {}),
1096
+ ...(ledger.generations ? { retryDelayMs: workflowSupervisorDispatchRetryDelayMs(ledger.lastGeneration) } : {}),
1097
+ exhausted,
1098
+ };
1099
+ });
1100
+ }
1101
+ /**
1102
+ * Read-only classification of why a non-terminal task is or is not advancing.
1103
+ * It derives only from the existing effect/observation ledger, so it adds no
1104
+ * second durable state and no new lifecycle authority.
1105
+ */
1106
+ taskStall(taskId: string): WorkflowSupervisorTaskStall {
1107
+ if (this.terminalAction(taskId)) return { state: 'terminal' };
1108
+ const next = this.nextBrowserEffect(taskId);
1109
+ if (next) {
1110
+ if (next.mode === 'send') return { state: 'deliverable', effectId: next.effect.effectId, mode: 'send', generation: next.generation };
1111
+ return { state: 'spaced', effectId: next.effect.effectId, reason: 'unknown_observation_spacing' };
1112
+ }
1113
+ const pending = this.currentUnappliedEffect(taskId);
1114
+ if (pending) {
1115
+ const ledger = this.read((db) => ({ ...effectDispatchLedger(db, pending.effectId), proof: latestNotAppliedProofEventId(db, pending.effectId), preflightBlocked: this.preflightBlocked(db, pending.effectId) }));
1116
+ if (ledger.preflightBlocked) {
1117
+ const failure = this.read((db) => statement(db,
1118
+ "SELECT payload_json FROM events WHERE effect_id = ? AND kind = 'effect_preflight_rejected' ORDER BY event_id DESC LIMIT 1",
1119
+ (s) => s.get(pending.effectId)) as { payload_json?: string } | undefined);
1120
+ const record = parsedObject(failure?.payload_json);
1121
+ const reasonCode = boundedText(record.reason)?.slice(0, 256);
1122
+ const diagnostic = record.composer_observation;
1123
+ const preflightComposerObservation = diagnostic && typeof diagnostic === 'object' && !Array.isArray(diagnostic)
1124
+ ? diagnostic as WorkflowSupervisorPreflightComposerObservation : undefined;
1125
+ return { state: 'spaced', effectId: pending.effectId, reason: 'pre_send_infrastructure_blocked',
1126
+ ...(reasonCode ? { preflightReasonCode: reasonCode } : {}),
1127
+ ...(preflightComposerObservation ? { preflightComposerObservation } : {}) };
1128
+ }
1129
+ const ceiling = WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS + ledger.budgetRefunds;
1130
+ const retryAuthorized = ledger.generations === 0 || ledger.proof > ledger.lastEventId;
1131
+ // An exhausted retry ceiling is the operator-facing state: no amount of
1132
+ // waiting can mint another generation. Everything else is ordinary
1133
+ // mechanical spacing and needs no operator action.
1134
+ if (retryAuthorized && ledger.generations >= ceiling) {
1135
+ return { state: 'retryable', effectId: pending.effectId, generations: ledger.generations, maxGenerations: ceiling };
1136
+ }
1137
+ return { state: 'spaced', effectId: pending.effectId, reason: retryAuthorized ? 'retry_spacing' : 'unknown_observation_spacing' };
1138
+ }
1139
+ const leaf = this.latestAppliedLeafEffectWithoutCompletion(taskId);
1140
+ if (leaf && this.providerResumeExhausted(leaf.effectId)) return { state: 'provider_resume_exhausted', effectId: leaf.effectId };
1141
+ // An applied effect awaiting its receipt is a live obligation even when its
1142
+ // bounded resume is exhausted and its recovery child was retired, so it is
1143
+ // never reported as inert.
1144
+ if (this.hasAppliedEffectAwaitingCompletion(taskId)) {
1145
+ return { state: 'spaced', ...(leaf ? { effectId: leaf.effectId } : {}), reason: 'awaiting_completion_receipt' };
1146
+ }
1147
+ const waiting = this.read((db) => {
1148
+ const taskRow = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(taskId)) as Record<string, unknown> | undefined;
1149
+ if (!taskRow) return undefined;
1150
+ const task = taskFromRow(taskRow);
1151
+ const scheduleId = boundedText(task.continuationPolicy.scheduled_wake_schedule_id);
1152
+ if (!scheduleId) return undefined;
1153
+ const latest = statement(db, "SELECT c.completion_fingerprint, c.action FROM completions c WHERE c.task_id = ? ORDER BY COALESCE((SELECT MAX(completion_event.event_id) FROM events completion_event WHERE completion_event.task_id = c.task_id AND completion_event.completion_fingerprint = c.completion_fingerprint AND completion_event.kind = 'assistant_completion'), c.rowid) DESC LIMIT 1", (s) => s.get(taskId)) as { completion_fingerprint?: string; action?: string } | undefined;
1154
+ return latest?.action === 'WAIT' && latest.completion_fingerprint
1155
+ ? { state: 'waiting' as const, scheduleId, completionFingerprint: latest.completion_fingerprint }
1156
+ : undefined;
1157
+ });
1158
+ if (waiting) return waiting;
1159
+ return { state: 'inert' };
1160
+ }
1161
+ /**
1162
+ * Consecutive genuinely failed task-local attempts since the last durable
1163
+ * provider progress or conversation move. Counts across failure codes and
1164
+ * Runtime restarts; an unknown submission observation is NOT a failed send.
1165
+ * This is a projection of the existing Supervisor journal, not another
1166
+ * mutable retry or delivery authority.
1167
+ */
1168
+ automaticConversationFailureEvidence(taskId: string): { count: number; latestEventId: number; lastFailureAtMs: number } {
1169
+ return this.read((db) => {
1170
+ const row = statement(db, `SELECT COUNT(*) AS total, COALESCE(MAX(event_id),0) AS latest_id, MAX(occurred_at) AS last_at
1171
+ FROM events WHERE task_id = ? AND kind IN (
1172
+ 'effect_preflight_rejected', 'assistant_provider_failed',
1173
+ 'assistant_recovery_exhausted', 'browser_delivery_attempt_failed'
1174
+ ) AND event_id > COALESCE((
1175
+ SELECT MAX(event_id) FROM events WHERE task_id = ?
1176
+ AND kind IN ('effect_applied','assistant_completion','conversation_migrated')
1177
+ ),0)`, (s) => s.get(taskId, taskId)) as { total?: number; latest_id?: number; last_at?: string };
1178
+ return { count: Number(row?.total ?? 0), latestEventId: Number(row?.latest_id ?? 0),
1179
+ lastFailureAtMs: Date.parse(row?.last_at ?? '') || 0 };
1180
+ });
1181
+ }
1182
+
1183
+ /** Journal one attempted task-local Computer operation, never a guessed Send. */
1184
+ recordBrowserDeliveryAttemptFailure(input: { taskId: string; conversationId: string; code: string; effectId?: string }): void {
1185
+ this.transaction((db) => {
1186
+ const task = statement(db, 'SELECT conversation_id FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as { conversation_id?: string } | undefined;
1187
+ // An old tab or pre-migration provider result cannot charge the new chat.
1188
+ if (!task || task.conversation_id !== input.conversationId) return;
1189
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1190
+ input.taskId, `browser-delivery-failed:${input.taskId}:${randomUUID()}`,
1191
+ 'browser_delivery_attempt_failed', input.effectId ?? null,
1192
+ json({ conversation_id: input.conversationId, code: input.code.slice(0, 128), phase: 'task_local_pre_mutation' }), now(),
1193
+ ));
1194
+ });
1195
+ }
1196
+
1197
+ /** One operator-approved repair of a proven pre-send failure, never a provider retry refund. */
1198
+ authorizeOperatorPreflightRetry(input: { effectId: string; requestId: string; reason: string; authorizedBy: string }): { deduplicated: boolean } {
1199
+ return this.transaction((db) => {
1200
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(input.effectId)) as { task_id?: string } | undefined;
1201
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1202
+ if (statement(db, "SELECT 1 FROM events WHERE effect_id = ? AND kind IN ('effect_applied','effect_outcome_unknown_superseded') LIMIT 1", (s) => s.get(input.effectId))) {
1203
+ throw new Error('WORKFLOW_SUPERVISOR_PRE_SEND_EFFECT_TERMINAL');
1204
+ }
1205
+ if (effectDispatchLedger(db, input.effectId).generations !== 0) throw new Error('WORKFLOW_SUPERVISOR_PRE_SEND_RECOVERY_SEND_ALREADY_RECORDED');
1206
+ const key = `effect-preflight-operator-rearmed:${input.effectId}:${input.requestId}`;
1207
+ if (statement(db, 'SELECT 1 FROM events WHERE event_key = ?', (s) => s.get(key))) return { deduplicated: true };
1208
+ const failed = effectPreflightFailureReleaseIds(db, input.effectId);
1209
+ const releaseId = this.activeReleaseId()?.trim() || 'unidentified-runtime';
1210
+ // An operator may authorize ONE bounded no-Send re-observation after a
1211
+ // verified repair, including on a successor Runtime after the automatic
1212
+ // three-release cap. Never erase the prior failures or refund Send budget.
1213
+ if (statement(db, "SELECT 1 FROM events WHERE effect_id = ? AND kind = 'effect_preflight_operator_rearmed' LIMIT 1", (s) => s.get(input.effectId))) {
1214
+ throw new Error('WORKFLOW_SUPERVISOR_PRE_SEND_OPERATOR_REARM_EXHAUSTED');
1215
+ }
1216
+ if (!this.preflightBlocked(db, input.effectId)
1217
+ || (failed.length < WORKFLOW_SUPERVISOR_MAX_PREFLIGHT_RELEASE_ATTEMPTS
1218
+ && !failed.some((entry) => entry.releaseId === releaseId))) {
1219
+ throw new Error('WORKFLOW_SUPERVISOR_PRE_SEND_RECOVERY_NOT_BLOCKED');
1220
+ }
1221
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1222
+ effect.task_id!, key, 'effect_preflight_operator_rearmed', input.effectId,
1223
+ json({ request_id: input.requestId, authorized_by: input.authorizedBy, reason: boundedText(input.reason)?.slice(0, 512), active_release_id: releaseId, attestation: 'operator_authorized_after_proven_no_send' }), now(),
1224
+ ));
1225
+ return { deduplicated: false };
1226
+ });
1227
+ }
1228
+
1229
+ /**
1230
+ * Explicit operator authority to continue a mechanically proven un-applied
1231
+ * send after its retry ceiling, without replaying an outcome-unknown mutation.
1232
+ * This refund path is distinct from zero-generation UI preparation repair.
1233
+ */
1234
+ authorizeOperatorRetry(input: { effectId: string; requestId: string; reason: string; authorizedBy: string }): { refunds: number; generations: number; deduplicated: boolean } {
1235
+ return this.transaction((db) => {
1236
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(input.effectId)) as { task_id?: string } | undefined;
1237
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1238
+ if (statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(input.effectId))) {
1239
+ throw new Error('WORKFLOW_SUPERVISOR_EFFECT_ALREADY_APPLIED');
1240
+ }
1241
+ if (statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_outcome_unknown_superseded' LIMIT 1", (s) => s.get(input.effectId))) {
1242
+ throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_EFFECT_SUPERSEDED');
1243
+ }
1244
+ const ledger = effectDispatchLedger(db, input.effectId);
1245
+ if (ledger.generations === 0 || latestNotAppliedProofEventId(db, input.effectId) <= ledger.lastEventId) {
1246
+ throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_OUTCOME_UNKNOWN');
1247
+ }
1248
+ const key = `effect-dispatch-operator-refund:${input.effectId}:${input.requestId}`;
1249
+ const existing = statement(db, 'SELECT 1 AS ok FROM events WHERE event_key = ?', (s) => s.get(key));
1250
+ if (existing) return { refunds: operatorAuthorizedRefundCount(db, input.effectId), generations: ledger.generations, deduplicated: true };
1251
+ if (operatorAuthorizedRefundCount(db, input.effectId) >= WORKFLOW_SUPERVISOR_MAX_OPERATOR_REFUNDS) {
1252
+ throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_REFUND_BUDGET_EXHAUSTED');
1253
+ }
1254
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1255
+ effect.task_id!, key, 'effect_dispatch_budget_refunded', input.effectId,
1256
+ json({
1257
+ reason: 'operator_authorized_retry', request_id: input.requestId, authorized_by: input.authorizedBy,
1258
+ reason_text: input.reason, prior_generations: ledger.generations, attestation: 'mechanical_pre_send_negative_proof',
1259
+ }), now()));
1260
+ return { refunds: operatorAuthorizedRefundCount(db, input.effectId), generations: ledger.generations, deduplicated: false };
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Explicit operator authority to retire an effect whose submission outcome is
1265
+ * genuinely unknown. It is never resent; the durable fact records who decided
1266
+ * to stop waiting, and the replacement turn is reserved in the same
1267
+ * transaction so the task can never sit in a half-superseded state. Requires
1268
+ * the caller to have stated the intent.
1269
+ */
1270
+ supersedeUnknownEffect(input: {
1271
+ effectId: string;
1272
+ requestId: string;
1273
+ reason: string;
1274
+ authorizedBy: string;
1275
+ replacement: { effectId: string; prompt: string };
1276
+ }): { superseded: boolean; recoveryEffect: WorkflowSupervisorEffect } {
1277
+ const originKey = `operator-recovery:${input.effectId}:${input.requestId}`;
1278
+ return this.transaction((db) => {
1279
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(input.effectId)) as { task_id?: string } | undefined;
1280
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1281
+ const taskId = effect.task_id;
1282
+ const reserve = (): WorkflowSupervisorEffect => this.reserveEffectWithin(db, {
1283
+ taskId, effectId: input.replacement.effectId, kind: 'recovery', originKey, prompt: input.replacement.prompt,
1284
+ });
1285
+ const existingReplacement = statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(originKey)) as Record<string, unknown> | undefined;
1286
+ if (existingReplacement) return { superseded: true, recoveryEffect: effectFromRow(existingReplacement) };
1287
+ if (statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(input.effectId))) {
1288
+ throw new Error('WORKFLOW_SUPERVISOR_EFFECT_ALREADY_APPLIED');
1289
+ }
1290
+ const ledger = effectDispatchLedger(db, input.effectId);
1291
+ if (ledger.generations === 0) throw new Error('WORKFLOW_SUPERVISOR_SUPERSEDE_NOT_DISPATCHED');
1292
+ if (latestNotAppliedProofEventId(db, input.effectId) > ledger.lastEventId) {
1293
+ throw new Error('WORKFLOW_SUPERVISOR_SUPERSEDE_NOT_APPLIED_PROVEN');
1294
+ }
1295
+ const key = `effect-outcome-unknown-superseded:${input.effectId}:${input.requestId}`;
1296
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1297
+ taskId, key, 'effect_outcome_unknown_superseded', input.effectId,
1298
+ json({
1299
+ request_id: input.requestId, authorized_by: input.authorizedBy, reason: input.reason,
1300
+ generation: ledger.lastGeneration, replacement_effect_id: input.replacement.effectId,
1301
+ attestation: 'explicit_operator_authorization',
1302
+ }), now()));
1303
+ return { superseded: true, recoveryEffect: reserve() };
1304
+ });
1305
+ }
1306
+ terminalAction(taskId: string): WorkflowSupervisorTerminalState | undefined {
1307
+ return this.read((db) => {
1308
+ const row = statement(db, "SELECT kind FROM events WHERE task_id = ? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') ORDER BY event_id DESC LIMIT 1", (s) => s.get(taskId)) as { kind?: string } | undefined;
1309
+ return row?.kind === 'terminal_done' ? 'DONE' : row?.kind === 'terminal_needs_user' ? 'NEEDS_USER' : row?.kind === 'terminal_stopped' ? 'STOPPED' : undefined;
1310
+ });
1311
+ }
1312
+ stopTask(taskId: string, reason: string): { taskId: string; terminal: 'STOPPED'; deduplicated: boolean } {
1313
+ return this.transaction((db) => {
1314
+ const task = statement(db, 'SELECT task_id FROM tasks WHERE task_id = ?', (s) => s.get(taskId)) as { task_id?: string } | undefined;
1315
+ if (!task) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
1316
+ const existing = statement(db, "SELECT kind FROM events WHERE task_id = ? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') ORDER BY event_id DESC LIMIT 1", (s) => s.get(taskId)) as { kind?: string } | undefined;
1317
+ if (existing?.kind === 'terminal_stopped') return { taskId, terminal: 'STOPPED', deduplicated: true };
1318
+ if (existing?.kind) {
1319
+ const terminal = existing.kind === 'terminal_done' ? 'DONE' : 'NEEDS_USER';
1320
+ throw new Error(`WORKFLOW_SUPERVISOR_TASK_TERMINAL:${terminal}`);
1321
+ }
1322
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(taskId, `task-stop:${taskId}`, 'terminal_stopped', json({ reason }), now()));
1323
+ return { taskId, terminal: 'STOPPED', deduplicated: false };
1324
+ });
1325
+ }
1326
+ effectApplied(effectId: string): boolean { return this.read((db) => Boolean(statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effectId)))); }
1327
+ bootstrapAppliedReconcileDue(effectId: string): boolean {
1328
+ return this.read((db) => {
1329
+ const dispatch = effectDispatchLedger(db, effectId);
1330
+ return dispatch.generations > 0 && unknownObservationDue(db, effectId, dispatch.lastEventId, this.clockMs());
1331
+ });
1332
+ }
1333
+ providerResumeExhausted(effectId: string): boolean { return this.read((db) => Boolean(statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'assistant_recovery_exhausted' LIMIT 1", (s) => s.get(effectId)))); }
1334
+ latestAppliedEffectWithoutCompletion(taskId: string): WorkflowSupervisorEffect | undefined {
1335
+ return this.read((db) => {
1336
+ const row = statement(db, `SELECT e.* FROM effects e
1337
+ WHERE e.task_id = ?
1338
+ AND EXISTS (SELECT 1 FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied')
1339
+ AND NOT EXISTS (SELECT 1 FROM completions c WHERE c.task_id = e.task_id AND c.source_effect_id = e.effect_id)
1340
+ AND NOT EXISTS (SELECT 1 FROM effects child WHERE child.origin_key = 'provider-recovery:' || e.effect_id)
1341
+ AND NOT EXISTS (SELECT 1 FROM events exhausted WHERE exhausted.effect_id = e.effect_id AND exhausted.kind = 'assistant_recovery_exhausted')
1342
+ ORDER BY (SELECT MAX(event_id) FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied') DESC
1343
+ LIMIT 1`, (s) => s.get(taskId)) as Record<string, unknown> | undefined;
1344
+ return row ? effectFromRow(row) : undefined;
1345
+ });
1346
+ }
1347
+ latestAppliedLeafEffectWithoutCompletion(taskId: string): WorkflowSupervisorEffect | undefined {
1348
+ return this.read((db) => {
1349
+ const row = statement(db, `SELECT e.* FROM effects e
1350
+ WHERE e.task_id = ?
1351
+ AND EXISTS (SELECT 1 FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied')
1352
+ AND NOT EXISTS (SELECT 1 FROM completions c WHERE c.task_id = e.task_id AND c.source_effect_id = e.effect_id)
1353
+ AND NOT EXISTS (SELECT 1 FROM effects child
1354
+ WHERE child.origin_key = 'provider-recovery:' || e.effect_id
1355
+ AND EXISTS (SELECT 1 FROM events started
1356
+ WHERE started.effect_id = child.effect_id AND started.kind = 'effect_dispatch_started'))
1357
+ ORDER BY (SELECT MAX(event_id) FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied') DESC
1358
+ LIMIT 1`, (s) => s.get(taskId)) as Record<string, unknown> | undefined;
1359
+ return row ? effectFromRow(row) : undefined;
1360
+ });
1361
+ }
1362
+
1363
+ /**
1364
+ * An applied browser effect remains a Supervisor-owned external observation
1365
+ * obligation even when its lower ControllerRound temporarily waits. This
1366
+ * intentionally includes effects that already have a bounded provider
1367
+ * recovery child; the native adapter must stay alive long enough to observe
1368
+ * and dispatch that recovery instead of abandoning the browser task.
1369
+ */
1370
+ hasAppliedEffectAwaitingCompletion(taskId: string): boolean {
1371
+ return this.read((db) => Boolean(statement(db, `SELECT 1 AS ok FROM effects e
1372
+ WHERE e.task_id = ?
1373
+ AND EXISTS (SELECT 1 FROM events applied WHERE applied.effect_id = e.effect_id AND applied.kind = 'effect_applied')
1374
+ AND NOT EXISTS (SELECT 1 FROM completions c WHERE c.task_id = e.task_id AND c.source_effect_id = e.effect_id)
1375
+ LIMIT 1`, (s) => s.get(taskId))));
1376
+ }
1377
+
1378
+ observeProviderTurn(input: {
1379
+ taskId: string;
1380
+ effectId: string;
1381
+ generating: boolean;
1382
+ assistantDigest: string;
1383
+ providerFailureCode?: string;
1384
+ receiptMissing?: boolean;
1385
+ observedAtMs: number;
1386
+ graceMs: number;
1387
+ recovery: { effectId: string; prompt: string };
1388
+ }): { state: 'none' | 'generating' | 'idle_pending' | 'recovery_reserved' | 'exhausted'; recoveryEffect?: WorkflowSupervisorEffect } {
1389
+ if (!Number.isFinite(input.observedAtMs)) throw new Error('WORKFLOW_SUPERVISOR_PROVIDER_OBSERVED_AT_INVALID');
1390
+ const graceMs = Math.max(1_000, Math.min(10 * 60_000, Math.floor(input.graceMs)));
1391
+ const digest = input.assistantDigest.trim().slice(0, 128);
1392
+ const observedAt = new Date(input.observedAtMs).toISOString();
1393
+ return this.transaction((db) => {
1394
+ const row = statement(db, 'SELECT * FROM effects WHERE effect_id = ?', (s) => s.get(input.effectId)) as Record<string, unknown> | undefined;
1395
+ if (!row) return { state: 'none' };
1396
+ const effect = effectFromRow(row);
1397
+ if (effect.taskId !== input.taskId) throw new Error('WORKFLOW_SUPERVISOR_PROVIDER_EFFECT_TASK_MISMATCH');
1398
+ const applied = statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effect.effectId));
1399
+ const completed = statement(db, 'SELECT 1 AS ok FROM completions WHERE task_id = ? AND source_effect_id = ? LIMIT 1', (s) => s.get(input.taskId, effect.effectId));
1400
+ if (!applied || completed) return { state: 'none' };
1401
+ const providerRecoveryDepth = providerRecoveryDepthWithin(db, effect.effectId);
1402
+ const recoveryOrigin = `provider-recovery:${effect.effectId}`;
1403
+ const providerFailureCode = input.providerFailureCode?.trim().slice(0, 128);
1404
+ if (providerFailureCode) {
1405
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1406
+ input.taskId,
1407
+ `assistant-provider-failed:${effect.effectId}:${providerFailureCode}`,
1408
+ 'assistant_provider_failed',
1409
+ effect.effectId,
1410
+ json({ code: providerFailureCode, assistant_digest: digest }),
1411
+ observedAt,
1412
+ ));
1413
+ }
1414
+ const existingRecovery = statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(recoveryOrigin)) as Record<string, unknown> | undefined;
1415
+ if (existingRecovery) return { state: 'recovery_reserved', recoveryEffect: effectFromRow(existingRecovery) };
1416
+ if (providerFailureCode) {
1417
+ if (providerRecoveryDepth >= WORKFLOW_SUPERVISOR_MAX_PROVIDER_RECOVERY_TURNS) {
1418
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(input.taskId, `assistant-recovery-exhausted:${effect.effectId}`, 'assistant_recovery_exhausted', effect.effectId, json({ assistant_digest: digest, provider_failure_code: providerFailureCode, exactly_once_resume: true, recovery_depth: providerRecoveryDepth, max_recovery_turns: WORKFLOW_SUPERVISOR_MAX_PROVIDER_RECOVERY_TURNS }), observedAt));
1419
+ return { state: 'exhausted' };
1420
+ }
1421
+ const recoveryEffect = this.reserveEffectWithin(db, { taskId: input.taskId, effectId: input.recovery.effectId, kind: 'recovery', originKey: recoveryOrigin, prompt: input.recovery.prompt });
1422
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(input.taskId, `assistant-recovery-reserved:${effect.effectId}`, 'assistant_recovery_reserved', effect.effectId, json({ recovery_effect_id: recoveryEffect.effectId, provider_failure_code: providerFailureCode, exactly_once_resume: true }), observedAt));
1423
+ return { state: 'recovery_reserved', recoveryEffect };
1424
+ }
1425
+
1426
+ const latest = statement(db, `SELECT event_id,kind,payload_json,occurred_at FROM events
1427
+ WHERE effect_id = ? AND kind IN ('assistant_provider_generating','assistant_provider_idle')
1428
+ ORDER BY event_id DESC LIMIT 1`, (s) => s.get(effect.effectId)) as { event_id?: number; kind?: string; payload_json?: string; occurred_at?: string } | undefined;
1429
+ const latestEvidence = parsedObject(latest?.payload_json);
1430
+ const desiredKind = input.generating ? 'assistant_provider_generating' : 'assistant_provider_idle';
1431
+ const sameState = latest?.kind === desiredKind && latestEvidence.assistant_digest === digest;
1432
+ if (!sameState) {
1433
+ const eventKey = `assistant-provider-state:${effect.effectId}:${desiredKind}:${Number(latest?.event_id ?? 0) + 1}:${digest || 'empty'}`;
1434
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(input.taskId, eventKey, desiredKind, effect.effectId, json({ assistant_digest: digest }), observedAt));
1435
+ return { state: input.generating ? 'generating' : 'idle_pending' };
1436
+ }
1437
+ const unchangedSinceMs = Date.parse(String(latest?.occurred_at ?? ''));
1438
+ if (!Number.isFinite(unchangedSinceMs) || input.observedAtMs - unchangedSinceMs < graceMs) {
1439
+ return { state: input.generating ? 'generating' : 'idle_pending' };
1440
+ }
1441
+
1442
+ // Ordinary idle text cannot authorize recovery. Only a stable, exact
1443
+ // assistant response proven to lack its receipt can reserve the existing
1444
+ // bounded, separately identified recovery; never replay the source effect.
1445
+ if (!input.generating && !input.receiptMissing) return { state: 'idle_pending' };
1446
+
1447
+ // Progress, not the provider's visible "generating" control, is the
1448
+ // liveness authority. A frozen page can leave that control present for
1449
+ // hours after tool/output progress stopped. Once the assistant/activity
1450
+ // digest is unchanged for the existing grace window, reserve the next
1451
+ // separately identified recovery turn without replaying the applied source.
1452
+ // The causal chain is bounded to two automatic recovery turns.
1453
+ if (providerRecoveryDepth >= WORKFLOW_SUPERVISOR_MAX_PROVIDER_RECOVERY_TURNS) {
1454
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(input.taskId, `assistant-recovery-exhausted:${effect.effectId}`, 'assistant_recovery_exhausted', effect.effectId, json({ assistant_digest: digest, exactly_once_resume: true, stale_generation: input.generating, recovery_depth: providerRecoveryDepth, max_recovery_turns: WORKFLOW_SUPERVISOR_MAX_PROVIDER_RECOVERY_TURNS }), observedAt));
1455
+ return { state: 'exhausted' };
1456
+ }
1457
+ const recoveryEffect = this.reserveEffectWithin(db, { taskId: input.taskId, effectId: input.recovery.effectId, kind: 'recovery', originKey: recoveryOrigin, prompt: input.recovery.prompt });
1458
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(input.taskId, `assistant-recovery-reserved:${effect.effectId}`, 'assistant_recovery_reserved', effect.effectId, json({ recovery_effect_id: recoveryEffect.effectId, exactly_once_resume: true, stale_generation: input.generating }), observedAt));
1459
+ return { state: 'recovery_reserved', recoveryEffect };
1460
+ });
1461
+ }
1462
+
1463
+ reserveEffect(input: { taskId: string; effectId: string; kind: WorkflowEffectKind; originKey: string; sourceCompletionFingerprint?: string; prompt: string }): WorkflowSupervisorEffect {
1464
+ return this.transaction((db) => this.reserveEffectWithin(db, input));
1465
+ }
1466
+ reserveOperatorRecovery(input: { taskId: string; sourceEffectId: string; requestId: string; reason: string; effectId: string; prompt: string }): WorkflowSupervisorEffect {
1467
+ return this.transaction((db) => {
1468
+ if (oldestUnappliedEffect(db, input.taskId)) throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_OUTCOME_UNKNOWN');
1469
+ const source = statement(db, `SELECT e.* FROM effects e WHERE e.task_id = ?
1470
+ AND EXISTS (SELECT 1 FROM events applied WHERE applied.effect_id=e.effect_id AND applied.kind='effect_applied')
1471
+ AND NOT EXISTS (SELECT 1 FROM completions c WHERE c.source_effect_id=e.effect_id)
1472
+ AND NOT EXISTS (SELECT 1 FROM effects child WHERE child.origin_key='provider-recovery:' || e.effect_id)
1473
+ ORDER BY e.created_at DESC LIMIT 1`, (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
1474
+ if (source?.effect_id !== input.sourceEffectId) throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_SOURCE_CHANGED');
1475
+ if (statement(db, "SELECT 1 FROM events WHERE task_id=? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') LIMIT 1", (s) => s.get(input.taskId))) throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
1476
+ const effect = this.reserveEffectWithin(db, { taskId: input.taskId, effectId: input.effectId, kind: 'recovery', originKey: `provider-recovery:${input.sourceEffectId}`, prompt: input.prompt });
1477
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1478
+ input.taskId, `assistant-recovery-reserved:${input.sourceEffectId}`, 'assistant_recovery_reserved', input.sourceEffectId,
1479
+ json({ recovery_effect_id: effect.effectId, requested_by: 'user', request_id: input.requestId, reason: input.reason, exactly_once_resume: true }), now()));
1480
+ return effect;
1481
+ });
1482
+ }
1483
+ /**
1484
+ * Explicit operator recovery for an applied effect whose one bounded provider
1485
+ * resume was already exhausted. The source effect stays applied and completed
1486
+ * history is untouched; this only reserves one new, separately identified
1487
+ * provider turn, so it can never replay the source mutation.
1488
+ */
1489
+ reserveOperatorProviderRecovery(input: { taskId: string; sourceEffectId: string; requestId: string; reason: string; authorizedBy: string; originKey: string; effectId: string; prompt: string }): WorkflowSupervisorEffect {
1490
+ return this.transaction((db) => {
1491
+ if (oldestUnappliedEffect(db, input.taskId)) throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_OUTCOME_UNKNOWN');
1492
+ if (statement(db, "SELECT 1 FROM events WHERE task_id=? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') LIMIT 1", (s) => s.get(input.taskId))) {
1493
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
1494
+ }
1495
+ const source = statement(db, `SELECT e.* FROM effects e WHERE e.task_id = ? AND e.effect_id = ?
1496
+ AND EXISTS (SELECT 1 FROM events applied WHERE applied.effect_id=e.effect_id AND applied.kind='effect_applied')
1497
+ AND NOT EXISTS (SELECT 1 FROM completions c WHERE c.source_effect_id=e.effect_id)
1498
+ LIMIT 1`, (s) => s.get(input.taskId, input.sourceEffectId)) as Record<string, unknown> | undefined;
1499
+ if (!source) throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_SOURCE_CHANGED');
1500
+ // This class exists precisely because the bounded automatic resume ran
1501
+ // out. Without that durable fact there is no authorization here.
1502
+ if (!providerResumeExhaustedWithin(db, input.sourceEffectId)) throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_NOT_EXHAUSTED');
1503
+ const originKey = input.originKey;
1504
+ const effect = this.reserveEffectWithin(db, { taskId: input.taskId, effectId: input.effectId, kind: 'recovery', originKey, prompt: input.prompt });
1505
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1506
+ input.taskId, `assistant-recovery-reserved:${originKey}`, 'assistant_recovery_reserved', input.sourceEffectId,
1507
+ json({
1508
+ recovery_effect_id: effect.effectId, requested_by: 'user', authorized_by: input.authorizedBy,
1509
+ request_id: input.requestId, reason: input.reason, exactly_once_resume: true, operator_authorized: true,
1510
+ }), now()));
1511
+ return effect;
1512
+ });
1513
+ }
1514
+ freshConversationMigrationContext(taskId: string): { fromConversationId: string; requestId: string } | undefined {
1515
+ return this.read((db) => {
1516
+ const row = statement(db, "SELECT payload_json FROM events WHERE task_id = ? AND kind = 'conversation_migrated' ORDER BY event_id DESC LIMIT 1", (s) => s.get(taskId)) as { payload_json?: string } | undefined;
1517
+ const payload = parsedObject(row?.payload_json);
1518
+ if (payload.fresh_conversation !== true || payload.to_conversation_id !== `bootstrap:${taskId}`) return undefined;
1519
+ const fromConversationId = typeof payload.from_conversation_id === 'string' ? payload.from_conversation_id.trim() : '';
1520
+ const requestId = typeof payload.request_id === 'string' ? payload.request_id.trim() : '';
1521
+ if (!fromConversationId || !requestId) return undefined;
1522
+ return { fromConversationId, requestId };
1523
+ });
1524
+ }
1525
+
1526
+ /**
1527
+ * Explicit operator switch of an exhausted task to a *fresh* conversation in
1528
+ * its configured Project. This is the one path that legitimately opens a new
1529
+ * chat: it exists for an exact conversation that is durably unusable, never as
1530
+ * a reaction to provider backpressure. The whole move and its replacement
1531
+ * enrollment turn commit in one transaction, so the task can never be left on
1532
+ * a fresh conversation holding a prompt that assumes prior context.
1533
+ */
1534
+ migrateToFreshConversation(input: {
1535
+ taskId: string;
1536
+ expectedConversationId: string;
1537
+ requestId: string;
1538
+ reason: string;
1539
+ authorizedBy: string;
1540
+ replacement: { effectId: string; prompt: string };
1541
+ }): { task: WorkflowSupervisorTask; migrated: boolean; retiredEffectId?: string } {
1542
+ return this.transaction((db) => {
1543
+ const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
1544
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
1545
+ const task = taskFromRow(row);
1546
+ if (task.conversationId !== input.expectedConversationId) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_STALE');
1547
+ if (statement(db, "SELECT 1 FROM events WHERE task_id=? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') LIMIT 1", (s) => s.get(input.taskId))) {
1548
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
1549
+ }
1550
+ const targetConversationId = `bootstrap:${input.taskId}`;
1551
+ if (task.conversationId === targetConversationId) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_ALREADY_PENDING');
1552
+ // Bound repeated unsuccessful moves, not the lifetime of a healthy task.
1553
+ // A genuine completion reopens the budget for its next 15-turn rotation.
1554
+ const prior = statement(db, `SELECT COUNT(*) AS total FROM events
1555
+ WHERE task_id = ? AND kind = 'conversation_migrated'
1556
+ AND event_id > COALESCE(
1557
+ (SELECT MAX(event_id) FROM events WHERE task_id = ? AND kind = 'assistant_completion'), 0
1558
+ )`, (s) => s.get(input.taskId, input.taskId)) as { total?: number } | undefined;
1559
+ if (Number(prior?.total ?? 0) >= WORKFLOW_SUPERVISOR_MAX_CONVERSATION_MIGRATIONS) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_BUDGET_EXHAUSTED');
1560
+ // A submitted mutation whose outcome is unknown must be reconciled, never
1561
+ // abandoned by moving the task elsewhere.
1562
+ const pending = oldestUnappliedEffect(db, input.taskId);
1563
+ if (pending) {
1564
+ const ledger = effectDispatchLedger(db, pending.effectId);
1565
+ const negativeProof = latestNotAppliedProofEventId(db, pending.effectId) > ledger.lastEventId;
1566
+ if (ledger.generations > 0 && !negativeProof) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_OUTCOME_UNKNOWN');
1567
+ }
1568
+ const originKey = `fresh-conversation:${input.taskId}:${input.requestId}`;
1569
+ const replacement = this.reserveEffectWithin(db, {
1570
+ taskId: input.taskId, effectId: input.replacement.effectId, kind: 'enrollment', originKey, prompt: input.replacement.prompt,
1571
+ });
1572
+ if (pending && pending.effectId !== replacement.effectId) {
1573
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1574
+ input.taskId, `effect-retired-fresh-conversation:${pending.effectId}:${input.requestId}`, 'effect_outcome_unknown_superseded',
1575
+ pending.effectId,
1576
+ json({
1577
+ request_id: input.requestId, authorized_by: input.authorizedBy, reason: input.reason,
1578
+ generation: 0, replacement_effect_id: replacement.effectId, attestation: 'never_dispatched_fresh_conversation',
1579
+ }), now()));
1580
+ }
1581
+ statement(db, 'UPDATE tasks SET conversation_id = ?, conversation_url = ? WHERE task_id = ?', (s) => s.run(targetConversationId, 'https://chatgpt.com/', input.taskId));
1582
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(
1583
+ input.taskId, `conversation-migrated:${input.taskId}:${input.requestId}`, 'conversation_migrated',
1584
+ json({
1585
+ from_conversation_id: task.conversationId, to_conversation_id: targetConversationId, to_conversation_url: 'https://chatgpt.com/',
1586
+ fresh_conversation: true, replacement_effect_id: replacement.effectId,
1587
+ request_id: input.requestId, authorized_by: input.authorizedBy, reason: input.reason,
1588
+ ...(pending ? { retired_effect_id: pending.effectId } : {}),
1589
+ }), now()));
1590
+ const updated = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
1591
+ if (!updated) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
1592
+ return {
1593
+ task: taskFromRow(updated), migrated: true,
1594
+ ...(pending && pending.effectId !== replacement.effectId ? { retiredEffectId: pending.effectId } : {}),
1595
+ };
1596
+ });
1597
+ }
1598
+
1599
+ migrateConversation(input: { taskId: string; expectedConversationId: string; conversationId: string; conversationUrl: string; requestId: string; reason: string; authorizedBy: string }): { task: WorkflowSupervisorTask; migrated: boolean } {
1600
+ return this.transaction((db) => {
1601
+ const row = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
1602
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
1603
+ const task = taskFromRow(row);
1604
+ if (task.conversationId !== input.expectedConversationId) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_STALE');
1605
+ if (statement(db, "SELECT 1 FROM events WHERE task_id=? AND kind IN ('terminal_done','terminal_needs_user','terminal_stopped') LIMIT 1", (s) => s.get(input.taskId))) {
1606
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_TERMINAL');
1607
+ }
1608
+ if (task.conversationId === input.conversationId) {
1609
+ if (task.conversationUrl !== input.conversationUrl) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_CONVERSATION_CONFLICT');
1610
+ return { task, migrated: false };
1611
+ }
1612
+ const conflict = statement(db, 'SELECT task_id FROM tasks WHERE conversation_id = ? AND task_id <> ?', (s) => s.get(input.conversationId, input.taskId)) as { task_id?: string } | undefined;
1613
+ if (conflict) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_CONVERSATION_CONFLICT');
1614
+ // Bound repeated unsuccessful moves, not the lifetime of a healthy task.
1615
+ // A genuine completion reopens the budget for its next 15-turn rotation.
1616
+ const prior = statement(db, `SELECT COUNT(*) AS total FROM events
1617
+ WHERE task_id = ? AND kind = 'conversation_migrated'
1618
+ AND event_id > COALESCE(
1619
+ (SELECT MAX(event_id) FROM events WHERE task_id = ? AND kind = 'assistant_completion'), 0
1620
+ )`, (s) => s.get(input.taskId, input.taskId)) as { total?: number } | undefined;
1621
+ if (Number(prior?.total ?? 0) >= WORKFLOW_SUPERVISOR_MAX_CONVERSATION_MIGRATIONS) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_BUDGET_EXHAUSTED');
1622
+ const pending = oldestUnappliedEffect(db, input.taskId);
1623
+ if (pending) {
1624
+ const ledger = effectDispatchLedger(db, pending.effectId);
1625
+ const negativeProof = latestNotAppliedProofEventId(db, pending.effectId) > ledger.lastEventId;
1626
+ if (ledger.generations > 0 && !negativeProof) throw new Error('WORKFLOW_SUPERVISOR_MIGRATION_OUTCOME_UNKNOWN');
1627
+ }
1628
+ statement(db, 'UPDATE tasks SET conversation_id = ?, conversation_url = ? WHERE task_id = ?', (s) => s.run(input.conversationId, input.conversationUrl, input.taskId));
1629
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(
1630
+ input.taskId, `conversation-migrated:${input.taskId}:${input.requestId}`, 'conversation_migrated',
1631
+ json({
1632
+ from_conversation_id: task.conversationId, to_conversation_id: input.conversationId, to_conversation_url: input.conversationUrl,
1633
+ request_id: input.requestId, authorized_by: input.authorizedBy, reason: input.reason,
1634
+ ...(pending ? { pending_effect_id: pending.effectId } : {}),
1635
+ }), now()));
1636
+ const updated = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown> | undefined;
1637
+ if (!updated) throw new Error('WORKFLOW_SUPERVISOR_TASK_PERSIST_FAILED');
1638
+ return { task: taskFromRow(updated), migrated: true };
1639
+ });
1640
+ }
1641
+ reconcileNativePreSendFailure(effectId: string, requestId: string): boolean {
1642
+ // Bounded compatibility repair for the pre-20261002 native dispatcher.
1643
+ // These exact returned reasons occur before Send is clicked. Other unknown
1644
+ // observations, including transport exceptions, are never negative proof.
1645
+ const row = this.read((db) => {
1646
+ const dispatch = effectDispatchLedger(db, effectId);
1647
+ const owner = statement(db, "SELECT payload_json FROM events WHERE event_id=? AND kind='effect_dispatch_started'", (s) => s.get(dispatch.lastEventId)) as { payload_json?: string } | undefined;
1648
+ if (parsedObject(owner?.payload_json).surface !== 'macos-native') return undefined;
1649
+ // Other observers can report a transport error while the native sender
1650
+ // is still preparing. Only that generation's dispatch owner can prove
1651
+ // it returned before clicking Send.
1652
+ const unknown = statement(db, "SELECT payload_json FROM events WHERE effect_id=? AND kind='effect_unknown' AND event_id>? AND json_extract(payload_json,'$.surface')='macos-native' ORDER BY event_id LIMIT 1", (s) => s.get(effectId, dispatch.lastEventId)) as { payload_json?: string } | undefined;
1653
+ return unknown ? { ...unknown, dispatch_generation: dispatch.lastGeneration } : undefined;
1654
+ });
1655
+ const evidence = parsedObject(row?.payload_json);
1656
+ const beforeSend = new Set(['composer_missing', 'composer_not_empty', 'composer_selection_unavailable', 'composer_text_insertion_rejected', 'composer_text_unconfirmed', 'composer_submit_mismatch', 'send_button_missing']);
1657
+ if (evidence.surface !== 'macos-native' || evidence.reconciliation === true || !beforeSend.has(String(evidence.reason))) return false;
1658
+ this.recordEffectNotAppliedProof(effectId, `operator-pre-send:${requestId}`, {
1659
+ ...evidence, send_clicked: false, dispatch_generation: row?.dispatch_generation, pre_send_rejection: true,
1660
+ requested_by: 'user', request_id: requestId, attestation: 'persisted_native_pre_send_return',
1661
+ });
1662
+ return true;
1663
+ }
1664
+ private reserveEffectWithin(db: Database, input: { taskId: string; effectId: string; kind: WorkflowEffectKind; originKey: string; sourceCompletionFingerprint?: string; prompt: string }): WorkflowSupervisorEffect {
1665
+ statement(db, 'INSERT OR IGNORE INTO effects(effect_id,task_id,kind,origin_key,source_completion_fingerprint,prompt_text,created_at) VALUES (?,?,?,?,?,?,?)', (s) => s.run(input.effectId, input.taskId, input.kind, input.originKey, input.sourceCompletionFingerprint ?? null, input.prompt, now()));
1666
+ const row = statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(input.originKey)) as Record<string, unknown> | undefined;
1667
+ if (!row) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_RESERVE_FAILED');
1668
+ const effect = effectFromRow(row);
1669
+ if (effect.effectId !== input.effectId) throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_ID_CONFLICT:${input.originKey}:${effect.effectId}:${input.effectId}`);
1670
+ if (effect.taskId !== input.taskId) throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_TASK_CONFLICT:${input.originKey}`);
1671
+ if (effect.kind !== input.kind) throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_KIND_CONFLICT:${input.originKey}`);
1672
+ if ((effect.sourceCompletionFingerprint ?? '') !== (input.sourceCompletionFingerprint ?? '')) {
1673
+ throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_SOURCE_CONFLICT:${input.originKey}`);
1674
+ }
1675
+ if (effect.prompt !== input.prompt) throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_PROMPT_CONFLICT:${input.originKey}`);
1676
+ return effect;
1677
+ }
1678
+
1679
+ /** Persist a bounded provider pre-send rejection; a started generation requires a durable exact no-click proof first. */
1680
+ recordEffectPreparationFailure(effectId: string, reasonCode: string, composerObservation?: WorkflowSupervisorPreflightComposerObservation): void {
1681
+ this.transaction((db) => {
1682
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(effectId)) as { task_id?: string } | undefined;
1683
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1684
+ const applied = statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effectId));
1685
+ const dispatch = effectDispatchLedger(db, effectId);
1686
+ if (applied || (dispatch.generations > 0 && latestNotAppliedProofEventId(db, effectId) <= dispatch.lastEventId)) {
1687
+ throw new Error('WORKFLOW_SUPERVISOR_PRE_SEND_FAILURE_OUTCOME_NOT_PROVEN');
1688
+ }
1689
+ const activeReleaseId = this.activeReleaseId()?.trim() || 'unidentified-runtime';
1690
+ // After one explicit repair the next real preparation failure MUST be
1691
+ // recorded again; otherwise INSERT OR IGNORE could permit infinite retries.
1692
+ const rearm = statement(db, "SELECT COUNT(*) AS count FROM events WHERE effect_id = ? AND kind = 'effect_preflight_operator_rearmed'", (s) => s.get(effectId)) as { count?: number } | undefined;
1693
+ const key = `effect-preflight-rejected:${effectId}:${activeReleaseId}${Number(rearm?.count ?? 0) > 0 ? ':operator-rearmed' : ''}`;
1694
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1695
+ effect.task_id!, key, 'effect_preflight_rejected', effectId,
1696
+ json({ active_release_id: activeReleaseId, reason: (boundedText(reasonCode) ?? 'COMPUTER_PREPARE_REJECTED').slice(0, 256), send_clicked: false, attestation: 'computer_prepare_returned_before_supervisor_send_intent', ...(composerObservation ? { composer_observation: composerObservation } : {}) }), now(),
1697
+ ));
1698
+ });
1699
+ }
1700
+
1701
+ recordEffectDispatchStarted(effectId: string, generation: number, dispatchId: string, evidence: Record<string, unknown> = {}): boolean {
1702
+ if (!Number.isInteger(generation) || generation < 1 || generation > 1_000_000) throw new Error('WORKFLOW_SUPERVISOR_DISPATCH_GENERATION_INVALID');
1703
+ return this.transaction((db) => {
1704
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(effectId)) as { task_id?: string } | undefined;
1705
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1706
+ // Completion and recovery Send compete in this same Store transaction.
1707
+ // A command obtained before a late completion cannot revive its recovery.
1708
+ if (statement(db, `SELECT 1 FROM effects e JOIN completions c
1709
+ ON c.task_id = e.task_id AND e.origin_key = 'provider-recovery:' || c.source_effect_id
1710
+ WHERE e.effect_id = ? AND e.kind = 'recovery' LIMIT 1`, (s) => s.get(effectId))) return false;
1711
+ const applied = statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effectId));
1712
+ if (applied) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_ALREADY_APPLIED');
1713
+ const refunds = statement(db, "SELECT event_id FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_budget_refunded'", (s) => s.all(effectId)) as Array<{ event_id?: number }>;
1714
+ if (generation > WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS + refunds.length || this.preflightBlocked(db, effectId)) return false;
1715
+ const prior = statement(db, "SELECT event_id,payload_json FROM events WHERE effect_id = ? AND kind = 'effect_dispatch_started' ORDER BY event_id DESC LIMIT 1", (s) => s.get(effectId)) as { event_id?: number; payload_json?: string } | undefined;
1716
+ const currentGeneration = prior?.event_id ? storedGeneration(prior.payload_json) : 0;
1717
+ const retryAuthorized = !prior?.event_id || latestNotAppliedProofEventId(db, effectId) > Number(prior.event_id);
1718
+ if (!retryAuthorized || generation !== currentGeneration + 1) return false;
1719
+ // `dispatched_at_ms` is the mechanical retry-spacing fact. It is recorded
1720
+ // inside the evidence payload so retry spacing stays deterministic under
1721
+ // an injected clock without changing durable event timestamps.
1722
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(effect.task_id, `effect-dispatch:${effectId}:${generation}`, 'effect_dispatch_started', effectId, json({ dispatchId, generation, dispatched_at_ms: this.clockMs(), ...evidence }), now()));
1723
+ return true;
1724
+ });
1725
+ }
1726
+
1727
+ recordEffectObservation(effectId: string, observationId: string, outcome: WorkflowEffectOutcome, evidence: Record<string, unknown> = {}): void {
1728
+ if (outcome === 'not_applied') throw new Error('WORKFLOW_SUPERVISOR_NOT_APPLIED_PROOF_REQUIRED');
1729
+ this.transaction((db) => {
1730
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(effectId)) as { task_id?: string } | undefined;
1731
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1732
+ const key = `effect-observation:${effectId}:${observationId}`;
1733
+ const kind = `effect_${outcome}`;
1734
+ const existing = statement(db, 'SELECT kind,payload_json FROM events WHERE event_key = ?', (s) => s.get(key)) as { kind?: string; payload_json?: string } | undefined;
1735
+ const payload = json(evidence);
1736
+ if (existing && (existing.kind !== kind || existing.payload_json !== payload)) throw new Error('WORKFLOW_SUPERVISOR_OBSERVATION_ID_CONFLICT');
1737
+ // Identical unknown observations remain append-only evidence at the capped
1738
+ // reconciliation cadence. Permanently suppressing them freezes the last
1739
+ // observation timestamp and makes a transient outcome-unknown state unable
1740
+ // to discover late provider evidence after Runtime/browser recovery.
1741
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(effect.task_id, key, kind, effectId, payload, now()));
1742
+ });
1743
+ }
1744
+
1745
+ recordEffectNotAppliedProof(effectId: string, observationId: string, proof: Record<string, unknown>): void {
1746
+ this.transaction((db) => {
1747
+ const effect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(effectId)) as { task_id?: string } | undefined;
1748
+ if (!effect?.task_id) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_UNKNOWN');
1749
+ const applied = statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(effectId));
1750
+ if (applied) throw new Error('WORKFLOW_SUPERVISOR_EFFECT_ALREADY_APPLIED');
1751
+ const key = `effect-observation:${effectId}:${observationId}`;
1752
+ const payload = json(proof);
1753
+ const existing = statement(db, 'SELECT kind,payload_json FROM events WHERE event_key = ?', (s) => s.get(key)) as { kind?: string; payload_json?: string } | undefined;
1754
+ if (existing && (existing.kind !== 'effect_not_applied' || existing.payload_json !== payload)) throw new Error('WORKFLOW_SUPERVISOR_OBSERVATION_ID_CONFLICT');
1755
+ if (existing) return; // An identical old receipt is idempotent, not fresh retry authority.
1756
+ const ledger = effectDispatchLedger(db, effectId);
1757
+ const proofGeneration = Number(proof.dispatch_generation);
1758
+ if (!Number.isInteger(proofGeneration) || proofGeneration < 1 || proofGeneration !== ledger.lastGeneration) {
1759
+ throw new Error('WORKFLOW_SUPERVISOR_NOT_APPLIED_PROOF_GENERATION_STALE');
1760
+ }
1761
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(effect.task_id, key, 'effect_not_applied', effectId, payload, now()));
1762
+ const legacyRefund = statement(db, 'SELECT event_id FROM events WHERE event_key = ?', (s) => s.get(`effect-dispatch-budget-refund:${effectId}`)) as { event_id?: number } | undefined;
1763
+ const legacyRefundCoversGeneration = proofGeneration === WORKFLOW_SUPERVISOR_MAX_DISPATCH_GENERATIONS && Boolean(legacyRefund);
1764
+ if (proof.pre_send_rejection === true
1765
+ && Number.isInteger(proofGeneration) && proofGeneration > 0
1766
+ && proofGeneration === ledger.lastGeneration
1767
+ && !legacyRefundCoversGeneration) {
1768
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(
1769
+ effect.task_id, `effect-dispatch-budget-refund:${effectId}:${proofGeneration}`, 'effect_dispatch_budget_refunded', effectId,
1770
+ json({ reason: 'pre_send_rejection_did_not_reach_provider', dispatch_generation: proofGeneration }), now(),
1771
+ ));
1772
+ }
1773
+ });
1774
+ }
1775
+
1776
+ commitCompletion(input: WorkflowSupervisorCompletion, successor?: { effectId: string; kind: WorkflowEffectKind; prompt: string; freshEnrollmentPrompt?: string }): { completion: WorkflowSupervisorCompletion; successorEffect?: WorkflowSupervisorEffect; deduplicated: boolean } {
1777
+ return this.transaction((db) => {
1778
+ const task = statement(db, 'SELECT 1 AS ok FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId));
1779
+ if (!task) throw new Error('WORKFLOW_SUPERVISOR_TASK_UNKNOWN');
1780
+ const sourceEffect = statement(db, 'SELECT task_id FROM effects WHERE effect_id = ?', (s) => s.get(input.sourceEffectId)) as { task_id?: string } | undefined;
1781
+ if (sourceEffect?.task_id !== input.taskId) throw new Error('WORKFLOW_SUPERVISOR_SOURCE_EFFECT_MISMATCH');
1782
+ const applied = statement(db, "SELECT 1 AS ok FROM events WHERE effect_id = ? AND kind = 'effect_applied' LIMIT 1", (s) => s.get(input.sourceEffectId));
1783
+ if (!applied) throw new Error('WORKFLOW_SUPERVISOR_SOURCE_EFFECT_NOT_APPLIED');
1784
+ const priorForEffect = statement(db, 'SELECT completion_fingerprint FROM completions WHERE task_id = ? AND source_effect_id = ?', (s) => s.get(input.taskId, input.sourceEffectId)) as { completion_fingerprint?: string } | undefined;
1785
+ if (priorForEffect?.completion_fingerprint && priorForEffect.completion_fingerprint !== input.completionFingerprint) {
1786
+ throw new Error('WORKFLOW_SUPERVISOR_SOURCE_EFFECT_COMPLETION_CONFLICT');
1787
+ }
1788
+ const existing = statement(db, 'SELECT completion_fingerprint FROM completions WHERE completion_fingerprint = ?', (s) => s.get(input.completionFingerprint));
1789
+ // Once the recovery has a Send intent, its outcome may be unknown. A
1790
+ // late source reply must not create a parallel successor or rotate away
1791
+ // from that obligation. Exact historical completion replay stays inert.
1792
+ if (!existing && statement(db, `SELECT 1 FROM effects child JOIN events started
1793
+ ON started.effect_id = child.effect_id AND started.kind = 'effect_dispatch_started'
1794
+ WHERE child.task_id = ? AND child.origin_key = ? LIMIT 1`,
1795
+ (s) => s.get(input.taskId, 'provider-recovery:' + input.sourceEffectId))) {
1796
+ throw new Error('WORKFLOW_SUPERVISOR_SOURCE_EFFECT_RECOVERY_DISPATCHED');
1797
+ }
1798
+ if (!existing) statement(db, 'INSERT INTO completions VALUES (?,?,?,?,?,?,?,?)', (s) => s.run(input.completionFingerprint, input.taskId, input.sourceEffectId, input.action, input.responseSha256, input.controlBlockSha256, json(input.proposal), input.committedAt));
1799
+ statement(db, 'INSERT OR IGNORE INTO events(task_id,event_key,kind,effect_id,completion_fingerprint,payload_json,occurred_at) VALUES (?,?,?,?,?,?,?)', (s) => s.run(input.taskId, `completion:${input.completionFingerprint}`, 'assistant_completion', input.sourceEffectId, input.completionFingerprint, json(input.proposal), input.committedAt));
1800
+ let successorEffect: WorkflowSupervisorEffect | undefined;
1801
+ if (successor) {
1802
+ const originKey = `completion:${input.completionFingerprint}`;
1803
+ const priorSuccessorRow = existing
1804
+ ? statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(originKey)) as Record<string, unknown> | undefined
1805
+ : undefined;
1806
+ if (priorSuccessorRow) {
1807
+ const priorSuccessor = effectFromRow(priorSuccessorRow);
1808
+ if (priorSuccessor.taskId !== input.taskId) throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_TASK_CONFLICT:${originKey}`);
1809
+ if (priorSuccessor.kind !== successor.kind && !(successor.freshEnrollmentPrompt && priorSuccessor.kind === 'enrollment')) {
1810
+ throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_KIND_CONFLICT:${originKey}`);
1811
+ }
1812
+ if ((priorSuccessor.sourceCompletionFingerprint ?? '') !== input.completionFingerprint) {
1813
+ throw new Error(`WORKFLOW_SUPERVISOR_EFFECT_SOURCE_CONFLICT:${originKey}`);
1814
+ }
1815
+ successorEffect = priorSuccessor;
1816
+ } else {
1817
+ // User-authorized periodic rotation is a mechanical cadence over
1818
+ // completed CONTINUE receipts, never sends, elapsed time or Work state.
1819
+ // The completion, fresh enrollment, binding change and migration
1820
+ // evidence commit atomically in the existing Supervisor journal.
1821
+ const current = statement(db, 'SELECT * FROM tasks WHERE task_id = ?', (s) => s.get(input.taskId)) as Record<string, unknown>;
1822
+ const currentTask = taskFromRow(current);
1823
+ const lastMigration = statement(db, "SELECT MAX(event_id) AS id FROM events WHERE task_id=? AND kind='conversation_migrated'", (s) => s.get(input.taskId)) as { id?: number };
1824
+ const completedSinceMigration = statement(db, `SELECT COUNT(*) AS total FROM events e
1825
+ JOIN completions c ON c.completion_fingerprint=e.completion_fingerprint
1826
+ WHERE e.task_id=? AND e.kind='assistant_completion' AND e.event_id>? AND c.action='CONTINUE'`,
1827
+ (s) => s.get(input.taskId, lastMigration?.id ?? 0)) as { total?: number };
1828
+ const rotate = successor.freshEnrollmentPrompt !== undefined
1829
+ && currentTask.continuationPolicy.kind === 'standalone_supervisor'
1830
+ && !currentTask.conversationId.startsWith('bootstrap:')
1831
+ && (completedSinceMigration.total ?? 0) >= 15;
1832
+ successorEffect = this.reserveEffectWithin(db, {
1833
+ taskId: input.taskId, effectId: successor.effectId,
1834
+ kind: rotate ? 'enrollment' : successor.kind, originKey,
1835
+ sourceCompletionFingerprint: input.completionFingerprint,
1836
+ prompt: rotate ? successor.freshEnrollmentPrompt! : successor.prompt,
1837
+ });
1838
+ if (rotate) {
1839
+ const sourceConversationId = currentTask.conversationId;
1840
+ const bootstrapConversationId = `bootstrap:${input.taskId}`;
1841
+ statement(db, 'UPDATE tasks SET conversation_id=?, conversation_url=? WHERE task_id=?',
1842
+ (s) => s.run(bootstrapConversationId, 'https://chatgpt.com/', input.taskId));
1843
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,payload_json,occurred_at) VALUES (?,?,?,?,?)', (s) => s.run(
1844
+ input.taskId, `conversation-migrated:auto:${input.completionFingerprint}`, 'conversation_migrated',
1845
+ json({ from_conversation_id: sourceConversationId, to_conversation_id: bootstrapConversationId,
1846
+ to_conversation_url: 'https://chatgpt.com/', fresh_conversation: true,
1847
+ replacement_effect_id: successorEffect!.effectId, request_id: `automatic-receipt:${input.completionFingerprint}`,
1848
+ authorized_by: 'user-configured-standalone-rotation', reason: '15_confirmed_continue_receipts' }), now()));
1849
+ }
1850
+ }
1851
+ }
1852
+ return { completion: input, ...(successorEffect ? { successorEffect } : {}), deduplicated: Boolean(existing) };
1853
+ });
1854
+ }
1855
+
1856
+ resolveTerminal(input: { completionFingerprint: string; taskId: string; action: 'DONE' | 'NEEDS_USER'; accepted: boolean; reason: string; correction?: { effectId: string; prompt: string } }): { successorEffect?: WorkflowSupervisorEffect; deduplicated: boolean } {
1857
+ return this.transaction((db) => {
1858
+ const key = `terminal-resolution:${input.completionFingerprint}`;
1859
+ const existing = statement(db, 'SELECT kind,payload_json FROM events WHERE event_key = ?', (s) => s.get(key)) as { kind?: string; payload_json?: string } | undefined;
1860
+ if (existing) {
1861
+ const effect = statement(db, 'SELECT * FROM effects WHERE origin_key = ?', (s) => s.get(`completion:${input.completionFingerprint}`));
1862
+ return { ...(effect ? { successorEffect: effectFromRow(effect as Record<string, unknown>) } : {}), deduplicated: true };
1863
+ }
1864
+ const kind = input.accepted ? (input.action === 'DONE' ? 'terminal_done' : 'terminal_needs_user') : 'terminal_rejected';
1865
+ statement(db, 'INSERT INTO events(task_id,event_key,kind,completion_fingerprint,payload_json,occurred_at) VALUES (?,?,?,?,?,?)', (s) => s.run(input.taskId, key, kind, input.completionFingerprint, json({ reason: input.reason }), now()));
1866
+ const successorEffect = !input.accepted && input.correction ? this.reserveEffectWithin(db, { taskId: input.taskId, effectId: input.correction.effectId, kind: 'correction', originKey: `completion:${input.completionFingerprint}`, sourceCompletionFingerprint: input.completionFingerprint, prompt: input.correction.prompt }) : undefined;
1867
+ return { ...(successorEffect ? { successorEffect } : {}), deduplicated: false };
1868
+ });
1869
+ }
1870
+ }