@moretea-labs/forge 1.8.0 → 1.8.1

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