@moretea-labs/forge 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/AGENTS.md +5 -2
  2. package/CHANGELOG.md +20 -0
  3. package/README.en.md +24 -3
  4. package/README.md +10 -6
  5. package/README.zh-CN.md +5 -3
  6. package/THIRD_PARTY_NOTICES.md +1 -2
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
  8. package/adapters/chatgpt/provider-delivery.ts +1 -1
  9. package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
  10. package/adapters/computer/chatgpt-extension-target.ts +295 -50
  11. package/adapters/computer/chrome-extension/background.js +227 -71
  12. package/adapters/computer/chrome-extension/content.js +181 -157
  13. package/adapters/computer/chrome-extension/manifest.json +3 -1
  14. package/adapters/computer/desktop-operator-provider.ts +18 -6
  15. package/adapters/computer/interaction-target-authority.ts +39 -9
  16. package/adapters/mcp/access-mode.ts +18 -39
  17. package/adapters/mcp/auth.ts +2 -6
  18. package/adapters/mcp/instructions.ts +2 -2
  19. package/adapters/mcp/multi-repository.ts +7 -28
  20. package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
  21. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
  22. package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
  23. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
  24. package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
  25. package/adapters/mcp/runtime-gateway/router.ts +3 -5
  26. package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
  27. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
  28. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
  29. package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
  30. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
  31. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
  32. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
  33. package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
  34. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
  35. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
  36. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
  37. package/adapters/mcp/server.ts +24 -3
  38. package/adapters/mcp/setup.ts +4 -10
  39. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  40. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
  41. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
  42. package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
  43. package/adapters/mcp/toolset-names.ts +11 -42
  44. package/adapters/mcp/toolset.ts +16 -90
  45. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  46. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  47. package/adapters/mcp/transports/http-observation.ts +18 -6
  48. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  49. package/adapters/mcp/transports/http.ts +54 -14
  50. package/adapters/mcp/types.ts +0 -6
  51. package/assets/hooks/lib/session-state.sh +0 -38
  52. package/assets/hooks/post-bash.sh +1 -1
  53. package/assets/hooks/post-tool-observer.sh +0 -5
  54. package/assets/hooks/prompt-guard.sh +0 -55
  55. package/assets/partials/04-project-structure.partial.md +1 -1
  56. package/assets/partials/07-footer.partial.md +1 -1
  57. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  58. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  59. package/assets/reference-configs/agentic-development-flow.md +1 -1
  60. package/assets/reference-configs/external-tooling.md +0 -76
  61. package/assets/reference-configs/release-deploy.md +1 -1
  62. package/assets/templates/helpers/architecture-queue.sh +1 -1
  63. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  64. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  65. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  66. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  67. package/assets/workflow-contract.v1.json +0 -2
  68. package/docs/README.md +5 -2
  69. package/docs/architecture/CURRENT.md +114 -30
  70. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  71. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
  72. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
  73. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
  74. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  75. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  76. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  77. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  78. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  79. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  80. package/docs/architecture/domains/public-surface.md +3 -5
  81. package/docs/architecture/domains/verification.md +5 -5
  82. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
  83. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  84. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  85. package/docs/forge-plugin-management.md +14 -2
  86. package/docs/operations/controller-browser-plugin.md +23 -0
  87. package/docs/operations/features.md +6 -7
  88. package/docs/operations/features.zh-CN.md +1 -1
  89. package/docs/operations/github-repository.md +4 -4
  90. package/docs/operations/github-semantic-release-projection.md +20 -0
  91. package/docs/operations/homebrew.md +2 -2
  92. package/docs/operations/mcp-tool-exposure.md +13 -54
  93. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  94. package/docs/operations/standalone-disaster-recovery.md +1 -1
  95. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  96. package/docs/tutorials/03-first-repository-task.md +2 -0
  97. package/docs/wiki/Computer-and-Plugins.md +78 -0
  98. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  99. package/docs/wiki/Home.md +16 -2
  100. package/docs/wiki/Implementation.md +24 -12
  101. package/docs/wiki/Installation.md +28 -32
  102. package/docs/wiki/Integrations.md +4 -0
  103. package/docs/wiki/Quick-Start.md +35 -22
  104. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  105. package/docs/wiki/_Sidebar.md +5 -1
  106. package/package.json +1 -12
  107. package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
  108. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
  109. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  110. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
  111. package/packages/kernel/work/application/work-service.ts +1 -0
  112. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  113. package/packages/kernel/work/domain/state-machine.ts +9 -2
  114. package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
  115. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  116. package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
  117. package/scripts/assemble-template.ts +11 -72
  118. package/scripts/check-mcp-compatibility.ts +55 -74
  119. package/scripts/check-package-identity.mjs +0 -5
  120. package/scripts/check-runtime-architecture.mjs +305 -28
  121. package/scripts/init-project.sh +0 -46
  122. package/scripts/install-ios-hid-toolchain.sh +60 -0
  123. package/scripts/install-standalone-recovery.ts +1 -1
  124. package/scripts/lib/project-init-lib.sh +3 -3
  125. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  126. package/scripts/open-source-audit-allowlist.txt +0 -1
  127. package/scripts/run-governed-gate.ts +2 -0
  128. package/scripts/smoke-ios-physical-device.ts +38 -10
  129. package/scripts/smoke-mcp-http-runtime.ts +7 -8
  130. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  131. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
  132. package/scripts/sync-generated-authority.mjs +0 -1
  133. package/src/cli/agent-jobs/executor-health.ts +1 -1
  134. package/src/cli/agent-jobs/integration.ts +2 -2
  135. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  136. package/src/cli/commands/doctor.ts +1 -143
  137. package/src/cli/commands/global-runtime.ts +3 -196
  138. package/src/cli/commands/init-hook.ts +6 -23
  139. package/src/cli/commands/init.ts +3 -252
  140. package/src/cli/commands/mcp.ts +0 -5
  141. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  142. package/src/cli/commands/recovery.ts +2 -14
  143. package/src/cli/commands/repository.ts +17 -1
  144. package/src/cli/commands/setup-execution.ts +41 -0
  145. package/src/cli/controller/completion-orchestrator.ts +1 -1
  146. package/src/cli/controller/composite-operations.ts +1 -1
  147. package/src/cli/controller/context/instruction-context.ts +1 -1
  148. package/src/cli/controller/context/known-paths.ts +2 -2
  149. package/src/cli/controller/context/query-planning.ts +11 -4
  150. package/src/cli/controller/context/rule-resolution.ts +2 -2
  151. package/src/cli/controller/context/source-materializer.ts +2 -2
  152. package/src/cli/controller/context/structural-provider.ts +84 -0
  153. package/src/cli/controller/context/structural.ts +6 -6
  154. package/src/cli/controller/context/types.ts +8 -9
  155. package/src/cli/controller/context-pack.ts +97 -76
  156. package/src/cli/controller/operational-plan.ts +1 -1
  157. package/src/cli/controller/systemd-user.ts +4 -10
  158. package/src/cli/editing/edit-session.ts +17 -5
  159. package/src/cli/github/github.ts +4 -4
  160. package/src/cli/github/semantic-projection.ts +174 -0
  161. package/src/cli/hook/prompt-intents.ts +0 -9
  162. package/src/cli/index.ts +1 -43
  163. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  164. package/src/cli/local-bridge/console-automations.ts +1 -1
  165. package/src/cli/local-bridge/console-view-models.ts +1 -1
  166. package/src/cli/local-bridge/facade-api.ts +0 -2
  167. package/src/cli/repositories/command-classifier.ts +0 -9
  168. package/src/cli/repositories/command-scope.ts +2 -2
  169. package/src/cli/repositories/locks.ts +20 -0
  170. package/src/cli/repositories/registry.ts +2 -1
  171. package/src/cli/repositories/safe-patch.ts +2 -12
  172. package/src/cli/repositories/structured-git.ts +39 -3
  173. package/src/cli/repository/inspector.ts +185 -343
  174. package/src/effects/process-runner.ts +17 -1
  175. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  176. package/src/runtime/context/semantic-navigation-contract.ts +7 -0
  177. package/src/runtime/context/semantic-navigation.ts +1 -1
  178. package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
  179. package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
  180. package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
  181. package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
  182. package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
  183. package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
  184. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  185. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
  186. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
  187. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
  188. package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
  189. package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
  190. package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
  191. package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
  192. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
  193. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  194. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
  195. package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
  196. package/src/runtime/evidence/sensitive-output.ts +22 -2
  197. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  198. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  199. package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
  200. package/src/runtime/execution/process-runtime/runtime.ts +20 -16
  201. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  202. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  203. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  204. package/src/runtime/execution/workers/executor.ts +1 -2
  205. package/src/runtime/maintenance/cleanup.ts +41 -5
  206. package/src/runtime/plugins/action-application.ts +12 -1
  207. package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
  208. package/src/runtime/plugins/browser-adapter.ts +381 -313
  209. package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
  210. package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
  211. package/src/runtime/plugins/browser-registration.ts +2 -0
  212. package/src/runtime/plugins/browser-runtime.ts +26 -35
  213. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
  214. package/src/runtime/plugins/computer-registration.ts +76 -9
  215. package/src/runtime/plugins/github-adapter.ts +30 -3
  216. package/src/runtime/plugins/gmail-adapter.ts +166 -1
  217. package/src/runtime/plugins/google-shared.ts +10 -5
  218. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  219. package/src/runtime/plugins/ios-adapter.ts +45 -40
  220. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  221. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  222. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  223. package/src/runtime/plugins/local-system-adapter.ts +1 -99
  224. package/src/runtime/plugins/store.ts +80 -7
  225. package/src/runtime/plugins/types.ts +4 -0
  226. package/src/runtime/projections/git-status-sampler.ts +2 -19
  227. package/src/runtime/recovery/actions.ts +1 -1
  228. package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
  229. package/src/runtime/recovery/probe.ts +2 -2
  230. package/src/runtime/release/release-coordinator.ts +20 -4
  231. package/src/runtime/root/controller-progression-composition.ts +67 -5
  232. package/src/runtime/root/package-connector-service.ts +123 -11
  233. package/src/runtime/root/package-runtime-release.ts +6 -18
  234. package/src/runtime/root/package-runtime-service.ts +7 -13
  235. package/src/runtime/root/release-execution-canary.ts +18 -5
  236. package/src/runtime/root/release-manifest.ts +0 -34
  237. package/src/runtime/root/release-materialize.ts +0 -78
  238. package/src/runtime/root/runtime.ts +1 -2
  239. package/src/runtime/root/scheduled-controller-composition.ts +2 -1
  240. package/src/runtime/root/service.ts +90 -11
  241. package/src/runtime/root/types.ts +0 -6
  242. package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
  243. package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
  244. package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
  245. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  246. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  247. package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
  248. package/src/runtime/shared/atomic-text-file.ts +21 -0
  249. package/src/runtime/shared/json-files.ts +8 -3
  250. package/src/runtime/standalone-recovery/core.ts +189 -174
  251. package/src/runtime/standalone-recovery/entry.ts +62 -30
  252. package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
  253. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
  254. package/src/runtime/workflow/schedules/engine.ts +36 -14
  255. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  256. package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
  257. package/supervisor/client.ts +31 -14
  258. package/supervisor/control-plane.ts +210 -115
  259. package/supervisor/forge-validators.ts +88 -21
  260. package/supervisor/native-browser-adapter.ts +393 -28
  261. package/supervisor/native-messaging/host.ts +21 -2
  262. package/supervisor/protocol.ts +74 -26
  263. package/supervisor/server.ts +16 -8
  264. package/supervisor/store.ts +476 -57
  265. package/supervisor/types.ts +19 -13
  266. package/SKILL.md +0 -427
  267. package/adapters/computer/chatgpt-target-router.ts +0 -103
  268. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
  269. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  270. package/adapters/mcp/toolset-selection.ts +0 -41
  271. package/agents/openai.yaml +0 -4
  272. package/assets/skill-hooks.json +0 -41
  273. package/assets/skill-version.json +0 -189
  274. package/assets/skills/claude-review/SKILL.md +0 -231
  275. package/assets/skills/codex-review/SKILL.md +0 -103
  276. package/assets/templates/helpers/check-skill-version.ts +0 -193
  277. package/scripts/check-skill-version.ts +0 -5
  278. package/scripts/ensure-codegraph.sh +0 -16
  279. package/scripts/run-skill-hook.ts +0 -244
  280. package/scripts/sync-codex-installed-copies.sh +0 -182
  281. package/src/cli/commands/tools.ts +0 -167
  282. package/src/cli/mcp/access-mode.ts +0 -2
  283. package/src/cli/mcp/access-tools.ts +0 -2
  284. package/src/cli/mcp/audit.ts +0 -2
  285. package/src/cli/mcp/auth.ts +0 -2
  286. package/src/cli/mcp/instructions.ts +0 -2
  287. package/src/cli/mcp/legacy-context.ts +0 -2
  288. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  289. package/src/cli/mcp/multi-repository.ts +0 -2
  290. package/src/cli/mcp/oauth.ts +0 -2
  291. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  292. package/src/cli/mcp/paths.ts +0 -2
  293. package/src/cli/mcp/policy.ts +0 -2
  294. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  295. package/src/cli/mcp/proxy-env.ts +0 -2
  296. package/src/cli/mcp/readiness-projection.ts +0 -2
  297. package/src/cli/mcp/redaction.ts +0 -2
  298. package/src/cli/mcp/repo.ts +0 -2
  299. package/src/cli/mcp/repository-tools.ts +0 -2
  300. package/src/cli/mcp/server.ts +0 -2
  301. package/src/cli/mcp/setup.ts +0 -2
  302. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  303. package/src/cli/mcp/tool-contract.ts +0 -2
  304. package/src/cli/mcp/tools.ts +0 -2
  305. package/src/cli/mcp/toolset-names.ts +0 -2
  306. package/src/cli/mcp/toolset-selection.ts +0 -2
  307. package/src/cli/mcp/toolset.ts +0 -2
  308. package/src/cli/mcp/transports/http.ts +0 -2
  309. package/src/cli/mcp/transports/session-registry.ts +0 -2
  310. package/src/cli/mcp/transports/stdio.ts +0 -2
  311. package/src/cli/mcp/types.ts +0 -2
  312. package/src/cli/mcp/umbrella-tools.ts +0 -2
  313. package/src/cli/tools/codegraph.ts +0 -695
  314. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  315. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  316. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  317. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  318. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  319. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  320. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  321. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  322. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  323. package/src/runtime/gateway/mcp/router.ts +0 -2
  324. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  325. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  326. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  327. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
  328. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
@@ -25,6 +25,8 @@ export interface SemanticNavigationResult {
25
25
  target: { path: string; line: number; column: number };
26
26
  locations: SemanticNavigationLocation[];
27
27
  policyDeniedReads?: number;
28
+ /** Denied requested/material reads, excluding incidental dependency probes. */
29
+ materialPolicyDeniedReads?: number;
28
30
  details?: Record<string, unknown>;
29
31
  }
30
32
 
@@ -68,3 +70,8 @@ export interface IndexedSemanticNavigationOutcome {
68
70
  index: number;
69
71
  outcome: SemanticNavigationOutcome;
70
72
  }
73
+
74
+ /** Dependency probes rejected by policy cannot certify missing requested source relationships. */
75
+ export function isIncidentalSemanticDependencyProbe(path: string): boolean {
76
+ return /(?:^|\/)node_modules\/[^/]+/.test(path.replace(/\\/g, '/'));
77
+ }
@@ -45,7 +45,7 @@ const swiftProvider: SemanticNavigationProvider = {
45
45
  if (request.tsconfigPath) {
46
46
  invalid.set(index, { ok: false, code: 'SWIFT_SEMANTIC_TSCONFIG_UNSUPPORTED', message: 'tsconfig_path applies only to TypeScript semantic navigation.' });
47
47
  } else if (access.profile !== 'controller') {
48
- invalid.set(index, { ok: false, code: 'SWIFT_SEMANTIC_READ_SCOPE_UNSUPPORTED', message: 'External SourceKit-LSP navigation is restricted to the controller read profile; use lexical/CodeGraph evidence under narrower read policies.' });
48
+ invalid.set(index, { ok: false, code: 'SWIFT_SEMANTIC_READ_SCOPE_UNSUPPORTED', message: 'External SourceKit-LSP navigation is restricted to the controller read profile; use bounded lexical/raw source evidence under narrower read policies.' });
49
49
  } else {
50
50
  eligible.push({ index, request });
51
51
  }
@@ -107,14 +107,14 @@ function treeEntry(root: string, ref: string, path: string): string | undefined
107
107
 
108
108
  export function proveWorkPreservationContained(
109
109
  repositoryRoot: string,
110
- handle: Pick<WorkHandleState, 'baseCommit' | 'deliveryBaseCommit' | 'expectedHead' | 'cleanupReceipt'>,
110
+ handle: Pick<WorkHandleState, 'baseCommit' | 'deliveryBaseCommit'>,
111
111
  targetBranch: string,
112
+ protectedCandidate: string | undefined,
112
113
  ): WorkPreservationContainmentProof {
113
114
  const base = workDeliveryBaseRevision(handle);
114
115
  if (!base || !revision(repositoryRoot, base)) {
115
116
  return { contained: false, reason: 'base_revision_unavailable', comparedPaths: [] };
116
117
  }
117
- const protectedCandidate = handle.cleanupReceipt?.preservation.checkpointCommit?.trim() || handle.expectedHead?.trim();
118
118
  if (!protectedCandidate) {
119
119
  return { contained: false, reason: 'protected_revision_unavailable', comparedPaths: [] };
120
120
  }
@@ -336,7 +336,7 @@ export function cleanupWorkPreservationArtifacts(
336
336
  skip(report, 'retention_grace');
337
337
  continue;
338
338
  }
339
- const proof = proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch);
339
+ const proof = proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch, bundleProtectedRevision(repository.canonicalRoot, bundlePath));
340
340
  if (!proof.contained) {
341
341
  report.retained += 1;
342
342
  skip(report, `containment_${proof.reason}`);
@@ -468,7 +468,7 @@ export function cleanupWorkPreservationArtifacts(
468
468
 
469
469
  const receipt = handle?.cleanupReceipt;
470
470
  const handleProof = handle && receipt?.complete
471
- ? proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch)
471
+ ? proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch, bundleProtectedRevision(repository.canonicalRoot, bundlePath))
472
472
  : undefined;
473
473
  const orphanProof = !handle && repository.defaultBranch
474
474
  ? proveOrphanBundleContained(repository.canonicalRoot, bundlePath, repository.defaultBranch)
@@ -1,10 +1,12 @@
1
- import { createHash } from 'crypto';
1
+ import { createHash, randomUUID } from 'crypto';
2
2
  import { spawnSync } from 'child_process';
3
3
  import type { McpExecutionContext } from '../../../../packages/protocols/mcp/execution-context';
4
4
  import {
5
5
  getWorkContract,
6
6
  isRepositoryCompletionReceipt,
7
+ reviseWorkSemanticContext,
7
8
  semanticWorkState,
9
+ workSemanticView,
8
10
  type CompletionMaintenanceWarning,
9
11
  type RepositoryCompletionReceipt,
10
12
  } from '../../../../packages/kernel/work/api/index';
@@ -15,6 +17,8 @@ import {
15
17
  } from '../../../cli/repositories/registry';
16
18
  import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
17
19
  import { listActiveLeases } from '../../resources/leases/store';
20
+ import { withCheckoutMutationGate } from '../../execution/thin-harness/mutation-gate';
21
+ import { runBoundedGit } from '../../execution/thin-harness/async-process';
18
22
  import { assertRuntimeMayWriteOrThrow, isRuntimeWriteFenceError } from '../../root/write-fence';
19
23
  import { cleanupTerminalWork } from './work-terminal-cleanup';
20
24
  import { recordWorkDeliveryReceipt } from './work-completion-authority';
@@ -141,9 +145,11 @@ function assertTargetMutationAvailable(
141
145
  targetBranch: string,
142
146
  expectedTargetHead: string,
143
147
  targetRoot: string,
148
+ ownedLeaseJobId?: string,
144
149
  ): void {
145
150
  const blockingLease = listActiveLeases(controllerHome, handle.repositoryId)
146
- .find((lease) => lease.checkoutId === targetCheckoutId && lease.workId !== handle.workId);
151
+ .find((lease) => lease.checkoutId === targetCheckoutId
152
+ && lease.ownerJobId !== ownedLeaseJobId && lease.workId !== handle.workId);
147
153
  if (blockingLease) {
148
154
  throw new Error(`WORK_DELIVERY_TARGET_LEASE_ACTIVE: ${blockingLease.leaseId}`);
149
155
  }
@@ -239,6 +245,33 @@ function cleanupEvidence(receipt: WorkCleanupReceipt, recordedAt: string): Repos
239
245
  return { status: 'maintenance_warning', warnings, blockers: [], recordedAt };
240
246
  }
241
247
 
248
+ /** Explicit model-selected semantic completion; physical Git delivery alone never closes Work. */
249
+ function semanticCompletionAfterDelivery(
250
+ ctx: McpExecutionContext,
251
+ handle: WorkHandleState,
252
+ receipt: RepositoryCompletionReceipt,
253
+ completeWork: boolean,
254
+ expectedSemanticRevision: number,
255
+ ): Record<string, unknown> {
256
+ if (!completeWork) return { semanticCompletionRequired: true };
257
+ if (receipt.cleanup.status !== 'complete' || receipt.cleanup.blockers.length > 0) {
258
+ // Git can already be integrated. Leave the semantic Work open for recovery.
259
+ return { semanticCompletionRequired: true, semanticCompletionBlocked: 'WORK_DELIVERY_CLEANUP_INCOMPLETE' };
260
+ }
261
+ const store = { controllerHome: ctx.controllerHome, repoId: handle.repositoryId };
262
+ const workId = handle.workContractId ?? handle.workId;
263
+ const current = getWorkContract(store, workId);
264
+ if (!current || semanticWorkState(current) !== 'open') {
265
+ throw new Error(`WORK_DELIVERY_SEMANTIC_WORK_NOT_OPEN: ${current ? semanticWorkState(current) : 'missing'}`);
266
+ }
267
+ const completed = reviseWorkSemanticContext(store, workId, {
268
+ // Do not close a Work whose objective changed while Git delivery was running.
269
+ expectedRevision: expectedSemanticRevision,
270
+ state: 'completed',
271
+ });
272
+ return { semanticCompletionRequired: false, semanticWork: workSemanticView(completed) };
273
+ }
274
+
242
275
  function recordDelivery(
243
276
  ctx: McpExecutionContext,
244
277
  handle: WorkHandleState,
@@ -249,7 +282,8 @@ function recordDelivery(
249
282
  strategy: RepositoryCompletionReceipt['delivery']['strategy'],
250
283
  cleanupReceipt: WorkCleanupReceipt,
251
284
  ): RepositoryCompletionReceipt {
252
- const baseRevision = handle.deliveryBaseCommit ?? handle.baseCommit;
285
+ // Receipt scope is the immutable Work origin, not a movable integration baseline.
286
+ const baseRevision = handle.baseCommit;
253
287
  const paths = baseRevision ? changedPaths(targetRoot, baseRevision, sourceRevision) : [];
254
288
  const recordedAt = new Date().toISOString();
255
289
  const receipt: RepositoryCompletionReceipt = {
@@ -292,6 +326,8 @@ async function reconcileDeliveredWork(
292
326
  targetBranch: string,
293
327
  targetRoot: string,
294
328
  sourceRevision: string,
329
+ completeWork: boolean,
330
+ expectedSemanticRevision: number,
295
331
  ): Promise<Record<string, unknown>> {
296
332
  const targetRevision = exactRevision(targetRoot, targetBranch, 'TARGET_HEAD');
297
333
  if (!git(targetRoot, ['merge-base', '--is-ancestor', sourceRevision, targetRevision]).ok) {
@@ -328,7 +364,7 @@ async function reconcileDeliveredWork(
328
364
  delivery: receipt,
329
365
  cleanup: cleanup.receipt,
330
366
  recoveredAfterDelivery: true,
331
- semanticCompletionRequired: true,
367
+ ...semanticCompletionAfterDelivery(ctx, handle, receipt, completeWork, expectedSemanticRevision),
332
368
  });
333
369
  }
334
370
 
@@ -347,8 +383,12 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
347
383
  handle.workContractId ?? handle.workId,
348
384
  );
349
385
  if (!contract) throw new Error(`WORK_CONTRACT_MISSING: ${handle.workContractId ?? handle.workId}`);
350
- if (semanticWorkState(contract) !== 'open') {
351
- throw new Error(`WORK_DELIVERY_SEMANTIC_WORK_NOT_OPEN: ${semanticWorkState(contract)}`);
386
+ const completeWork = args.complete_work === true;
387
+ const allowMergeCommit = args.allow_merge_commit === true;
388
+ const semanticState = semanticWorkState(contract);
389
+ const expectedSemanticRevision = workSemanticView(contract).revision;
390
+ if (semanticState !== 'open' && !(completeWork && semanticState === 'completed')) {
391
+ throw new Error(`WORK_DELIVERY_SEMANTIC_WORK_NOT_OPEN: ${semanticState}`);
352
392
  }
353
393
  if (contract.workKind !== 'repository_change') {
354
394
  throw new Error(`WORK_DELIVERY_WORK_KIND_INVALID: ${contract.workKind}`);
@@ -360,6 +400,22 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
360
400
  const targetRoot = target.canonicalRoot;
361
401
  const targetHead = exactRevision(targetRoot, targetStatus.head ?? 'HEAD', 'TARGET_HEAD');
362
402
 
403
+ // Repeated explicit delivery after a successful semantic close is read-only.
404
+ // Never rewrite a terminal Work receipt or reopen its managed worktree.
405
+ if (semanticState === 'completed') {
406
+ const receipt = contract.completionReceipt;
407
+ if (!receipt || !isRepositoryCompletionReceipt(receipt) || !receipt.sourceRevision
408
+ || receipt.delivery.status !== 'integrated' || receipt.cleanup.status !== 'complete'
409
+ || receipt.targetBranch !== targetBranch || receipt.sourceRevision !== handle.expectedHead
410
+ || !git(targetRoot, ['merge-base', '--is-ancestor', receipt.sourceRevision, targetHead]).ok) {
411
+ throw new Error('WORK_DELIVERY_TERMINAL_RECEIPT_UNPROVEN');
412
+ }
413
+ return makeBoundedWorkResult(ctx, session, handle.repositoryId, handle.workId, 'finalization', {
414
+ work: compactHandle(handle), delivery: receipt, cleanup: handle.cleanupReceipt,
415
+ recoveredAfterDelivery: true, semanticCompletionRequired: false, semanticWork: workSemanticView(contract),
416
+ });
417
+ }
418
+
363
419
  if (
364
420
  (handle.state === 'merged' || handle.state === 'failed_terminal_cleanup' || handle.state === 'cleaned')
365
421
  && handle.expectedHead
@@ -371,6 +427,8 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
371
427
  targetBranch,
372
428
  targetRoot,
373
429
  exactRevision(targetRoot, handle.expectedHead, 'SOURCE_REVISION'),
430
+ completeWork,
431
+ expectedSemanticRevision,
374
432
  );
375
433
  }
376
434
 
@@ -416,33 +474,94 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
416
474
  if (git(targetRoot, ['merge-base', '--is-ancestor', sourceRevision, targetHead]).ok) {
417
475
  strategy = 'already_integrated';
418
476
  } else {
419
- if (!git(targetRoot, ['merge-base', '--is-ancestor', targetHead, sourceRevision]).ok) {
477
+ const targetIsAncestor = git(targetRoot, ['merge-base', '--is-ancestor', targetHead, sourceRevision]).ok;
478
+ if (!targetIsAncestor && !allowMergeCommit) {
420
479
  throw new Error(`WORK_DELIVERY_NON_FAST_FORWARD: target ${targetHead} and source ${sourceRevision} diverged`);
421
480
  }
422
- assertTargetMutationAvailable(
423
- ctx.controllerHome,
424
- handle,
425
- target.activeCheckoutId,
426
- targetBranch,
427
- targetHead,
428
- targetRoot,
429
- );
430
- if (handle.state !== 'committed') {
431
- handle = transitionWorkHandle(ctx.controllerHome, handle, 'committed', {
432
- expectedHead: sourceRevision,
433
- failureReason: undefined,
434
- finalization: { ...handle.finalization, commit: 'done', lastError: undefined },
435
- });
436
- }
437
- const merged = git(targetRoot, ['merge', '--ff-only', sourceRevision], 60_000);
438
- if (!merged.ok) {
439
- throw new Error(`WORK_DELIVERY_FAST_FORWARD_FAILED: ${merged.stderr || merged.stdout || 'git merge failed'}`);
440
- }
441
- integratedTargetHead = exactRevision(targetRoot, 'HEAD', 'TARGET_HEAD');
442
- if (integratedTargetHead !== sourceRevision) {
443
- throw new Error(`WORK_DELIVERY_TARGET_HEAD_UNEXPECTED: expected ${sourceRevision}, found ${integratedTargetHead}`);
481
+ // Only the actual Git write owns the shared target. An unrelated source Work
482
+ // never holds this lease throughout planning, review or build validation.
483
+ const gated = await withCheckoutMutationGate({
484
+ controllerHome: ctx.controllerHome,
485
+ repoId: handle.repositoryId,
486
+ checkoutId: target.activeCheckoutId,
487
+ repoRoot: targetRoot,
488
+ owner: `deliver-work:${handle.workId}:${randomUUID()}`,
489
+ ttlMs: 120_000,
490
+ ownership: { mutatesGitIndex: true, mutatesGitRefs: true },
491
+ ownerIdentity: {
492
+ repositoryId: handle.repositoryId,
493
+ checkoutId: target.activeCheckoutId,
494
+ worktreeId: targetRoot,
495
+ branch: targetBranch,
496
+ principalId: session.principalId,
497
+ },
498
+ }, async (gate, ownership) => {
499
+ assertTargetMutationAvailable(ctx.controllerHome, handle, target.activeCheckoutId,
500
+ targetBranch, targetHead, targetRoot, gate.ownerJobId);
501
+ ownership.assert();
502
+ const preview = !targetIsAncestor
503
+ ? await runBoundedGit(targetRoot, ['merge-tree', '--write-tree', targetHead, sourceRevision], {
504
+ timeoutMs: 30_000, maxOutputBytes: 128 * 1024, signal: ownership.signal,
505
+ })
506
+ : undefined;
507
+ if (preview && !preview.ok) {
508
+ throw new Error(`WORK_DELIVERY_MERGE_CONFLICT: ${preview.stderr || preview.stdout || 'merge-tree failed'}`);
509
+ }
510
+ if (handle.state !== 'committed') {
511
+ handle = transitionWorkHandle(ctx.controllerHome, handle, 'committed', {
512
+ expectedHead: sourceRevision,
513
+ failureReason: undefined,
514
+ finalization: { ...handle.finalization, commit: 'done', lastError: undefined },
515
+ });
516
+ }
517
+ ownership.assert();
518
+ const merged = await runBoundedGit(targetRoot,
519
+ targetIsAncestor
520
+ ? ['merge', '--ff-only', sourceRevision]
521
+ : ['merge', '--no-ff', '--no-edit', sourceRevision],
522
+ { timeoutMs: 60_000, maxOutputBytes: 128 * 1024, signal: ownership.signal });
523
+ if (!merged.ok) {
524
+ if (ownership.ownershipLost()) throw new Error('WORK_DELIVERY_MUTATION_OWNERSHIP_LOST');
525
+ // A conflicted merge must never strand the shared integration checkout.
526
+ // Abort only our own in-progress merge while retaining the same lease.
527
+ const mergeHead = git(targetRoot, ['rev-parse', '--verify', 'MERGE_HEAD']);
528
+ if (mergeHead.ok) {
529
+ const aborted = await runBoundedGit(targetRoot, ['merge', '--abort'], {
530
+ timeoutMs: 30_000, maxOutputBytes: 128 * 1024, signal: ownership.signal,
531
+ });
532
+ if (!aborted.ok) {
533
+ throw new Error(`WORK_DELIVERY_MERGE_ABORT_FAILED: ${aborted.stderr || aborted.stdout}; merge error: ${merged.stderr || merged.stdout}`);
534
+ }
535
+ }
536
+ throw new Error(`WORK_DELIVERY_GIT_MERGE_FAILED: ${merged.stderr || merged.stdout || 'git merge failed'}`);
537
+ }
538
+ ownership.assert();
539
+ const afterMergeStatus = git(targetRoot, ['status', '--porcelain=v1']);
540
+ if (!afterMergeStatus.ok || afterMergeStatus.stdout) {
541
+ throw new Error('WORK_DELIVERY_TARGET_DIRTY_AFTER_MERGE');
542
+ }
543
+ const newHead = exactRevision(targetRoot, 'HEAD', 'TARGET_HEAD');
544
+ if (targetIsAncestor && newHead !== sourceRevision) {
545
+ throw new Error(`WORK_DELIVERY_TARGET_HEAD_UNEXPECTED: expected ${sourceRevision}, found ${newHead}`);
546
+ }
547
+ if (!targetIsAncestor) {
548
+ const ancestry = git(targetRoot, ['rev-list', '--parents', '-n', '1', newHead]);
549
+ const [, firstParent, secondParent, ...extraParents] = ancestry.stdout.split(/\s+/);
550
+ if (!ancestry.ok || firstParent !== targetHead || secondParent !== sourceRevision || extraParents.length > 0) {
551
+ throw new Error('WORK_DELIVERY_MERGE_PARENT_MISMATCH');
552
+ }
553
+ const actualTree = git(targetRoot, ['rev-parse', `${newHead}^{tree}`]);
554
+ if (!actualTree.ok || actualTree.stdout !== preview!.stdout.split(/\s+/)[0]) {
555
+ throw new Error('WORK_DELIVERY_MERGE_TREE_MISMATCH');
556
+ }
557
+ }
558
+ return { newHead, mergeStrategy: (targetIsAncestor ? 'work_fast_forward' : 'work_merge_commit') as RepositoryCompletionReceipt['delivery']['strategy'] };
559
+ });
560
+ if (!gated.ok) {
561
+ throw new Error(`WORK_DELIVERY_TARGET_LEASE_ACTIVE: ${gated.busy.blockers.map((blocker) => blocker.owner).join(', ')}`);
444
562
  }
445
- strategy = 'work_fast_forward';
563
+ integratedTargetHead = gated.value.newHead;
564
+ strategy = gated.value.mergeStrategy;
446
565
  }
447
566
 
448
567
  if (!git(targetRoot, ['merge-base', '--is-ancestor', sourceRevision, integratedTargetHead]).ok) {
@@ -492,6 +611,6 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
492
611
  work: compactHandle(refreshed),
493
612
  delivery: receipt,
494
613
  cleanup: cleanup.receipt,
495
- semanticCompletionRequired: true,
614
+ ...semanticCompletionAfterDelivery(ctx, handle, receipt, completeWork, expectedSemanticRevision),
496
615
  });
497
616
  }
@@ -2,7 +2,7 @@ import { resolve } from 'path';
2
2
  import type { RepositoryRecord } from '../../../cli/repositories/types';
3
3
  import { getRepository, resolveRepositorySelection, selectRepositoryCheckout } from '../../../cli/repositories/registry';
4
4
  import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
5
- import { appendWorkEvidence, getWorkContract, recordWorkEvidenceState, semanticWorkState } from '../../../../packages/kernel/work/api/index';
5
+ import { appendWorkEvidence, getWorkContract, promoteWorkToRepositoryChange, recordWorkEvidenceState, semanticWorkState } from '../../../../packages/kernel/work/api/index';
6
6
  import { currentPermissionSnapshotVersion } from './validation';
7
7
  import { listWorkHandles, readWorkHandle, transitionWorkHandle, writeWorkHandle, type WorkHandleState } from './work-handle-store';
8
8
  import { gitIsAncestor } from './work-execution-support';
@@ -371,7 +371,7 @@ export function ensureRepositoryMutationWorkHandle(input: {
371
371
  principalId: string;
372
372
  sessionId?: string;
373
373
  deferEffectPromotion?: boolean;
374
- }): { handle: WorkHandleState; promotedFrom?: 'local_effect' | 'remote_effect' } {
374
+ }): { handle: WorkHandleState; promotedFrom?: 'investigation' | 'local_effect' | 'remote_effect' } {
375
375
  const store = { controllerHome: input.controllerHome, repoId: input.repository.repoId };
376
376
  const contract = getWorkContract(store, input.workId);
377
377
  if (!contract) throw new Error(`WORK_NOT_FOUND: ${input.workId}`);
@@ -421,6 +421,14 @@ export function ensureRepositoryMutationWorkHandle(input: {
421
421
  handle,
422
422
  });
423
423
  assertManagedRepositoryMutationAuthority({ repository: input.repository, handle });
424
- return { handle };
424
+ // This is the first point at which concrete repository mutation has passed
425
+ // the exact WorkHandle checkout, ownership, and managed-worktree fences.
426
+ // WorkContract remains the sole writer for its semantic classification.
427
+ const promotedFrom = contract.workKind === 'investigation'
428
+ || contract.workKind === 'local_effect'
429
+ || contract.workKind === 'remote_effect'
430
+ ? contract.workKind
431
+ : undefined;
432
+ promoteWorkToRepositoryChange(store, input.workId);
433
+ return promotedFrom ? { handle, promotedFrom } : { handle };
425
434
  }
426
-
@@ -4,7 +4,8 @@ import { join } from 'path';
4
4
  import { scopedOperationRoot } from '../../../cli/repositories/controller-home';
5
5
  import { readJsonFile, sanitizeFileComponent } from '../../shared/json-files';
6
6
  import type { ProcessCheckExecutionIdentity } from '../../evidence/process-check-execution';
7
- import { listControlPlaneRecords, mutateControlPlaneRecord, readOrImportControlPlaneRecord, writeControlPlaneRecord } from '../persistence/sqlite-store';
7
+ import { listControlPlaneRecords, listControlPlaneRecordsWithinTransaction, mutateControlPlaneRecord, readControlPlaneRecordWithinTransaction, readOrImportControlPlaneRecord, writeControlPlaneRecord, writeControlPlaneRecordWithinTransaction } from '../persistence/sqlite-store';
8
+ import { updateWorkContractPlacementAtomically } from '../../../../packages/kernel/work/api/index';
8
9
 
9
10
  export const WORK_HANDLE_STATES = [
10
11
  'prepared',
@@ -304,6 +305,9 @@ function updateValidationIndexMembership(
304
305
  validating: boolean,
305
306
  ): void {
306
307
  const existing = readValidationIndex(controllerHome, repositoryId);
308
+ // The index describes pending validation membership, not WorkHandle activity.
309
+ // Other lifecycle writes do not change its membership or need an index CAS.
310
+ if (existing && existing.workIds.includes(workId) === validating) return;
307
311
  if (!existing) {
308
312
  // The WorkHandle write is already durable. Rebuild once from authoritative
309
313
  // handles so legacy repositories establish an exact index without a scan
@@ -376,6 +380,54 @@ export function writeWorkHandle(controllerHome: string, handle: WorkHandleState)
376
380
  return { ...record.value, recordRevision: record.revision };
377
381
  }
378
382
 
383
+ /**
384
+ * Atomically rebind the one existing execution handle and its Kernel Work
385
+ * placement. The application service already proved exact Git ancestry and
386
+ * clean exclusive target; this store owns only the two-record CAS mutation.
387
+ */
388
+ export function relocateWorkHandlePlacementAtomically(
389
+ controllerHome: string,
390
+ prior: WorkHandleState,
391
+ target: { checkoutId: string; root: string; branch: string; head: string },
392
+ ): WorkHandleState {
393
+ const moved: WorkHandleState = {
394
+ ...prior, checkoutId: target.checkoutId, worktreePath: target.root, branch: target.branch,
395
+ expectedHead: target.head, managedWorktree: true,
396
+ validationRun: undefined, validatedInputFingerprint: undefined,
397
+ finalization: { validation: 'pending', commit: 'pending', merge: 'pending', branchCleanup: 'pending', worktreeCleanup: 'pending' },
398
+ };
399
+ updateWorkContractPlacementAtomically({ controllerHome, repoId: prior.repositoryId }, prior.workId, {
400
+ checkoutId: target.checkoutId,
401
+ worktreeRef: target.root,
402
+ executionPlacement: { schemaVersion: 1, repositoryId: prior.repositoryId, checkoutId: target.checkoutId },
403
+ worktreePolicy: { required: true, reason: 'Explicit identity-preserving managed checkout relocation', ref: target.root },
404
+ }, (database, updatedContract) => {
405
+ if (updatedContract.checkoutId !== moved.checkoutId) throw new Error('WORK_RELOCATION_TRANSACTION_CONTRACT_MISMATCH');
406
+ const row = readControlPlaneRecordWithinTransaction<WorkHandleState>(
407
+ database, 'execution_work_handle', prior.repositoryId, prior.workId,
408
+ );
409
+ if (!row || row.revision !== prior.recordRevision || row.value.checkoutId !== prior.checkoutId
410
+ || row.value.expectedHead !== prior.expectedHead) {
411
+ throw new Error('WORK_RELOCATION_HANDLE_CAS_CONFLICT');
412
+ }
413
+ const competing = listControlPlaneRecordsWithinTransaction<WorkHandleState>(database, {
414
+ namespace: 'execution_work_handle', scope: prior.repositoryId, limit: 5000,
415
+ }).some((entry) => entry.value.workId !== prior.workId && entry.value.checkoutId === target.checkoutId
416
+ && entry.value.state !== 'cleaned');
417
+ if (competing) throw new Error('WORK_RELOCATION_TARGET_OWNED');
418
+ const { recordRevision: _revision, ...persisted } = moved;
419
+ writeControlPlaneRecordWithinTransaction(database, {
420
+ namespace: 'execution_work_handle', scope: prior.repositoryId, key: prior.workId,
421
+ schemaVersion: 1, expectedRevision: row.revision,
422
+ value: { ...persisted, updatedAt: new Date().toISOString() },
423
+ action: 'work_handle_checkout_relocated',
424
+ });
425
+ });
426
+ const refreshed = readWorkHandle(controllerHome, prior.repositoryId, prior.workId);
427
+ if (!refreshed || refreshed.checkoutId !== target.checkoutId) throw new Error('WORK_RELOCATION_COMMIT_NOT_VERIFIED');
428
+ return refreshed;
429
+ }
430
+
379
431
  /**
380
432
  * Refresh legacy controller identity fields as provenance only. Repository
381
433
  * mutation authority remains WorkHandle CAS plus concrete checkout/resource fences.
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from 'crypto';
2
2
  import { isAbsolute, relative, resolve } from 'path';
3
3
  import type { McpExecutionContext } from '../../../../packages/protocols/mcp/execution-context';
4
- import { repositoryGitStatus, repositoryGitDiff } from '../../../cli/repositories/structured-git';
4
+ import { repositoryGitStatus, repositoryGitWorkspaceChanges } from '../../../cli/repositories/structured-git';
5
5
  import { classifyRepositoryCommand } from '../../../cli/repositories/command-classifier';
6
6
  import { listControllerChecks, readLatestControllerCheckEvidence } from '../../../cli/controller/check-runner';
7
7
  import { readRepositoryAccessPolicy } from '../governance/access-policy';
@@ -21,7 +21,7 @@ import { projectTerminalCheckVerification } from '../../execution/process-runtim
21
21
  import { claimProcessInvocation, getProcessRecord } from '../../execution/process-runtime/store';
22
22
  import { runPersistedCheckViaProcessRuntime } from '../../execution/process-runtime/persisted-check';
23
23
  import { markWorkValidationCurrentFromReusedEvidence, markWorkValidationPending, projectWorkValidationOutcome } from './work-validation-reconciler';
24
- import { compactHandle, contractFor, gitHead, identityFor, makeBoundedWorkResult, reconcileTerminalCleanup, requireSession, terminalCleanupOutcome, workForSession } from './work-execution-support';
24
+ import { compactHandle, contractFor, identityFor, makeBoundedWorkResult, reconcileTerminalCleanup, requireSession, terminalCleanupOutcome, workForSession } from './work-execution-support';
25
25
 
26
26
  function commandInputs(args: Record<string, unknown>): Array<Record<string, unknown>> {
27
27
  if (Array.isArray(args.commands)) return args.commands.filter((value): value is Record<string, unknown> => Boolean(value && typeof value === 'object' && !Array.isArray(value)));
@@ -173,11 +173,18 @@ export async function executeWork(ctx: McpExecutionContext, args: Record<string,
173
173
  }
174
174
  return ordered;
175
175
  })();
176
- const branch = repositoryGitStatus(cheap.worktreeRepository).branch;
177
- const head = gitHead(cheap.worktreeRepository.canonicalRoot);
176
+ // Reuse the exact post-command Git snapshot for both branch and HEAD.
177
+ // A second rev-parse can observe a different instant during concurrent writes.
178
+ const postCommandStatus = repositoryGitStatus(cheap.worktreeRepository);
179
+ const branch = postCommandStatus.branch;
180
+ const head = postCommandStatus.head ?? undefined;
178
181
  let nextHandle = handle;
179
182
  if (branch !== handle.branch) nextHandle = markWorkHandleFailed(ctx.controllerHome, handle, `command changed the bound branch to ${branch ?? 'detached'}`);
180
- else nextHandle = transitionWorkHandle(ctx.controllerHome, handle, 'editing', { expectedHead: head, failureReason: undefined });
183
+ else if (handle.state !== 'editing' || handle.expectedHead !== head || handle.failureReason !== undefined) {
184
+ // A read-only command on an already-current Work must not consume a CAS
185
+ // revision (or rewrite the validation index) just to reaffirm the same HEAD.
186
+ nextHandle = transitionWorkHandle(ctx.controllerHome, handle, 'editing', { expectedHead: head, failureReason: undefined });
187
+ }
181
188
  const value = {
182
189
  work: compactHandle(nextHandle),
183
190
  commands: executions,
@@ -237,9 +244,9 @@ export async function validateWork(ctx: McpExecutionContext, args: Record<string
237
244
  }
238
245
  const validated = validateWorkHandle(ctx.controllerHome, handle, identityFor(ctx, args), 'full', 'validate');
239
246
  const contract = contractFor(ctx, handle);
240
- const changed = repositoryGitDiff(validated.worktreeRepository, { maxBytes: 64 * 1024 });
241
- const workspaceChangedPaths = Array.isArray(changed.nameOnly) ? changed.nameOnly.map(String) : [];
242
- const validationStatus = repositoryGitStatus(validated.worktreeRepository);
247
+ const workspaceChanges = repositoryGitWorkspaceChanges(validated.worktreeRepository);
248
+ const workspaceChangedPaths = workspaceChanges.changedPaths;
249
+ const validationStatus = workspaceChanges.status;
243
250
  const validationHead = validationStatus.head
244
251
  ?? handle.expectedHead
245
252
  ?? handle.baseCommit
@@ -469,9 +476,12 @@ export async function validateWork(ctx: McpExecutionContext, args: Record<string
469
476
  : acceptedFailure
470
477
  ? String(acceptedFailure.summary ?? 'targeted validation failed')
471
478
  : undefined;
479
+ // A missing/stale Check receipt is retryable validation evidence, not a
480
+ // failed Work execution. Keep the original Work state so repair commands
481
+ // remain authorized; accepted test failures retain the existing failed path.
472
482
  const nextState = !completed
473
483
  ? 'validating'
474
- : passed
484
+ : passed || infrastructureFailure
475
485
  ? validationRun.resumeState
476
486
  : 'failed';
477
487
  const validation = !completed ? 'pending' : passed ? 'done' : 'failed';
@@ -482,7 +492,7 @@ export async function validateWork(ctx: McpExecutionContext, args: Record<string
482
492
  lastError: failureSummary,
483
493
  },
484
494
  validationRun: completed ? undefined : validationRun,
485
- ...(passed ? { validatedInputFingerprint: validationFingerprint } : {}),
495
+ validatedInputFingerprint: passed ? validationFingerprint : undefined,
486
496
  ...(failureSummary ? { failureReason: failureSummary } : { failureReason: undefined }),
487
497
  });
488
498
  if (completed) {