@moretea-labs/forge 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (900) hide show
  1. package/AGENTS.md +121 -62
  2. package/CHANGELOG.md +40 -0
  3. package/CLAUDE.md +57 -62
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.md +2 -2
  6. package/SKILL.md +0 -1
  7. package/THIRD_PARTY_NOTICES.md +5 -2
  8. package/adapters/browser/index.ts +2 -0
  9. package/adapters/chatgpt/browser-delivery-host.ts +77 -0
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
  11. package/adapters/chatgpt/controller-binding-store.ts +134 -0
  12. package/adapters/chatgpt/controller-round-host.ts +68 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
  14. package/adapters/chatgpt/provider-delivery.ts +340 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  18. package/adapters/computer/chatgpt-target-router.ts +103 -0
  19. package/adapters/computer/chrome-extension/background.js +385 -0
  20. package/adapters/computer/chrome-extension/content.js +378 -0
  21. package/adapters/computer/chrome-extension/core.js +24 -0
  22. package/adapters/computer/chrome-extension/manifest.json +30 -0
  23. package/adapters/computer/desktop-operator-contract.ts +7 -0
  24. package/adapters/computer/desktop-operator-discovery.ts +181 -0
  25. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  26. package/adapters/computer/desktop-operator-provider.ts +312 -0
  27. package/adapters/computer/index.ts +1 -0
  28. package/adapters/computer/interaction-target-authority.ts +1068 -0
  29. package/adapters/controller-process/binding-store.ts +34 -0
  30. package/adapters/controller-process/controller-host.ts +26 -0
  31. package/adapters/mcp/access-mode.ts +132 -0
  32. package/adapters/mcp/audit.ts +32 -0
  33. package/adapters/mcp/auth.ts +406 -0
  34. package/adapters/mcp/instructions.ts +37 -0
  35. package/adapters/mcp/legacy-context.ts +18 -0
  36. package/adapters/mcp/multi-repository.ts +421 -0
  37. package/adapters/mcp/oauth.ts +464 -0
  38. package/adapters/mcp/paths.ts +143 -0
  39. package/adapters/mcp/policy.ts +232 -0
  40. package/adapters/mcp/proxy-env.ts +62 -0
  41. package/adapters/mcp/readiness-projection.ts +24 -0
  42. package/adapters/mcp/redaction.ts +21 -0
  43. package/adapters/mcp/repo.ts +24 -0
  44. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  45. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  46. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  47. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  48. package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
  49. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  50. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
  51. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  52. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
  53. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  54. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  55. package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
  56. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  57. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
  58. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  59. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  60. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  61. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  62. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
  63. package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
  64. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
  65. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
  66. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  67. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  68. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
  69. package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
  70. package/adapters/mcp/runtime-gateway/router.ts +242 -0
  71. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  72. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
  73. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  74. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
  75. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  76. package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
  77. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
  78. package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
  79. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
  80. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  81. package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
  82. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  83. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  84. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  85. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  86. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  87. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  88. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  89. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  90. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  91. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  92. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  93. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  94. package/adapters/mcp/server.ts +1092 -0
  95. package/adapters/mcp/setup.ts +789 -0
  96. package/adapters/mcp/tool-capability-cache.ts +87 -0
  97. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  98. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
  99. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
  100. package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
  101. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  102. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  103. package/adapters/mcp/toolset-names.ts +79 -0
  104. package/adapters/mcp/toolset-selection.ts +41 -0
  105. package/adapters/mcp/toolset.ts +337 -0
  106. package/adapters/mcp/transports/http-observation.ts +365 -0
  107. package/adapters/mcp/transports/http.ts +933 -0
  108. package/adapters/mcp/transports/oauth-http.ts +322 -0
  109. package/adapters/mcp/transports/session-registry.ts +519 -0
  110. package/adapters/mcp/transports/stdio.ts +8 -0
  111. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
  112. package/adapters/mcp/types.ts +41 -0
  113. package/assets/AGENTS.md +2 -16
  114. package/assets/CLAUDE.md +2 -16
  115. package/assets/hooks/AGENTS.md +3 -14
  116. package/assets/hooks/CLAUDE.md +3 -14
  117. package/assets/hooks/hook-input.sh +9 -2
  118. package/assets/hooks/lib/workflow-state.sh +28 -10
  119. package/assets/hooks/prompt-guard.sh +63 -37
  120. package/assets/hooks/security-sentinel.sh +2 -1
  121. package/assets/hooks/session-start-context.sh +7 -5
  122. package/assets/hooks/stop-orchestrator.sh +13 -3
  123. package/assets/initializer-question-pack.v4.json +11 -7
  124. package/assets/partials/05-workflow.partial.md +18 -73
  125. package/assets/partials/07-footer.partial.md +1 -1
  126. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  127. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  128. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  129. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  130. package/assets/plan-map.json +11 -11
  131. package/assets/plugin-registry.v1.json +11 -3
  132. package/assets/plugin-trust-policy.v1.json +6 -0
  133. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  134. package/assets/recovery/forge-wsl-rescue +271 -0
  135. package/assets/reference-configs/agentic-development-flow.md +25 -0
  136. package/assets/reference-configs/development-protocol.md +11 -15
  137. package/assets/reference-configs/document-generation.md +1 -1
  138. package/assets/reference-configs/global-working-rules.md +28 -1
  139. package/assets/reference-configs/harness-overview.md +22 -112
  140. package/assets/reference-configs/hook-operations.md +1 -1
  141. package/assets/reference-configs/sprint-contracts.md +2 -2
  142. package/assets/skill-version.json +2 -2
  143. package/assets/templates/helpers/architecture-event.ts +3 -34
  144. package/assets/templates/helpers/architecture-queue.sh +61 -10
  145. package/assets/templates/helpers/archive-workflow.sh +90 -180
  146. package/assets/templates/helpers/capability-config.ts +8 -11
  147. package/assets/templates/helpers/capability-resolver.ts +37 -19
  148. package/assets/templates/helpers/capture-plan.sh +2 -2
  149. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  150. package/assets/templates/helpers/check-task-sync.sh +82 -11
  151. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  152. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  153. package/assets/templates/helpers/contract-worktree.sh +26 -24
  154. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  155. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  156. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  157. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  158. package/assets/templates/helpers/new-plan.sh +2 -2
  159. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  160. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  161. package/assets/templates/helpers/switch-plan.sh +1 -1
  162. package/assets/templates/helpers/workflow-contract.ts +1 -0
  163. package/assets/workflow-contract.v1.json +63 -171
  164. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  165. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  166. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  167. package/docs/architecture/CURRENT.md +233 -28
  168. package/docs/architecture/EVOLUTION.md +13 -0
  169. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  170. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  171. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  172. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  173. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  174. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  175. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
  176. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
  177. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  178. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  179. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  180. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  181. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  182. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  183. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  184. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  185. package/docs/architecture/domains/verification.md +8 -0
  186. package/docs/architecture/domains/workflow-engine.md +7 -1
  187. package/docs/architecture/index.md +3 -3
  188. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  189. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  190. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  191. package/docs/forge-plugin-management.md +20 -4
  192. package/docs/forge-runtime-self-healing-loop.md +2 -0
  193. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  194. package/docs/operations/controller-browser-plugin.md +3 -1
  195. package/docs/operations/controller-performance-and-502.md +17 -0
  196. package/docs/operations/controller-reliability-runbook.md +2 -2
  197. package/docs/operations/features.md +1 -1
  198. package/docs/operations/features.zh-CN.md +1 -1
  199. package/docs/operations/homebrew.md +2 -2
  200. package/docs/operations/mcp-tool-exposure.md +2 -0
  201. package/docs/operations/platform-support.md +16 -8
  202. package/docs/operations/platform-support.zh-CN.md +2 -2
  203. package/docs/operations/releasing.md +2 -0
  204. package/docs/operations/standalone-disaster-recovery.md +38 -5
  205. package/docs/operations/uu-remote-rescue.md +41 -0
  206. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  207. package/docs/public-usage-guide.md +1 -1
  208. package/docs/public-usage-guide.zh-CN.md +1 -1
  209. package/docs/tutorials/01-install-and-start.md +1 -1
  210. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  211. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  212. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  213. package/docs/wiki/Implementation.md +1 -1
  214. package/package.json +46 -26
  215. package/packages/kernel/cognition/api/index.ts +6 -0
  216. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  217. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  218. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  219. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  220. package/packages/kernel/cognition/domain/learning.ts +106 -0
  221. package/packages/kernel/cognition/domain/memory.ts +254 -0
  222. package/packages/kernel/controller/api/index.ts +3 -0
  223. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  224. package/packages/kernel/controller/application/controller-round-service.ts +41 -0
  225. package/packages/kernel/controller/application/controller-service.ts +30 -0
  226. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
  227. package/packages/kernel/controller/domain/controller-round.ts +110 -0
  228. package/packages/kernel/controller/domain/execution-quality.ts +135 -0
  229. package/packages/kernel/controller/domain/types.ts +56 -0
  230. package/packages/kernel/controller/index.ts +1 -0
  231. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  232. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
  233. package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
  234. package/packages/kernel/controller/ports/controller-host.ts +18 -0
  235. package/packages/kernel/goal/api/index.ts +1 -0
  236. package/packages/kernel/goal/domain/types.ts +40 -0
  237. package/packages/kernel/goal/index.ts +1 -0
  238. package/packages/kernel/identity/api/index.ts +15 -0
  239. package/packages/kernel/identity/application/identity-service.ts +67 -0
  240. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  241. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  242. package/packages/kernel/identity/domain/grant.ts +72 -0
  243. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  244. package/packages/kernel/identity/domain/scope.ts +63 -0
  245. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  246. package/packages/kernel/identity/domain/types.ts +44 -0
  247. package/packages/kernel/identity/domain/user-request.ts +93 -0
  248. package/packages/kernel/identity/domain/workspace.ts +56 -0
  249. package/packages/kernel/identity/index.ts +1 -0
  250. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  251. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  252. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  253. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  254. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  255. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  256. package/packages/kernel/memory/api/index.ts +5 -0
  257. package/packages/kernel/memory/application/experience-service.ts +118 -0
  258. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  259. package/packages/kernel/memory/domain/experience.ts +103 -0
  260. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  261. package/packages/kernel/memory/index.ts +1 -0
  262. package/packages/kernel/scheduler/api/index.ts +4 -0
  263. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  264. package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
  265. package/packages/kernel/scheduler/application/settlement.ts +182 -0
  266. package/packages/kernel/scheduler/domain/schedule.ts +236 -0
  267. package/packages/kernel/scheduler/index.ts +1 -0
  268. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
  269. package/packages/kernel/work/api/index.ts +16 -0
  270. package/packages/kernel/work/application/work-service.ts +123 -0
  271. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  272. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  273. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  274. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  275. package/packages/kernel/work/domain/engineering-design.ts +286 -0
  276. package/packages/kernel/work/domain/engineering-profile.ts +238 -0
  277. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  278. package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
  279. package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
  280. package/packages/kernel/work/domain/implementation-review.ts +193 -0
  281. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  282. package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
  283. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  284. package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
  285. package/packages/kernel/work/domain/types.ts +534 -0
  286. package/packages/kernel/work/index.ts +1 -0
  287. package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
  288. package/packages/kernel/work/ports/work-contract-store.ts +21 -0
  289. package/packages/plugin-runtime/browser/index.ts +3 -0
  290. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  291. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  292. package/packages/plugin-runtime/browser/session-authority.ts +62 -0
  293. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  294. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  295. package/packages/plugin-runtime/computer/index.ts +8 -0
  296. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  297. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  298. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  299. package/packages/plugin-runtime/computer/provider.ts +19 -0
  300. package/packages/plugin-runtime/computer/target-authority.ts +215 -0
  301. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  302. package/packages/plugin-runtime/external/index.ts +1 -0
  303. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
  304. package/packages/plugin-runtime/index.ts +3 -0
  305. package/packages/protocols/browser/index.ts +1 -0
  306. package/packages/protocols/browser/session.ts +98 -0
  307. package/packages/protocols/computer/contract.ts +225 -0
  308. package/packages/protocols/computer/index.ts +1 -0
  309. package/packages/protocols/failure/contract.ts +158 -0
  310. package/packages/protocols/failure/index.ts +1 -0
  311. package/packages/protocols/handoff/index.ts +1 -0
  312. package/packages/protocols/handoff/status.ts +22 -0
  313. package/packages/protocols/mcp/execution-context.ts +11 -0
  314. package/packages/protocols/mcp/index.ts +2 -0
  315. package/packages/protocols/mcp/tool-contract.ts +42 -0
  316. package/packages/workflow-runtime/api/index.ts +2 -0
  317. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  318. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  319. package/packages/workflow-runtime/index.ts +1 -0
  320. package/scripts/activate-source-baseline.ts +7 -9
  321. package/scripts/assemble-template.ts +1 -1
  322. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  323. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  324. package/scripts/canary-controller-semantic-wait.ts +183 -0
  325. package/scripts/canary-global-hook.sh +2 -2
  326. package/scripts/check-mcp-compatibility.ts +85 -7
  327. package/scripts/check-platform-support.mjs +28 -2
  328. package/scripts/check-public-docs.sh +1 -1
  329. package/scripts/check-repository-hygiene.mjs +92 -0
  330. package/scripts/check-runtime-architecture.mjs +1643 -127
  331. package/scripts/check-source-duplication.mjs +9 -1
  332. package/scripts/controller-ngrok-rotation.sh +5 -4
  333. package/scripts/create-project-dirs.sh +10 -74
  334. package/scripts/forge-local-recovery-helper.mjs +282 -0
  335. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  336. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  337. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  338. package/scripts/hook-shim.sh +2 -2
  339. package/scripts/init-project.sh +12 -70
  340. package/scripts/install-desktop-operator-registration.ts +4 -2
  341. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  342. package/scripts/install-standalone-recovery.ts +122 -10
  343. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  344. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  345. package/scripts/lib/controller-home-env.sh +6 -3
  346. package/scripts/lib/project-init-lib.sh +42 -582
  347. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  348. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  349. package/scripts/open-source-audit-allowlist.txt +11 -1
  350. package/scripts/portability-bindings.mjs +70 -0
  351. package/scripts/run-bounded-command.mjs +60 -0
  352. package/scripts/run-bun-test-file.ts +22 -6
  353. package/scripts/run-governed-gate.ts +4 -2
  354. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  355. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  356. package/scripts/smoke-mcp-http-runtime.ts +6 -1
  357. package/scripts/smoke-runtime-recovery.ts +13 -11
  358. package/scripts/smoke-schedule-engine.ts +24 -16
  359. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  360. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  361. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  362. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  363. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  364. package/scripts/smoke-workflow-supervisor.ts +103 -0
  365. package/scripts/test-governance.ts +51 -2
  366. package/scripts/verify-forge-runtime.sh +2 -1
  367. package/src/cli/agent-jobs/executor-health.ts +11 -34
  368. package/src/cli/agent-jobs/integration.ts +18 -49
  369. package/src/cli/agent-jobs/job-manager.ts +17 -4
  370. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  371. package/src/cli/chatgpt-browser/binding.ts +20 -10
  372. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  373. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  374. package/src/cli/chatgpt-browser/engine.ts +10 -11
  375. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  376. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  377. package/src/cli/chatgpt-browser/types.ts +1 -0
  378. package/src/cli/commands/bootstrap-control.ts +334 -0
  379. package/src/cli/commands/brain-assistant.ts +52 -0
  380. package/src/cli/commands/brain.ts +191 -40
  381. package/src/cli/commands/chatgpt.ts +32 -67
  382. package/src/cli/commands/computer.ts +437 -0
  383. package/src/cli/commands/doctor.ts +18 -1
  384. package/src/cli/commands/init-hook.ts +71 -61
  385. package/src/cli/commands/init.ts +3 -1
  386. package/src/cli/commands/mcp.ts +16 -10
  387. package/src/cli/commands/plugin.ts +38 -12
  388. package/src/cli/commands/recovery.ts +507 -80
  389. package/src/cli/commands/runtime.ts +12 -7
  390. package/src/cli/commands/setup-profile.ts +97 -39
  391. package/src/cli/commands/status.ts +2 -1
  392. package/src/cli/controller/check-runner.ts +500 -100
  393. package/src/cli/controller/completion-backlog.ts +3 -3
  394. package/src/cli/controller/completion-orchestrator.ts +4 -43
  395. package/src/cli/controller/completion-target.ts +3 -72
  396. package/src/cli/controller/context/source-materializer.ts +81 -100
  397. package/src/cli/controller/context/types.ts +7 -1
  398. package/src/cli/controller/context-pack.ts +28 -6
  399. package/src/cli/controller/execution-completion.ts +4 -7
  400. package/src/cli/controller/execution-policy.ts +36 -153
  401. package/src/cli/controller/governance.ts +2 -3
  402. package/src/cli/controller/issue-store.ts +4 -99
  403. package/src/cli/controller/launch-agents.ts +59 -18
  404. package/src/cli/controller/operational-plan.ts +24 -51
  405. package/src/cli/controller/runtime-config.ts +5 -1
  406. package/src/cli/controller/stuck-state-migration.ts +2 -3
  407. package/src/cli/controller/systemd-user.ts +148 -0
  408. package/src/cli/controller/types.ts +10 -70
  409. package/src/cli/controller/worklog.ts +67 -13
  410. package/src/cli/editing/edit-operation-contract.ts +92 -0
  411. package/src/cli/editing/edit-session.ts +290 -20
  412. package/src/cli/github/plugin.ts +17 -12
  413. package/src/cli/hook/route-registry.ts +3 -5
  414. package/src/cli/hook/runtime.ts +49 -3
  415. package/src/cli/index.ts +3 -0
  416. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  417. package/src/cli/local-bridge/console-automations.ts +20 -15
  418. package/src/cli/local-bridge/console-view-models.ts +3 -12
  419. package/src/cli/local-bridge/facade-api.ts +105 -205
  420. package/src/cli/local-bridge/job-store.ts +1 -1
  421. package/src/cli/local-bridge/server.ts +48 -174
  422. package/src/cli/local-bridge/types.ts +0 -1
  423. package/src/cli/mcp/access-mode.ts +2 -132
  424. package/src/cli/mcp/access-tools.ts +2 -387
  425. package/src/cli/mcp/audit.ts +2 -32
  426. package/src/cli/mcp/auth.ts +2 -404
  427. package/src/cli/mcp/instructions.ts +2 -34
  428. package/src/cli/mcp/legacy-context.ts +2 -16
  429. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  430. package/src/cli/mcp/multi-repository.ts +2 -436
  431. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  432. package/src/cli/mcp/oauth.ts +2 -375
  433. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  434. package/src/cli/mcp/paths.ts +2 -140
  435. package/src/cli/mcp/policy.ts +2 -232
  436. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  437. package/src/cli/mcp/proxy-env.ts +2 -62
  438. package/src/cli/mcp/readiness-projection.ts +2 -24
  439. package/src/cli/mcp/redaction.ts +2 -21
  440. package/src/cli/mcp/repo.ts +2 -24
  441. package/src/cli/mcp/repository-tools.ts +2 -1613
  442. package/src/cli/mcp/server.ts +2 -951
  443. package/src/cli/mcp/setup.ts +2 -737
  444. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  445. package/src/cli/mcp/tool-contract.ts +2 -14
  446. package/src/cli/mcp/tools.ts +2 -9
  447. package/src/cli/mcp/toolset-names.ts +2 -70
  448. package/src/cli/mcp/toolset-selection.ts +2 -41
  449. package/src/cli/mcp/toolset.ts +2 -336
  450. package/src/cli/mcp/transports/AGENTS.md +1 -10
  451. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  452. package/src/cli/mcp/transports/http.ts +2 -1414
  453. package/src/cli/mcp/transports/session-registry.ts +2 -490
  454. package/src/cli/mcp/transports/stdio.ts +2 -8
  455. package/src/cli/mcp/types.ts +2 -41
  456. package/src/cli/mcp/umbrella-tools.ts +2 -117
  457. package/src/cli/repositories/command-executor.ts +91 -39
  458. package/src/cli/repositories/command-scope.ts +139 -4
  459. package/src/cli/repositories/controller-home.ts +167 -1
  460. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  461. package/src/cli/repositories/git-command-executor.ts +2 -2
  462. package/src/cli/repositories/identity.ts +13 -0
  463. package/src/cli/repositories/registry.ts +145 -60
  464. package/src/cli/repositories/repository-snapshot.ts +53 -7
  465. package/src/cli/repositories/runtime-storage.ts +27 -2
  466. package/src/cli/repositories/safe-patch.ts +34 -38
  467. package/src/cli/repositories/selected-path-actions.ts +22 -2
  468. package/src/cli/repositories/structured-git.ts +167 -27
  469. package/src/cli/repository/session-cache.ts +92 -6
  470. package/src/cli/runtime/helper-runner.ts +7 -0
  471. package/src/cli/runtime-invocation.ts +8 -1
  472. package/src/cli/tools/codegraph.ts +44 -25
  473. package/src/core/adoption/manifest-templates.ts +1 -1
  474. package/src/core/adoption/plan.ts +3 -26
  475. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  476. package/src/effects/process-runner.ts +8 -14
  477. package/src/runtime/context/assistant-context.ts +167 -0
  478. package/src/runtime/context/assistant-work-context.ts +209 -0
  479. package/src/runtime/context/automatic-learning.ts +565 -0
  480. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  481. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  482. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  483. package/src/runtime/context/cognitive-attention.ts +173 -0
  484. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  485. package/src/runtime/context/context-closure.ts +305 -0
  486. package/src/runtime/context/context-pack-process.ts +103 -0
  487. package/src/runtime/context/context-plane.ts +203 -0
  488. package/src/runtime/context/direct-controller-learning.ts +298 -0
  489. package/src/runtime/context/project-engineering-contract.ts +60 -0
  490. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  491. package/src/runtime/context/semantic-navigation.ts +6 -34
  492. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  493. package/src/runtime/context/typescript-navigation.ts +89 -3
  494. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  495. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  496. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  497. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  498. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  499. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  500. package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
  501. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  502. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
  503. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
  504. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
  505. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  506. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  507. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  508. package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
  509. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  510. package/src/runtime/control-plane/execution/validation.ts +34 -11
  511. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
  512. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  513. package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
  514. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  515. package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
  516. package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
  517. package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
  518. package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
  519. package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
  520. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
  521. package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
  522. package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
  523. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  524. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  525. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
  526. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
  527. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  528. package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
  529. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
  530. package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
  531. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  532. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  533. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  534. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  535. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
  536. package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
  537. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
  538. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
  539. package/src/runtime/control-plane/facade/index.ts +4 -3
  540. package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
  541. package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
  542. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  543. package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
  544. package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
  545. package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
  546. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
  547. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  548. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  549. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
  550. package/src/runtime/control-plane/facade/types.ts +218 -620
  551. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  552. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  553. package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
  554. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  555. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
  556. package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
  557. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  558. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  559. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
  560. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  561. package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
  562. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  563. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  564. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  565. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  566. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
  567. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  568. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
  569. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  570. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
  571. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
  572. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  573. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  574. package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
  575. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  576. package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
  577. package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
  578. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  579. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  580. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  581. package/src/runtime/control-plane/release-retention.ts +154 -26
  582. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  583. package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
  584. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  585. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  586. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
  587. package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
  588. package/src/runtime/diagnostics/incident-repair.ts +573 -0
  589. package/src/runtime/diagnostics/mcp-timing.ts +239 -6
  590. package/src/runtime/diagnostics/performance.ts +6 -14
  591. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  592. package/src/runtime/evidence/artifact-store.ts +216 -35
  593. package/src/runtime/evidence/evidence-store.ts +112 -8
  594. package/src/runtime/evidence/operational-shadow.ts +38 -0
  595. package/src/runtime/evidence/output-handle-index.ts +225 -0
  596. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  597. package/src/runtime/evidence/result-store.ts +158 -44
  598. package/src/runtime/evidence/sensitive-output.ts +4 -4
  599. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  600. package/src/runtime/execution/jobs/store.ts +124 -6
  601. package/src/runtime/execution/managed-workspace.ts +112 -5
  602. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  603. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  604. package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
  605. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  606. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  607. package/src/runtime/execution/process-runtime/check-result.ts +177 -2
  608. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
  609. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  610. package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
  611. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  612. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  613. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  614. package/src/runtime/execution/process-runtime/index.ts +4 -0
  615. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  616. package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
  617. package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
  618. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  619. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  620. package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
  621. package/src/runtime/execution/process-runtime/runtime.ts +167 -17
  622. package/src/runtime/execution/process-runtime/store.ts +30 -2
  623. package/src/runtime/execution/process-runtime/types.ts +27 -0
  624. package/src/runtime/execution/workers/executor.ts +18 -19
  625. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  626. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  627. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  628. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  629. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  630. package/src/runtime/gateway/mcp/router.ts +2 -1125
  631. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  632. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  633. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  634. package/src/runtime/maintenance/cleanup.ts +38 -24
  635. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  636. package/src/runtime/platform/computer-platform.ts +14 -0
  637. package/src/runtime/platform/executable-discovery.ts +54 -0
  638. package/src/runtime/platform/fetch-http-server.ts +90 -0
  639. package/src/runtime/platform/service-inventory.ts +130 -0
  640. package/src/runtime/platform/service-manager.ts +146 -0
  641. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  642. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  643. package/src/runtime/plugins/action-application.ts +192 -0
  644. package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
  645. package/src/runtime/plugins/browser-adapter.ts +898 -135
  646. package/src/runtime/plugins/browser-automation-service.ts +33 -34
  647. package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
  648. package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
  649. package/src/runtime/plugins/browser-native-session-service.ts +64 -14
  650. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  651. package/src/runtime/plugins/browser-registration.ts +32 -0
  652. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  653. package/src/runtime/plugins/browser-runtime.ts +55 -10
  654. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  655. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  656. package/src/runtime/plugins/browser-session-store.ts +243 -80
  657. package/src/runtime/plugins/browser-session-types.ts +2 -98
  658. package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
  659. package/src/runtime/plugins/catalog-trust.ts +82 -0
  660. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  661. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  662. package/src/runtime/plugins/computer-registration.ts +1050 -0
  663. package/src/runtime/plugins/config-store.ts +119 -0
  664. package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
  665. package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
  666. package/src/runtime/plugins/errors.ts +8 -1
  667. package/src/runtime/plugins/external-adapter.ts +82 -323
  668. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  669. package/src/runtime/plugins/external-registration.ts +30 -2
  670. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  671. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  672. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  673. package/src/runtime/plugins/first-party-registry.ts +3 -1
  674. package/src/runtime/plugins/github-adapter.ts +1 -2
  675. package/src/runtime/plugins/gmail-adapter.ts +29 -8
  676. package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
  677. package/src/runtime/plugins/google-shared.ts +45 -53
  678. package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
  679. package/src/runtime/plugins/ios-adapter.ts +2 -3
  680. package/src/runtime/plugins/lightweight-action.ts +208 -2
  681. package/src/runtime/plugins/local-system-adapter.ts +192 -2
  682. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  683. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  684. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  685. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  686. package/src/runtime/plugins/resend-adapter.ts +15 -24
  687. package/src/runtime/plugins/store.ts +1003 -387
  688. package/src/runtime/plugins/types.ts +28 -1
  689. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  690. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  691. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  692. package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
  693. package/src/runtime/projections/controller-context.ts +12 -6
  694. package/src/runtime/projections/git-status-sampler.ts +73 -22
  695. package/src/runtime/projections/invalidation.ts +7 -3
  696. package/src/runtime/projections/materialized-view.ts +93 -43
  697. package/src/runtime/recovery/actions.ts +3 -3
  698. package/src/runtime/recovery/capability-action-application.ts +229 -0
  699. package/src/runtime/recovery/index.ts +3 -0
  700. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  701. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  702. package/src/runtime/recovery/maintenance-executor.ts +701 -168
  703. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  704. package/src/runtime/recovery/store.ts +29 -11
  705. package/src/runtime/release/identity.ts +6 -0
  706. package/src/runtime/release/release-coordinator.ts +296 -0
  707. package/src/runtime/release/release-session.ts +716 -0
  708. package/src/runtime/release/release-state-migration.ts +25 -0
  709. package/src/runtime/resources/leases/store.ts +56 -25
  710. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  711. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  712. package/src/runtime/root/browser-session-composition.ts +59 -0
  713. package/src/runtime/root/computer-composition.ts +376 -0
  714. package/src/runtime/root/computer-target-composition.ts +16 -0
  715. package/src/runtime/root/computer-target-persistence.ts +39 -0
  716. package/src/runtime/root/controller-progression-composition.ts +192 -0
  717. package/src/runtime/root/controller-round-composition.ts +126 -0
  718. package/src/runtime/root/controller-services.ts +2 -0
  719. package/src/runtime/root/deployment-topology.ts +106 -0
  720. package/src/runtime/root/entry.ts +8 -6
  721. package/src/runtime/root/gateway-adapter.ts +3 -3
  722. package/src/runtime/root/known-good-recovery.ts +184 -0
  723. package/src/runtime/root/local-bridge.ts +5 -3
  724. package/src/runtime/root/mcp-transport.ts +116 -23
  725. package/src/runtime/root/ownership.ts +261 -9
  726. package/src/runtime/root/package-connector-service.ts +212 -74
  727. package/src/runtime/root/package-runtime-release.ts +199 -33
  728. package/src/runtime/root/package-runtime-service.ts +77 -108
  729. package/src/runtime/root/release-execution-canary.ts +103 -0
  730. package/src/runtime/root/release-loader.ts +150 -0
  731. package/src/runtime/root/release-manifest.ts +185 -7
  732. package/src/runtime/root/release-materialize.ts +474 -31
  733. package/src/runtime/root/release-store.ts +425 -37
  734. package/src/runtime/root/runtime-lane.ts +191 -0
  735. package/src/runtime/root/runtime.ts +379 -32
  736. package/src/runtime/root/scheduled-controller-composition.ts +76 -0
  737. package/src/runtime/root/scheduler.ts +12 -4
  738. package/src/runtime/root/service-runner.ts +3 -3
  739. package/src/runtime/root/service.ts +73 -45
  740. package/src/runtime/root/status.ts +49 -0
  741. package/src/runtime/root/types.ts +55 -3
  742. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  743. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  744. package/src/runtime/root/write-fence.ts +50 -2
  745. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  746. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  747. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  748. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  749. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  750. package/src/runtime/shared/process-environment.ts +36 -10
  751. package/src/runtime/shared/response-budget.ts +1 -3
  752. package/src/runtime/shared/storage-capacity.ts +68 -0
  753. package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
  754. package/src/runtime/standalone-recovery/core.ts +4609 -495
  755. package/src/runtime/standalone-recovery/entry.ts +642 -81
  756. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  757. package/src/runtime/standalone-recovery/installer.ts +399 -177
  758. package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
  759. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  760. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  761. package/src/runtime/standalone-recovery/performance.ts +173 -0
  762. package/src/runtime/standalone-recovery/release.ts +58 -5
  763. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  764. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  765. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  766. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  767. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
  768. package/src/runtime/workflow/schedules/engine.ts +298 -484
  769. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  770. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  771. package/src/runtime/workflow/schedules/store.ts +2 -607
  772. package/src/runtime/workflow/schedules/types.ts +2 -127
  773. package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
  774. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  775. package/src/runtime/workflows/runtime.ts +482 -0
  776. package/src/testing/test-governance.ts +95 -30
  777. package/supervisor/chatgpt-conversation.ts +23 -0
  778. package/supervisor/client.ts +201 -0
  779. package/supervisor/control-plane.ts +868 -0
  780. package/supervisor/entry.ts +15 -0
  781. package/supervisor/forge-validators.ts +80 -0
  782. package/supervisor/index.ts +5 -0
  783. package/supervisor/native-browser-adapter.ts +786 -0
  784. package/supervisor/native-messaging/host.ts +39 -0
  785. package/supervisor/native-messaging/manifest.ts +26 -0
  786. package/supervisor/native-messaging/protocol.ts +27 -0
  787. package/supervisor/paths.ts +18 -0
  788. package/supervisor/protocol.ts +202 -0
  789. package/supervisor/server.ts +404 -0
  790. package/supervisor/store.ts +1451 -0
  791. package/supervisor/tsconfig.json +8 -0
  792. package/supervisor/types.ts +255 -0
  793. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  794. package/docs/architecture/history/README.md +0 -7
  795. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  796. package/docs/architecture/history/global-hook-runtime.md +0 -166
  797. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  798. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  799. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  800. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  801. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  802. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  803. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  804. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  805. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  806. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  807. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  808. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  809. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  810. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  811. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  812. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  813. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  814. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  815. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  816. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  817. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  818. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  819. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  820. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  821. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  822. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  823. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  824. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  825. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  826. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  827. package/docs/architecture/requests/archive/2026/root.md +0 -68
  828. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  829. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  830. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  831. package/scripts/archive-architecture-request.sh +0 -4
  832. package/scripts/benchmark-background-check-overlap.ts +0 -228
  833. package/scripts/benchmark-controller-hotpath.ts +0 -212
  834. package/scripts/benchmark-controller-round2.ts +0 -139
  835. package/scripts/benchmark-ios-agent-device.ts +0 -514
  836. package/scripts/benchmark-mcp-host.sh +0 -117
  837. package/scripts/benchmark-mcp-transport.ts +0 -170
  838. package/scripts/benchmark-quality-harness.ts +0 -146
  839. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  840. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  841. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  842. package/scripts/benchmark-thin-harness.ts +0 -251
  843. package/scripts/benchmark-typescript-navigation.ts +0 -186
  844. package/scripts/benchmark-work-efficiency.ts +0 -334
  845. package/scripts/build-controller-ui.ts +0 -28
  846. package/scripts/check-npm-release.sh +0 -32
  847. package/scripts/check-public-release-surface.sh +0 -19
  848. package/scripts/check-release-published.sh +0 -57
  849. package/scripts/check-release-readiness.sh +0 -37
  850. package/scripts/check-release-version.mjs +0 -74
  851. package/scripts/check-tarball-install-smoke.sh +0 -67
  852. package/scripts/package-source-archive.sh +0 -147
  853. package/scripts/public-release-files.txt +0 -39
  854. package/scripts/public-release.ts +0 -174
  855. package/scripts/publish-release-tarball.sh +0 -27
  856. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  857. package/scripts/run-skill-evals.ts +0 -1022
  858. package/scripts/stage-runtime-release.ts +0 -39
  859. package/src/cli/controller/progress.ts +0 -656
  860. package/src/cli/controller/work-mode.ts +0 -181
  861. package/src/cli/local-bridge/console-assets.ts +0 -17
  862. package/src/cli/local-bridge/dashboard.ts +0 -17
  863. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  864. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  865. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  866. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  867. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  868. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  869. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  870. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  871. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  872. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  873. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  874. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  875. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  876. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  877. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  878. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  879. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  880. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  881. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  882. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  883. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  884. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  885. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  886. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  887. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  888. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  889. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  890. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  891. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
  892. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
  893. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  894. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
  895. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
  896. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
  897. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  898. package/src/runtime/control-plane/routing/index.ts +0 -1
  899. package/src/runtime/control-plane/routing/route-policy.ts +0 -382
  900. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,10 +1,12 @@
1
+ import { createHash } from 'crypto';
1
2
  import { mkdirSync } from 'fs';
2
3
  import { join } from 'path';
3
- import { repositoryControllerRoot } from '../../../cli/repositories/controller-home';
4
+ import { scopedOperationRoot } from '../../../cli/repositories/controller-home';
4
5
  import { withControllerLock } from '../../../cli/repositories/locks';
5
6
  import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../../shared/json-files';
6
7
  import {
7
8
  normalizePlanScopeKey,
9
+ isPlanExtensionPredecessor,
8
10
  resolvePlanAdmission,
9
11
  withPlanAdmissionLock,
10
12
  withPlanAdmissionLockAsync,
@@ -13,34 +15,43 @@ import {
13
15
  } from './semantic-admission';
14
16
  import {
15
17
  listControlPlaneRecords,
18
+ readControlPlaneRecord,
16
19
  readControlPlaneRecordWithinTransaction,
17
20
  withControlPlaneTransaction,
18
21
  writeControlPlaneRecordWithinTransaction,
19
22
  } from '../persistence/sqlite-store';
20
23
  import { readRequirement } from '../persistence/requirement-store';
24
+ import type { ScopeRef } from '../../../../packages/kernel/identity/api/index';
21
25
  import {
22
26
  isTerminalPlanContractStatus,
23
27
  type EvidenceRef,
24
28
  type PlanContract,
25
29
  type PlanContractStatus,
26
30
  type PlanContractStore,
31
+ type PlanObligationDisposition,
32
+ type PlanRevisionDraft,
33
+ type PlanRevisionRecord,
27
34
  type PlanStep,
28
- type WorkContract,
29
35
  } from './types';
30
36
 
31
37
  export interface PlanContractStoreLocation {
32
38
  controllerHome?: string;
33
39
  repoId?: string;
40
+ /** Portable semantic scope key (for example the ForgeInstance semantic scope). Takes precedence over repoId. */
41
+ scopeKey?: string;
34
42
  root?: string;
35
43
  }
36
44
 
37
45
  export interface PlanContractStoreOptions extends PlanContractStoreLocation {
38
46
  now?: () => string;
47
+ /** Prove that one delivered Git revision is contained in a later Plan source. Missing/failed proof is fail-closed. */
48
+ revisionContains?: (ancestorRevision: string, descendantRevision: string) => boolean;
39
49
  }
40
50
 
41
51
  export interface CreatePlanContractInput {
42
52
  planId: string;
43
- repoId: string;
53
+ /** Optional node-local placement provenance; absent for a ForgeInstance-scoped Plan. */
54
+ repoId?: string;
44
55
  requirementId?: string;
45
56
  scopeKey: string;
46
57
  sourceRevision: string;
@@ -53,6 +64,7 @@ export interface CreatePlanContractInput {
53
64
  integrationStrategy?: string;
54
65
  steps: Array<Omit<PlanStep, 'status' | 'evidenceRefs'> & Partial<Pick<PlanStep, 'status' | 'evidenceRefs'>>>;
55
66
  evidenceRefs?: EvidenceRef[];
67
+ obligationDispositions?: PlanObligationDisposition[];
56
68
  }
57
69
 
58
70
  export interface AdmitPlanContractInput extends CreatePlanContractInput {
@@ -69,7 +81,9 @@ export type AdmitPlanContractResult = PlanAdmissionResolution;
69
81
 
70
82
  export interface PlanContractSummary {
71
83
  planId: string;
72
- repoId: string;
84
+ revision: number;
85
+ /** Node-local placement provenance; absent for a ForgeInstance-scoped Plan. */
86
+ repoId?: string;
73
87
  requirementId?: string;
74
88
  scopeKey: string;
75
89
  sourceRevision: string;
@@ -88,10 +102,150 @@ function bounded(values: readonly string[] | undefined, limit: number, maxLength
88
102
  return (values ?? []).map((value) => String(value).trim()).filter(Boolean).slice(0, limit).map((value) => value.slice(0, maxLength));
89
103
  }
90
104
 
105
+ function normalizePlanSemanticItems(items: readonly { id: string; objective: string; dependencies?: string[] }[] | undefined) {
106
+ const normalized = (items ?? []).slice(0, 100).map((item) => ({
107
+ id: String(item.id).trim().slice(0, 200),
108
+ objective: String(item.objective).trim().slice(0, 2_000),
109
+ dependencies: [...new Set((item.dependencies ?? []).map(String).map((value) => value.trim()).filter(Boolean))].slice(0, 100),
110
+ }));
111
+ if (normalized.some((item) => !item.id || !item.objective) || new Set(normalized.map((item) => item.id)).size !== normalized.length) {
112
+ throw new Error('PLAN_SEMANTIC_ITEMS_INVALID');
113
+ }
114
+ return normalized;
115
+ }
116
+
91
117
  function normalizeScopeKey(value: string): string {
92
118
  return normalizePlanScopeKey(value) || 'unknown';
93
119
  }
94
120
 
121
+ export function currentPlanRevision(plan: PlanContract): number {
122
+ const revision = Number(plan.revision);
123
+ return Number.isInteger(revision) && revision > 0 ? revision : 1;
124
+ }
125
+
126
+ export function currentPlanSemanticRevision(plan: PlanContract): number {
127
+ const revision = Number(plan.semanticRevision);
128
+ return Number.isInteger(revision) && revision > 0 ? revision : 1;
129
+ }
130
+
131
+ export interface PlanSemanticView {
132
+ planId: string;
133
+ revision: number;
134
+ semanticScope: ScopeRef;
135
+ requirementId?: string;
136
+ /** One-way lineage fact: this Plan is historical and its obligations continue in the named successor. */
137
+ supersededBy?: string;
138
+ requirementBasisRevision?: number;
139
+ sourceBasisRevision: string;
140
+ goal: string;
141
+ nonGoals: string[];
142
+ assumptions: string[];
143
+ resolvedDecisions: string[];
144
+ stopConditions: string[];
145
+ replanConditions: string[];
146
+ integrationStrategy?: string;
147
+ items: Array<{ id: string; objective: string; dependencies: string[] }>;
148
+ createdAt: string;
149
+ updatedAt: string;
150
+ }
151
+
152
+ export interface PlanSemanticRevisionRecord extends PlanSemanticView {
153
+ schemaVersion: 1;
154
+ recordedAt: string;
155
+ }
156
+
157
+ export interface RevisePlanSemanticInput {
158
+ expectedRevision: number;
159
+ /** Optional one-way successor relation. Once set it cannot be cleared or changed. */
160
+ supersededBy?: string;
161
+ requirementBasisRevision?: number;
162
+ sourceBasisRevision?: string;
163
+ goal?: string;
164
+ nonGoals?: string[];
165
+ assumptions?: string[];
166
+ resolvedDecisions?: string[];
167
+ stopConditions?: string[];
168
+ replanConditions?: string[];
169
+ integrationStrategy?: string | null;
170
+ items?: Array<{ id: string; objective: string; dependencies?: string[] }>;
171
+ }
172
+
173
+ export interface CreatePlanSemanticInput {
174
+ planId: string;
175
+ /** Optional node-local placement provenance; absent for a ForgeInstance-scoped Plan. */
176
+ repoId?: string;
177
+ requirementId?: string;
178
+ requirementBasisRevision?: number;
179
+ scopeKey?: string;
180
+ sourceBasisRevision?: string;
181
+ goal: string;
182
+ nonGoals?: string[];
183
+ assumptions?: string[];
184
+ resolvedDecisions?: string[];
185
+ stopConditions?: string[];
186
+ replanConditions?: string[];
187
+ integrationStrategy?: string;
188
+ items?: Array<{ id: string; objective: string; dependencies?: string[] }>;
189
+ }
190
+
191
+ function legacyPlanSemanticContext(plan: PlanContract) {
192
+ return {
193
+ requirementBasisRevision: undefined,
194
+ sourceBasisRevision: plan.sourceRevision,
195
+ goal: plan.goal,
196
+ nonGoals: [...plan.nonGoals],
197
+ assumptions: [...plan.assumptions],
198
+ resolvedDecisions: [...plan.resolvedDecisions],
199
+ stopConditions: [...plan.stopConditions],
200
+ replanConditions: [...plan.replanConditions],
201
+ integrationStrategy: plan.integrationStrategy,
202
+ items: plan.steps.map((step) => ({ id: step.id, objective: step.objective, dependencies: [...step.dependencies] })),
203
+ };
204
+ }
205
+
206
+ export function planSemanticView(plan: PlanContract): PlanSemanticView {
207
+ const semantic = plan.semanticContext ?? legacyPlanSemanticContext(plan);
208
+ const semanticScope: ScopeRef = plan.requirementId?.trim()
209
+ ? { schemaVersion: 1, kind: 'requirement', id: plan.requirementId.trim() }
210
+ : { schemaVersion: 1, kind: 'plan', id: plan.planId };
211
+ return {
212
+ planId: plan.planId,
213
+ revision: currentPlanSemanticRevision(plan),
214
+ semanticScope,
215
+ requirementId: plan.requirementId,
216
+ ...(plan.supersededBy?.trim() ? { supersededBy: plan.supersededBy.trim() } : {}),
217
+ requirementBasisRevision: semantic.requirementBasisRevision,
218
+ sourceBasisRevision: semantic.sourceBasisRevision,
219
+ goal: semantic.goal,
220
+ nonGoals: [...semantic.nonGoals],
221
+ assumptions: [...semantic.assumptions],
222
+ resolvedDecisions: [...semantic.resolvedDecisions],
223
+ stopConditions: [...semantic.stopConditions],
224
+ replanConditions: [...semantic.replanConditions],
225
+ integrationStrategy: semantic.integrationStrategy,
226
+ items: semantic.items.map((item) => ({ id: item.id, objective: item.objective, dependencies: [...item.dependencies] })),
227
+ createdAt: plan.createdAt,
228
+ updatedAt: plan.semanticUpdatedAt ?? plan.createdAt,
229
+ };
230
+ }
231
+
232
+ interface PlanRevisionRecordStore {
233
+ schemaVersion: 1;
234
+ updatedAt: string;
235
+ revisions: PlanRevisionRecord[];
236
+ }
237
+
238
+ interface PlanSemanticRevisionStore {
239
+ schemaVersion: 1;
240
+ updatedAt: string;
241
+ revisions: PlanSemanticRevisionRecord[];
242
+ }
243
+
244
+ function planRevisionRecordKey(planId: string, revision: number): string {
245
+ return `${sanitizeFileComponent(planId)}:r${revision}`;
246
+ }
247
+
248
+
95
249
  function normalizeStep(step: CreatePlanContractInput['steps'][number]): PlanStep {
96
250
  return {
97
251
  id: sanitizeFileComponent(step.id).slice(0, 120),
@@ -111,6 +265,60 @@ function normalizeStep(step: CreatePlanContractInput['steps'][number]): PlanStep
111
265
  };
112
266
  }
113
267
 
268
+ function normalizeObligationDisposition(value: PlanObligationDisposition): PlanObligationDisposition {
269
+ const disposition = value.disposition;
270
+ if (!['keep', 'change', 'defer', 'drop'].includes(disposition)) throw new Error(`PLAN_OBLIGATION_DISPOSITION_INVALID: ${String(disposition)}`);
271
+ return {
272
+ predecessorPlanId: sanitizeFileComponent(value.predecessorPlanId).slice(0, 160),
273
+ obligationId: String(value.obligationId ?? '').trim().slice(0, 160),
274
+ disposition,
275
+ successorRefs: bounded(value.successorRefs, 20, 240),
276
+ ...(value.rationale?.trim() ? { rationale: value.rationale.trim().slice(0, 1_000) } : {}),
277
+ };
278
+ }
279
+
280
+ export interface PlanObligation {
281
+ obligationId: string;
282
+ predecessorPlanId: string;
283
+ kind: 'step_objective' | 'step_acceptance' | 'non_goal' | 'resolved_decision' | 'stop_condition' | 'replan_condition';
284
+ sourceRef: string;
285
+ summary: string;
286
+ }
287
+
288
+ function planObligationId(planId: string, kind: PlanObligation['kind'], sourceRef: string, summary: string): string {
289
+ const digest = createHash('sha256').update(JSON.stringify({ planId, kind, sourceRef, summary })).digest('hex').slice(0, 24);
290
+ return `obl_${digest}`;
291
+ }
292
+
293
+ function listPlanObligations(plan: PlanContract, mode: 'unresolved' | 'completed_steps'): PlanObligation[] {
294
+ const out: PlanObligation[] = [];
295
+ const add = (kind: PlanObligation['kind'], sourceRef: string, summary: string) => {
296
+ const normalized = summary.trim();
297
+ if (!normalized) return;
298
+ out.push({ obligationId: planObligationId(plan.planId, kind, sourceRef, normalized), predecessorPlanId: plan.planId, kind, sourceRef, summary: normalized });
299
+ };
300
+ for (const step of plan.steps) {
301
+ if (mode === 'unresolved' && step.status === 'completed') continue;
302
+ if (mode === 'completed_steps' && step.status !== 'completed') continue;
303
+ add('step_objective', `step:${step.id}`, step.objective);
304
+ step.acceptanceCriteria.forEach((criterion, index) => add('step_acceptance', `step:${step.id}:acceptance:${index}`, criterion));
305
+ }
306
+ if (mode === 'unresolved') {
307
+ plan.nonGoals.forEach((value, index) => add('non_goal', `non_goal:${index}`, value));
308
+ plan.resolvedDecisions.forEach((value, index) => add('resolved_decision', `resolved_decision:${index}`, value));
309
+ plan.stopConditions.forEach((value, index) => add('stop_condition', `stop_condition:${index}`, value));
310
+ plan.replanConditions.forEach((value, index) => add('replan_condition', `replan_condition:${index}`, value));
311
+ }
312
+ return out;
313
+ }
314
+
315
+ export function listUnresolvedPlanObligations(plan: PlanContract): PlanObligation[] {
316
+ return listPlanObligations(plan, 'unresolved');
317
+ }
318
+
319
+
320
+
321
+
114
322
  function updatePlanContract(
115
323
  options: PlanContractStoreOptions,
116
324
  planId: string,
@@ -126,21 +334,24 @@ function updatePlanContract(
126
334
  writePlanContractStore(options, { schemaVersion: 1, updatedAt: next.updatedAt, contracts });
127
335
  return next;
128
336
  };
129
- if (!options.controllerHome || !options.repoId) return apply();
337
+ if (!options.controllerHome || !planContractStoreScopeKey(options)) return apply();
130
338
  return withControllerLock(
131
339
  options.controllerHome,
132
- { scope: 'task', repoId: options.repoId, taskId: `plan-${sanitizeFileComponent(planId)}` },
340
+ { scope: 'task', repoId: requirePlanContractStoreScopeKey(options), taskId: `plan-${sanitizeFileComponent(planId)}` },
133
341
  'plan-contract-store',
134
342
  apply,
135
343
  15_000,
136
344
  );
137
345
  }
138
346
 
347
+
139
348
  function assertRequirementReference(options: PlanContractStoreOptions, requirementId: string | undefined): void {
140
349
  const normalized = requirementId?.trim();
141
350
  if (!normalized || !options.controllerHome) return;
142
- if (!readRequirement({ controllerHome: options.controllerHome, now: options.now }, normalized)) {
143
- throw new Error(`PLAN_REQUIREMENT_NOT_FOUND: ${normalized}`);
351
+ const requirement = readRequirement({ controllerHome: options.controllerHome, now: options.now }, normalized)?.value;
352
+ if (!requirement) throw new Error(`PLAN_REQUIREMENT_NOT_FOUND: ${normalized}`);
353
+ if (requirement.state === 'done' || requirement.state === 'cancelled') {
354
+ throw new Error(`PLAN_REQUIREMENT_TERMINAL: ${normalized}:${requirement.state}`);
144
355
  }
145
356
  }
146
357
 
@@ -149,10 +360,10 @@ export function planContractRoot(location: PlanContractStoreLocation): string {
149
360
  mkdirSync(location.root, { recursive: true });
150
361
  return location.root;
151
362
  }
152
- if (!location.controllerHome || !location.repoId) {
153
- throw new Error('plan contract store requires either root or controllerHome + repoId');
363
+ if (!location.controllerHome || !planContractStoreScopeKey(location)) {
364
+ throw new Error('plan contract store requires either root or controllerHome + repoId/scopeKey');
154
365
  }
155
- const root = join(repositoryControllerRoot(location.controllerHome, location.repoId), 'plan-contracts');
366
+ const root = join(scopedOperationRoot(location.controllerHome, requirePlanContractStoreScopeKey(location)), 'plan-contracts');
156
367
  mkdirSync(root, { recursive: true });
157
368
  return root;
158
369
  }
@@ -161,11 +372,151 @@ export function planContractStorePath(location: PlanContractStoreLocation): stri
161
372
  return join(planContractRoot(location), 'index.json');
162
373
  }
163
374
 
375
+
376
+
377
+
378
+
379
+ export function planRevisionStorePath(location: PlanContractStoreLocation): string {
380
+ return join(planContractRoot(location), 'revisions.json');
381
+ }
382
+
383
+ function planSemanticRevisionStorePath(location: PlanContractStoreLocation): string {
384
+ return join(planContractRoot(location), 'semantic-revisions.json');
385
+ }
386
+
387
+ export function listPlanRevisionRecords(
388
+ options: PlanContractStoreOptions,
389
+ planId?: string,
390
+ ): PlanRevisionRecord[] {
391
+ const normalizedPlanId = planId ? sanitizeFileComponent(planId) : undefined;
392
+ if (!sqliteBacked(options)) {
393
+ const store = readJsonFile<PlanRevisionRecordStore>(planRevisionStorePath(options), { schemaVersion: 1, updatedAt: nowIso(options), revisions: [] });
394
+ return store.revisions
395
+ .filter((record) => !normalizedPlanId || record.planId === normalizedPlanId)
396
+ .sort((left, right) => right.revision - left.revision);
397
+ }
398
+ return listControlPlaneRecords<PlanRevisionRecord>(options.controllerHome, {
399
+ namespace: 'plan_revision', scope: requirePlanContractStoreScopeKey(options), limit: 5_000,
400
+ }).map((record) => record.value)
401
+ .filter((record) => !normalizedPlanId || record.planId === normalizedPlanId)
402
+ .sort((left, right) => right.revision - left.revision);
403
+ }
404
+
405
+ function appendJsonPlanRevisionRecord(options: PlanContractStoreOptions, record: PlanRevisionRecord): void {
406
+ const path = planRevisionStorePath(options);
407
+ const store = readJsonFile<PlanRevisionRecordStore>(path, { schemaVersion: 1, updatedAt: record.recordedAt, revisions: [] });
408
+ if (store.revisions.some((existing) => existing.planId === record.planId && existing.revision === record.revision)) {
409
+ throw new Error(`PLAN_REVISION_ALREADY_EXISTS: ${record.planId}:r${record.revision}`);
410
+ }
411
+ writeJsonAtomic(path, { schemaVersion: 1, updatedAt: record.recordedAt, revisions: [record, ...store.revisions] });
412
+ }
413
+
414
+ /** Exact historical lookup; revision-list limits are not version-existence gates. */
415
+ export function getPlanSemanticRevisionRecord(
416
+ options: PlanContractStoreOptions,
417
+ planId: string,
418
+ revision: number,
419
+ ): PlanSemanticRevisionRecord | undefined {
420
+ if (!Number.isInteger(revision) || revision < 1) return undefined;
421
+ const normalizedPlanId = sanitizeFileComponent(planId);
422
+ const record = sqliteBacked(options)
423
+ ? readControlPlaneRecord<PlanSemanticRevisionRecord>(
424
+ options.controllerHome, 'plan_semantic_revision', requirePlanContractStoreScopeKey(options),
425
+ planRevisionRecordKey(normalizedPlanId, revision),
426
+ )?.value
427
+ : readJsonFile<PlanSemanticRevisionStore>(
428
+ planSemanticRevisionStorePath(options), { schemaVersion: 1, updatedAt: nowIso(options), revisions: [] },
429
+ ).revisions.find((entry) => entry.planId === normalizedPlanId && entry.revision === revision);
430
+ return record ? {
431
+ ...record,
432
+ semanticScope: record.semanticScope ?? (record.requirementId
433
+ ? { schemaVersion: 1, kind: 'requirement', id: record.requirementId }
434
+ : { schemaVersion: 1, kind: 'plan', id: record.planId }),
435
+ } : undefined;
436
+ }
437
+
438
+ export function listPlanSemanticRevisionRecords(options: PlanContractStoreOptions, planId?: string): PlanSemanticRevisionRecord[] {
439
+ const normalizedPlanId = planId ? sanitizeFileComponent(planId) : undefined;
440
+ if (!sqliteBacked(options)) {
441
+ const store = readJsonFile<PlanSemanticRevisionStore>(planSemanticRevisionStorePath(options), { schemaVersion: 1, updatedAt: nowIso(options), revisions: [] });
442
+ return store.revisions.filter((record) => !normalizedPlanId || record.planId === normalizedPlanId)
443
+ .map((record) => ({
444
+ ...record,
445
+ semanticScope: record.semanticScope ?? (record.requirementId
446
+ ? { schemaVersion: 1, kind: 'requirement', id: record.requirementId }
447
+ : { schemaVersion: 1, kind: 'plan', id: record.planId }),
448
+ } as PlanSemanticRevisionRecord))
449
+ .sort((left, right) => right.revision - left.revision);
450
+ }
451
+ return listControlPlaneRecords<PlanSemanticRevisionRecord>(options.controllerHome, {
452
+ namespace: 'plan_semantic_revision', scope: requirePlanContractStoreScopeKey(options), limit: 5_000,
453
+ }).map((record) => record.value)
454
+ .filter((record) => !normalizedPlanId || record.planId === normalizedPlanId)
455
+ .map((record) => ({
456
+ ...record,
457
+ semanticScope: record.semanticScope ?? (record.requirementId
458
+ ? { schemaVersion: 1, kind: 'requirement', id: record.requirementId }
459
+ : { schemaVersion: 1, kind: 'plan', id: record.planId }),
460
+ } as PlanSemanticRevisionRecord))
461
+ .sort((left, right) => right.revision - left.revision);
462
+ }
463
+
464
+ function appendJsonPlanSemanticRevisionRecord(options: PlanContractStoreOptions, record: PlanSemanticRevisionRecord): void {
465
+ const path = planSemanticRevisionStorePath(options);
466
+ const store = readJsonFile<PlanSemanticRevisionStore>(path, { schemaVersion: 1, updatedAt: record.recordedAt, revisions: [] });
467
+ if (store.revisions.some((existing) => existing.planId === record.planId && existing.revision === record.revision)) return;
468
+ writeJsonAtomic(path, { schemaVersion: 1, updatedAt: record.recordedAt, revisions: [record, ...store.revisions].slice(0, 5_000) });
469
+ }
470
+ function planRevisionRecord(plan: PlanContract, input: { recordedAt: string; reason: string; requestedRevisionLabel?: string }): PlanRevisionRecord {
471
+ return {
472
+ schemaVersion: 1, repoId: plan.repoId, planId: plan.planId, revision: currentPlanRevision(plan),
473
+ requirementId: plan.requirementId, scopeKey: plan.scopeKey, sourceRevision: plan.sourceRevision, goal: plan.goal,
474
+ nonGoals: [...plan.nonGoals], assumptions: [...plan.assumptions], resolvedDecisions: [...plan.resolvedDecisions],
475
+ stopConditions: [...plan.stopConditions], replanConditions: [...plan.replanConditions], integrationStrategy: plan.integrationStrategy,
476
+ status: plan.status, steps: structuredClone(plan.steps), evidenceRefs: structuredClone(plan.evidenceRefs),
477
+ obligationDispositions: plan.obligationDispositions ? structuredClone(plan.obligationDispositions) : undefined,
478
+ recordedAt: input.recordedAt, reason: input.reason.slice(0, 1_000), requestedRevisionLabel: input.requestedRevisionLabel,
479
+ };
480
+ }
481
+
482
+ function revisionDraftFromCandidate(candidate: PlanContract, input: { requestedRevisionLabel?: string; createdAt: string; updatedAt: string }): PlanRevisionDraft {
483
+ return {
484
+ revision: currentPlanRevision(candidate), requestedRevisionLabel: input.requestedRevisionLabel,
485
+ sourceRevision: candidate.sourceRevision, goal: candidate.goal, nonGoals: [...candidate.nonGoals], assumptions: [...candidate.assumptions],
486
+ resolvedDecisions: [...candidate.resolvedDecisions], stopConditions: [...candidate.stopConditions], replanConditions: [...candidate.replanConditions],
487
+ integrationStrategy: candidate.integrationStrategy, steps: structuredClone(candidate.steps),
488
+ obligationDispositions: candidate.obligationDispositions ? structuredClone(candidate.obligationDispositions) : undefined,
489
+ deliveryCarries: candidate.deliveryCarries ? structuredClone(candidate.deliveryCarries) : undefined,
490
+ createdAt: input.createdAt, updatedAt: input.updatedAt,
491
+ };
492
+ }
493
+
494
+
495
+ function assertRevisionScopeAuthority(current: PlanContract, allPlans: readonly PlanContract[]): void {
496
+ const conflicting = allPlans.find((plan) => plan.planId !== current.planId
497
+ && !isTerminalPlanContractStatus(plan.status) && plan.scopeKey === current.scopeKey);
498
+ if (conflicting) throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${current.scopeKey}:${conflicting.planId}`);
499
+ }
500
+
501
+
164
502
  export function emptyPlanContractStore(updatedAt: string): PlanContractStore {
165
503
  return { schemaVersion: 1, updatedAt, contracts: [] };
166
504
  }
167
- function sqliteBacked(options: PlanContractStoreOptions): options is PlanContractStoreOptions & { controllerHome: string; repoId: string } {
168
- return Boolean(!options.root && options.controllerHome?.trim() && options.repoId?.trim());
505
+ /** Canonical Plan store scope key: an explicit portable scope, else repository placement. */
506
+ export function planContractStoreScopeKey(options: PlanContractStoreLocation): string | undefined {
507
+ return options.scopeKey?.trim() || options.repoId?.trim() || undefined;
508
+ }
509
+
510
+ function requirePlanContractStoreScopeKey(options: PlanContractStoreLocation): string {
511
+ const key = planContractStoreScopeKey(options);
512
+ if (!key) throw new Error('PLAN_STORE_SCOPE_REQUIRED: controllerHome requires repoId or scopeKey');
513
+ return key;
514
+ }
515
+
516
+ type ControlPlanePlanStoreOptions = PlanContractStoreOptions & { controllerHome: string };
517
+
518
+ function sqliteBacked(options: PlanContractStoreOptions): options is ControlPlanePlanStoreOptions {
519
+ return Boolean(!options.root && options.controllerHome?.trim() && planContractStoreScopeKey(options));
169
520
  }
170
521
 
171
522
  export function readPlanContractStore(options: PlanContractStoreOptions): PlanContractStore {
@@ -174,7 +525,7 @@ export function readPlanContractStore(options: PlanContractStoreOptions): PlanCo
174
525
  }
175
526
  const records = listControlPlaneRecords<PlanContract>(options.controllerHome, {
176
527
  namespace: 'plan_contract',
177
- scope: options.repoId,
528
+ scope: requirePlanContractStoreScopeKey(options),
178
529
  limit: 5_000,
179
530
  });
180
531
  if (records.length > 0) {
@@ -191,10 +542,10 @@ export function readPlanContractStore(options: PlanContractStoreOptions): PlanCo
191
542
  if (legacy.contracts.length > 0) {
192
543
  withControlPlaneTransaction(options.controllerHome, (database) => {
193
544
  for (const contract of legacy.contracts) {
194
- if (readControlPlaneRecordWithinTransaction<PlanContract>(database, 'plan_contract', options.repoId, contract.planId)) continue;
545
+ if (readControlPlaneRecordWithinTransaction<PlanContract>(database, 'plan_contract', requirePlanContractStoreScopeKey(options), contract.planId)) continue;
195
546
  writeControlPlaneRecordWithinTransaction(database, {
196
547
  namespace: 'plan_contract',
197
- scope: options.repoId,
548
+ scope: requirePlanContractStoreScopeKey(options),
198
549
  key: contract.planId,
199
550
  schemaVersion: 1,
200
551
  value: contract,
@@ -217,12 +568,15 @@ function writePlanContractStore(options: PlanContractStoreOptions, store: PlanCo
217
568
  const current = readControlPlaneRecordWithinTransaction<PlanContract>(
218
569
  database,
219
570
  'plan_contract',
220
- options.repoId,
571
+ requirePlanContractStoreScopeKey(options),
221
572
  contract.planId,
222
573
  );
574
+ // SQLite is authoritative per Plan row. A sibling Plan appearing in an
575
+ // aggregate compatibility snapshot is not itself a mutation.
576
+ if (current && JSON.stringify(current.value) === JSON.stringify(contract)) continue;
223
577
  writeControlPlaneRecordWithinTransaction(database, {
224
578
  namespace: 'plan_contract',
225
- scope: options.repoId,
579
+ scope: requirePlanContractStoreScopeKey(options),
226
580
  key: contract.planId,
227
581
  schemaVersion: 1,
228
582
  value: contract,
@@ -234,12 +588,14 @@ function writePlanContractStore(options: PlanContractStoreOptions, store: PlanCo
234
588
  return store;
235
589
  }
236
590
 
591
+
237
592
  function buildPlanContract(input: CreatePlanContractInput, at: string): PlanContract {
238
593
  const planId = sanitizeFileComponent(input.planId);
239
594
  if (!String(input.planId ?? '').trim() || planId === 'unknown') throw new Error('plan_id is required');
240
595
  return {
241
596
  schemaVersion: 1,
242
597
  planId,
598
+ revision: 1,
243
599
  repoId: input.repoId,
244
600
  requirementId: input.requirementId?.trim().slice(0, 160) || undefined,
245
601
  scopeKey: normalizeScopeKey(input.scopeKey),
@@ -254,6 +610,7 @@ function buildPlanContract(input: CreatePlanContractInput, at: string): PlanCont
254
610
  status: 'draft',
255
611
  steps: input.steps.slice(0, 30).map(normalizeStep),
256
612
  evidenceRefs: (input.evidenceRefs ?? []).slice(0, 20),
613
+ obligationDispositions: (input.obligationDispositions ?? []).slice(0, 512).map(normalizeObligationDisposition),
257
614
  createdAt: at,
258
615
  updatedAt: at,
259
616
  };
@@ -285,6 +642,14 @@ function draftContentErrors(plan: PlanContract): string[] {
285
642
  return [...new Set(errors)];
286
643
  }
287
644
 
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
288
653
  function createPlanContractUnlocked(options: PlanContractStoreOptions, input: CreatePlanContractInput): PlanContract {
289
654
  assertRequirementReference(options, input.requirementId);
290
655
  const at = nowIso(options);
@@ -308,22 +673,39 @@ function replacePlanContractUnlocked(
308
673
  const predecessorKey = sanitizeFileComponent(predecessorId);
309
674
  const predecessorIndex = store.contracts.findIndex((contract) => contract.planId === predecessorKey);
310
675
  if (predecessorIndex < 0) throw new Error(`plan contract not found: ${predecessorKey}`);
311
- const predecessor = store.contracts[predecessorIndex]!;
312
- if (isTerminalPlanContractStatus(predecessor.status)) throw new Error(`plan contract ${predecessor.planId} is terminal (${predecessor.status})`);
313
- const successor = buildPlanContract(input, at);
314
- if (successor.planId === predecessor.planId) throw new Error('PLAN_SUCCESSOR_ID_MUST_CHANGE');
315
- if (successor.repoId !== predecessor.repoId) throw new Error('PLAN_SUCCESSOR_REPOSITORY_MISMATCH');
316
- if (store.contracts.some((existing) => existing.planId === successor.planId)) {
317
- throw new Error(`plan contract already exists: ${successor.planId}`);
676
+ const current = store.contracts[predecessorIndex]!;
677
+ if (!isPlanExtensionPredecessor(current)) throw new Error(`plan contract ${current.planId} is terminal (${current.status})`);
678
+
679
+ // Draft extension keeps the stable Plan identity, but authored content is still
680
+ // a semantic revision: the previous head is archived and the current head advances.
681
+ if (current.status === 'draft') {
682
+ return repairDraftPlanContractUnlocked(options, current.planId, {
683
+ scopeKey: input.scopeKey, sourceRevision: input.sourceRevision, goal: input.goal, nonGoals: input.nonGoals, assumptions: input.assumptions,
684
+ resolvedDecisions: input.resolvedDecisions, stopConditions: input.stopConditions, replanConditions: input.replanConditions,
685
+ integrationStrategy: input.integrationStrategy, steps: input.steps, obligationDispositions: input.obligationDispositions,
686
+ });
318
687
  }
319
- const conflictingScope = store.contracts.find((existing, index) => index !== predecessorIndex
320
- && !isTerminalPlanContractStatus(existing.status)
321
- && existing.scopeKey === successor.scopeKey);
322
- if (conflictingScope) throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${successor.scopeKey}:${conflictingScope.planId}`);
688
+ if (current.pendingRevision) throw new Error(`PLAN_REVISION_DRAFT_ALREADY_EXISTS: ${current.planId}:r${current.pendingRevision.revision}`);
689
+ if (input.requirementId?.trim() !== current.requirementId?.trim()) throw new Error('PLAN_REVISION_REQUIREMENT_IDENTITY_MISMATCH');
690
+ if (normalizeScopeKey(input.scopeKey) !== current.scopeKey) throw new Error(`PLAN_REVISION_SCOPE_IDENTITY_MISMATCH: ${current.scopeKey}`);
691
+
692
+ const candidate = {
693
+ ...buildPlanContract({ ...input, planId: current.planId, repoId: current.repoId, requirementId: current.requirementId, scopeKey: current.scopeKey }, at),
694
+ revision: currentPlanRevision(current) + 1,
695
+ supersedes: current.supersedes ? [...current.supersedes] : undefined,
696
+ };
697
+ const contentErrors = draftContentErrors(candidate);
698
+ if (contentErrors.length > 0) throw new Error(`PLAN_REVISION_DRAFT_INVALID: ${contentErrors.join('; ')}`);
699
+ assertRevisionScopeAuthority(current, store.contracts);
700
+ const requestedRevisionLabel = sanitizeFileComponent(input.planId);
701
+ const pendingRevision = revisionDraftFromCandidate(candidate, {
702
+ requestedRevisionLabel: requestedRevisionLabel !== current.planId ? requestedRevisionLabel : undefined, createdAt: at, updatedAt: at,
703
+ });
704
+ const next: PlanContract = { ...current, status: 'replanning', pendingRevision, updatedAt: at };
323
705
  const contracts = [...store.contracts];
324
- contracts[predecessorIndex] = { ...predecessor, status: 'superseded', supersededBy: successor.planId, updatedAt: at };
325
- writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts: [successor, ...contracts] });
326
- return successor;
706
+ contracts[predecessorIndex] = next;
707
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
708
+ return next;
327
709
  }
328
710
 
329
711
  function repairDraftPlanContractUnlocked(
@@ -336,9 +718,11 @@ function repairDraftPlanContractUnlocked(
336
718
  const index = store.contracts.findIndex((contract) => contract.planId === key);
337
719
  if (index < 0) throw new Error(`plan contract not found: ${key}`);
338
720
  const current = store.contracts[index]!;
339
- if (current.status !== 'draft') throw new Error(`PLAN_DRAFT_REPAIR_STATUS_INVALID: ${current.planId}:${current.status}`);
340
- if (input.expectedSourceRevision !== undefined && current.sourceRevision !== input.expectedSourceRevision) {
341
- throw new Error(`PLAN_DRAFT_REPAIR_STALE_SOURCE: ${current.planId}:expected=${input.expectedSourceRevision}:actual=${current.sourceRevision}`);
721
+ const pending = current.status === 'replanning' ? current.pendingRevision : undefined;
722
+ if (current.status !== 'draft' && !pending) throw new Error(`PLAN_DRAFT_REPAIR_STATUS_INVALID: ${current.planId}:${current.status}`);
723
+ const observedSourceRevision = pending?.sourceRevision ?? current.sourceRevision;
724
+ if (input.expectedSourceRevision !== undefined && observedSourceRevision !== input.expectedSourceRevision) {
725
+ throw new Error(`PLAN_DRAFT_REPAIR_STALE_SOURCE: ${current.planId}:expected=${input.expectedSourceRevision}:actual=${observedSourceRevision}`);
342
726
  }
343
727
  assertRequirementReference(options, current.requirementId);
344
728
  const at = nowIso(options);
@@ -349,19 +733,73 @@ function repairDraftPlanContractUnlocked(
349
733
  requirementId: current.requirementId,
350
734
  evidenceRefs: current.evidenceRefs,
351
735
  }, at);
352
- const contentErrors = draftContentErrors(candidate);
736
+ const candidateWithLineage = {
737
+ ...candidate,
738
+ ...(current.supersedes?.length ? { supersedes: [...current.supersedes] } : {}),
739
+ };
740
+ const contentErrors = draftContentErrors(candidateWithLineage);
353
741
  if (contentErrors.length > 0) {
354
742
  throw new Error(`PLAN_DRAFT_REPAIR_INVALID: ${contentErrors.join('; ')}`);
355
743
  }
744
+ if (pending) {
745
+ if (normalizeScopeKey(input.scopeKey) !== current.scopeKey) throw new Error(`PLAN_REVISION_SCOPE_IDENTITY_MISMATCH: ${current.scopeKey}`);
746
+ assertRevisionScopeAuthority(current, store.contracts);
747
+ let revisionCandidate: PlanContract = { ...candidateWithLineage, revision: pending.revision, supersedes: current.supersedes ? [...current.supersedes] : undefined };
748
+ const repairedPending = revisionDraftFromCandidate(revisionCandidate, {
749
+ requestedRevisionLabel: pending.requestedRevisionLabel, createdAt: pending.createdAt, updatedAt: at,
750
+ });
751
+ const repairedCurrent: PlanContract = { ...current, pendingRevision: repairedPending, updatedAt: at };
752
+ const contracts = [...store.contracts];
753
+ contracts[index] = repairedCurrent;
754
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
755
+ return repairedCurrent;
756
+ }
757
+ const explicitPredecessors = new Set(candidateWithLineage.supersedes ?? []);
356
758
  const conflictingScope = store.contracts.find((existing, candidateIndex) => candidateIndex !== index
357
759
  && !isTerminalPlanContractStatus(existing.status)
358
- && existing.scopeKey === candidate.scopeKey);
359
- if (conflictingScope) throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${candidate.scopeKey}:${conflictingScope.planId}`);
760
+ && existing.scopeKey === candidateWithLineage.scopeKey
761
+ && !explicitPredecessors.has(existing.planId));
762
+ if (conflictingScope) throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${candidateWithLineage.scopeKey}:${conflictingScope.planId}`);
763
+ const previousSemantic = planSemanticView(current);
764
+ const candidateSemantic = legacyPlanSemanticContext(candidateWithLineage);
360
765
  const repaired: PlanContract = {
361
- ...candidate,
766
+ ...candidateWithLineage,
767
+ // Mechanical lifecycle revision is independent from the model-facing semantic
768
+ // head. Draft/extend compatibility must never silently overwrite semantic r1.
769
+ revision: current.revision,
770
+ semanticRevision: previousSemantic.revision + 1,
771
+ semanticUpdatedAt: at,
772
+ semanticContext: {
773
+ ...candidateSemantic,
774
+ requirementBasisRevision: previousSemantic.requirementBasisRevision,
775
+ },
362
776
  createdAt: current.createdAt,
363
777
  updatedAt: at,
364
778
  };
779
+ if (sqliteBacked(options)) {
780
+ return withControlPlaneTransaction(options.controllerHome, (database) => {
781
+ const scope = requirePlanContractStoreScopeKey(options);
782
+ const currentRecord = readControlPlaneRecordWithinTransaction<PlanContract>(database, 'plan_contract', scope, current.planId);
783
+ if (!currentRecord) throw new Error(`plan contract not found: ${current.planId}`);
784
+ const actualSemanticRevision = currentPlanSemanticRevision(currentRecord.value);
785
+ if (actualSemanticRevision !== previousSemantic.revision) {
786
+ throw new Error(`PLAN_REVISION_CONFLICT:${current.planId}:expected=${previousSemantic.revision}:actual=${actualSemanticRevision}`);
787
+ }
788
+ const semanticRevisionKey = `${current.planId}:r${previousSemantic.revision}`;
789
+ if (!readControlPlaneRecordWithinTransaction<PlanSemanticRevisionRecord>(database, 'plan_semantic_revision', scope, semanticRevisionKey)) {
790
+ writeControlPlaneRecordWithinTransaction(database, {
791
+ namespace: 'plan_semantic_revision', scope, key: semanticRevisionKey, schemaVersion: 1,
792
+ value: { schemaVersion: 1, ...previousSemantic, recordedAt: at },
793
+ action: 'plan_semantic_revision_archived', expectedRevision: null,
794
+ });
795
+ }
796
+ return writeControlPlaneRecordWithinTransaction(database, {
797
+ namespace: 'plan_contract', scope, key: current.planId, schemaVersion: 1,
798
+ value: repaired, action: 'plan_semantic_revised', expectedRevision: currentRecord.revision,
799
+ }).value;
800
+ });
801
+ }
802
+ appendJsonPlanSemanticRevisionRecord(options, { schemaVersion: 1, ...previousSemantic, recordedAt: at });
365
803
  const contracts = [...store.contracts];
366
804
  contracts[index] = repaired;
367
805
  writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
@@ -401,18 +839,95 @@ export function createPlanContract(options: PlanContractStoreOptions, input: Cre
401
839
  });
402
840
  }
403
841
 
842
+ export function createPlanSemanticContext(options: PlanContractStoreOptions, input: CreatePlanSemanticInput): PlanContract {
843
+ return withPlanAdmissionLock(options, () => {
844
+ assertRequirementReference(options, input.requirementId);
845
+ const store = readPlanContractStore(options);
846
+ const planId = sanitizeFileComponent(input.planId);
847
+ if (!String(input.planId ?? '').trim() || planId === 'unknown') throw new Error('plan_id is required');
848
+ if (store.contracts.some((existing) => existing.planId === planId)) throw new Error(`plan contract already exists: ${planId}`);
849
+
850
+ // Thin Plan scope identifies one semantic planning lineage; it is not an
851
+ // execution mutex. The same Requirement + scope has one current Plan head,
852
+ // while genuinely independent goals remain free to use distinct scopes.
853
+ const scopeKey = normalizeScopeKey(input.scopeKey?.trim() || planId);
854
+ const requirementId = input.requirementId?.trim().slice(0, 160) || undefined;
855
+ const existingLineage = store.contracts.find((existing) =>
856
+ existing.planId !== planId
857
+ && isCurrentPlanContract(existing)
858
+ && existing.scopeKey === scopeKey
859
+ && (existing.requirementId?.trim() || undefined) === requirementId);
860
+ if (existingLineage) {
861
+ throw new Error(`PLAN_SEMANTIC_LINEAGE_ALREADY_EXISTS:${scopeKey}:${existingLineage.planId}`);
862
+ }
863
+
864
+ const goal = String(input.goal ?? '').trim().slice(0, 2_000);
865
+ if (!goal) throw new Error('PLAN_GOAL_REQUIRED');
866
+ const requirementBasisRevision = input.requirementBasisRevision === undefined
867
+ ? undefined
868
+ : (() => {
869
+ if (!Number.isInteger(input.requirementBasisRevision) || Number(input.requirementBasisRevision) < 1) {
870
+ throw new Error('PLAN_REQUIREMENT_BASIS_REVISION_INVALID');
871
+ }
872
+ return Number(input.requirementBasisRevision);
873
+ })();
874
+ const sourceBasisRevision = String(input.sourceBasisRevision ?? '').trim().slice(0, 200);
875
+ const items = normalizePlanSemanticItems(input.items);
876
+ const at = nowIso(options);
877
+ const semanticContext = {
878
+ requirementBasisRevision,
879
+ sourceBasisRevision,
880
+ goal,
881
+ nonGoals: bounded(input.nonGoals, 20),
882
+ assumptions: bounded(input.assumptions, 30),
883
+ resolvedDecisions: bounded(input.resolvedDecisions, 30),
884
+ stopConditions: bounded(input.stopConditions, 20),
885
+ replanConditions: bounded(input.replanConditions, 20),
886
+ integrationStrategy: input.integrationStrategy?.trim().slice(0, 1_000) || undefined,
887
+ items,
888
+ };
889
+ const plan: PlanContract = {
890
+ schemaVersion: 1,
891
+ planId,
892
+ revision: 1,
893
+ semanticRevision: 1,
894
+ semanticUpdatedAt: at,
895
+ semanticContext,
896
+ repoId: input.repoId,
897
+ requirementId,
898
+ scopeKey,
899
+ sourceRevision: sourceBasisRevision,
900
+ goal,
901
+ nonGoals: [...semanticContext.nonGoals],
902
+ assumptions: [...semanticContext.assumptions],
903
+ resolvedDecisions: [...semanticContext.resolvedDecisions],
904
+ stopConditions: [...semanticContext.stopConditions],
905
+ replanConditions: [...semanticContext.replanConditions],
906
+ integrationStrategy: semanticContext.integrationStrategy,
907
+ // Legacy lifecycle projection only. Thin Plan items never become PlanSteps.
908
+ status: 'approved',
909
+ steps: [],
910
+ evidenceRefs: [],
911
+ createdAt: at,
912
+ updatedAt: at,
913
+ };
914
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts: [plan, ...store.contracts] });
915
+ return plan;
916
+ });
917
+ }
918
+
404
919
  function admitPlanContractUnlocked(options: PlanContractStoreOptions, input: AdmitPlanContractInput): AdmitPlanContractResult {
405
920
  assertRequirementReference(options, input.requirementId);
406
- const activePlans = listPlanContracts({ ...options, status: 'active', limit: 100 });
407
- const resolution = resolvePlanAdmission(activePlans, {
921
+ const plans = listPlanContracts({ ...options, status: 'all', limit: 100 });
922
+ const resolution = resolvePlanAdmission(plans, {
408
923
  requirementId: input.requirementId,
409
924
  scopeKey: input.scopeKey,
410
925
  planRelation: input.planRelation,
411
926
  relatedPlanId: input.relatedPlanId,
412
927
  });
413
928
  if (resolution.admissionDecision === 'extend_existing' && resolution.plan) {
414
- const successor = replacePlanContractUnlocked(options, resolution.plan.planId, input);
415
- return { ...resolution, admissionDecision: 'create_new', plan: successor };
929
+ const revised = replacePlanContractUnlocked(options, resolution.plan.planId, input);
930
+ return { ...resolution, admissionDecision: 'reuse_existing', plan: revised };
416
931
  }
417
932
  if (resolution.admissionDecision !== 'create_new') return resolution;
418
933
  return { ...resolution, plan: createPlanContractUnlocked(options, input) };
@@ -430,11 +945,122 @@ export function getPlanContract(options: PlanContractStoreOptions, planId: strin
430
945
  return readPlanContractStore(options).contracts.find((contract) => contract.planId === sanitizeFileComponent(planId));
431
946
  }
432
947
 
948
+ export function revisePlanSemanticContext(
949
+ options: PlanContractStoreOptions,
950
+ planIdInput: string,
951
+ input: RevisePlanSemanticInput,
952
+ ): PlanContract {
953
+ if (!Number.isInteger(input.expectedRevision) || input.expectedRevision < 1) throw new Error('PLAN_EXPECTED_REVISION_INVALID');
954
+ const planId = sanitizeFileComponent(planIdInput);
955
+ const requestedSuccessor = input.supersededBy === undefined
956
+ ? undefined
957
+ : (() => {
958
+ const raw = String(input.supersededBy).trim();
959
+ if (!raw) throw new Error('PLAN_SUCCESSOR_REQUIRED');
960
+ const successorId = sanitizeFileComponent(raw);
961
+ if (!successorId || successorId === 'unknown') throw new Error('PLAN_SUCCESSOR_INVALID');
962
+ if (successorId === planId) throw new Error(`PLAN_SUCCESSOR_SELF_REFERENCE:${planId}`);
963
+ return successorId;
964
+ })();
965
+ return withPlanAdmissionLock(options, () => {
966
+ const applyRevision = (current: PlanContract, at: string): { previous: PlanSemanticView; next: PlanContract } => {
967
+ const revision = currentPlanSemanticRevision(current);
968
+ if (revision !== input.expectedRevision) throw new Error(`PLAN_REVISION_CONFLICT:${planId}:expected=${input.expectedRevision}:actual=${revision}`);
969
+ const previous = planSemanticView(current);
970
+ const existingSuccessor = current.supersededBy?.trim() || undefined;
971
+ if (existingSuccessor && requestedSuccessor && existingSuccessor !== requestedSuccessor) {
972
+ throw new Error(`PLAN_SUCCESSOR_IMMUTABLE:${planId}:current=${existingSuccessor}:requested=${requestedSuccessor}`);
973
+ }
974
+ const supersededBy = requestedSuccessor ?? existingSuccessor;
975
+ const goal = input.goal === undefined ? previous.goal : String(input.goal).trim().slice(0, 2_000);
976
+ if (!goal) throw new Error('PLAN_GOAL_REQUIRED');
977
+ const requirementBasisRevision = input.requirementBasisRevision === undefined
978
+ ? previous.requirementBasisRevision
979
+ : (() => {
980
+ if (!Number.isInteger(input.requirementBasisRevision) || Number(input.requirementBasisRevision) < 1) throw new Error('PLAN_REQUIREMENT_BASIS_REVISION_INVALID');
981
+ return Number(input.requirementBasisRevision);
982
+ })();
983
+ const sourceBasisRevision = input.sourceBasisRevision === undefined ? previous.sourceBasisRevision : String(input.sourceBasisRevision).trim().slice(0, 200);
984
+ const items = input.items === undefined ? previous.items : normalizePlanSemanticItems(input.items);
985
+ const semanticContext = {
986
+ requirementBasisRevision,
987
+ sourceBasisRevision,
988
+ goal,
989
+ nonGoals: input.nonGoals === undefined ? previous.nonGoals : bounded(input.nonGoals, 20),
990
+ assumptions: input.assumptions === undefined ? previous.assumptions : bounded(input.assumptions, 30),
991
+ resolvedDecisions: input.resolvedDecisions === undefined ? previous.resolvedDecisions : bounded(input.resolvedDecisions, 30),
992
+ stopConditions: input.stopConditions === undefined ? previous.stopConditions : bounded(input.stopConditions, 20),
993
+ replanConditions: input.replanConditions === undefined ? previous.replanConditions : bounded(input.replanConditions, 20),
994
+ integrationStrategy: input.integrationStrategy === undefined ? previous.integrationStrategy : input.integrationStrategy === null ? undefined : String(input.integrationStrategy).trim().slice(0, 1_000) || undefined,
995
+ items,
996
+ };
997
+ return {
998
+ previous,
999
+ next: {
1000
+ ...current,
1001
+ ...(supersededBy ? { supersededBy } : {}),
1002
+ semanticRevision: revision + 1,
1003
+ semanticUpdatedAt: at,
1004
+ semanticContext,
1005
+ updatedAt: at,
1006
+ },
1007
+ };
1008
+ };
1009
+
1010
+ if (sqliteBacked(options)) {
1011
+ return withControlPlaneTransaction(options.controllerHome, (database) => {
1012
+ const scope = requirePlanContractStoreScopeKey(options);
1013
+ const currentRecord = readControlPlaneRecordWithinTransaction<PlanContract>(database, 'plan_contract', scope, planId);
1014
+ if (!currentRecord) throw new Error(`plan contract not found: ${planId}`);
1015
+ if (requestedSuccessor) {
1016
+ const successorRecord = readControlPlaneRecordWithinTransaction<PlanContract>(database, 'plan_contract', scope, requestedSuccessor);
1017
+ if (!successorRecord) throw new Error(`PLAN_SUCCESSOR_NOT_FOUND:${requestedSuccessor}`);
1018
+ if (!isCurrentPlanContract(successorRecord.value)) throw new Error(`PLAN_SUCCESSOR_NOT_CURRENT:${requestedSuccessor}`);
1019
+ }
1020
+ const at = nowIso(options);
1021
+ const { previous, next } = applyRevision(currentRecord.value, at);
1022
+ const semanticRevisionKey = `${planId}:r${previous.revision}`;
1023
+ if (!readControlPlaneRecordWithinTransaction<PlanSemanticRevisionRecord>(database, 'plan_semantic_revision', requirePlanContractStoreScopeKey(options), semanticRevisionKey)) {
1024
+ writeControlPlaneRecordWithinTransaction(database, {
1025
+ namespace: 'plan_semantic_revision', scope, key: semanticRevisionKey, schemaVersion: 1,
1026
+ value: { schemaVersion: 1, ...previous, recordedAt: at },
1027
+ action: 'plan_semantic_revision_archived', expectedRevision: null,
1028
+ });
1029
+ }
1030
+ return writeControlPlaneRecordWithinTransaction(database, {
1031
+ namespace: 'plan_contract', scope, key: planId, schemaVersion: 1,
1032
+ value: next, action: 'plan_semantic_revised', expectedRevision: currentRecord.revision,
1033
+ }).value;
1034
+ });
1035
+ }
1036
+
1037
+ const store = readPlanContractStore(options);
1038
+ const index = store.contracts.findIndex((contract) => contract.planId === planId);
1039
+ if (index < 0) throw new Error(`plan contract not found: ${planId}`);
1040
+ if (requestedSuccessor) {
1041
+ const successor = store.contracts.find((contract) => contract.planId === requestedSuccessor);
1042
+ if (!successor) throw new Error(`PLAN_SUCCESSOR_NOT_FOUND:${requestedSuccessor}`);
1043
+ if (!isCurrentPlanContract(successor)) throw new Error(`PLAN_SUCCESSOR_NOT_CURRENT:${requestedSuccessor}`);
1044
+ }
1045
+ const at = nowIso(options);
1046
+ const { previous, next } = applyRevision(store.contracts[index]!, at);
1047
+ appendJsonPlanSemanticRevisionRecord(options, { schemaVersion: 1, ...previous, recordedAt: at });
1048
+ const contracts = [...store.contracts];
1049
+ contracts[index] = next;
1050
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
1051
+ return next;
1052
+ });
1053
+ }
1054
+
1055
+ export function isCurrentPlanContract(contract: PlanContract): boolean {
1056
+ return !isTerminalPlanContractStatus(contract.status) && !contract.supersededBy?.trim();
1057
+ }
1058
+
433
1059
  export function listPlanContracts(options: PlanContractStoreOptions & { status?: PlanContractStatus | 'active' | 'all'; limit?: number }): PlanContract[] {
434
1060
  const status = options.status ?? 'active';
435
1061
  const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 50), 100));
436
1062
  return readPlanContractStore(options).contracts
437
- .filter((contract) => status === 'all' || (status === 'active' ? !isTerminalPlanContractStatus(contract.status) : contract.status === status))
1063
+ .filter((contract) => status === 'all' || (status === 'active' ? isCurrentPlanContract(contract) : contract.status === status))
438
1064
  .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
439
1065
  .slice(0, limit);
440
1066
  }
@@ -442,6 +1068,7 @@ export function listPlanContracts(options: PlanContractStoreOptions & { status?:
442
1068
  export function summarizePlanContract(plan: PlanContract): PlanContractSummary {
443
1069
  return {
444
1070
  planId: plan.planId,
1071
+ revision: currentPlanRevision(plan),
445
1072
  repoId: plan.repoId,
446
1073
  requirementId: plan.requirementId,
447
1074
  scopeKey: plan.scopeKey,
@@ -453,204 +1080,3 @@ export function summarizePlanContract(plan: PlanContract): PlanContractSummary {
453
1080
  updatedAt: plan.updatedAt,
454
1081
  };
455
1082
  }
456
-
457
- function approvalErrors(plan: PlanContract, allPlans: readonly PlanContract[]): string[] {
458
- const errors = draftContentErrors(plan).map((error) => `${error} before approval`);
459
- const scopeIsCommitted = (status: PlanContractStatus): boolean => [
460
- 'approved',
461
- 'executing',
462
- 'replanning',
463
- 'verifying',
464
- 'ready_to_finalize',
465
- ].includes(status);
466
- if (allPlans.some((existing) => existing.planId !== plan.planId && existing.scopeKey === plan.scopeKey && scopeIsCommitted(existing.status))) {
467
- errors.push(`active plan already owns scope_key ${plan.scopeKey}`);
468
- }
469
- return [...new Set(errors)];
470
- }
471
-
472
- function approvePlanContractUnlocked(options: PlanContractStoreOptions, planId: string): PlanContract {
473
- const store = readPlanContractStore(options);
474
- const index = store.contracts.findIndex((contract) => contract.planId === sanitizeFileComponent(planId));
475
- if (index < 0) throw new Error(`plan contract not found: ${sanitizeFileComponent(planId)}`);
476
- const current = store.contracts[index];
477
- assertRequirementReference(options, current.requirementId);
478
- if (current.status !== 'draft' && current.status !== 'reviewing') throw new Error(`plan contract ${current.planId} cannot be approved from ${current.status}`);
479
- const errors = approvalErrors(current, store.contracts);
480
- if (errors.length > 0) throw new Error(`plan contract ${current.planId} cannot be approved: ${errors.join('; ')}`);
481
- const at = nowIso(options);
482
- const next = { ...current, status: 'approved' as const, updatedAt: at };
483
- const contracts = [...store.contracts];
484
- contracts[index] = next;
485
- writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
486
- return next;
487
- }
488
-
489
- export function approvePlanContract(options: PlanContractStoreOptions, planId: string): PlanContract {
490
- return withPlanAdmissionLock(options, () => approvePlanContractUnlocked(options, planId));
491
- }
492
-
493
- export async function approvePlanContractAsync(options: PlanContractStoreOptions, planId: string): Promise<PlanContract> {
494
- return await withPlanAdmissionLockAsync(options, () => approvePlanContractUnlocked(options, planId));
495
- }
496
-
497
- export function supersedePlanContract(options: PlanContractStoreOptions, planId: string, supersededBy: string): PlanContract {
498
- return withPlanAdmissionLock(options, () => {
499
- const store = readPlanContractStore(options);
500
- const index = store.contracts.findIndex((contract) => contract.planId === sanitizeFileComponent(planId));
501
- if (index < 0) throw new Error(`plan contract not found: ${sanitizeFileComponent(planId)}`);
502
- const current = store.contracts[index]!;
503
- if (isTerminalPlanContractStatus(current.status)) throw new Error(`plan contract ${current.planId} is terminal (${current.status})`);
504
- const replacement = sanitizeFileComponent(supersededBy);
505
- if (!replacement || replacement === 'unknown') throw new Error('superseded_by is required');
506
- if (replacement === current.planId) throw new Error('PLAN_SUCCESSOR_ID_MUST_CHANGE');
507
- const successor = store.contracts.find((contract) => contract.planId === replacement);
508
- if (!successor) throw new Error(`PLAN_SUCCESSOR_NOT_FOUND: ${replacement}`);
509
- const at = nowIso(options);
510
- const next = { ...current, status: 'superseded' as const, supersededBy: successor.planId, updatedAt: at };
511
- const contracts = [...store.contracts];
512
- contracts[index] = next;
513
- writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
514
- return next;
515
- });
516
- }
517
-
518
- /**
519
- * Atomically reserve one eligible step for one WorkContract. This is the
520
- * Plan-to-Work concurrency boundary: competing callers cannot bind the same
521
- * step, and a stale source revision causes no WorkContract side effect.
522
- */
523
- export function claimPlanStepForWork(
524
- options: PlanContractStoreOptions,
525
- input: { planId: string; stepId: string; workId: string; sourceRevision: string },
526
- ): PlanContract {
527
- return updatePlanContract(options, input.planId, (current) => {
528
- assertRequirementReference(options, current.requirementId);
529
- if (current.status !== 'approved' && current.status !== 'executing') {
530
- throw new Error(`PLAN_NOT_EXECUTABLE: ${current.planId} is ${current.status}`);
531
- }
532
- if (current.sourceRevision !== input.sourceRevision) {
533
- return { ...current, status: 'invalidated_by_drift', updatedAt: nowIso(options) };
534
- }
535
- const stepIndex = current.steps.findIndex((step) => step.id === sanitizeFileComponent(input.stepId));
536
- if (stepIndex < 0) throw new Error(`PLAN_STEP_NOT_FOUND: ${input.stepId}`);
537
- const step = current.steps[stepIndex];
538
- if (step.status === 'executing' || step.workId) throw new Error(`PLAN_STEP_ALREADY_ACTIVE: ${step.id}`);
539
- if (step.status === 'completed') throw new Error(`PLAN_STEP_ALREADY_COMPLETED: ${step.id}`);
540
- const unresolved = step.dependencies.filter((dependency) => current.steps.find((candidate) => candidate.id === dependency)?.status !== 'completed');
541
- if (unresolved.length > 0) throw new Error(`PLAN_STEP_DEPENDENCIES_PENDING: ${unresolved.join(', ')}`);
542
- const at = nowIso(options);
543
- const steps = [...current.steps];
544
- steps[stepIndex] = { ...step, status: 'executing', workId: input.workId };
545
- return { ...current, status: 'executing', steps, updatedAt: at };
546
- });
547
- }
548
-
549
- export function repairDanglingPlanStepWorkBinding(
550
- options: PlanContractStoreOptions,
551
- input: { planId: string; stepId: string; expectedWorkId: string; reason: string },
552
- ): PlanContract {
553
- const expectedWorkId = input.expectedWorkId.trim();
554
- const reason = input.reason.trim();
555
- if (!expectedWorkId) throw new Error('PLAN_STEP_REPAIR_EXPECTED_WORK_REQUIRED');
556
- if (!reason) throw new Error('PLAN_STEP_REPAIR_REASON_REQUIRED');
557
- return updatePlanContract(options, input.planId, (current) => {
558
- if (isTerminalPlanContractStatus(current.status)) throw new Error(`PLAN_STEP_REPAIR_PLAN_TERMINAL: ${current.planId} is ${current.status}`);
559
- const stepIndex = current.steps.findIndex((step) => step.id === sanitizeFileComponent(input.stepId));
560
- if (stepIndex < 0) throw new Error(`PLAN_STEP_NOT_FOUND: ${input.stepId}`);
561
- const step = current.steps[stepIndex];
562
- if (step.workId !== expectedWorkId) {
563
- throw new Error(`PLAN_STEP_REPAIR_BINDING_CHANGED: ${step.id} is bound to ${step.workId ?? 'no Work'}, expected ${expectedWorkId}`);
564
- }
565
- if (step.status !== 'executing') {
566
- throw new Error(`PLAN_STEP_REPAIR_STATUS_INVALID: ${step.id} is ${step.status}`);
567
- }
568
- const at = nowIso(options);
569
- const steps = [...current.steps];
570
- steps[stepIndex] = {
571
- ...step,
572
- status: 'ready',
573
- workId: undefined,
574
- evidenceRefs: [{
575
- title: 'dangling Work binding repaired',
576
- summary: `${expectedWorkId}: ${reason}`.slice(0, 2_000),
577
- detailLevel: 'summary' as const,
578
- }, ...step.evidenceRefs].slice(0, 20),
579
- };
580
- // This is admission-state repair, not semantic replanning: the Plan and its
581
- // acceptance criteria remain authoritative. Return the exact step to ready
582
- // so a later start can atomically admit one replacement for the missing
583
- // record without forcing a second Plan authority.
584
- return { ...current, status: 'executing', steps, updatedAt: at };
585
- });
586
- }
587
-
588
- export function completePlanStepForWork(
589
- options: PlanContractStoreOptions,
590
- input: {
591
- planId: string;
592
- stepId: string;
593
- work: Pick<WorkContract, 'workId' | 'status' | 'phase' | 'evidenceState' | 'completionOutcome' | 'completionReceipt' | 'evidenceRefs'>;
594
- },
595
- ): PlanContract {
596
- return updatePlanContract(options, input.planId, (current) => {
597
- const stepIndex = current.steps.findIndex((step) => step.id === sanitizeFileComponent(input.stepId));
598
- if (stepIndex < 0) throw new Error(`PLAN_STEP_NOT_FOUND: ${input.stepId}`);
599
- const step = current.steps[stepIndex];
600
- if (step.workId !== input.work.workId) throw new Error(`PLAN_STEP_WORK_MISMATCH: ${input.stepId}`);
601
- if (step.status === 'completed') return current;
602
- const delivered = input.work.status === 'completed'
603
- && input.work.phase === 'cleanup'
604
- && input.work.evidenceState === 'valid'
605
- && Boolean(input.work.completionOutcome && input.work.completionOutcome !== 'superseded')
606
- && Boolean(input.work.completionReceipt);
607
- const failed = input.work.status === 'failed' || input.work.status === 'cancelled';
608
- if (!delivered && !failed) throw new Error(`PLAN_STEP_WORK_NOT_TERMINAL: ${input.work.workId}`);
609
- const at = nowIso(options);
610
- const steps = [...current.steps];
611
- steps[stepIndex] = {
612
- ...step,
613
- // Work completion proves machine delivery only. The Plan step remains in
614
- // semantic validation until an explicit Controller acceptance records it.
615
- // Failed/cancelled Work returns the slice to ready for replanning.
616
- status: delivered ? 'validating' : 'ready',
617
- // Successful delivery keeps the exact Work identity through semantic
618
- // validation. Failed/cancelled Work releases the step authority so the
619
- // replanning path cannot retain a ghost binding to a terminal Work.
620
- workId: delivered ? step.workId : undefined,
621
- evidenceRefs: input.work.evidenceRefs.length > 0 ? input.work.evidenceRefs.slice(0, 20) : step.evidenceRefs,
622
- };
623
- return { ...current, status: delivered ? 'verifying' : 'replanning', steps, updatedAt: at };
624
- });
625
- }
626
-
627
- export function acceptPlanStepEvidence(
628
- options: PlanContractStoreOptions,
629
- input: { planId: string; stepId: string; reviewer: string; rationale: string; acceptedSourceRevision?: string },
630
- ): PlanContract {
631
- const reviewer = input.reviewer.trim();
632
- const rationale = input.rationale.trim();
633
- if (!reviewer || !rationale) throw new Error('PLAN_STEP_SEMANTIC_ACCEPTANCE_METADATA_REQUIRED');
634
- return updatePlanContract(options, input.planId, (current) => {
635
- const stepIndex = current.steps.findIndex((step) => step.id === sanitizeFileComponent(input.stepId));
636
- if (stepIndex < 0) throw new Error(`PLAN_STEP_NOT_FOUND: ${input.stepId}`);
637
- const step = current.steps[stepIndex];
638
- if (step.status === 'completed') return current;
639
- if (step.status !== 'validating') throw new Error(`PLAN_STEP_NOT_READY_FOR_SEMANTIC_ACCEPTANCE: ${step.id} is ${step.status}`);
640
- const steps = [...current.steps];
641
- steps[stepIndex] = {
642
- ...step,
643
- status: 'completed',
644
- evidenceRefs: [{ title: 'semantic acceptance', summary: `${reviewer}: ${rationale}`, detailLevel: 'summary' as const }, ...step.evidenceRefs].slice(0, 20),
645
- };
646
- const allCompleted = steps.every((candidate) => candidate.status === 'completed');
647
- const acceptedSourceRevision = input.acceptedSourceRevision?.trim();
648
- return {
649
- ...current,
650
- ...(acceptedSourceRevision ? { sourceRevision: acceptedSourceRevision } : {}),
651
- status: allCompleted ? 'finalized' : 'executing',
652
- steps,
653
- updatedAt: nowIso(options),
654
- };
655
- });
656
- }