@moretea-labs/forge 1.7.2 → 1.8.0

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