@moretea-labs/forge 1.8.0 → 1.8.2

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