@moretea-labs/forge 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (900) hide show
  1. package/AGENTS.md +121 -62
  2. package/CHANGELOG.md +40 -0
  3. package/CLAUDE.md +57 -62
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.md +2 -2
  6. package/SKILL.md +0 -1
  7. package/THIRD_PARTY_NOTICES.md +5 -2
  8. package/adapters/browser/index.ts +2 -0
  9. package/adapters/chatgpt/browser-delivery-host.ts +77 -0
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
  11. package/adapters/chatgpt/controller-binding-store.ts +134 -0
  12. package/adapters/chatgpt/controller-round-host.ts +68 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
  14. package/adapters/chatgpt/provider-delivery.ts +340 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  18. package/adapters/computer/chatgpt-target-router.ts +103 -0
  19. package/adapters/computer/chrome-extension/background.js +385 -0
  20. package/adapters/computer/chrome-extension/content.js +378 -0
  21. package/adapters/computer/chrome-extension/core.js +24 -0
  22. package/adapters/computer/chrome-extension/manifest.json +30 -0
  23. package/adapters/computer/desktop-operator-contract.ts +7 -0
  24. package/adapters/computer/desktop-operator-discovery.ts +181 -0
  25. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  26. package/adapters/computer/desktop-operator-provider.ts +312 -0
  27. package/adapters/computer/index.ts +1 -0
  28. package/adapters/computer/interaction-target-authority.ts +1068 -0
  29. package/adapters/controller-process/binding-store.ts +34 -0
  30. package/adapters/controller-process/controller-host.ts +26 -0
  31. package/adapters/mcp/access-mode.ts +132 -0
  32. package/adapters/mcp/audit.ts +32 -0
  33. package/adapters/mcp/auth.ts +406 -0
  34. package/adapters/mcp/instructions.ts +37 -0
  35. package/adapters/mcp/legacy-context.ts +18 -0
  36. package/adapters/mcp/multi-repository.ts +421 -0
  37. package/adapters/mcp/oauth.ts +464 -0
  38. package/adapters/mcp/paths.ts +143 -0
  39. package/adapters/mcp/policy.ts +232 -0
  40. package/adapters/mcp/proxy-env.ts +62 -0
  41. package/adapters/mcp/readiness-projection.ts +24 -0
  42. package/adapters/mcp/redaction.ts +21 -0
  43. package/adapters/mcp/repo.ts +24 -0
  44. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  45. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  46. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  47. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  48. package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
  49. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  50. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
  51. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  52. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
  53. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  54. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  55. package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
  56. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  57. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
  58. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  59. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  60. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  61. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  62. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
  63. package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
  64. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
  65. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
  66. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  67. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  68. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
  69. package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
  70. package/adapters/mcp/runtime-gateway/router.ts +242 -0
  71. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  72. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
  73. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  74. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
  75. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  76. package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
  77. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
  78. package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
  79. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
  80. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  81. package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
  82. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  83. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  84. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  85. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  86. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  87. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  88. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  89. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  90. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  91. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  92. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  93. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  94. package/adapters/mcp/server.ts +1092 -0
  95. package/adapters/mcp/setup.ts +789 -0
  96. package/adapters/mcp/tool-capability-cache.ts +87 -0
  97. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  98. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
  99. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
  100. package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
  101. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  102. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  103. package/adapters/mcp/toolset-names.ts +79 -0
  104. package/adapters/mcp/toolset-selection.ts +41 -0
  105. package/adapters/mcp/toolset.ts +337 -0
  106. package/adapters/mcp/transports/http-observation.ts +365 -0
  107. package/adapters/mcp/transports/http.ts +933 -0
  108. package/adapters/mcp/transports/oauth-http.ts +322 -0
  109. package/adapters/mcp/transports/session-registry.ts +519 -0
  110. package/adapters/mcp/transports/stdio.ts +8 -0
  111. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
  112. package/adapters/mcp/types.ts +41 -0
  113. package/assets/AGENTS.md +2 -16
  114. package/assets/CLAUDE.md +2 -16
  115. package/assets/hooks/AGENTS.md +3 -14
  116. package/assets/hooks/CLAUDE.md +3 -14
  117. package/assets/hooks/hook-input.sh +9 -2
  118. package/assets/hooks/lib/workflow-state.sh +28 -10
  119. package/assets/hooks/prompt-guard.sh +63 -37
  120. package/assets/hooks/security-sentinel.sh +2 -1
  121. package/assets/hooks/session-start-context.sh +7 -5
  122. package/assets/hooks/stop-orchestrator.sh +13 -3
  123. package/assets/initializer-question-pack.v4.json +11 -7
  124. package/assets/partials/05-workflow.partial.md +18 -73
  125. package/assets/partials/07-footer.partial.md +1 -1
  126. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  127. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  128. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  129. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  130. package/assets/plan-map.json +11 -11
  131. package/assets/plugin-registry.v1.json +11 -3
  132. package/assets/plugin-trust-policy.v1.json +6 -0
  133. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  134. package/assets/recovery/forge-wsl-rescue +271 -0
  135. package/assets/reference-configs/agentic-development-flow.md +25 -0
  136. package/assets/reference-configs/development-protocol.md +11 -15
  137. package/assets/reference-configs/document-generation.md +1 -1
  138. package/assets/reference-configs/global-working-rules.md +28 -1
  139. package/assets/reference-configs/harness-overview.md +22 -112
  140. package/assets/reference-configs/hook-operations.md +1 -1
  141. package/assets/reference-configs/sprint-contracts.md +2 -2
  142. package/assets/skill-version.json +2 -2
  143. package/assets/templates/helpers/architecture-event.ts +3 -34
  144. package/assets/templates/helpers/architecture-queue.sh +61 -10
  145. package/assets/templates/helpers/archive-workflow.sh +90 -180
  146. package/assets/templates/helpers/capability-config.ts +8 -11
  147. package/assets/templates/helpers/capability-resolver.ts +37 -19
  148. package/assets/templates/helpers/capture-plan.sh +2 -2
  149. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  150. package/assets/templates/helpers/check-task-sync.sh +82 -11
  151. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  152. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  153. package/assets/templates/helpers/contract-worktree.sh +26 -24
  154. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  155. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  156. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  157. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  158. package/assets/templates/helpers/new-plan.sh +2 -2
  159. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  160. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  161. package/assets/templates/helpers/switch-plan.sh +1 -1
  162. package/assets/templates/helpers/workflow-contract.ts +1 -0
  163. package/assets/workflow-contract.v1.json +63 -171
  164. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  165. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  166. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  167. package/docs/architecture/CURRENT.md +233 -28
  168. package/docs/architecture/EVOLUTION.md +13 -0
  169. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  170. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  171. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  172. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  173. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  174. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  175. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
  176. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
  177. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  178. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  179. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  180. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  181. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  182. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  183. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  184. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  185. package/docs/architecture/domains/verification.md +8 -0
  186. package/docs/architecture/domains/workflow-engine.md +7 -1
  187. package/docs/architecture/index.md +3 -3
  188. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  189. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  190. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  191. package/docs/forge-plugin-management.md +20 -4
  192. package/docs/forge-runtime-self-healing-loop.md +2 -0
  193. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  194. package/docs/operations/controller-browser-plugin.md +3 -1
  195. package/docs/operations/controller-performance-and-502.md +17 -0
  196. package/docs/operations/controller-reliability-runbook.md +2 -2
  197. package/docs/operations/features.md +1 -1
  198. package/docs/operations/features.zh-CN.md +1 -1
  199. package/docs/operations/homebrew.md +2 -2
  200. package/docs/operations/mcp-tool-exposure.md +2 -0
  201. package/docs/operations/platform-support.md +16 -8
  202. package/docs/operations/platform-support.zh-CN.md +2 -2
  203. package/docs/operations/releasing.md +2 -0
  204. package/docs/operations/standalone-disaster-recovery.md +38 -5
  205. package/docs/operations/uu-remote-rescue.md +41 -0
  206. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  207. package/docs/public-usage-guide.md +1 -1
  208. package/docs/public-usage-guide.zh-CN.md +1 -1
  209. package/docs/tutorials/01-install-and-start.md +1 -1
  210. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  211. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  212. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  213. package/docs/wiki/Implementation.md +1 -1
  214. package/package.json +46 -26
  215. package/packages/kernel/cognition/api/index.ts +6 -0
  216. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  217. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  218. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  219. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  220. package/packages/kernel/cognition/domain/learning.ts +106 -0
  221. package/packages/kernel/cognition/domain/memory.ts +254 -0
  222. package/packages/kernel/controller/api/index.ts +3 -0
  223. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  224. package/packages/kernel/controller/application/controller-round-service.ts +41 -0
  225. package/packages/kernel/controller/application/controller-service.ts +30 -0
  226. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
  227. package/packages/kernel/controller/domain/controller-round.ts +110 -0
  228. package/packages/kernel/controller/domain/execution-quality.ts +135 -0
  229. package/packages/kernel/controller/domain/types.ts +56 -0
  230. package/packages/kernel/controller/index.ts +1 -0
  231. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  232. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
  233. package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
  234. package/packages/kernel/controller/ports/controller-host.ts +18 -0
  235. package/packages/kernel/goal/api/index.ts +1 -0
  236. package/packages/kernel/goal/domain/types.ts +40 -0
  237. package/packages/kernel/goal/index.ts +1 -0
  238. package/packages/kernel/identity/api/index.ts +15 -0
  239. package/packages/kernel/identity/application/identity-service.ts +67 -0
  240. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  241. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  242. package/packages/kernel/identity/domain/grant.ts +72 -0
  243. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  244. package/packages/kernel/identity/domain/scope.ts +63 -0
  245. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  246. package/packages/kernel/identity/domain/types.ts +44 -0
  247. package/packages/kernel/identity/domain/user-request.ts +93 -0
  248. package/packages/kernel/identity/domain/workspace.ts +56 -0
  249. package/packages/kernel/identity/index.ts +1 -0
  250. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  251. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  252. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  253. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  254. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  255. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  256. package/packages/kernel/memory/api/index.ts +5 -0
  257. package/packages/kernel/memory/application/experience-service.ts +118 -0
  258. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  259. package/packages/kernel/memory/domain/experience.ts +103 -0
  260. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  261. package/packages/kernel/memory/index.ts +1 -0
  262. package/packages/kernel/scheduler/api/index.ts +4 -0
  263. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  264. package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
  265. package/packages/kernel/scheduler/application/settlement.ts +182 -0
  266. package/packages/kernel/scheduler/domain/schedule.ts +236 -0
  267. package/packages/kernel/scheduler/index.ts +1 -0
  268. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
  269. package/packages/kernel/work/api/index.ts +16 -0
  270. package/packages/kernel/work/application/work-service.ts +123 -0
  271. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  272. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  273. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  274. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  275. package/packages/kernel/work/domain/engineering-design.ts +286 -0
  276. package/packages/kernel/work/domain/engineering-profile.ts +238 -0
  277. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  278. package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
  279. package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
  280. package/packages/kernel/work/domain/implementation-review.ts +193 -0
  281. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  282. package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
  283. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  284. package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
  285. package/packages/kernel/work/domain/types.ts +534 -0
  286. package/packages/kernel/work/index.ts +1 -0
  287. package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
  288. package/packages/kernel/work/ports/work-contract-store.ts +21 -0
  289. package/packages/plugin-runtime/browser/index.ts +3 -0
  290. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  291. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  292. package/packages/plugin-runtime/browser/session-authority.ts +62 -0
  293. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  294. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  295. package/packages/plugin-runtime/computer/index.ts +8 -0
  296. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  297. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  298. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  299. package/packages/plugin-runtime/computer/provider.ts +19 -0
  300. package/packages/plugin-runtime/computer/target-authority.ts +215 -0
  301. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  302. package/packages/plugin-runtime/external/index.ts +1 -0
  303. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
  304. package/packages/plugin-runtime/index.ts +3 -0
  305. package/packages/protocols/browser/index.ts +1 -0
  306. package/packages/protocols/browser/session.ts +98 -0
  307. package/packages/protocols/computer/contract.ts +225 -0
  308. package/packages/protocols/computer/index.ts +1 -0
  309. package/packages/protocols/failure/contract.ts +158 -0
  310. package/packages/protocols/failure/index.ts +1 -0
  311. package/packages/protocols/handoff/index.ts +1 -0
  312. package/packages/protocols/handoff/status.ts +22 -0
  313. package/packages/protocols/mcp/execution-context.ts +11 -0
  314. package/packages/protocols/mcp/index.ts +2 -0
  315. package/packages/protocols/mcp/tool-contract.ts +42 -0
  316. package/packages/workflow-runtime/api/index.ts +2 -0
  317. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  318. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  319. package/packages/workflow-runtime/index.ts +1 -0
  320. package/scripts/activate-source-baseline.ts +7 -9
  321. package/scripts/assemble-template.ts +1 -1
  322. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  323. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  324. package/scripts/canary-controller-semantic-wait.ts +183 -0
  325. package/scripts/canary-global-hook.sh +2 -2
  326. package/scripts/check-mcp-compatibility.ts +85 -7
  327. package/scripts/check-platform-support.mjs +28 -2
  328. package/scripts/check-public-docs.sh +1 -1
  329. package/scripts/check-repository-hygiene.mjs +92 -0
  330. package/scripts/check-runtime-architecture.mjs +1643 -127
  331. package/scripts/check-source-duplication.mjs +9 -1
  332. package/scripts/controller-ngrok-rotation.sh +5 -4
  333. package/scripts/create-project-dirs.sh +10 -74
  334. package/scripts/forge-local-recovery-helper.mjs +282 -0
  335. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  336. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  337. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  338. package/scripts/hook-shim.sh +2 -2
  339. package/scripts/init-project.sh +12 -70
  340. package/scripts/install-desktop-operator-registration.ts +4 -2
  341. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  342. package/scripts/install-standalone-recovery.ts +122 -10
  343. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  344. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  345. package/scripts/lib/controller-home-env.sh +6 -3
  346. package/scripts/lib/project-init-lib.sh +42 -582
  347. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  348. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  349. package/scripts/open-source-audit-allowlist.txt +11 -1
  350. package/scripts/portability-bindings.mjs +70 -0
  351. package/scripts/run-bounded-command.mjs +60 -0
  352. package/scripts/run-bun-test-file.ts +22 -6
  353. package/scripts/run-governed-gate.ts +4 -2
  354. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  355. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  356. package/scripts/smoke-mcp-http-runtime.ts +6 -1
  357. package/scripts/smoke-runtime-recovery.ts +13 -11
  358. package/scripts/smoke-schedule-engine.ts +24 -16
  359. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  360. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  361. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  362. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  363. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  364. package/scripts/smoke-workflow-supervisor.ts +103 -0
  365. package/scripts/test-governance.ts +51 -2
  366. package/scripts/verify-forge-runtime.sh +2 -1
  367. package/src/cli/agent-jobs/executor-health.ts +11 -34
  368. package/src/cli/agent-jobs/integration.ts +18 -49
  369. package/src/cli/agent-jobs/job-manager.ts +17 -4
  370. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  371. package/src/cli/chatgpt-browser/binding.ts +20 -10
  372. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  373. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  374. package/src/cli/chatgpt-browser/engine.ts +10 -11
  375. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  376. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  377. package/src/cli/chatgpt-browser/types.ts +1 -0
  378. package/src/cli/commands/bootstrap-control.ts +334 -0
  379. package/src/cli/commands/brain-assistant.ts +52 -0
  380. package/src/cli/commands/brain.ts +191 -40
  381. package/src/cli/commands/chatgpt.ts +32 -67
  382. package/src/cli/commands/computer.ts +437 -0
  383. package/src/cli/commands/doctor.ts +18 -1
  384. package/src/cli/commands/init-hook.ts +71 -61
  385. package/src/cli/commands/init.ts +3 -1
  386. package/src/cli/commands/mcp.ts +16 -10
  387. package/src/cli/commands/plugin.ts +38 -12
  388. package/src/cli/commands/recovery.ts +507 -80
  389. package/src/cli/commands/runtime.ts +12 -7
  390. package/src/cli/commands/setup-profile.ts +97 -39
  391. package/src/cli/commands/status.ts +2 -1
  392. package/src/cli/controller/check-runner.ts +500 -100
  393. package/src/cli/controller/completion-backlog.ts +3 -3
  394. package/src/cli/controller/completion-orchestrator.ts +4 -43
  395. package/src/cli/controller/completion-target.ts +3 -72
  396. package/src/cli/controller/context/source-materializer.ts +81 -100
  397. package/src/cli/controller/context/types.ts +7 -1
  398. package/src/cli/controller/context-pack.ts +28 -6
  399. package/src/cli/controller/execution-completion.ts +4 -7
  400. package/src/cli/controller/execution-policy.ts +36 -153
  401. package/src/cli/controller/governance.ts +2 -3
  402. package/src/cli/controller/issue-store.ts +4 -99
  403. package/src/cli/controller/launch-agents.ts +59 -18
  404. package/src/cli/controller/operational-plan.ts +24 -51
  405. package/src/cli/controller/runtime-config.ts +5 -1
  406. package/src/cli/controller/stuck-state-migration.ts +2 -3
  407. package/src/cli/controller/systemd-user.ts +148 -0
  408. package/src/cli/controller/types.ts +10 -70
  409. package/src/cli/controller/worklog.ts +67 -13
  410. package/src/cli/editing/edit-operation-contract.ts +92 -0
  411. package/src/cli/editing/edit-session.ts +290 -20
  412. package/src/cli/github/plugin.ts +17 -12
  413. package/src/cli/hook/route-registry.ts +3 -5
  414. package/src/cli/hook/runtime.ts +49 -3
  415. package/src/cli/index.ts +3 -0
  416. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  417. package/src/cli/local-bridge/console-automations.ts +20 -15
  418. package/src/cli/local-bridge/console-view-models.ts +3 -12
  419. package/src/cli/local-bridge/facade-api.ts +105 -205
  420. package/src/cli/local-bridge/job-store.ts +1 -1
  421. package/src/cli/local-bridge/server.ts +48 -174
  422. package/src/cli/local-bridge/types.ts +0 -1
  423. package/src/cli/mcp/access-mode.ts +2 -132
  424. package/src/cli/mcp/access-tools.ts +2 -387
  425. package/src/cli/mcp/audit.ts +2 -32
  426. package/src/cli/mcp/auth.ts +2 -404
  427. package/src/cli/mcp/instructions.ts +2 -34
  428. package/src/cli/mcp/legacy-context.ts +2 -16
  429. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  430. package/src/cli/mcp/multi-repository.ts +2 -436
  431. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  432. package/src/cli/mcp/oauth.ts +2 -375
  433. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  434. package/src/cli/mcp/paths.ts +2 -140
  435. package/src/cli/mcp/policy.ts +2 -232
  436. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  437. package/src/cli/mcp/proxy-env.ts +2 -62
  438. package/src/cli/mcp/readiness-projection.ts +2 -24
  439. package/src/cli/mcp/redaction.ts +2 -21
  440. package/src/cli/mcp/repo.ts +2 -24
  441. package/src/cli/mcp/repository-tools.ts +2 -1613
  442. package/src/cli/mcp/server.ts +2 -951
  443. package/src/cli/mcp/setup.ts +2 -737
  444. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  445. package/src/cli/mcp/tool-contract.ts +2 -14
  446. package/src/cli/mcp/tools.ts +2 -9
  447. package/src/cli/mcp/toolset-names.ts +2 -70
  448. package/src/cli/mcp/toolset-selection.ts +2 -41
  449. package/src/cli/mcp/toolset.ts +2 -336
  450. package/src/cli/mcp/transports/AGENTS.md +1 -10
  451. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  452. package/src/cli/mcp/transports/http.ts +2 -1414
  453. package/src/cli/mcp/transports/session-registry.ts +2 -490
  454. package/src/cli/mcp/transports/stdio.ts +2 -8
  455. package/src/cli/mcp/types.ts +2 -41
  456. package/src/cli/mcp/umbrella-tools.ts +2 -117
  457. package/src/cli/repositories/command-executor.ts +91 -39
  458. package/src/cli/repositories/command-scope.ts +139 -4
  459. package/src/cli/repositories/controller-home.ts +167 -1
  460. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  461. package/src/cli/repositories/git-command-executor.ts +2 -2
  462. package/src/cli/repositories/identity.ts +13 -0
  463. package/src/cli/repositories/registry.ts +145 -60
  464. package/src/cli/repositories/repository-snapshot.ts +53 -7
  465. package/src/cli/repositories/runtime-storage.ts +27 -2
  466. package/src/cli/repositories/safe-patch.ts +34 -38
  467. package/src/cli/repositories/selected-path-actions.ts +22 -2
  468. package/src/cli/repositories/structured-git.ts +167 -27
  469. package/src/cli/repository/session-cache.ts +92 -6
  470. package/src/cli/runtime/helper-runner.ts +7 -0
  471. package/src/cli/runtime-invocation.ts +8 -1
  472. package/src/cli/tools/codegraph.ts +44 -25
  473. package/src/core/adoption/manifest-templates.ts +1 -1
  474. package/src/core/adoption/plan.ts +3 -26
  475. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  476. package/src/effects/process-runner.ts +8 -14
  477. package/src/runtime/context/assistant-context.ts +167 -0
  478. package/src/runtime/context/assistant-work-context.ts +209 -0
  479. package/src/runtime/context/automatic-learning.ts +565 -0
  480. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  481. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  482. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  483. package/src/runtime/context/cognitive-attention.ts +173 -0
  484. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  485. package/src/runtime/context/context-closure.ts +305 -0
  486. package/src/runtime/context/context-pack-process.ts +103 -0
  487. package/src/runtime/context/context-plane.ts +203 -0
  488. package/src/runtime/context/direct-controller-learning.ts +298 -0
  489. package/src/runtime/context/project-engineering-contract.ts +60 -0
  490. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  491. package/src/runtime/context/semantic-navigation.ts +6 -34
  492. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  493. package/src/runtime/context/typescript-navigation.ts +89 -3
  494. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  495. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  496. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  497. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  498. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  499. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  500. package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
  501. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  502. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
  503. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
  504. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
  505. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  506. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  507. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  508. package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
  509. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  510. package/src/runtime/control-plane/execution/validation.ts +34 -11
  511. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
  512. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  513. package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
  514. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  515. package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
  516. package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
  517. package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
  518. package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
  519. package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
  520. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
  521. package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
  522. package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
  523. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  524. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  525. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
  526. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
  527. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  528. package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
  529. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
  530. package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
  531. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  532. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  533. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  534. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  535. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
  536. package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
  537. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
  538. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
  539. package/src/runtime/control-plane/facade/index.ts +4 -3
  540. package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
  541. package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
  542. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  543. package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
  544. package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
  545. package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
  546. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
  547. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  548. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  549. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
  550. package/src/runtime/control-plane/facade/types.ts +218 -620
  551. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  552. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  553. package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
  554. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  555. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
  556. package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
  557. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  558. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  559. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
  560. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  561. package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
  562. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  563. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  564. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  565. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  566. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
  567. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  568. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
  569. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  570. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
  571. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
  572. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  573. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  574. package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
  575. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  576. package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
  577. package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
  578. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  579. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  580. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  581. package/src/runtime/control-plane/release-retention.ts +154 -26
  582. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  583. package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
  584. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  585. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  586. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
  587. package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
  588. package/src/runtime/diagnostics/incident-repair.ts +573 -0
  589. package/src/runtime/diagnostics/mcp-timing.ts +239 -6
  590. package/src/runtime/diagnostics/performance.ts +6 -14
  591. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  592. package/src/runtime/evidence/artifact-store.ts +216 -35
  593. package/src/runtime/evidence/evidence-store.ts +112 -8
  594. package/src/runtime/evidence/operational-shadow.ts +38 -0
  595. package/src/runtime/evidence/output-handle-index.ts +225 -0
  596. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  597. package/src/runtime/evidence/result-store.ts +158 -44
  598. package/src/runtime/evidence/sensitive-output.ts +4 -4
  599. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  600. package/src/runtime/execution/jobs/store.ts +124 -6
  601. package/src/runtime/execution/managed-workspace.ts +112 -5
  602. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  603. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  604. package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
  605. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  606. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  607. package/src/runtime/execution/process-runtime/check-result.ts +177 -2
  608. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
  609. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  610. package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
  611. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  612. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  613. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  614. package/src/runtime/execution/process-runtime/index.ts +4 -0
  615. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  616. package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
  617. package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
  618. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  619. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  620. package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
  621. package/src/runtime/execution/process-runtime/runtime.ts +167 -17
  622. package/src/runtime/execution/process-runtime/store.ts +30 -2
  623. package/src/runtime/execution/process-runtime/types.ts +27 -0
  624. package/src/runtime/execution/workers/executor.ts +18 -19
  625. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  626. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  627. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  628. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  629. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  630. package/src/runtime/gateway/mcp/router.ts +2 -1125
  631. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  632. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  633. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  634. package/src/runtime/maintenance/cleanup.ts +38 -24
  635. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  636. package/src/runtime/platform/computer-platform.ts +14 -0
  637. package/src/runtime/platform/executable-discovery.ts +54 -0
  638. package/src/runtime/platform/fetch-http-server.ts +90 -0
  639. package/src/runtime/platform/service-inventory.ts +130 -0
  640. package/src/runtime/platform/service-manager.ts +146 -0
  641. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  642. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  643. package/src/runtime/plugins/action-application.ts +192 -0
  644. package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
  645. package/src/runtime/plugins/browser-adapter.ts +898 -135
  646. package/src/runtime/plugins/browser-automation-service.ts +33 -34
  647. package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
  648. package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
  649. package/src/runtime/plugins/browser-native-session-service.ts +64 -14
  650. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  651. package/src/runtime/plugins/browser-registration.ts +32 -0
  652. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  653. package/src/runtime/plugins/browser-runtime.ts +55 -10
  654. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  655. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  656. package/src/runtime/plugins/browser-session-store.ts +243 -80
  657. package/src/runtime/plugins/browser-session-types.ts +2 -98
  658. package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
  659. package/src/runtime/plugins/catalog-trust.ts +82 -0
  660. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  661. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  662. package/src/runtime/plugins/computer-registration.ts +1050 -0
  663. package/src/runtime/plugins/config-store.ts +119 -0
  664. package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
  665. package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
  666. package/src/runtime/plugins/errors.ts +8 -1
  667. package/src/runtime/plugins/external-adapter.ts +82 -323
  668. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  669. package/src/runtime/plugins/external-registration.ts +30 -2
  670. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  671. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  672. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  673. package/src/runtime/plugins/first-party-registry.ts +3 -1
  674. package/src/runtime/plugins/github-adapter.ts +1 -2
  675. package/src/runtime/plugins/gmail-adapter.ts +29 -8
  676. package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
  677. package/src/runtime/plugins/google-shared.ts +45 -53
  678. package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
  679. package/src/runtime/plugins/ios-adapter.ts +2 -3
  680. package/src/runtime/plugins/lightweight-action.ts +208 -2
  681. package/src/runtime/plugins/local-system-adapter.ts +192 -2
  682. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  683. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  684. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  685. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  686. package/src/runtime/plugins/resend-adapter.ts +15 -24
  687. package/src/runtime/plugins/store.ts +1003 -387
  688. package/src/runtime/plugins/types.ts +28 -1
  689. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  690. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  691. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  692. package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
  693. package/src/runtime/projections/controller-context.ts +12 -6
  694. package/src/runtime/projections/git-status-sampler.ts +73 -22
  695. package/src/runtime/projections/invalidation.ts +7 -3
  696. package/src/runtime/projections/materialized-view.ts +93 -43
  697. package/src/runtime/recovery/actions.ts +3 -3
  698. package/src/runtime/recovery/capability-action-application.ts +229 -0
  699. package/src/runtime/recovery/index.ts +3 -0
  700. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  701. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  702. package/src/runtime/recovery/maintenance-executor.ts +701 -168
  703. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  704. package/src/runtime/recovery/store.ts +29 -11
  705. package/src/runtime/release/identity.ts +6 -0
  706. package/src/runtime/release/release-coordinator.ts +296 -0
  707. package/src/runtime/release/release-session.ts +716 -0
  708. package/src/runtime/release/release-state-migration.ts +25 -0
  709. package/src/runtime/resources/leases/store.ts +56 -25
  710. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  711. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  712. package/src/runtime/root/browser-session-composition.ts +59 -0
  713. package/src/runtime/root/computer-composition.ts +376 -0
  714. package/src/runtime/root/computer-target-composition.ts +16 -0
  715. package/src/runtime/root/computer-target-persistence.ts +39 -0
  716. package/src/runtime/root/controller-progression-composition.ts +192 -0
  717. package/src/runtime/root/controller-round-composition.ts +126 -0
  718. package/src/runtime/root/controller-services.ts +2 -0
  719. package/src/runtime/root/deployment-topology.ts +106 -0
  720. package/src/runtime/root/entry.ts +8 -6
  721. package/src/runtime/root/gateway-adapter.ts +3 -3
  722. package/src/runtime/root/known-good-recovery.ts +184 -0
  723. package/src/runtime/root/local-bridge.ts +5 -3
  724. package/src/runtime/root/mcp-transport.ts +116 -23
  725. package/src/runtime/root/ownership.ts +261 -9
  726. package/src/runtime/root/package-connector-service.ts +212 -74
  727. package/src/runtime/root/package-runtime-release.ts +199 -33
  728. package/src/runtime/root/package-runtime-service.ts +77 -108
  729. package/src/runtime/root/release-execution-canary.ts +103 -0
  730. package/src/runtime/root/release-loader.ts +150 -0
  731. package/src/runtime/root/release-manifest.ts +185 -7
  732. package/src/runtime/root/release-materialize.ts +474 -31
  733. package/src/runtime/root/release-store.ts +425 -37
  734. package/src/runtime/root/runtime-lane.ts +191 -0
  735. package/src/runtime/root/runtime.ts +379 -32
  736. package/src/runtime/root/scheduled-controller-composition.ts +76 -0
  737. package/src/runtime/root/scheduler.ts +12 -4
  738. package/src/runtime/root/service-runner.ts +3 -3
  739. package/src/runtime/root/service.ts +73 -45
  740. package/src/runtime/root/status.ts +49 -0
  741. package/src/runtime/root/types.ts +55 -3
  742. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  743. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  744. package/src/runtime/root/write-fence.ts +50 -2
  745. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  746. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  747. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  748. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  749. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  750. package/src/runtime/shared/process-environment.ts +36 -10
  751. package/src/runtime/shared/response-budget.ts +1 -3
  752. package/src/runtime/shared/storage-capacity.ts +68 -0
  753. package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
  754. package/src/runtime/standalone-recovery/core.ts +4609 -495
  755. package/src/runtime/standalone-recovery/entry.ts +642 -81
  756. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  757. package/src/runtime/standalone-recovery/installer.ts +399 -177
  758. package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
  759. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  760. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  761. package/src/runtime/standalone-recovery/performance.ts +173 -0
  762. package/src/runtime/standalone-recovery/release.ts +58 -5
  763. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  764. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  765. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  766. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  767. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
  768. package/src/runtime/workflow/schedules/engine.ts +298 -484
  769. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  770. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  771. package/src/runtime/workflow/schedules/store.ts +2 -607
  772. package/src/runtime/workflow/schedules/types.ts +2 -127
  773. package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
  774. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  775. package/src/runtime/workflows/runtime.ts +482 -0
  776. package/src/testing/test-governance.ts +95 -30
  777. package/supervisor/chatgpt-conversation.ts +23 -0
  778. package/supervisor/client.ts +201 -0
  779. package/supervisor/control-plane.ts +868 -0
  780. package/supervisor/entry.ts +15 -0
  781. package/supervisor/forge-validators.ts +80 -0
  782. package/supervisor/index.ts +5 -0
  783. package/supervisor/native-browser-adapter.ts +786 -0
  784. package/supervisor/native-messaging/host.ts +39 -0
  785. package/supervisor/native-messaging/manifest.ts +26 -0
  786. package/supervisor/native-messaging/protocol.ts +27 -0
  787. package/supervisor/paths.ts +18 -0
  788. package/supervisor/protocol.ts +202 -0
  789. package/supervisor/server.ts +404 -0
  790. package/supervisor/store.ts +1451 -0
  791. package/supervisor/tsconfig.json +8 -0
  792. package/supervisor/types.ts +255 -0
  793. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  794. package/docs/architecture/history/README.md +0 -7
  795. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  796. package/docs/architecture/history/global-hook-runtime.md +0 -166
  797. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  798. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  799. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  800. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  801. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  802. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  803. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  804. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  805. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  806. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  807. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  808. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  809. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  810. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  811. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  812. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  813. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  814. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  815. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  816. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  817. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  818. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  819. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  820. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  821. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  822. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  823. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  824. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  825. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  826. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  827. package/docs/architecture/requests/archive/2026/root.md +0 -68
  828. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  829. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  830. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  831. package/scripts/archive-architecture-request.sh +0 -4
  832. package/scripts/benchmark-background-check-overlap.ts +0 -228
  833. package/scripts/benchmark-controller-hotpath.ts +0 -212
  834. package/scripts/benchmark-controller-round2.ts +0 -139
  835. package/scripts/benchmark-ios-agent-device.ts +0 -514
  836. package/scripts/benchmark-mcp-host.sh +0 -117
  837. package/scripts/benchmark-mcp-transport.ts +0 -170
  838. package/scripts/benchmark-quality-harness.ts +0 -146
  839. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  840. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  841. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  842. package/scripts/benchmark-thin-harness.ts +0 -251
  843. package/scripts/benchmark-typescript-navigation.ts +0 -186
  844. package/scripts/benchmark-work-efficiency.ts +0 -334
  845. package/scripts/build-controller-ui.ts +0 -28
  846. package/scripts/check-npm-release.sh +0 -32
  847. package/scripts/check-public-release-surface.sh +0 -19
  848. package/scripts/check-release-published.sh +0 -57
  849. package/scripts/check-release-readiness.sh +0 -37
  850. package/scripts/check-release-version.mjs +0 -74
  851. package/scripts/check-tarball-install-smoke.sh +0 -67
  852. package/scripts/package-source-archive.sh +0 -147
  853. package/scripts/public-release-files.txt +0 -39
  854. package/scripts/public-release.ts +0 -174
  855. package/scripts/publish-release-tarball.sh +0 -27
  856. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  857. package/scripts/run-skill-evals.ts +0 -1022
  858. package/scripts/stage-runtime-release.ts +0 -39
  859. package/src/cli/controller/progress.ts +0 -656
  860. package/src/cli/controller/work-mode.ts +0 -181
  861. package/src/cli/local-bridge/console-assets.ts +0 -17
  862. package/src/cli/local-bridge/dashboard.ts +0 -17
  863. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  864. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  865. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  866. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  867. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  868. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  869. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  870. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  871. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  872. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  873. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  874. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  875. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  876. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  877. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  878. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  879. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  880. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  881. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  882. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  883. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  884. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  885. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  886. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  887. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  888. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  889. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  890. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  891. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
  892. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
  893. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  894. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
  895. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
  896. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
  897. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  898. package/src/runtime/control-plane/routing/index.ts +0 -1
  899. package/src/runtime/control-plane/routing/route-policy.ts +0 -382
  900. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -0,0 +1,1967 @@
1
+ import { createHash, randomUUID } from 'crypto';
2
+ import { existsSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { ensureControllerHome, scopedOperationRoot, SEMANTIC_SCOPE_KEY } from '../../../../src/cli/repositories/controller-home';
5
+ import { withControllerLock } from '../../../../src/cli/repositories/locks';
6
+ import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../../../../src/runtime/shared/json-files';
7
+ import {
8
+ listControlPlaneRecordKeys,
9
+ listControlPlaneRecords,
10
+ listControlPlaneRecordsWithinTransaction,
11
+ listControlPlaneRecordsExcludingPayloadTextValues,
12
+ initializeControlPlanePayloadTextExclusionIndex,
13
+ readControlPlaneRecord,
14
+ readControlPlaneRecordWithinTransaction,
15
+ withControlPlaneTransaction,
16
+ writeControlPlaneRecordWithinTransaction,
17
+ } from '../../../../src/runtime/control-plane/persistence/sqlite-store';
18
+ import {
19
+ MAX_IMPLEMENTATION_REVIEW_HISTORY,
20
+ validateImplementationReviewRecord,
21
+ type WorkImplementationReviewRecord,
22
+ } from '../domain/implementation-review';
23
+ import { phaseIndex, suggestedActionsForSemanticState, transitionPhaseEvidence, validateWorkSemanticTransition, validateWorkSemantics } from '../domain/state-machine';
24
+ import { normalizeWorkObjectiveRelationIds, validateWorkObjectiveRelationShape } from '../domain/objective-graph';
25
+ import {
26
+ WORK_PHASES,
27
+ type EvidenceRef,
28
+ type CompletionOutcome,
29
+ type DispatchState,
30
+ type EvidenceState,
31
+ type PolicyDecision,
32
+ type SuggestedNextAction,
33
+ type VerificationRecord,
34
+ type WorkReconciliationRecord,
35
+ type SubmittedWorkOperation,
36
+ type WorkContract,
37
+ type WorkSemanticView,
38
+ type SemanticWorkState,
39
+ type WorkRisk,
40
+ type WorkKind,
41
+ type WorkPhase,
42
+ type WorkPhaseEvidence,
43
+ type WorkPhaseEvidenceMap,
44
+ type WorkPhaseEvidenceState,
45
+ type WorkContractStore,
46
+ executionPlacementForWork,
47
+ isDirectEditWorkCompletionReceipt,
48
+ isRepositoryCompletionReceipt,
49
+ isTerminalSemanticWorkState,
50
+ TERMINAL_SEMANTIC_WORK_STATES,
51
+ semanticScopeRefForWork,
52
+ } from '../domain/types';
53
+
54
+ import type { WorkContractStoreLocation, WorkContractStoreOptions } from '../ports/work-contract-store';
55
+ export type { WorkContractStoreLocation, WorkContractStoreOptions } from '../ports/work-contract-store';
56
+
57
+ export type CreateWorkContractInput = Omit<
58
+ WorkContract,
59
+ 'schemaVersion' | 'semanticState' | 'createdAt' | 'updatedAt' | 'risk' | 'workKind' | 'dispatchState' | 'evidenceState' | 'completionOutcome' | 'phase' | 'phaseEvidence' | 'completionReceipt' | 'evidenceRefs' | 'handoffRefs' | 'suggestedNextActions' | 'policyDecisions' | 'checkRefs' | 'implementationReviews' | 'reconciliations' | 'worktreePolicy' | 'evidencePolicy' | 'approvalPolicy' | 'recoveryPolicy'
60
+ > & {
61
+ risk?: WorkRisk;
62
+ createdAt?: string;
63
+ updatedAt?: string;
64
+ workKind?: WorkKind;
65
+ dispatchState?: DispatchState;
66
+ evidenceState?: EvidenceState;
67
+ completionOutcome?: CompletionOutcome;
68
+ phase?: WorkContract['phase'];
69
+ completionReceipt?: WorkContract['completionReceipt'];
70
+ evidenceRefs?: EvidenceRef[];
71
+ handoffRefs?: string[];
72
+ suggestedNextActions?: SuggestedNextAction[];
73
+ policyDecisions?: PolicyDecision[];
74
+ checkRefs?: VerificationRecord[];
75
+ reconciliations?: WorkReconciliationRecord[];
76
+ worktreePolicy?: WorkContract['worktreePolicy'];
77
+ evidencePolicy?: WorkContract['evidencePolicy'];
78
+ approvalPolicy?: WorkContract['approvalPolicy'];
79
+ recoveryPolicy?: WorkContract['recoveryPolicy'];
80
+ /**
81
+ * Legacy ingress-only fields, accepted only so a pre-20261004 frozen MCP
82
+ * client can still submit without the retired fields being recreated on the
83
+ * persisted Work contract.
84
+ *
85
+ * Replacement owner: concrete mutation scope belongs to the EditSession (and
86
+ * the execution resource bound to it); a Work carries semantic scope and
87
+ * evidence only. Bounded consumers: frozen MCP clients whose advertised
88
+ * `work_prepare` schema still includes `allowed_paths`. Removal condition:
89
+ * delete these fields (and the matching `AcceptSubmittedWorkInput` pair) as
90
+ * soon as no supported client sends them; a non-empty value is never
91
+ * enforced and must never be treated as authorization evidence.
92
+ */
93
+ allowedPaths?: string[];
94
+ forbiddenPaths?: string[];
95
+ };
96
+
97
+ export interface ListWorkContractOptions extends WorkContractStoreOptions {
98
+ state?: SemanticWorkState | 'active' | 'all';
99
+ limit?: number;
100
+ detailLevel?: 'summary' | 'detail' | 'raw';
101
+ }
102
+
103
+ export interface InvalidActiveWorkCandidate {
104
+ workId: string;
105
+ updatedAt: string;
106
+ checkoutId?: string;
107
+ requirementId?: string;
108
+ planId?: string;
109
+ planStepId?: string;
110
+ semanticScopeKeys: string[];
111
+ isolation: 'shared' | 'isolated';
112
+ error: string;
113
+ }
114
+
115
+ export interface ActiveWorkCandidateSnapshot {
116
+ contracts: WorkContract[];
117
+ invalid: InvalidActiveWorkCandidate[];
118
+ }
119
+
120
+ export interface WorkContractLineageSnapshot {
121
+ contracts: WorkContract[];
122
+ invalid: Array<{
123
+ workId: string;
124
+ parentWorkId?: string;
125
+ predecessorWorkId?: string;
126
+ supersedes: string[];
127
+ supersededBy?: string;
128
+ error: string;
129
+ }>;
130
+ }
131
+
132
+ export interface WorkSemanticRevisionRecord extends WorkSemanticView {
133
+ schemaVersion: 1;
134
+ recordedAt: string;
135
+ }
136
+
137
+ export interface CreateWorkSemanticInput {
138
+ workId: string;
139
+ objective: string;
140
+ requestId?: string;
141
+ requirementId?: string;
142
+ requirementRevision?: number;
143
+ planId?: string;
144
+ planRevision?: number;
145
+ semanticParentWorkId?: string;
146
+ dependsOnWorkIds?: string[];
147
+ requestedBy?: 'chatgpt' | 'user' | 'system' | 'scheduler';
148
+ }
149
+
150
+ export interface ReviseWorkSemanticInput {
151
+ expectedRevision: number;
152
+ objective?: string;
153
+ state?: SemanticWorkState;
154
+ requirementRevision?: number;
155
+ planRevision?: number;
156
+ semanticParentWorkId?: string;
157
+ dependsOnWorkIds?: string[];
158
+ resultRefs?: string[];
159
+ }
160
+
161
+ interface WorkSemanticRevisionStore {
162
+ schemaVersion: 1;
163
+ records: WorkSemanticRevisionRecord[];
164
+ }
165
+
166
+ export interface WorkContractSummary {
167
+ workId: string;
168
+ repoId: string;
169
+ /** The one thin authored Work state. */
170
+ state: SemanticWorkState;
171
+ phase: WorkContract['phase'];
172
+ objective: string;
173
+ updatedAt: string;
174
+ handoffCount: number;
175
+ evidenceCount: number;
176
+ checkCount: number;
177
+ engineering?: {
178
+ profileVersion: string;
179
+ riskClass: string;
180
+ missingAdmissionEvidence: string[];
181
+ missingCompletionEvidence: string[];
182
+ };
183
+ }
184
+
185
+ function nowIso(options: WorkContractStoreOptions): string {
186
+ return options.now?.() ?? new Date().toISOString();
187
+ }
188
+
189
+ export function workContractStoreScopeKey(location: WorkContractStoreLocation): string {
190
+ const key = location.scopeKey?.trim() || location.repoId?.trim() || '';
191
+ if (!key) throw new Error('WORK_STORE_SCOPE_REQUIRED: controllerHome requires scopeKey or repoId');
192
+ return key;
193
+ }
194
+
195
+ export function workContractRoot(location: WorkContractStoreLocation): string {
196
+ if (location.root) {
197
+ mkdirSync(location.root, { recursive: true });
198
+ return location.root;
199
+ }
200
+ if (!location.controllerHome) {
201
+ throw new Error('work contract store requires either root or controllerHome + scopeKey/repoId');
202
+ }
203
+ const root = join(scopedOperationRoot(location.controllerHome, workContractStoreScopeKey(location)), 'work-contracts');
204
+ mkdirSync(root, { recursive: true });
205
+ return root;
206
+ }
207
+
208
+ export function workContractStorePath(location: WorkContractStoreLocation): string {
209
+ return join(workContractRoot(location), 'index.json');
210
+ }
211
+
212
+ export function emptyWorkContractStore(updatedAt: string): WorkContractStore {
213
+ return { schemaVersion: 4, updatedAt, contracts: [] };
214
+ }
215
+
216
+ function currentWorkSemanticRevision(work: WorkContract): number {
217
+ const revision = Number(work.semanticRevision);
218
+ return Number.isInteger(revision) && revision > 0 ? revision : 1;
219
+ }
220
+
221
+ /**
222
+ * Resolve the one thin semantic Work state. An explicit authored state is
223
+ * authoritative; a row that predates explicit semantic state derives only its
224
+ * terminal projection from mechanically recorded completion/cancellation.
225
+ * Execution/verification/review/delivery/cleanup vocabulary is never a semantic
226
+ * state: a mechanically blocked or failed Work stays semantically `open` until
227
+ * an explicit CAS close or cancellation.
228
+ */
229
+ export function semanticWorkState(work: Pick<WorkContract, 'semanticState'>): SemanticWorkState {
230
+ return work.semanticState;
231
+ }
232
+
233
+ export function workSemanticView(work: WorkContract): WorkSemanticView {
234
+ const resultRefs = [...new Set((work.semanticResultRefs ?? []).map((value) => value.trim()).filter(Boolean))].slice(0, 100);
235
+ return {
236
+ workId: work.workId,
237
+ revision: currentWorkSemanticRevision(work),
238
+ semanticScope: semanticScopeRefForWork(work),
239
+ objective: work.objective,
240
+ state: semanticWorkState(work),
241
+ ...(work.requirementId?.trim() ? { requirementId: work.requirementId.trim() } : {}),
242
+ ...(Number.isInteger(work.requirementRevision) && Number(work.requirementRevision) > 0 ? { requirementRevision: Number(work.requirementRevision) } : {}),
243
+ ...(work.planId?.trim() ? { planId: work.planId.trim() } : {}),
244
+ ...(Number.isInteger(work.planRevision) && Number(work.planRevision) > 0 ? { planRevision: Number(work.planRevision) } : {}),
245
+ ...(work.semanticParentWorkId?.trim() ? { semanticParentWorkId: work.semanticParentWorkId.trim() } : {}),
246
+ ...(normalizeWorkObjectiveRelationIds(work.dependsOnWorkIds).length > 0 ? { dependsOnWorkIds: normalizeWorkObjectiveRelationIds(work.dependsOnWorkIds) } : {}),
247
+ resultRefs,
248
+ createdAt: work.createdAt,
249
+ updatedAt: work.semanticUpdatedAt ?? work.createdAt,
250
+ };
251
+ }
252
+
253
+ function workSemanticRevisionKey(workId: string, revision: number): string {
254
+ return `${sanitizeFileComponent(workId)}-r${revision}`;
255
+ }
256
+
257
+ function validateWorkObjectiveRelationIntegrity(candidate: WorkContract, contracts: readonly WorkContract[]): void {
258
+ validateWorkObjectiveRelationShape(candidate);
259
+ const parent = candidate.semanticParentWorkId?.trim();
260
+ const dependencies = normalizeWorkObjectiveRelationIds(candidate.dependsOnWorkIds);
261
+ if (!parent && dependencies.length === 0) return;
262
+ const byId = new Map(contracts.map((contract) => [contract.workId, contract] as const));
263
+ byId.set(candidate.workId, candidate);
264
+ for (const relatedWorkId of [...(parent ? [parent] : []), ...dependencies]) {
265
+ const related = byId.get(relatedWorkId);
266
+ if (!related) throw new Error(`WORK_OBJECTIVE_RELATION_NOT_FOUND: ${relatedWorkId}`);
267
+ if ((related.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`WORK_OBJECTIVE_RELATION_TARGET_NOT_PRIMARY: ${relatedWorkId}`);
268
+ }
269
+ const seenParents = new Set<string>();
270
+ let parentCursor = parent;
271
+ while (parentCursor) {
272
+ if (parentCursor === candidate.workId) throw new Error('WORK_SEMANTIC_PARENT_CYCLE');
273
+ if (seenParents.has(parentCursor)) throw new Error('WORK_SEMANTIC_PARENT_TARGET_CYCLE');
274
+ seenParents.add(parentCursor);
275
+ parentCursor = byId.get(parentCursor)?.semanticParentWorkId?.trim();
276
+ }
277
+ const dependencyReachesCandidate = (workId: string, visiting: Set<string>): boolean => {
278
+ if (workId === candidate.workId) return true;
279
+ if (visiting.has(workId)) return false;
280
+ visiting.add(workId);
281
+ const work = byId.get(workId);
282
+ if (!work) return false;
283
+ return normalizeWorkObjectiveRelationIds(work.dependsOnWorkIds)
284
+ .some((dependency) => dependencyReachesCandidate(dependency, visiting));
285
+ };
286
+ for (const dependency of dependencies) {
287
+ if (dependencyReachesCandidate(dependency, new Set())) throw new Error('WORK_DEPENDENCY_CYCLE');
288
+ }
289
+ }
290
+
291
+ function workSemanticRevisionStorePath(options: WorkContractStoreOptions): string {
292
+ return join(workContractRoot(options), 'semantic-revisions.json');
293
+ }
294
+
295
+ function readWorkSemanticRevisionStore(options: WorkContractStoreOptions): WorkSemanticRevisionStore {
296
+ return readJsonFile<WorkSemanticRevisionStore>(workSemanticRevisionStorePath(options), { schemaVersion: 1, records: [] });
297
+ }
298
+
299
+ function initialLifecycleForNewWork(): Pick<WorkContract, 'phase' | 'dispatchState' | 'evidenceState'> {
300
+ return { phase: 'implementation', dispatchState: 'not_dispatched', evidenceState: 'none' };
301
+ }
302
+
303
+ function initialPhaseEvidenceForNewWork(
304
+ input: Pick<WorkContract, 'phase' | 'evidenceRefs' | 'updatedAt'> & { phaseState?: WorkPhaseEvidenceState },
305
+ ): WorkPhaseEvidenceMap {
306
+ const currentIndex = phaseIndex(input.phase);
307
+ return Object.fromEntries((['implementation', 'verification', 'review', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => {
308
+ const index = phaseIndex(phase);
309
+ const state: WorkPhaseEvidenceState = index < currentIndex
310
+ ? 'satisfied'
311
+ : index > currentIndex
312
+ ? 'pending'
313
+ : input.phaseState ?? 'active';
314
+ return [phase, {
315
+ state,
316
+ source: 'recorded' as const,
317
+ summary: index < currentIndex
318
+ ? `Canonical Work was admitted after phase ${phase}.`
319
+ : index > currentIndex
320
+ ? `Waiting for Work phase ${phase}.`
321
+ : `Canonical Work admitted in ${phase}.`,
322
+ evidenceRefs: index <= currentIndex ? input.evidenceRefs.slice(0, 20) : [],
323
+ recordedAt: input.updatedAt,
324
+ } satisfies WorkPhaseEvidence];
325
+ })) as WorkPhaseEvidenceMap;
326
+ }
327
+
328
+ function legacyPhaseEvidence(
329
+ contract: Pick<WorkContract, 'phase' | 'evidenceRefs' | 'completionReceipt' | 'updatedAt'> & { legacyStatus?: 'open' | 'running' | 'blocked' | 'ready' | 'completed' | 'failed' | 'cancelled' },
330
+ source: WorkPhaseEvidence['source'] = 'legacy_inferred',
331
+ ): WorkPhaseEvidenceMap {
332
+ const currentIndex = phaseIndex(contract.phase);
333
+ const completedByReceipt = Boolean(contract.completionReceipt);
334
+ return Object.fromEntries((['implementation', 'verification', 'review', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => {
335
+ const index = phaseIndex(phase);
336
+ let state: WorkPhaseEvidenceState = index < currentIndex ? 'satisfied' : index === currentIndex ? 'active' : 'pending';
337
+ if (completedByReceipt) state = phase === 'review' ? 'skipped' : 'satisfied';
338
+ else if (contract.legacyStatus === 'failed' && phase === contract.phase) state = 'failed';
339
+ else if (contract.legacyStatus === 'cancelled' && phase === contract.phase) state = 'skipped';
340
+ const receiptId = contract.completionReceipt && (phase === 'delivery' || phase === 'cleanup')
341
+ ? contract.completionReceipt.receiptId
342
+ : undefined;
343
+ return [phase, {
344
+ state,
345
+ source: completedByReceipt ? 'recorded' : source,
346
+ summary: completedByReceipt
347
+ ? phase === 'review'
348
+ ? `Legacy completed Work predates first-class implementation review; review phase is compatibility-skipped without synthesizing approval.`
349
+ : `Phase ${phase} satisfied by Work completion receipt ${contract.completionReceipt!.receiptId}.`
350
+ : index < currentIndex
351
+ ? `Legacy Work advanced beyond ${phase}.`
352
+ : `Legacy Work phase ${phase} is ${state}.`,
353
+ evidenceRefs: contract.evidenceRefs.slice(0, 20),
354
+ recordedAt: contract.updatedAt,
355
+ ...(receiptId ? { receiptId } : {}),
356
+ } satisfies WorkPhaseEvidence];
357
+ })) as WorkPhaseEvidenceMap;
358
+ }
359
+
360
+ function sqliteBacked(options: WorkContractStoreOptions): options is WorkContractStoreOptions & { controllerHome: string } {
361
+ // A caller-provided root is a test/portable compatibility store. Runtime
362
+ // control-plane state uses one explicit scope key: semantic scope for authored
363
+ // Work, repository scope for legacy/repository execution Work.
364
+ return Boolean(!options.root && options.controllerHome?.trim() && (options.scopeKey?.trim() || options.repoId?.trim()));
365
+ }
366
+
367
+ function migrateLegacyWorkContract(legacy: WorkContract): WorkContract {
368
+ // One-way schema migration only: v1-v3 rows may still carry retired status.
369
+ // v4 strips it permanently and keeps semanticState as the only Work lifecycle state.
370
+ // Removal condition: once supported Controller Homes contain no schema<4 Work rows for one full release boundary, delete legacyStatus handling entirely.
371
+ const legacyStatus = (legacy as WorkContract & { status?: 'open' | 'running' | 'blocked' | 'ready' | 'completed' | 'failed' | 'cancelled' }).status;
372
+ const semanticState: SemanticWorkState = legacy.semanticState
373
+ ?? (legacyStatus === 'completed' ? 'completed' : legacyStatus === 'cancelled' ? 'cancelled' : 'open');
374
+ // Phase is a mechanical checkpoint projection only. A legacy row without a
375
+ // persisted phase is admitted at the neutral first checkpoint; terminal
376
+ // status never advances phase, and status is never a phase-transition authority.
377
+ const phase = legacy.completionReceipt
378
+ ? 'cleanup'
379
+ : semanticState === 'completed'
380
+ ? 'delivery'
381
+ : legacy.phase ?? 'implementation';
382
+ const legacyDefaults = legacyPhaseEvidence({
383
+ phase,
384
+ legacyStatus,
385
+ evidenceRefs: legacy.evidenceRefs ?? [],
386
+ completionReceipt: legacy.completionReceipt,
387
+ updatedAt: legacy.updatedAt,
388
+ });
389
+ const existingPhaseEvidence = legacy.phaseEvidence as Partial<WorkPhaseEvidenceMap> | undefined;
390
+ const existingReview = existingPhaseEvidence?.review;
391
+ const legacyReviewAdvancedPastCheckpoint = Boolean(
392
+ existingReview
393
+ && existingReview.source === 'legacy_inferred'
394
+ && existingReview.state === 'pending'
395
+ && phaseIndex(phase) > phaseIndex('review'),
396
+ );
397
+ const phaseEvidence: WorkPhaseEvidenceMap = existingPhaseEvidence
398
+ ? {
399
+ ...legacyDefaults,
400
+ ...existingPhaseEvidence,
401
+ review: existingReview
402
+ ? legacyReviewAdvancedPastCheckpoint
403
+ ? {
404
+ ...existingReview,
405
+ state: 'skipped' as const,
406
+ summary: 'Legacy Work advanced beyond first-class implementation review before that checkpoint existed; compatibility-skipped without synthesizing Controller approval.',
407
+ }
408
+ : existingReview
409
+ : {
410
+ ...legacyDefaults.review,
411
+ ...(phaseIndex(phase) > phaseIndex('review')
412
+ ? {
413
+ state: 'skipped' as const,
414
+ summary: 'Legacy Work advanced beyond first-class implementation review; compatibility-skipped without synthesizing Controller approval.',
415
+ }
416
+ : {}),
417
+ },
418
+ }
419
+ : legacyDefaults;
420
+ const {
421
+ status: _retiredStatus,
422
+ allowedPaths: _retiredAllowedPaths,
423
+ forbiddenPaths: _retiredForbiddenPaths,
424
+ ...legacyWithoutRetiredFields
425
+ } = legacy as WorkContract & { status?: unknown; allowedPaths?: unknown; forbiddenPaths?: unknown };
426
+ void _retiredStatus;
427
+ void _retiredAllowedPaths;
428
+ void _retiredForbiddenPaths;
429
+ // Before Work kind and completion outcome were made a single typed contract,
430
+ // delivery-reconciliation records retained their operational label even after
431
+ // a repository receipt proved that they advanced the target revision. The
432
+ // current contract rightly rejects that combination. Preserve the receipt and
433
+ // semantic terminal state, but migrate the obsolete label to the current
434
+ // repository-change classification so terminal history remains readable and
435
+ // cannot poison scheduler relay discovery forever.
436
+ const workKind = legacy.workKind === 'reconciliation'
437
+ && legacy.completionOutcome === 'completed_changed'
438
+ && legacy.completionReceipt
439
+ && isRepositoryCompletionReceipt(legacy.completionReceipt)
440
+ ? 'repository_change' as const
441
+ : legacy.workKind ?? 'repository_change';
442
+ return validateWorkSemantics({
443
+ ...legacyWithoutRetiredFields,
444
+ schemaVersion: 4,
445
+ scopeRef: semanticScopeRefForWork(legacy),
446
+ executionPlacement: executionPlacementForWork(legacy),
447
+ semanticState,
448
+ semanticRevision: legacy.semanticRevision && legacy.semanticRevision > 0 ? legacy.semanticRevision : 1,
449
+ semanticUpdatedAt: legacy.semanticUpdatedAt ?? legacy.updatedAt,
450
+ phase,
451
+ phaseEvidence,
452
+ risk: legacy.risk ?? 'medium',
453
+ workKind,
454
+ dispatchState: legacy.dispatchState ?? 'not_dispatched',
455
+ evidenceState: legacy.evidenceState ?? 'none',
456
+ suggestedNextActions: suggestedActionsForSemanticState(semanticState, legacy.suggestedNextActions ?? []),
457
+ implementationReviews: legacy.implementationReviews ?? [],
458
+ reconciliations: legacy.reconciliations ?? [],
459
+ });
460
+ }
461
+
462
+ function validateCanonicalWorkContract(contract: WorkContract): WorkContract {
463
+ if (contract.schemaVersion !== 4) {
464
+ throw new Error(`WORK_CONTRACT_SCHEMA_MIGRATION_REQUIRED: ${contract.workId}:schema=${String(contract.schemaVersion)}`);
465
+ }
466
+ return validateWorkSemantics(contract);
467
+ }
468
+
469
+ function storedWorkContractNeedsMigration(contract: WorkContract, persistedSchemaVersion: number = contract.schemaVersion): boolean {
470
+ const phaseEvidence = contract.phaseEvidence as Partial<WorkPhaseEvidenceMap> | undefined;
471
+ // Some pre-review-checkpoint rows were stamped with the current outer
472
+ // schema while omitting only `review`. That exact historical shape remains
473
+ // migration-compatible, but it must be persisted once rather than inferred
474
+ // repeatedly by the normal read path.
475
+ const legacyReviewGap = persistedSchemaVersion === 3
476
+ && phaseEvidence
477
+ && !phaseEvidence.review
478
+ && phaseEvidence.implementation
479
+ && phaseEvidence.verification
480
+ && phaseEvidence.delivery
481
+ && phaseEvidence.cleanup;
482
+ return persistedSchemaVersion !== 4
483
+ || contract.semanticState === undefined
484
+ || Object.prototype.hasOwnProperty.call(contract as object, 'status')
485
+ || Object.prototype.hasOwnProperty.call(contract as object, 'allowedPaths')
486
+ || Object.prototype.hasOwnProperty.call(contract as object, 'forbiddenPaths')
487
+ || Boolean(legacyReviewGap);
488
+ }
489
+
490
+ function workContractAtPersistedSchema(contract: WorkContract, persistedSchemaVersion: number): WorkContract {
491
+ return contract.schemaVersion === persistedSchemaVersion
492
+ ? contract
493
+ : { ...contract, schemaVersion: persistedSchemaVersion as WorkContract['schemaVersion'] };
494
+ }
495
+
496
+ function canonicalizeStoredWorkContract(contract: WorkContract, persistedSchemaVersion: number = contract.schemaVersion): WorkContract {
497
+ // The control-plane record envelope is the persisted schema authority. Some
498
+ // historical writers advanced the payload first, leaving it stamped v4 under
499
+ // a v3 envelope. Read it as the envelope declares so the one-way migration
500
+ // repairs the whole stored row rather than accepting a partially migrated
501
+ // contract indefinitely.
502
+ const persisted = workContractAtPersistedSchema(contract, persistedSchemaVersion);
503
+ return storedWorkContractNeedsMigration(persisted, persistedSchemaVersion)
504
+ ? migrateLegacyWorkContract(persisted)
505
+ : validateCanonicalWorkContract(persisted);
506
+ }
507
+
508
+ /**
509
+ * Normalize one row at a transaction boundary without reading or mutating a
510
+ * second Work authority. Higher-level control-plane transactions use this to
511
+ * validate legacy rows against the same canonical Work semantics as reads.
512
+ */
513
+ export function canonicalizeWorkContractForAuthority(contract: WorkContract): WorkContract {
514
+ return canonicalizeStoredWorkContract(contract);
515
+ }
516
+
517
+ function normalizeWorkContractStore(store: WorkContractStore): WorkContractStore {
518
+ // v1-v3 rows are a one-way schema migration into v4. v4 rows are validated as-is;
519
+ // retired status is never inferred or exposed during normal reads.
520
+ // Removal condition: delete this migration branch after supported Controller Homes report zero schema<4 Work rows across one full release boundary.
521
+ return { schemaVersion: 4, updatedAt: store.updatedAt, contracts: store.contracts.map((contract) => canonicalizeStoredWorkContract(contract)) };
522
+ }
523
+
524
+ export function readWorkContractStore(options: WorkContractStoreOptions): WorkContractStore {
525
+ if (!sqliteBacked(options)) {
526
+ const raw = readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
527
+ const normalized = normalizeWorkContractStore(raw);
528
+ if (raw.schemaVersion !== 4 || raw.contracts.some((contract) => storedWorkContractNeedsMigration(contract))) {
529
+ writeJsonAtomic(workContractStorePath(options), normalized);
530
+ }
531
+ return normalized;
532
+ }
533
+ const records = listControlPlaneRecords<WorkContract>(options.controllerHome, {
534
+ namespace: 'work_contract',
535
+ scope: workContractStoreScopeKey(options),
536
+ limit: 5_000,
537
+ });
538
+ if (records.length > 0) {
539
+ const normalized = normalizeWorkContractStore({
540
+ schemaVersion: 4,
541
+ updatedAt: records[0]?.updatedAt ?? nowIso(options),
542
+ contracts: records.map((record) => workContractAtPersistedSchema(record.value, record.schemaVersion)),
543
+ });
544
+ const legacyRows = records
545
+ .map((record, index) => ({ record, contract: normalized.contracts[index]! }))
546
+ .filter(({ record }) => storedWorkContractNeedsMigration(record.value, record.schemaVersion));
547
+ if (legacyRows.length > 0) {
548
+ withControlPlaneTransaction(options.controllerHome, (database) => {
549
+ for (const { record, contract } of legacyRows) {
550
+ writeControlPlaneRecordWithinTransaction(database, {
551
+ namespace: 'work_contract',
552
+ scope: workContractStoreScopeKey(options),
553
+ key: contract.workId,
554
+ schemaVersion: 4,
555
+ value: contract,
556
+ action: 'work_contract_schema_v4_migrated',
557
+ expectedRevision: record.revision,
558
+ });
559
+ }
560
+ });
561
+ }
562
+ return normalized;
563
+ }
564
+
565
+ // One-time import only. Once a per-Work row exists, legacy index/file data is
566
+ // never consulted again and is never written back.
567
+ const legacyRecord = readControlPlaneRecord<WorkContractStore>(
568
+ options.controllerHome,
569
+ 'work_contract_store',
570
+ workContractStoreScopeKey(options),
571
+ 'index',
572
+ );
573
+ const legacy = legacyRecord?.value
574
+ ?? readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
575
+ const normalized = normalizeWorkContractStore(legacy);
576
+ if (normalized.contracts.length > 0) {
577
+ withControlPlaneTransaction(options.controllerHome, (database) => {
578
+ for (const contract of normalized.contracts) {
579
+ if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', workContractStoreScopeKey(options), contract.workId)) continue;
580
+ writeControlPlaneRecordWithinTransaction(database, {
581
+ namespace: 'work_contract',
582
+ scope: workContractStoreScopeKey(options),
583
+ key: contract.workId,
584
+ schemaVersion: 4,
585
+ value: contract,
586
+ action: 'work_contract_legacy_import',
587
+ expectedRevision: null,
588
+ });
589
+ }
590
+ });
591
+ }
592
+ return normalized;
593
+ }
594
+
595
+ /**
596
+ * Read valid Work rows without allowing an unrelated corrupt historical row to
597
+ * poison a lineage-scoped ControllerRound projection. Invalid rows retain only
598
+ * their identity and explicit lineage references so callers can fail closed if
599
+ * the malformed row may belong to the requested lineage.
600
+ */
601
+ export function readWorkContractLineageSnapshot(options: WorkContractStoreOptions): WorkContractLineageSnapshot {
602
+ if (!sqliteBacked(options)) {
603
+ const store = readJsonFile<WorkContractStore>(workContractStorePath(options), emptyWorkContractStore(nowIso(options)));
604
+ const contracts: WorkContract[] = [];
605
+ const invalid: WorkContractLineageSnapshot['invalid'] = [];
606
+ for (const raw of store.contracts) {
607
+ try {
608
+ contracts.push(canonicalizeStoredWorkContract(raw));
609
+ } catch (error) {
610
+ invalid.push(lineageInvalidWork(raw, error));
611
+ }
612
+ }
613
+ return { contracts, invalid };
614
+ }
615
+
616
+ const records = listControlPlaneRecords<WorkContract>(options.controllerHome, {
617
+ namespace: 'work_contract',
618
+ scope: workContractStoreScopeKey(options),
619
+ limit: 5_000,
620
+ });
621
+ if (records.length === 0) return { contracts: readWorkContractStore(options).contracts, invalid: [] };
622
+
623
+ const contracts: WorkContract[] = [];
624
+ const invalid: WorkContractLineageSnapshot['invalid'] = [];
625
+ for (const record of records) {
626
+ try {
627
+ contracts.push(canonicalizeStoredWorkContract(record.value, record.schemaVersion));
628
+ } catch (error) {
629
+ invalid.push(lineageInvalidWork(record.value, error, record.key));
630
+ }
631
+ }
632
+ return { contracts, invalid };
633
+ }
634
+
635
+ function lineageInvalidWork(
636
+ raw: WorkContract,
637
+ error: unknown,
638
+ recordKey?: string,
639
+ ): WorkContractLineageSnapshot['invalid'][number] {
640
+ const clean = (id: unknown): string | undefined => typeof id === 'string' && id.trim() ? id.trim() : undefined;
641
+ return {
642
+ workId: clean(raw.workId) ?? recordKey ?? '',
643
+ ...(clean(raw.parentWorkId) ? { parentWorkId: clean(raw.parentWorkId) } : {}),
644
+ ...(clean(raw.predecessorWorkId) ? { predecessorWorkId: clean(raw.predecessorWorkId) } : {}),
645
+ supersedes: Array.isArray(raw.supersedes) ? raw.supersedes.map(clean).filter((id): id is string => Boolean(id)) : [],
646
+ ...(clean(raw.supersededBy) ? { supersededBy: clean(raw.supersededBy) } : {}),
647
+ error: error instanceof Error ? error.message : String(error),
648
+ };
649
+ }
650
+
651
+ export function writeWorkContractStore(options: WorkContractStoreOptions, store: WorkContractStore): WorkContractStore {
652
+ if (!sqliteBacked(options)) {
653
+ writeJsonAtomic(workContractStorePath(options), store);
654
+ return store;
655
+ }
656
+ withControlPlaneTransaction(options.controllerHome, (database) => {
657
+ for (const contract of store.contracts) {
658
+ const value = validateCanonicalWorkContract(contract);
659
+ const current = readControlPlaneRecordWithinTransaction<WorkContract>(
660
+ database,
661
+ 'work_contract',
662
+ workContractStoreScopeKey(options),
663
+ contract.workId,
664
+ );
665
+ // SQLite is authoritative per Work row. Aggregate-store callers may still
666
+ // construct a complete compatibility snapshot, but unchanged siblings are
667
+ // not authoritative mutations and must not advance revision/audit history.
668
+ if (current && JSON.stringify(current.value) === JSON.stringify(value)) continue;
669
+ writeControlPlaneRecordWithinTransaction(database, {
670
+ namespace: 'work_contract',
671
+ scope: workContractStoreScopeKey(options),
672
+ key: contract.workId,
673
+ schemaVersion: 4,
674
+ value,
675
+ action: 'work_contract_write',
676
+ expectedRevision: current?.revision ?? null,
677
+ });
678
+ }
679
+ });
680
+ return store;
681
+ }
682
+
683
+ function withWorkContractStoreWrite<T>(options: WorkContractStoreOptions, operation: () => T): T {
684
+ if (!sqliteBacked(options)) return operation();
685
+ return withControllerLock(
686
+ options.controllerHome,
687
+ { scope: 'global', resource: `work-contract-store-${sanitizeFileComponent(workContractStoreScopeKey(options))}` },
688
+ `work-contract-store:${workContractStoreScopeKey(options)}`,
689
+ operation,
690
+ undefined,
691
+ 5_000,
692
+ );
693
+ }
694
+
695
+ function withExactWorkContractWrite<T>(
696
+ options: WorkContractStoreOptions,
697
+ workIdInput: string,
698
+ operation: () => T,
699
+ ): T {
700
+ if (!sqliteBacked(options)) return operation();
701
+ const workId = sanitizeFileComponent(workIdInput);
702
+ return withControllerLock(
703
+ options.controllerHome,
704
+ { scope: 'global', resource: `work-contract-${workId}` },
705
+ `work-contract:${workId}`,
706
+ operation,
707
+ undefined,
708
+ 5_000,
709
+ );
710
+ }
711
+
712
+
713
+ export function createWorkSemanticContext(options: WorkContractStoreOptions, input: CreateWorkSemanticInput): WorkContract {
714
+ const semanticOptions = options.scopeKey?.trim() || !options.repoId?.trim()
715
+ ? options
716
+ : { ...options, scopeKey: options.repoId.trim() };
717
+ return createWorkContract(semanticOptions, {
718
+ workId: input.workId,
719
+ repoId: options.repoId?.trim() ?? '',
720
+ objective: input.objective,
721
+ acceptanceCriteria: [],
722
+ constraints: {},
723
+ workKind: 'investigation',
724
+ lifecycleRole: 'primary',
725
+ requestedBy: input.requestedBy ?? 'chatgpt',
726
+ checks: [],
727
+ ...(input.requirementId?.trim() ? { requirementId: input.requirementId.trim() } : {}),
728
+ ...(Number.isInteger(input.requirementRevision) ? { requirementRevision: input.requirementRevision } : {}),
729
+ ...(input.planId?.trim() ? { planId: input.planId.trim() } : {}),
730
+ ...(Number.isInteger(input.planRevision) ? { planRevision: input.planRevision } : {}),
731
+ ...(input.semanticParentWorkId !== undefined ? { semanticParentWorkId: input.semanticParentWorkId.trim() || undefined } : {}),
732
+ ...(input.dependsOnWorkIds !== undefined ? { dependsOnWorkIds: normalizeWorkObjectiveRelationIds(input.dependsOnWorkIds) } : {}),
733
+ ...(input.requestId?.trim() ? { requestId: input.requestId.trim() } : {}),
734
+ });
735
+ }
736
+
737
+ export function createWorkContract(options: WorkContractStoreOptions, input: CreateWorkContractInput): WorkContract {
738
+ // Thin semantic Work is authored context. Repository execution and concurrency
739
+ // are fenced by placement/resource ownership rather than a global migration gate.
740
+ const runtimeInput = input as CreateWorkContractInput & Record<string, unknown>;
741
+ if (Object.prototype.hasOwnProperty.call(runtimeInput, 'status')) throw new Error('WORK_RETIRED_FIELD_REJECTED: status');
742
+ for (const field of ['semanticState', 'semanticRevision', 'semanticUpdatedAt']) {
743
+ if (Object.prototype.hasOwnProperty.call(runtimeInput, field)) throw new Error(`WORK_SEMANTIC_FIELDS_REQUIRE_REVISION_API: ${field}`);
744
+ }
745
+ if (input.completionReceipt || input.completionOutcome) {
746
+ throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
747
+ }
748
+ const create = (): WorkContract => {
749
+ const at = input.createdAt ?? input.updatedAt ?? nowIso(options);
750
+ const workId = sanitizeFileComponent(input.workId);
751
+ const predecessorWorkId = input.predecessorWorkId ? sanitizeFileComponent(input.predecessorWorkId) : undefined;
752
+ if (predecessorWorkId === workId) throw new Error('WORK_PREDECESSOR_SELF_REFERENCE');
753
+ const contract: WorkContract = validateWorkSemantics({
754
+ schemaVersion: 4,
755
+ workId,
756
+ scopeRef: semanticScopeRefForWork({
757
+ workId,
758
+ scopeRef: input.scopeRef,
759
+ requirementId: input.requirementId,
760
+ planId: input.planId,
761
+ planStepId: input.planStepId,
762
+ }),
763
+ executionPlacement: input.executionPlacement
764
+ ?? (input.repoId?.trim() ? executionPlacementForWork({ repoId: input.repoId, checkoutId: input.checkoutId }) : undefined),
765
+ repoId: input.repoId,
766
+ checkoutId: input.checkoutId,
767
+ principalId: input.principalId,
768
+ controllerInstanceId: input.controllerInstanceId,
769
+ baseRevision: input.baseRevision,
770
+ repositoryBaseState: input.repositoryBaseState,
771
+ workspaceFingerprint: input.workspaceFingerprint,
772
+ objective: input.objective.slice(0, 2_000),
773
+ semanticRevision: 1,
774
+ semanticUpdatedAt: input.updatedAt ?? at,
775
+ semanticState: 'open',
776
+ acceptanceCriteria: (input.acceptanceCriteria ?? []).slice(0, 20).map((item) => item.slice(0, 500)),
777
+ constraints: input.constraints ?? {},
778
+ risk: input.risk ?? 'medium',
779
+ engineeringContext: input.engineeringContext,
780
+ workKind: input.workKind ?? 'repository_change',
781
+ lifecycleRole: input.lifecycleRole ?? 'primary',
782
+ parentWorkId: input.parentWorkId?.trim() || undefined,
783
+ semanticParentWorkId: input.semanticParentWorkId?.trim() || undefined,
784
+ dependsOnWorkIds: normalizeWorkObjectiveRelationIds(input.dependsOnWorkIds),
785
+ predecessorWorkId: predecessorWorkId && predecessorWorkId !== 'unknown' ? predecessorWorkId : undefined,
786
+ supersedes: input.supersedes?.map((value) => sanitizeFileComponent(value)).filter((value) => value !== 'unknown').slice(0, 50),
787
+ supersededBy: input.supersededBy ? sanitizeFileComponent(input.supersededBy) : undefined,
788
+ supersessionReason: input.supersessionReason?.trim().slice(0, 500),
789
+ dispatchState: input.dispatchState ?? initialLifecycleForNewWork().dispatchState,
790
+ evidenceState: input.evidenceState ?? initialLifecycleForNewWork().evidenceState,
791
+ completionOutcome: input.completionOutcome,
792
+ phase: input.phase ?? initialLifecycleForNewWork().phase,
793
+ phaseEvidence: initialPhaseEvidenceForNewWork({
794
+ phase: input.phase ?? initialLifecycleForNewWork().phase,
795
+ evidenceRefs: input.evidenceRefs ?? [],
796
+ updatedAt: input.updatedAt ?? at,
797
+ }),
798
+ completionReceipt: input.completionReceipt,
799
+ createdAt: at,
800
+ updatedAt: input.updatedAt ?? at,
801
+ issueId: input.issueId,
802
+ taskId: input.taskId,
803
+ requirementId: input.requirementId,
804
+ requirementRevision: input.requirementRevision,
805
+ planId: input.planId,
806
+ planRevision: input.planRevision,
807
+ planStepId: input.planStepId,
808
+ planSourceRevision: input.planSourceRevision,
809
+ scopeSummary: input.scopeSummary?.slice(0, 1_000),
810
+ scopeEvidence: input.scopeEvidence ? {
811
+ initialLikelyPaths: [...new Set(input.scopeEvidence.initialLikelyPaths)].slice(0, 100),
812
+ inspectedPaths: [...new Set(input.scopeEvidence.inspectedPaths)].slice(0, 500),
813
+ actualChangedPaths: [...new Set(input.scopeEvidence.actualChangedPaths)].slice(0, 500),
814
+ recordedAt: input.scopeEvidence.recordedAt,
815
+ } : undefined,
816
+ checks: (input.checks ?? []).slice(0, 30),
817
+ worktreePolicy: input.worktreePolicy ?? {
818
+ required: input.constraints?.workspaceMode === 'isolated',
819
+ reason: input.constraints?.workspaceMode === 'isolated'
820
+ ? 'Typed workspace placement requires an isolated worktree.'
821
+ : undefined,
822
+ },
823
+ evidencePolicy: input.evidencePolicy ?? {
824
+ defaultDetailLevel: 'summary',
825
+ allowRawOptIn: true,
826
+ maxEvidenceRefs: 20,
827
+ },
828
+ approvalPolicy: input.approvalPolicy ?? { required: false, reasons: [], confirmed: false },
829
+ recoveryPolicy: input.recoveryPolicy ?? {
830
+ allowSelfHealing: false,
831
+ maxInfrastructureRetries: 0,
832
+ handoffOnAmbiguity: true,
833
+ },
834
+ requestedBy: input.requestedBy ?? 'chatgpt',
835
+ evidenceRefs: (input.evidenceRefs ?? []).slice(0, 20),
836
+ handoffRefs: (input.handoffRefs ?? []).slice(0, 20),
837
+ suggestedNextActions: (input.suggestedNextActions ?? []).slice(0, 8),
838
+ policyDecisions: (input.policyDecisions ?? []).slice(0, 20),
839
+ checkRefs: (input.checkRefs ?? []).slice(0, 50),
840
+ implementationReviews: [],
841
+ reconciliations: (input.reconciliations ?? []).slice(0, 20),
842
+ continuationPrompt: input.continuationPrompt?.slice(0, 2_000),
843
+ worktreeRef: input.worktreeRef,
844
+ workerRef: input.workerRef,
845
+ requestId: input.requestId?.trim() || undefined,
846
+ submittedOperation: input.submittedOperation,
847
+ });
848
+
849
+ if (sqliteBacked(options)) {
850
+ withControlPlaneTransaction(options.controllerHome, (database) => {
851
+ const scope = workContractStoreScopeKey(options);
852
+ if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', scope, contract.workId)) {
853
+ throw new Error(`work contract already exists: ${contract.workId}`);
854
+ }
855
+ if (contract.semanticParentWorkId?.trim() || contract.dependsOnWorkIds?.length) {
856
+ const relationContracts = listControlPlaneRecordsWithinTransaction<WorkContract>(database, {
857
+ namespace: 'work_contract', scope, limit: 5_000,
858
+ }).map((record) => canonicalizeStoredWorkContract(record.value));
859
+ validateWorkObjectiveRelationIntegrity(contract, relationContracts);
860
+ }
861
+ writeControlPlaneRecordWithinTransaction(database, {
862
+ namespace: 'work_contract',
863
+ scope,
864
+ key: contract.workId,
865
+ schemaVersion: 4,
866
+ value: contract,
867
+ action: 'work_contract_created',
868
+ expectedRevision: null,
869
+ });
870
+ });
871
+ return contract;
872
+ }
873
+ const store = readWorkContractStore(options);
874
+ if (store.contracts.some((existing) => existing.workId === contract.workId)) {
875
+ throw new Error(`work contract already exists: ${contract.workId}`);
876
+ }
877
+ if (contract.semanticParentWorkId?.trim() || contract.dependsOnWorkIds?.length) {
878
+ validateWorkObjectiveRelationIntegrity(contract, store.contracts);
879
+ }
880
+ const nextStore: WorkContractStore = {
881
+ schemaVersion: 4,
882
+ updatedAt: contract.updatedAt,
883
+ contracts: [contract, ...store.contracts],
884
+ };
885
+ writeWorkContractStore(options, nextStore);
886
+ return contract;
887
+ };
888
+ const relationAware = Boolean(input.semanticParentWorkId?.trim() || input.dependsOnWorkIds?.length);
889
+ return withExactWorkContractWrite(options, input.workId, () => relationAware ? withWorkContractStoreWrite(options, create) : create());
890
+ }
891
+
892
+ interface WorkRequestIndexRecord {
893
+ requestId: string;
894
+ repoId: string;
895
+ workId: string;
896
+ semanticKey: string;
897
+ createdAt: string;
898
+ }
899
+
900
+ function workRequestIndexPath(controllerHome: string, requestId: string): string {
901
+ const hash = createHash('sha256').update(requestId).digest('hex');
902
+ const root = join(ensureControllerHome(controllerHome), 'indexes', 'work-contracts', 'requests');
903
+ mkdirSync(root, { recursive: true });
904
+ return join(root, `${hash}.json`);
905
+ }
906
+
907
+ export function getWorkContractByRequestId(
908
+ controllerHome: string,
909
+ requestId: string,
910
+ expectedRepoId?: string,
911
+ ): WorkContract | undefined {
912
+ const normalizedRequestId = requestId.trim();
913
+ if (!normalizedRequestId) return undefined;
914
+ const recordPath = workRequestIndexPath(controllerHome, normalizedRequestId);
915
+ if (!existsSync(recordPath)) return undefined;
916
+ try {
917
+ const record = readJsonFile<WorkRequestIndexRecord>(recordPath);
918
+ if (record.requestId !== normalizedRequestId) return undefined;
919
+ if (expectedRepoId && record.repoId !== expectedRepoId) return undefined;
920
+ return getWorkContract({ controllerHome, repoId: record.repoId }, record.workId);
921
+ } catch {
922
+ return undefined;
923
+ }
924
+ }
925
+
926
+ /**
927
+ * Create or reuse the execution-child Work used by typed callers. This is not
928
+ * a generic MCP submission authority and never creates an ExecutionJob.
929
+ */
930
+ export function acceptSubmittedWorkContract(
931
+ controllerHome: string,
932
+ input: AcceptSubmittedWorkInput,
933
+ options: WorkContractStoreOptions = {},
934
+ ): { contract: WorkContract; deduplicated: boolean } {
935
+ const home = ensureControllerHome(controllerHome);
936
+ const requestId = input.requestId.trim();
937
+ const semanticKey = input.semanticKey.trim();
938
+ if (!requestId) throw new Error('INVALID_ARGUMENT: typed execution is missing request_id');
939
+ if (!semanticKey) throw new Error('INVALID_ARGUMENT: typed execution requires a semantic operation key');
940
+ if (!input.repoId.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing repo_id');
941
+ if (!input.operation?.name?.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing operation');
942
+ const parentWorkId = input.parentWorkId?.trim() || undefined;
943
+ if (parentWorkId) {
944
+ const parent = getWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, parentWorkId);
945
+ if (!parent) throw new Error(`PARENT_WORK_NOT_FOUND: ${parentWorkId}`);
946
+ if (semanticWorkState(parent) !== 'open') throw new Error(`PARENT_WORK_TERMINAL: ${parentWorkId}:${semanticWorkState(parent)}`);
947
+ if ((parent.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`PARENT_WORK_NOT_PRIMARY: ${parentWorkId}`);
948
+ }
949
+ const lockId = createHash('sha256').update(requestId).digest('hex').slice(0, 24);
950
+ return withControllerLock(home, { scope: 'global', resource: `work-request-${lockId}` }, `accept-work:${requestId}`, () => {
951
+ const recordPath = workRequestIndexPath(home, requestId);
952
+ if (existsSync(recordPath)) {
953
+ const record = readJsonFile<WorkRequestIndexRecord>(recordPath);
954
+ if (record.repoId !== input.repoId) throw new Error(`REQUEST_ID_REPO_CONFLICT: ${requestId} already belongs to repository ${record.repoId}`);
955
+ if (record.semanticKey !== semanticKey) throw new Error(`REQUEST_ID_CONFLICT: ${requestId} already belongs to ${record.semanticKey}`);
956
+ const existing = getWorkContract({ controllerHome: home, repoId: record.repoId, now: options.now }, record.workId);
957
+ if (!existing) throw new Error(`WORK_ACCEPTANCE_LOST: request ${requestId} is indexed without a readable WorkContract`);
958
+ return { contract: existing, deduplicated: true };
959
+ }
960
+ const contract = createWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, {
961
+ workId: `WORK-${Date.now()}-${randomUUID().slice(0, 8)}`,
962
+ repoId: input.repoId,
963
+ principalId: input.principalId,
964
+ controllerInstanceId: input.controllerInstanceId,
965
+ lifecycleRole: 'execution_child',
966
+ parentWorkId,
967
+ objective: (input.objective ?? `Typed operation ${input.operation.name}`).slice(0, 2_000),
968
+ acceptanceCriteria: input.acceptanceCriteria ?? [],
969
+ workKind: input.workKind,
970
+ risk: input.risk,
971
+ constraints: input.constraints ?? {},
972
+ checks: input.checks ?? [],
973
+ requestedBy: input.requestedBy ?? 'chatgpt',
974
+ requestId,
975
+ submittedOperation: input.operation,
976
+ suggestedNextActions: [],
977
+ });
978
+ writeJsonAtomic(recordPath, { requestId, repoId: input.repoId, workId: contract.workId, semanticKey, createdAt: contract.createdAt } satisfies WorkRequestIndexRecord);
979
+ return { contract, deduplicated: false };
980
+ });
981
+ }
982
+
983
+ export function isCurrentWorkContract(contract: WorkContract): boolean {
984
+ return semanticWorkState(contract) === 'open'
985
+ && !contract.supersededBy?.trim()
986
+ && contract.workKind !== 'superseded'
987
+ && contract.completionOutcome !== 'superseded';
988
+ }
989
+
990
+ export function listWorkContracts(options: ListWorkContractOptions): WorkContract[] {
991
+ const state = options.state ?? 'active';
992
+ const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 50), 100));
993
+ if (state === 'active' && sqliteBacked(options)) {
994
+ const active = readActiveWorkCandidates({ ...options, limit });
995
+ if (active.invalid.length > 0) {
996
+ // Preserve the aggregate list API's fail-closed semantics. Callers that
997
+ // intentionally need row-isolated corruption diagnostics use
998
+ // readActiveWorkCandidates() directly and can inspect every invalid row.
999
+ throw new Error(active.invalid[0]!.error);
1000
+ }
1001
+ return active.contracts;
1002
+ }
1003
+ const store = readWorkContractStore(options);
1004
+ return store.contracts
1005
+ .filter((contract) => {
1006
+ if (state === 'all') return true;
1007
+ if (state === 'active') return isCurrentWorkContract(contract);
1008
+ return semanticWorkState(contract) === state;
1009
+ })
1010
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
1011
+ .slice(0, limit);
1012
+ }
1013
+
1014
+ function workExecutionSemanticScopeKeys(contract: WorkContract): string[] {
1015
+ const declared = contract.engineeringContext?.semanticScope?.keys
1016
+ ?.map((value) => value.trim())
1017
+ .filter(Boolean) ?? [];
1018
+ if (declared.length > 0) return [...new Set(declared)].sort();
1019
+ if (contract.planId?.trim() && contract.planStepId?.trim()) {
1020
+ return [`plan-step:${contract.planId.trim()}:${contract.planStepId.trim()}`];
1021
+ }
1022
+ return [`work:${contract.workId}`];
1023
+ }
1024
+
1025
+ function workExecutionIsolation(contract: WorkContract): 'shared' | 'isolated' {
1026
+ return contract.worktreePolicy?.required === true
1027
+ || contract.constraints?.workspaceMode === 'isolated'
1028
+ ? 'isolated'
1029
+ : 'shared';
1030
+ }
1031
+
1032
+ function rawWorkMayBeCurrent(contract: WorkContract): boolean {
1033
+ return semanticWorkState(contract) === 'open'
1034
+ && !contract.supersededBy?.trim()
1035
+ && contract.workKind !== 'superseded'
1036
+ && contract.completionOutcome !== 'superseded';
1037
+ }
1038
+
1039
+ /**
1040
+ * Row-isolated active Work admission projection. Canonical aggregate Work reads
1041
+ * remain strict. Each SQLite row is normalized independently so one malformed
1042
+ * legacy sibling cannot erase otherwise valid Work authority from control-plane
1043
+ * admission. Invalid active rows stay explicit and conservative by identity,
1044
+ * lineage, scope and isolation; they are never mutated or silently accepted.
1045
+ */
1046
+ export function initializeWorkCandidateIndex(controllerHome: string): void {
1047
+ initializeControlPlanePayloadTextExclusionIndex(controllerHome, {
1048
+ namespace: 'work_contract', field: 'semanticState', excludedValues: TERMINAL_SEMANTIC_WORK_STATES,
1049
+ });
1050
+ }
1051
+
1052
+ export function readActiveWorkCandidates(
1053
+ options: WorkContractStoreOptions & { limit?: number },
1054
+ ): ActiveWorkCandidateSnapshot {
1055
+ const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 1_000), 1_000));
1056
+ if (!sqliteBacked(options)) {
1057
+ return { contracts: listWorkContracts({ ...options, state: 'active', limit }), invalid: [] };
1058
+ }
1059
+ const requestedScope = workContractStoreScopeKey(options);
1060
+ const records = listControlPlaneRecordsExcludingPayloadTextValues<WorkContract>(options.controllerHome, {
1061
+ namespace: 'work_contract',
1062
+ scope: requestedScope,
1063
+ field: 'semanticState',
1064
+ excludedValues: TERMINAL_SEMANTIC_WORK_STATES,
1065
+ limit: 5_000,
1066
+ });
1067
+ const semanticAuthorityWorkIds = requestedScope === SEMANTIC_SCOPE_KEY
1068
+ ? new Set<string>()
1069
+ : new Set(listControlPlaneRecordKeys(options.controllerHome, {
1070
+ namespace: 'work_contract',
1071
+ scope: SEMANTIC_SCOPE_KEY,
1072
+ limit: 5_000,
1073
+ }));
1074
+ const contracts: WorkContract[] = [];
1075
+ const invalid: InvalidActiveWorkCandidate[] = [];
1076
+ const migrations: Array<{ record: (typeof records)[number]; contract: WorkContract }> = [];
1077
+ for (const record of records) {
1078
+ // Exact Work reads already prefer semantic scope over repository-scoped
1079
+ // compatibility rows. Collection reads must preserve the same one-authority
1080
+ // rule or a stale open compatibility shadow can resurrect terminal Work.
1081
+ if (semanticAuthorityWorkIds.has(record.key)) continue;
1082
+ const raw = record.value;
1083
+ if (!rawWorkMayBeCurrent(raw)) continue;
1084
+ try {
1085
+ const normalized = canonicalizeStoredWorkContract(raw, record.schemaVersion);
1086
+ if (storedWorkContractNeedsMigration(raw, record.schemaVersion)) migrations.push({ record, contract: normalized });
1087
+ if (isCurrentWorkContract(normalized)) contracts.push(normalized);
1088
+ } catch (error) {
1089
+ invalid.push({
1090
+ workId: raw.workId,
1091
+ updatedAt: raw.updatedAt,
1092
+ ...(raw.checkoutId?.trim() ? { checkoutId: raw.checkoutId.trim() } : {}),
1093
+ ...(raw.requirementId?.trim() ? { requirementId: raw.requirementId.trim() } : {}),
1094
+ ...(raw.planId?.trim() ? { planId: raw.planId.trim() } : {}),
1095
+ ...(raw.planStepId?.trim() ? { planStepId: raw.planStepId.trim() } : {}),
1096
+ semanticScopeKeys: workExecutionSemanticScopeKeys(raw),
1097
+ isolation: workExecutionIsolation(raw),
1098
+ error: error instanceof Error ? error.message : String(error),
1099
+ });
1100
+ }
1101
+ }
1102
+ if (migrations.length > 0) {
1103
+ withControlPlaneTransaction(options.controllerHome, (database) => {
1104
+ for (const { record, contract } of migrations) {
1105
+ writeControlPlaneRecordWithinTransaction(database, {
1106
+ namespace: 'work_contract',
1107
+ scope: workContractStoreScopeKey(options),
1108
+ key: contract.workId,
1109
+ schemaVersion: 4,
1110
+ value: contract,
1111
+ action: 'work_contract_schema_v4_migrated',
1112
+ expectedRevision: record.revision,
1113
+ });
1114
+ }
1115
+ });
1116
+ }
1117
+ contracts.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
1118
+ invalid.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
1119
+ return { contracts: contracts.slice(0, limit), invalid };
1120
+ }
1121
+
1122
+ export function listWorkSemanticRevisionRecords(
1123
+ options: WorkContractStoreOptions,
1124
+ workId?: string,
1125
+ limit = 200,
1126
+ ): WorkSemanticRevisionRecord[] {
1127
+ const normalizedId = workId ? sanitizeFileComponent(workId) : undefined;
1128
+ const boundedLimit = Math.max(1, Math.min(Math.trunc(limit), 1000));
1129
+ const authoritativeOptions = normalizedId
1130
+ ? authoritativeExistingWorkStoreOptions(options, normalizedId)
1131
+ : options;
1132
+ const records = sqliteBacked(authoritativeOptions)
1133
+ ? listControlPlaneRecords<WorkSemanticRevisionRecord>(authoritativeOptions.controllerHome, {
1134
+ namespace: 'work_semantic_revision', scope: workContractStoreScopeKey(authoritativeOptions), limit: 5_000,
1135
+ }).map((record) => record.value)
1136
+ : readWorkSemanticRevisionStore(authoritativeOptions).records;
1137
+ return records
1138
+ .filter((record) => !normalizedId || record.workId === normalizedId)
1139
+ .map((record) => ({
1140
+ ...record,
1141
+ semanticScope: record.semanticScope ?? semanticScopeRefForWork({
1142
+ workId: record.workId,
1143
+ requirementId: record.requirementId,
1144
+ planId: record.planId,
1145
+ planStepId: undefined,
1146
+ }),
1147
+ }))
1148
+ .sort((left, right) => right.revision - left.revision)
1149
+ .slice(0, boundedLimit);
1150
+ }
1151
+
1152
+ function authoritativeExistingWorkStoreOptions(
1153
+ options: WorkContractStoreOptions,
1154
+ workIdInput: string,
1155
+ ): WorkContractStoreOptions {
1156
+ if (!sqliteBacked(options)) return options;
1157
+ const requestedScope = workContractStoreScopeKey(options);
1158
+ if (requestedScope === SEMANTIC_SCOPE_KEY) return options;
1159
+ const workId = sanitizeFileComponent(workIdInput);
1160
+ const semantic = readControlPlaneRecord<WorkContract>(
1161
+ options.controllerHome,
1162
+ 'work_contract',
1163
+ SEMANTIC_SCOPE_KEY,
1164
+ workId,
1165
+ );
1166
+ return semantic ? { ...options, scopeKey: SEMANTIC_SCOPE_KEY } : options;
1167
+ }
1168
+
1169
+ export function reviseWorkSemanticContext(
1170
+ options: WorkContractStoreOptions,
1171
+ workIdInput: string,
1172
+ input: ReviseWorkSemanticInput,
1173
+ ): WorkContract {
1174
+ if (!Number.isInteger(input.expectedRevision) || input.expectedRevision < 1) throw new Error('WORK_EXPECTED_REVISION_INVALID');
1175
+ return withExactWorkContractWrite(options, workIdInput, () => {
1176
+ const authoritativeOptions = authoritativeExistingWorkStoreOptions(options, workIdInput);
1177
+ return withWorkContractStoreWrite(authoritativeOptions, () => {
1178
+ const workId = sanitizeFileComponent(workIdInput);
1179
+ const relationRevisionRequested = input.semanticParentWorkId !== undefined || input.dependsOnWorkIds !== undefined;
1180
+ const applyRevision = (current: WorkContract, at: string, relationContracts: readonly WorkContract[] = []): WorkContract => {
1181
+ const semanticRevision = currentWorkSemanticRevision(current);
1182
+ if (semanticRevision !== input.expectedRevision) {
1183
+ throw new Error(`WORK_REVISION_CONFLICT:${workId}:expected=${input.expectedRevision}:actual=${semanticRevision}`);
1184
+ }
1185
+ const currentSemanticState = semanticWorkState(current);
1186
+ if (currentSemanticState !== 'open' && input.state === 'open') {
1187
+ throw new Error(`WORK_SEMANTIC_REOPEN_FORBIDDEN:${workId}:${currentSemanticState}`);
1188
+ }
1189
+ const objective = input.objective === undefined ? current.objective : String(input.objective).trim().slice(0, 2_000);
1190
+ if (!objective) throw new Error('WORK_OBJECTIVE_REQUIRED');
1191
+ const positiveRevision = (value: number | undefined, code: string): number | undefined => {
1192
+ if (value === undefined) return undefined;
1193
+ if (!Number.isInteger(value) || value < 1) throw new Error(code);
1194
+ return value;
1195
+ };
1196
+ const nextSemanticState = input.state ?? currentSemanticState;
1197
+ if (currentSemanticState !== 'open' && (input.semanticParentWorkId !== undefined || input.dependsOnWorkIds !== undefined)) {
1198
+ throw new Error(`WORK_OBJECTIVE_RELATION_TERMINAL_IMMUTABLE:${workId}:${currentSemanticState}`);
1199
+ }
1200
+ const next = validateWorkSemantics({
1201
+ ...current,
1202
+ objective,
1203
+ semanticRevision: semanticRevision + 1,
1204
+ semanticUpdatedAt: at,
1205
+ semanticState: nextSemanticState,
1206
+ ...(input.requirementRevision !== undefined ? { requirementRevision: positiveRevision(input.requirementRevision, 'WORK_REQUIREMENT_REVISION_INVALID') } : {}),
1207
+ ...(input.planRevision !== undefined ? { planRevision: positiveRevision(input.planRevision, 'WORK_PLAN_REVISION_INVALID') } : {}),
1208
+ ...(input.semanticParentWorkId !== undefined ? { semanticParentWorkId: input.semanticParentWorkId.trim() || undefined } : {}),
1209
+ ...(input.dependsOnWorkIds !== undefined ? { dependsOnWorkIds: normalizeWorkObjectiveRelationIds(input.dependsOnWorkIds) } : {}),
1210
+ ...(input.resultRefs !== undefined ? { semanticResultRefs: [...new Set(input.resultRefs.map(String).map((value) => value.trim()).filter(Boolean))].slice(0, 100) } : {}),
1211
+ updatedAt: at,
1212
+ });
1213
+ if (relationRevisionRequested) validateWorkObjectiveRelationIntegrity(next, relationContracts);
1214
+ return next;
1215
+ };
1216
+
1217
+ if (sqliteBacked(authoritativeOptions)) {
1218
+ return withControlPlaneTransaction(authoritativeOptions.controllerHome, (database) => {
1219
+ const scope = workContractStoreScopeKey(authoritativeOptions);
1220
+ const currentRecord = readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', scope, workId);
1221
+ if (!currentRecord) throw new Error(`work contract not found: ${workId}`);
1222
+ const current = canonicalizeStoredWorkContract(currentRecord.value);
1223
+ const at = nowIso(authoritativeOptions);
1224
+ const relationContracts = relationRevisionRequested
1225
+ ? listControlPlaneRecordsWithinTransaction<WorkContract>(database, {
1226
+ namespace: 'work_contract', scope, limit: 5_000,
1227
+ }).map((record) => canonicalizeStoredWorkContract(record.value))
1228
+ : [];
1229
+ const next = applyRevision(current, at, relationContracts);
1230
+ const semanticRevision = currentWorkSemanticRevision(current);
1231
+ const revisionKey = workSemanticRevisionKey(workId, semanticRevision);
1232
+ if (!readControlPlaneRecordWithinTransaction<WorkSemanticRevisionRecord>(database, 'work_semantic_revision', scope, revisionKey)) {
1233
+ writeControlPlaneRecordWithinTransaction(database, {
1234
+ namespace: 'work_semantic_revision', scope, key: revisionKey, schemaVersion: 1,
1235
+ value: { schemaVersion: 1, ...workSemanticView(current), recordedAt: at },
1236
+ action: 'work_semantic_revision_archived', expectedRevision: null,
1237
+ });
1238
+ }
1239
+ return writeControlPlaneRecordWithinTransaction(database, {
1240
+ namespace: 'work_contract', scope, key: workId, schemaVersion: 4,
1241
+ value: next, action: 'work_semantic_revised', expectedRevision: currentRecord.revision,
1242
+ }).value;
1243
+ });
1244
+ }
1245
+
1246
+ // Semantic CAS validation must be side-effect free. The public read path may
1247
+ // persist a one-way legacy migration, so normalize the file-backed store only
1248
+ // in memory here and write it exactly once after the requested revision has
1249
+ // been validated successfully.
1250
+ const rawStore = readJsonFile<WorkContractStore>(
1251
+ workContractStorePath(authoritativeOptions),
1252
+ emptyWorkContractStore(nowIso(authoritativeOptions)),
1253
+ );
1254
+ const store = normalizeWorkContractStore(rawStore);
1255
+ const index = store.contracts.findIndex((contract) => contract.workId === workId);
1256
+ if (index < 0) throw new Error(`work contract not found: ${workId}`);
1257
+ const current = store.contracts[index]!;
1258
+ const at = nowIso(authoritativeOptions);
1259
+ const next = applyRevision(current, at, relationRevisionRequested ? store.contracts : []);
1260
+ const archived = { schemaVersion: 1 as const, ...workSemanticView(current), recordedAt: at };
1261
+ const history = readWorkSemanticRevisionStore(authoritativeOptions);
1262
+ if (!history.records.some((record) => record.workId === workId && record.revision === archived.revision)) {
1263
+ writeJsonAtomic(workSemanticRevisionStorePath(authoritativeOptions), { schemaVersion: 1, records: [...history.records, archived].slice(-5_000) });
1264
+ }
1265
+ const contracts = [...store.contracts];
1266
+ contracts[index] = next;
1267
+ writeWorkContractStore(authoritativeOptions, { schemaVersion: 4, updatedAt: at, contracts });
1268
+ return next;
1269
+ });
1270
+ });
1271
+ }
1272
+
1273
+ function readExactSqliteWorkContract(
1274
+ options: WorkContractStoreOptions & { controllerHome: string },
1275
+ scope: string,
1276
+ sanitizedId: string,
1277
+ ): WorkContract | undefined {
1278
+ const exact = readControlPlaneRecord<WorkContract>(options.controllerHome, 'work_contract', scope, sanitizedId);
1279
+ if (!exact) return undefined;
1280
+ const canonical = canonicalizeStoredWorkContract(exact.value, exact.schemaVersion);
1281
+ if (storedWorkContractNeedsMigration(exact.value, exact.schemaVersion)) {
1282
+ withControlPlaneTransaction(options.controllerHome, (database) => {
1283
+ writeControlPlaneRecordWithinTransaction(database, {
1284
+ namespace: 'work_contract',
1285
+ scope,
1286
+ key: canonical.workId,
1287
+ schemaVersion: 4,
1288
+ value: canonical,
1289
+ action: 'work_contract_schema_v4_migrated',
1290
+ expectedRevision: exact.revision,
1291
+ });
1292
+ });
1293
+ }
1294
+ return canonical;
1295
+ }
1296
+
1297
+ export function getWorkContract(options: WorkContractStoreOptions, workId: string): WorkContract | undefined {
1298
+ const sanitizedId = sanitizeFileComponent(workId);
1299
+ if (!sqliteBacked(options)) {
1300
+ return readWorkContractStore(options).contracts.find((contract) => contract.workId === sanitizedId);
1301
+ }
1302
+ const requestedScope = workContractStoreScopeKey(options);
1303
+ if (requestedScope !== SEMANTIC_SCOPE_KEY) {
1304
+ const semantic = readExactSqliteWorkContract(options, SEMANTIC_SCOPE_KEY, sanitizedId);
1305
+ if (semantic) return semantic;
1306
+ }
1307
+ const exact = readExactSqliteWorkContract(options, requestedScope, sanitizedId);
1308
+ if (exact) return exact;
1309
+
1310
+ // Preserve the one-time legacy import path only while this requested scope has
1311
+ // no per-Work rows. Canonical semantic rows are checked first and never copied
1312
+ // into repository scope; repository-scoped rows remain a read-only compatibility
1313
+ // fallback until their consumers are retired.
1314
+ const hasPerWorkRows = listControlPlaneRecords<WorkContract>(options.controllerHome, {
1315
+ namespace: 'work_contract',
1316
+ scope: requestedScope,
1317
+ limit: 1,
1318
+ }).length > 0;
1319
+ if (hasPerWorkRows) return undefined;
1320
+ return readWorkContractStore(options).contracts.find((contract) => contract.workId === sanitizedId);
1321
+ }
1322
+
1323
+ export interface SupersedeWorkContractInput {
1324
+ workId: string;
1325
+ supersededBy: string;
1326
+ reason: string;
1327
+ }
1328
+
1329
+ function workSupersessionWouldCycle(contracts: WorkContract[], predecessorId: string, successorId: string): boolean {
1330
+ const byId = new Map(contracts.map((contract) => [contract.workId, contract]));
1331
+ const visited = new Set<string>();
1332
+ let cursorId: string | undefined = successorId;
1333
+ while (cursorId) {
1334
+ if (cursorId === predecessorId) return true;
1335
+ if (visited.has(cursorId)) return true;
1336
+ visited.add(cursorId);
1337
+ cursorId = byId.get(cursorId)?.supersededBy?.trim() || undefined;
1338
+ }
1339
+ return false;
1340
+ }
1341
+
1342
+ /** Persist one explicit Work supersession edge without deleting or terminalizing either side. */
1343
+ export function supersedeWorkContract(
1344
+ options: WorkContractStoreOptions,
1345
+ input: SupersedeWorkContractInput,
1346
+ ): { predecessor: WorkContract; successor: WorkContract } {
1347
+ return withWorkContractStoreWrite(options, () => {
1348
+ const predecessorId = sanitizeFileComponent(input.workId);
1349
+ const successorId = sanitizeFileComponent(input.supersededBy);
1350
+ if (!predecessorId || predecessorId === 'unknown' || !successorId || successorId === 'unknown') throw new Error('WORK_SUPERSESSION_IDS_REQUIRED');
1351
+ if (predecessorId === successorId) throw new Error('WORK_SUCCESSOR_ID_MUST_CHANGE');
1352
+ const store = readWorkContractStore(options);
1353
+ const predecessorIndex = store.contracts.findIndex((contract) => contract.workId === predecessorId);
1354
+ const successorIndex = store.contracts.findIndex((contract) => contract.workId === successorId);
1355
+ if (predecessorIndex < 0) throw new Error(`WORK_PREDECESSOR_NOT_FOUND: ${predecessorId}`);
1356
+ if (successorIndex < 0) throw new Error(`WORK_SUCCESSOR_NOT_FOUND: ${successorId}`);
1357
+ const predecessor = store.contracts[predecessorIndex]!;
1358
+ const successor = store.contracts[successorIndex]!;
1359
+ if (predecessor.supersededBy && predecessor.supersededBy !== successorId) throw new Error(`WORK_SUPERSESSION_CONFLICT: ${predecessorId}:existing=${predecessor.supersededBy}:requested=${successorId}`);
1360
+ if ((predecessor.supersedes ?? []).includes(successorId) || workSupersessionWouldCycle(store.contracts, predecessorId, successorId)) throw new Error(`WORK_SUPERSESSION_CYCLE: ${predecessorId}:${successorId}`);
1361
+ const at = nowIso(options);
1362
+ const reason = String(input.reason ?? '').trim().slice(0, 500);
1363
+ if (!reason) throw new Error('WORK_SUPERSESSION_REASON_REQUIRED');
1364
+ const predecessorNext = validateWorkSemantics({ ...predecessor, supersededBy: successorId, supersessionReason: reason, updatedAt: at });
1365
+ const successorNext = validateWorkSemantics({ ...successor, supersedes: [...new Set([...(successor.supersedes ?? []), predecessorId])], updatedAt: at });
1366
+ const contracts = [...store.contracts];
1367
+ contracts[predecessorIndex] = predecessorNext;
1368
+ contracts[successorIndex] = successorNext;
1369
+ if (!sqliteBacked(options)) {
1370
+ writeJsonAtomic(workContractStorePath(options), { schemaVersion: 4, updatedAt: at, contracts });
1371
+ } else {
1372
+ withControlPlaneTransaction(options.controllerHome, (database) => {
1373
+ for (const contract of [predecessorNext, successorNext]) {
1374
+ const current = readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', workContractStoreScopeKey(options), contract.workId);
1375
+ if (!current) throw new Error(`WORK_LINEAGE_RECORD_MISSING: ${contract.workId}`);
1376
+ writeControlPlaneRecordWithinTransaction(database, { namespace: 'work_contract', scope: workContractStoreScopeKey(options), key: contract.workId, schemaVersion: 4, value: contract, action: 'work_contract_supersession_linked', expectedRevision: current.revision });
1377
+ }
1378
+ });
1379
+ }
1380
+ return { predecessor: predecessorNext, successor: successorNext };
1381
+ });
1382
+ }
1383
+
1384
+ export function summarizeWorkContract(contract: WorkContract): WorkContractSummary {
1385
+ return {
1386
+ workId: contract.workId,
1387
+ repoId: contract.repoId,
1388
+ state: semanticWorkState(contract),
1389
+ phase: contract.phase,
1390
+ objective: contract.objective.slice(0, 240),
1391
+ updatedAt: contract.updatedAt,
1392
+ handoffCount: contract.handoffRefs.length,
1393
+ evidenceCount: contract.evidenceRefs.length,
1394
+ checkCount: contract.checkRefs.length,
1395
+ ...(contract.engineeringContext ? {
1396
+ engineering: {
1397
+ profileVersion: contract.engineeringContext.profileVersion,
1398
+ riskClass: contract.engineeringContext.riskClass,
1399
+ missingAdmissionEvidence: contract.engineeringContext.missingAdmissionEvidence,
1400
+ missingCompletionEvidence: contract.engineeringContext.missingCompletionEvidence,
1401
+ },
1402
+ } : {}),
1403
+ };
1404
+ }
1405
+
1406
+ type WorkContractMutationPatch = Partial<Omit<WorkContract, 'schemaVersion' | 'workId' | 'repoId' | 'createdAt'>>;
1407
+ type WorkContractMutation = WorkContractMutationPatch | ((current: WorkContract, recordedAt: string) => WorkContractMutationPatch | undefined);
1408
+
1409
+ function updateWorkContractInternal(
1410
+ options: WorkContractStoreOptions,
1411
+ workId: string,
1412
+ mutation: WorkContractMutation,
1413
+ allowCompletionWrite: boolean,
1414
+ allowLifecycleWrite = false,
1415
+ allowImplementationReviewWrite = false,
1416
+ allowPhaseRegression = false,
1417
+ ): WorkContract {
1418
+ return withExactWorkContractWrite(options, workId, () => {
1419
+ const authoritativeOptions = authoritativeExistingWorkStoreOptions(options, workId);
1420
+ return withWorkContractStoreWrite(authoritativeOptions, () => {
1421
+ const sanitizedId = sanitizeFileComponent(workId);
1422
+ const exact = sqliteBacked(authoritativeOptions)
1423
+ ? readControlPlaneRecord<WorkContract>(
1424
+ authoritativeOptions.controllerHome,
1425
+ 'work_contract',
1426
+ workContractStoreScopeKey(authoritativeOptions),
1427
+ sanitizedId,
1428
+ )
1429
+ : undefined;
1430
+ const store = exact ? undefined : readWorkContractStore(authoritativeOptions);
1431
+ const index = store?.contracts.findIndex((contract) => contract.workId === sanitizedId) ?? -1;
1432
+ if (!exact && index < 0) throw new Error(`work contract not found: ${sanitizedId}`);
1433
+ const at = nowIso(authoritativeOptions);
1434
+ // A per-Work SQLite row is the mutable authority. Do not normalize every
1435
+ // sibling just to update this row: malformed historical evidence must stay
1436
+ // visible and fenced, but cannot prevent a different terminal Work from
1437
+ // recording its physical cleanup.
1438
+ const current = exact
1439
+ ? canonicalizeStoredWorkContract(exact.value)
1440
+ : store!.contracts[index]!;
1441
+ const mutationPatch = typeof mutation === 'function' ? mutation(current, at) : mutation;
1442
+ if (!mutationPatch) return current;
1443
+ const placementChanged = (
1444
+ Object.prototype.hasOwnProperty.call(mutationPatch, 'checkoutId')
1445
+ && mutationPatch.checkoutId !== current.checkoutId
1446
+ ) || (
1447
+ Object.prototype.hasOwnProperty.call(mutationPatch, 'worktreeRef')
1448
+ && mutationPatch.worktreeRef !== current.worktreeRef
1449
+ ) || (
1450
+ Object.prototype.hasOwnProperty.call(mutationPatch, 'executionPlacement')
1451
+ && JSON.stringify(mutationPatch.executionPlacement) !== JSON.stringify(current.executionPlacement)
1452
+ );
1453
+ // executionConcurrency is only a projection of the concrete placement that
1454
+ // existed when an execution attempt was admitted. A checkout/worktree move
1455
+ // makes that projection stale; clear it centrally unless the same atomic
1456
+ // mutation explicitly supplies a replacement projection for the new target.
1457
+ const patch = placementChanged && !Object.prototype.hasOwnProperty.call(mutationPatch, 'executionConcurrency')
1458
+ ? { ...mutationPatch, executionConcurrency: undefined }
1459
+ : mutationPatch;
1460
+ if (Object.prototype.hasOwnProperty.call(patch, 'status')) throw new Error('WORK_RETIRED_FIELD_REJECTED: status');
1461
+ for (const field of ['semanticState', 'semanticRevision', 'semanticUpdatedAt']) {
1462
+ if (!allowLifecycleWrite && Object.prototype.hasOwnProperty.call(patch, field)) {
1463
+ throw new Error(`WORK_SEMANTIC_FIELDS_REQUIRE_REVISION_API: ${field}`);
1464
+ }
1465
+ }
1466
+ const writesCompletionReceipt = Object.prototype.hasOwnProperty.call(patch, 'completionReceipt');
1467
+ const changesCompletionOutcome = patch.completionOutcome !== undefined && patch.completionOutcome !== current.completionOutcome;
1468
+ const writesPhase = Object.prototype.hasOwnProperty.call(patch, 'phase') || Object.prototype.hasOwnProperty.call(patch, 'phaseEvidence');
1469
+ const writesLifecycle = writesPhase
1470
+ || Object.prototype.hasOwnProperty.call(patch, 'dispatchState')
1471
+ || Object.prototype.hasOwnProperty.call(patch, 'evidenceState')
1472
+ || Object.prototype.hasOwnProperty.call(patch, 'workKind');
1473
+ const writesImplementationReviews = Object.prototype.hasOwnProperty.call(patch, 'implementationReviews');
1474
+ if (!allowLifecycleWrite && writesLifecycle) throw new Error('WORK_LIFECYCLE_REQUIRES_TRANSITION_API');
1475
+ if (!allowImplementationReviewWrite && writesImplementationReviews) throw new Error('WORK_IMPLEMENTATION_REVIEW_REQUIRES_RECORD_API');
1476
+ const projectedPhase = patch.phase ?? current.phase;
1477
+ const projectedPhaseEvidence = patch.phaseEvidence ?? current.phaseEvidence;
1478
+ if (!allowCompletionWrite && (writesCompletionReceipt || changesCompletionOutcome)) {
1479
+ throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
1480
+ }
1481
+ if (writesCompletionReceipt && patch.completionReceipt === undefined && current.completionReceipt) {
1482
+ throw new Error('WORK_COMPLETION_RECEIPT_IMMUTABLE');
1483
+ }
1484
+ const next: WorkContract = validateWorkSemanticTransition(current, validateWorkSemantics({
1485
+ ...current,
1486
+ ...patch,
1487
+ schemaVersion: 4,
1488
+ workId: current.workId,
1489
+ repoId: current.repoId,
1490
+ createdAt: current.createdAt,
1491
+ updatedAt: at,
1492
+ risk: patch.risk ?? current.risk,
1493
+ workKind: patch.workKind ?? current.workKind,
1494
+ phase: projectedPhase,
1495
+ phaseEvidence: projectedPhaseEvidence,
1496
+ dispatchState: patch.dispatchState ?? current.dispatchState,
1497
+ evidenceState: patch.evidenceState ?? current.evidenceState,
1498
+ completionOutcome: patch.completionOutcome ?? current.completionOutcome,
1499
+ evidenceRefs: (patch.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs),
1500
+ handoffRefs: (patch.handoffRefs ?? current.handoffRefs).slice(0, 20),
1501
+ suggestedNextActions: suggestedActionsForSemanticState(current.semanticState, patch.suggestedNextActions ?? current.suggestedNextActions),
1502
+ policyDecisions: (patch.policyDecisions ?? current.policyDecisions).slice(0, 20),
1503
+ checkRefs: (patch.checkRefs ?? current.checkRefs).slice(0, 50),
1504
+ implementationReviews: (patch.implementationReviews ?? current.implementationReviews ?? []).slice(-MAX_IMPLEMENTATION_REVIEW_HISTORY),
1505
+ reconciliations: (patch.reconciliations ?? current.reconciliations ?? []).slice(0, 20),
1506
+ objective: (patch.objective ?? current.objective).slice(0, 2_000),
1507
+ continuationPrompt: (patch.continuationPrompt ?? current.continuationPrompt)?.slice(0, 2_000),
1508
+ }), { allowPhaseRegression });
1509
+ if (exact && sqliteBacked(authoritativeOptions)) {
1510
+ withControlPlaneTransaction(authoritativeOptions.controllerHome, (database) => {
1511
+ writeControlPlaneRecordWithinTransaction(database, {
1512
+ namespace: 'work_contract',
1513
+ scope: workContractStoreScopeKey(authoritativeOptions),
1514
+ key: next.workId,
1515
+ schemaVersion: 4,
1516
+ value: next,
1517
+ action: 'work_contract_updated',
1518
+ expectedRevision: exact.revision,
1519
+ });
1520
+ });
1521
+ return next;
1522
+ }
1523
+ const contracts = [...store!.contracts];
1524
+ contracts[index] = next;
1525
+ writeWorkContractStore(authoritativeOptions, { schemaVersion: 4, updatedAt: at, contracts });
1526
+ return next;
1527
+ });
1528
+ });
1529
+ }
1530
+
1531
+
1532
+ /**
1533
+ * Build the only permitted in-place Plan rebind for an active Work: the same
1534
+ function cancellationPhaseEvidence(
1535
+ current: WorkContract,
1536
+ input: { summary: string; evidenceRefs?: EvidenceRef[]; recordedAt: string },
1537
+ ): WorkPhaseEvidenceMap {
1538
+ const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs);
1539
+ const existing = current.phaseEvidence[current.phase];
1540
+ if (!['pending', 'active'].includes(existing.state)) return current.phaseEvidence;
1541
+ return {
1542
+ ...current.phaseEvidence,
1543
+ [current.phase]: {
1544
+ state: 'skipped',
1545
+ source: 'recorded',
1546
+ summary: input.summary.trim().slice(0, 1_000) || 'Work cancelled.',
1547
+ evidenceRefs,
1548
+ recordedAt: input.recordedAt,
1549
+ },
1550
+ };
1551
+ }
1552
+
1553
+ /**
1554
+ * Retire technical Work authority when its owning Plan is no longer current.
1555
+ *
1556
+ * This is authority retirement, not history deletion. The cancelled Work and
1557
+ * its evidence stay durable for audit/recovery/retention, while semantic state and
1558
+ * dispatch become terminal immediately so schedulers, concurrency admission,
1559
+ * and UI current-state projections cannot keep executing an obsolete Plan.
1560
+ */
1561
+ function cancellationPhaseEvidence(
1562
+ current: WorkContract,
1563
+ input: { summary: string; evidenceRefs?: EvidenceRef[]; recordedAt: string },
1564
+ ): WorkPhaseEvidenceMap {
1565
+ const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs);
1566
+ const existing = current.phaseEvidence[current.phase];
1567
+ if (!['pending', 'active'].includes(existing.state)) return current.phaseEvidence;
1568
+ return {
1569
+ ...current.phaseEvidence,
1570
+ [current.phase]: {
1571
+ state: 'skipped',
1572
+ source: 'recorded',
1573
+ summary: input.summary.trim().slice(0, 1_000) || 'Work cancelled.',
1574
+ evidenceRefs,
1575
+ recordedAt: input.recordedAt,
1576
+ },
1577
+ };
1578
+ }
1579
+
1580
+ export type WorkContractMetadataPatch = Partial<Omit<
1581
+ WorkContract,
1582
+ | 'schemaVersion'
1583
+ | 'workId'
1584
+ | 'repoId'
1585
+ | 'createdAt'
1586
+ | 'updatedAt'
1587
+ | 'semanticState'
1588
+ | 'semanticRevision'
1589
+ | 'semanticUpdatedAt'
1590
+ | 'phase'
1591
+ | 'phaseEvidence'
1592
+ | 'dispatchState'
1593
+ | 'evidenceState'
1594
+ | 'completionReceipt'
1595
+ | 'completionOutcome'
1596
+ | 'implementationReviews'
1597
+ | 'workKind'
1598
+ >>;
1599
+
1600
+ export function updateWorkContract(
1601
+ options: WorkContractStoreOptions,
1602
+ workId: string,
1603
+ patch: WorkContractMetadataPatch,
1604
+ ): WorkContract {
1605
+ return updateWorkContractInternal(options, workId, patch, false);
1606
+ }
1607
+
1608
+ /** Explicit execution-classification transition when an open Work begins governed repository mutation. */
1609
+ export function promoteWorkToRepositoryChange(
1610
+ options: WorkContractStoreOptions,
1611
+ workId: string,
1612
+ ): WorkContract {
1613
+ return updateWorkContractInternal(options, workId, (current) => {
1614
+ if (semanticWorkState(current) !== 'open' || current.completionReceipt || current.completionOutcome) {
1615
+ throw new Error(`WORK_KIND_PROMOTION_TERMINAL: ${workId}`);
1616
+ }
1617
+ if (current.workKind === 'repository_change') return undefined;
1618
+ if (!['investigation', 'local_effect', 'remote_effect'].includes(current.workKind)) {
1619
+ throw new Error(`WORK_KIND_PROMOTION_INVALID: ${workId}:${current.workKind}`);
1620
+ }
1621
+ return { workKind: 'repository_change' };
1622
+ }, false, true);
1623
+ }
1624
+
1625
+ export function recordWorkEvidenceState(
1626
+ options: WorkContractStoreOptions,
1627
+ workId: string,
1628
+ evidenceState: EvidenceState,
1629
+ ): WorkContract {
1630
+ return updateWorkContractInternal(options, workId, { evidenceState }, false, true);
1631
+ }
1632
+
1633
+ export function activateWorkContract(
1634
+ options: WorkContractStoreOptions,
1635
+ workId: string,
1636
+ input: {
1637
+ summary: string;
1638
+ phase?: WorkPhase;
1639
+ worktreeRef?: string;
1640
+ evidenceState?: EvidenceState;
1641
+ },
1642
+ ): WorkContract {
1643
+ return updateWorkContractInternal(options, workId, (current, at) => {
1644
+ if (current.semanticState !== 'open') {
1645
+ throw new Error(`WORK_ACTIVATION_TERMINAL: ${workId}:${current.semanticState}`);
1646
+ }
1647
+ const phase = input.phase ?? current.phase;
1648
+ const phaseEvidence = transitionPhaseEvidence(current, phase, {
1649
+ state: 'active',
1650
+ summary: input.summary,
1651
+ recordedAt: at,
1652
+ source: 'recorded',
1653
+ });
1654
+ const evidenceState = input.evidenceState
1655
+ ?? (current.evidenceState === 'failed' ? 'partial' : current.evidenceState);
1656
+ return {
1657
+ phase,
1658
+ phaseEvidence,
1659
+ dispatchState: 'running',
1660
+ evidenceState,
1661
+ worktreeRef: input.worktreeRef ?? current.worktreeRef,
1662
+ };
1663
+ }, false, true, false, true);
1664
+ }
1665
+
1666
+ export function failWorkContract(
1667
+ options: WorkContractStoreOptions,
1668
+ workId: string,
1669
+ input: { phase: WorkPhase; summary: string; evidenceRefs?: EvidenceRef[] },
1670
+ ): WorkContract {
1671
+ return updateWorkContractInternal(options, workId, (current, at) => {
1672
+ if (current.semanticState !== 'open') {
1673
+ throw new Error(`WORK_FAILURE_TERMINAL: ${workId}:${current.semanticState}`);
1674
+ }
1675
+ if (input.phase !== current.phase) {
1676
+ throw new Error(`WORK_FAILURE_PHASE_MISMATCH: ${workId}:expected=${current.phase}:actual=${input.phase}`);
1677
+ }
1678
+ const phaseEvidence = transitionPhaseEvidence(current, current.phase, {
1679
+ state: 'failed',
1680
+ summary: input.summary,
1681
+ evidenceRefs: input.evidenceRefs,
1682
+ recordedAt: at,
1683
+ source: 'recorded',
1684
+ });
1685
+ return {
1686
+ phase: current.phase,
1687
+ phaseEvidence,
1688
+ dispatchState: 'blocked',
1689
+ evidenceState: 'failed',
1690
+ ...(input.evidenceRefs ? { evidenceRefs: input.evidenceRefs } : {}),
1691
+ };
1692
+ }, false, true);
1693
+ }
1694
+
1695
+ export function cancelWorkContract(
1696
+ options: WorkContractStoreOptions,
1697
+ workId: string,
1698
+ input: { summary: string; evidenceRefs?: EvidenceRef[] },
1699
+ ): WorkContract {
1700
+ const current = getWorkContract(options, workId);
1701
+ if (!current) throw new Error(`work contract not found: ${sanitizeFileComponent(workId)}`);
1702
+ if (current.semanticState === 'completed') throw new Error(`WORK_CANCEL_COMPLETED: ${workId}`);
1703
+ if (current.semanticState === 'cancelled') return current;
1704
+
1705
+ reviseWorkSemanticContext(options, workId, {
1706
+ expectedRevision: currentWorkSemanticRevision(current),
1707
+ state: 'cancelled',
1708
+ });
1709
+
1710
+ return updateWorkContractInternal(options, workId, (cancelled, at) => {
1711
+ if (cancelled.semanticState !== 'cancelled') {
1712
+ throw new Error(`WORK_CANCEL_SEMANTIC_STATE_REQUIRED: ${workId}:${cancelled.semanticState}`);
1713
+ }
1714
+ const phaseEvidence = cancellationPhaseEvidence(cancelled, {
1715
+ summary: input.summary,
1716
+ evidenceRefs: input.evidenceRefs,
1717
+ recordedAt: at,
1718
+ });
1719
+ return {
1720
+ phase: cancelled.phase,
1721
+ phaseEvidence,
1722
+ dispatchState: 'terminal',
1723
+ ...(input.evidenceRefs ? { evidenceRefs: input.evidenceRefs } : {}),
1724
+ suggestedNextActions: [],
1725
+ };
1726
+ }, false, true);
1727
+ }
1728
+
1729
+ /**
1730
+ * Record verified physical cleanup for an already-cancelled Work without
1731
+ * inventing semantic success. Phases that never ran are skipped, while prior
1732
+ * satisfied/skipped/blocked/failed evidence remains immutable. The cleanup
1733
+ * receipt is the authority for the only newly satisfied phase.
1734
+ */
1735
+ export function recordCancelledWorkCleanupCompleted(
1736
+ options: WorkContractStoreOptions,
1737
+ workId: string,
1738
+ input: { summary: string; receiptId: string; evidenceRefs?: EvidenceRef[] },
1739
+ ): WorkContract {
1740
+ return updateWorkContractInternal(options, workId, (current, at) => {
1741
+ if (current.semanticState !== 'cancelled') {
1742
+ throw new Error(`WORK_CANCELLED_CLEANUP_STATE_REQUIRED: ${workId}:${current.semanticState}`);
1743
+ }
1744
+ const summary = input.summary.trim().slice(0, 1_000);
1745
+ if (!summary) throw new Error('WORK_CANCELLED_CLEANUP_SUMMARY_REQUIRED');
1746
+ const receiptId = input.receiptId.trim();
1747
+ if (!receiptId) throw new Error('WORK_CANCELLED_CLEANUP_RECEIPT_REQUIRED');
1748
+ const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs);
1749
+ const phaseEvidence: WorkPhaseEvidenceMap = { ...current.phaseEvidence };
1750
+ for (const phase of WORK_PHASES) {
1751
+ if (phase === 'cleanup') continue;
1752
+ const existing = phaseEvidence[phase];
1753
+ if (!['pending', 'active'].includes(existing.state)) continue;
1754
+ phaseEvidence[phase] = {
1755
+ state: 'skipped',
1756
+ source: 'recorded',
1757
+ summary: `Skipped after Work cancellation: ${summary}`.slice(0, 1_000),
1758
+ evidenceRefs,
1759
+ recordedAt: at,
1760
+ };
1761
+ }
1762
+ phaseEvidence.cleanup = {
1763
+ state: 'satisfied',
1764
+ source: 'recorded',
1765
+ summary,
1766
+ evidenceRefs,
1767
+ recordedAt: at,
1768
+ receiptId,
1769
+ };
1770
+ return {
1771
+ phase: 'cleanup',
1772
+ phaseEvidence,
1773
+ dispatchState: 'terminal',
1774
+ evidenceRefs,
1775
+ suggestedNextActions: [],
1776
+ };
1777
+ }, false, true);
1778
+ }
1779
+
1780
+ /** Merge non-authoritative discovery/change evidence without changing policy fences. */
1781
+ export function recordWorkScopeEvidence(
1782
+ options: WorkContractStoreOptions,
1783
+ workId: string,
1784
+ input: { initialLikelyPaths?: string[]; inspectedPaths?: string[]; actualChangedPaths?: string[] },
1785
+ ): WorkContract {
1786
+ return updateWorkContractInternal(options, workId, (current, at) => {
1787
+ const previous = current.scopeEvidence ?? {
1788
+ initialLikelyPaths: [], inspectedPaths: [], actualChangedPaths: [], recordedAt: current.createdAt,
1789
+ };
1790
+ return {
1791
+ scopeEvidence: {
1792
+ initialLikelyPaths: [...new Set([...previous.initialLikelyPaths, ...(input.initialLikelyPaths ?? [])])].slice(0, 100),
1793
+ inspectedPaths: [...new Set([...previous.inspectedPaths, ...(input.inspectedPaths ?? [])])].slice(0, 500),
1794
+ actualChangedPaths: [...new Set([...previous.actualChangedPaths, ...(input.actualChangedPaths ?? [])])].slice(0, 500),
1795
+ recordedAt: at,
1796
+ },
1797
+ };
1798
+ }, false);
1799
+ }
1800
+
1801
+ export function transitionWorkContractPhase(
1802
+ options: WorkContractStoreOptions,
1803
+ workId: string,
1804
+ input: {
1805
+ phase: WorkPhase;
1806
+ state?: Exclude<WorkPhaseEvidenceState, 'pending'>;
1807
+ summary: string;
1808
+ evidenceRefs?: EvidenceRef[];
1809
+ dispatchState?: DispatchState;
1810
+ evidenceState?: EvidenceState;
1811
+ },
1812
+ ): WorkContract {
1813
+ return updateWorkContractInternal(options, workId, (current, at) => {
1814
+ const phaseEvidence = transitionPhaseEvidence(current, input.phase, {
1815
+ state: input.state,
1816
+ summary: input.summary,
1817
+ evidenceRefs: input.evidenceRefs,
1818
+ recordedAt: at,
1819
+ });
1820
+ return {
1821
+ phase: input.phase,
1822
+ phaseEvidence,
1823
+ dispatchState: input.dispatchState ?? current.dispatchState,
1824
+ evidenceState: input.evidenceState ?? current.evidenceState,
1825
+ };
1826
+ }, false, true, false, true);
1827
+ }
1828
+
1829
+ /**
1830
+ * Append immutable historical review evidence. This is evidence storage only:
1831
+ * the decision never changes semantic state or phase and never authorizes delivery.
1832
+ */
1833
+ export function recordWorkImplementationReview(
1834
+ options: WorkContractStoreOptions,
1835
+ workId: string,
1836
+ review: WorkImplementationReviewRecord,
1837
+ ): WorkContract {
1838
+ validateImplementationReviewRecord(review);
1839
+ if (review.derivation === 'content_equivalent_commit') {
1840
+ throw new Error('WORK_IMPLEMENTATION_REVIEW_DERIVATION_REQUIRES_TRANSFER_API');
1841
+ }
1842
+ return updateWorkContractInternal(options, workId, (current, at) => {
1843
+ if (semanticWorkState(current) !== 'open') throw new Error(`WORK_IMPLEMENTATION_REVIEW_TERMINAL: ${workId}`);
1844
+ if (review.workId !== current.workId) throw new Error('WORK_IMPLEMENTATION_REVIEW_WORK_ID_MISMATCH');
1845
+ const history = [...(current.implementationReviews ?? []), review];
1846
+ if (history.length > MAX_IMPLEMENTATION_REVIEW_HISTORY) throw new Error('WORK_IMPLEMENTATION_REVIEW_HISTORY_LIMIT');
1847
+ return {
1848
+ implementationReviews: history,
1849
+ };
1850
+ }, false, false, true, false);
1851
+ }
1852
+
1853
+ export function appendWorkEvidence(
1854
+ options: WorkContractStoreOptions,
1855
+ workId: string,
1856
+ evidence: EvidenceRef,
1857
+ ): WorkContract {
1858
+ return updateWorkContractInternal(options, workId, (current) => ({
1859
+ evidenceRefs: [evidence, ...current.evidenceRefs].slice(0, current.evidencePolicy.maxEvidenceRefs),
1860
+ }), false);
1861
+ }
1862
+
1863
+ export function appendWorkHandoffRef(
1864
+ options: WorkContractStoreOptions,
1865
+ workId: string,
1866
+ handoffId: string,
1867
+ ): WorkContract {
1868
+ const sanitizedHandoffId = sanitizeFileComponent(handoffId);
1869
+ return updateWorkContractInternal(options, workId, (current) => ({
1870
+ handoffRefs: [sanitizedHandoffId, ...current.handoffRefs.filter((id) => id !== sanitizedHandoffId)].slice(0, 20),
1871
+ }), false);
1872
+ }
1873
+
1874
+ function sameSuccessfulVerificationExecutionFact(left: VerificationRecord, right: VerificationRecord): boolean {
1875
+ const a = left.receipt;
1876
+ const b = right.receipt;
1877
+ if (left.outcome !== 'valid_pass' || right.outcome !== 'valid_pass' || !a || !b) return false;
1878
+ if (!left.sourceRevision || !right.sourceRevision || left.sourceRevision !== right.sourceRevision) return false;
1879
+ if (!left.workspaceFingerprint || !right.workspaceFingerprint || left.workspaceFingerprint !== right.workspaceFingerprint) return false;
1880
+ if (left.checkId !== right.checkId || a.processId !== b.processId) return false;
1881
+ if (!a.checkCacheKey || !a.checkRevision || !a.checkDefinitionDigest || !a.checkEnvironmentFingerprint) return false;
1882
+ if (!b.checkCacheKey || !b.checkRevision || !b.checkDefinitionDigest || !b.checkEnvironmentFingerprint) return false;
1883
+ return a.checkCacheKey === b.checkCacheKey
1884
+ && a.checkRevision === b.checkRevision
1885
+ && a.checkDefinitionDigest === b.checkDefinitionDigest
1886
+ && a.checkEnvironmentFingerprint === b.checkEnvironmentFingerprint;
1887
+ }
1888
+
1889
+ export function appendVerificationRecord(
1890
+ options: WorkContractStoreOptions,
1891
+ workId: string,
1892
+ record: VerificationRecord,
1893
+ ): WorkContract {
1894
+ return updateWorkContractInternal(options, workId, (current) => {
1895
+ // Re-consuming the exact same successful Process against the exact same
1896
+ // source/workspace is request provenance, not a new Work verification fact.
1897
+ // Keep the original durable VerificationRecord so an approved review remains
1898
+ // bound to one stable evidence identity. A changed source/workspace, Process,
1899
+ // Check definition, environment, or cache identity still appends normally.
1900
+ if (current.checkRefs.some((existing) => sameSuccessfulVerificationExecutionFact(existing, record))) {
1901
+ return undefined;
1902
+ }
1903
+ return { checkRefs: [record, ...current.checkRefs].slice(0, 50) };
1904
+ }, false);
1905
+ }
1906
+
1907
+ /**
1908
+ * Legacy field name retained for storage compatibility. This records durable
1909
+ * delivery/effect evidence only. It MUST NOT complete semantic Work, advance a
1910
+ * Work lifecycle/phase, satisfy review policy, or infer the next action.
1911
+ * Semantic completion is exclusively reviseWorkSemanticContext/complete CAS.
1912
+ */
1913
+ export function recordWorkCompletionReceipt(
1914
+ options: WorkContractStoreOptions,
1915
+ workId: string,
1916
+ receipt: NonNullable<WorkContract['completionReceipt']>,
1917
+ completionOutcome: NonNullable<WorkContract['completionOutcome']>,
1918
+ _completionWorkKind?: WorkKind,
1919
+ ): WorkContract {
1920
+ return updateWorkContractInternal(options, workId, (current) => {
1921
+ if (receipt.workId !== current.workId) throw new Error('WORK_COMPLETION_RECEIPT_IDENTITY_MISMATCH');
1922
+ if (current.completionReceipt) {
1923
+ if (current.completionReceipt.receiptId === receipt.receiptId) return undefined;
1924
+ // Legacy field names store current mechanical delivery evidence. An open
1925
+ // semantic Work may legitimately deliver again after a retained production
1926
+ // canary repair. Terminal semantic Work keeps its delivery receipt immutable.
1927
+ if (semanticWorkState(current) !== 'open') throw new Error('WORK_COMPLETION_RECEIPT_ALREADY_RECORDED');
1928
+ if ((isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)) && current.evidenceState !== 'valid') {
1929
+ throw new Error('WORK_DELIVERY_RECEIPT_REPLACEMENT_VALIDATION_REQUIRED');
1930
+ }
1931
+ }
1932
+ const recordedAt = receipt.recordedAt;
1933
+ const receiptChangedPaths = isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)
1934
+ ? receipt.changedPaths
1935
+ : [];
1936
+ return {
1937
+ completionOutcome,
1938
+ completionReceipt: receipt,
1939
+ scopeEvidence: {
1940
+ initialLikelyPaths: current.scopeEvidence?.initialLikelyPaths ?? [],
1941
+ inspectedPaths: current.scopeEvidence?.inspectedPaths ?? [],
1942
+ actualChangedPaths: [...new Set(receiptChangedPaths)].slice(0, 500),
1943
+ recordedAt,
1944
+ },
1945
+ };
1946
+ }, true, false);
1947
+ }
1948
+ export interface AcceptSubmittedWorkInput {
1949
+ requestId: string;
1950
+ repoId: string;
1951
+ parentWorkId?: string;
1952
+ semanticKey: string;
1953
+ operation: SubmittedWorkOperation;
1954
+ objective?: string;
1955
+ requestedBy?: WorkContract['requestedBy'];
1956
+ principalId?: string;
1957
+ controllerInstanceId?: string;
1958
+ workKind?: WorkKind;
1959
+ risk?: WorkRisk;
1960
+ /** Compatibility input only; execution scope is owned by EditSession/resources. */
1961
+ allowedPaths?: string[];
1962
+ /** Compatibility input only; execution scope is owned by EditSession/resources. */
1963
+ forbiddenPaths?: string[];
1964
+ checks?: string[];
1965
+ acceptanceCriteria?: string[];
1966
+ constraints?: WorkContract['constraints'];
1967
+ }