@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.141

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 (493) hide show
  1. package/CHANGELOG.md +740 -0
  2. package/README.md +143 -1
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +376 -0
  6. package/dist/cli/command-metadata.js +197 -0
  7. package/dist/cli/command-option-descriptions.js +191 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/error-output.js +52 -0
  11. package/dist/cli/executor-onboarding.js +76 -0
  12. package/dist/cli/flows.js +29 -0
  13. package/dist/cli/harnesses.js +84 -0
  14. package/dist/cli/help.js +328 -18
  15. package/dist/cli/hook-ingress.js +78 -0
  16. package/dist/cli/input-preparation.js +165 -0
  17. package/dist/cli/memory-flow-admission.js +44 -0
  18. package/dist/cli/memory-init-invocation.js +78 -0
  19. package/dist/cli/memory-init.js +240 -0
  20. package/dist/cli/memory-storage-admission.js +53 -0
  21. package/dist/cli/models.js +39 -0
  22. package/dist/cli/native-hook-ingress.js +80 -0
  23. package/dist/cli/operator-profiles.js +61 -0
  24. package/dist/cli/output-options.js +35 -0
  25. package/dist/cli/response-file.js +71 -0
  26. package/dist/cli/run-cli.js +1689 -545
  27. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  28. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  29. package/dist/cli/runtime-process.js +153 -0
  30. package/dist/cli/setup.js +149 -0
  31. package/dist/cli.js +71 -2
  32. package/dist/domain/stage-catalog.js +1 -1
  33. package/dist/domain/stage-handler-identities.js +20 -0
  34. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  35. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  36. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  37. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  38. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  39. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  40. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  41. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  42. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  43. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  44. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  45. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  46. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  47. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  48. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  49. package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
  50. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  51. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +728 -0
  52. package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
  53. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  54. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
  55. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  56. package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
  57. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +140 -0
  58. package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
  59. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
  60. package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
  61. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +557 -0
  62. package/dist/harness-runtime/lib/dd-opencode.mjs +363 -0
  63. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +751 -0
  64. package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
  65. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  66. package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
  67. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  68. package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
  69. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  70. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  71. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  72. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  73. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  74. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  75. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  76. package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
  77. package/dist/harness-runtime/lib/managed-daemon.mjs +683 -0
  78. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  79. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  80. package/dist/harness-runtime/lib/native-children.d.mts +16 -0
  81. package/dist/harness-runtime/lib/native-children.mjs +113 -0
  82. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  83. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  84. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  85. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  86. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  87. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  88. package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
  89. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  90. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  91. package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
  92. package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
  93. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  94. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  95. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  96. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  97. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  98. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  99. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  100. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  101. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  102. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  103. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  104. package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
  105. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  106. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  107. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  108. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  109. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  110. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  111. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  112. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  113. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  114. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  115. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  116. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  117. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  118. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  119. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  120. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  121. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  133. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  134. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  135. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  136. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  137. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  138. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  139. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  140. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  141. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  142. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  143. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  144. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  145. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  146. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  147. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  148. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  153. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  154. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  155. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  156. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  157. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  158. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  159. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  160. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  161. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  162. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  163. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  164. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  165. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  166. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  167. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  172. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  173. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  174. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  175. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  176. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  177. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  178. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  179. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  180. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  181. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  182. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  183. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  184. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  185. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  186. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  187. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  188. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  189. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  190. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  191. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  192. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  193. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  194. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  195. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  196. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  197. package/dist/protocol/local-files.js +18 -9
  198. package/dist/runtime/context.js +8 -4
  199. package/dist/schemas/agent-profile.schema.json +1 -1
  200. package/dist/schemas/code-review-result.schema.json +1 -1
  201. package/dist/schemas/code-verification.schema.json +7 -5
  202. package/dist/schemas/code-work-batch.schema.json +2 -2
  203. package/dist/schemas/discovery-result.schema.json +184 -0
  204. package/dist/schemas/execution-bundle.schema.json +205 -0
  205. package/dist/schemas/execution-settings.schema.json +162 -0
  206. package/dist/schemas/flow-descriptor.schema.json +54 -0
  207. package/dist/schemas/flow-run.schema.json +28 -0
  208. package/dist/schemas/harness-catalog.schema.json +80 -0
  209. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  210. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  211. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  212. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  213. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  214. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  215. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  216. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  217. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  218. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  219. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  220. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  221. package/dist/schemas/model-catalog.schema.json +153 -0
  222. package/dist/schemas/plan-review-decision.schema.json +3 -3
  223. package/dist/schemas/plan-review-result.schema.json +2 -2
  224. package/dist/schemas/profile-settings.schema.json +122 -0
  225. package/dist/schemas/release-manifest.schema.json +291 -0
  226. package/dist/schemas/run-control-receipt.schema.json +118 -0
  227. package/dist/schemas/run-control-request.schema.json +36 -0
  228. package/dist/schemas/run-engine-binding.schema.json +1 -1
  229. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  230. package/dist/services/branch-context.js +3 -3
  231. package/dist/services/canon.js +11 -8
  232. package/dist/services/check-runner-evidence.js +250 -0
  233. package/dist/services/cleanup.js +133 -40
  234. package/dist/services/cli-operation-classifier.js +25 -62
  235. package/dist/services/cli-upgrade-allowlist.js +27 -0
  236. package/dist/services/code-checks.js +1168 -107
  237. package/dist/services/codex-hook-delivery.js +32 -0
  238. package/dist/services/command-context.js +80 -0
  239. package/dist/services/compatibility-preflight.js +13 -3
  240. package/dist/services/config.js +11 -8
  241. package/dist/services/continuation-outcome.js +20 -0
  242. package/dist/services/controller-child-capacity.js +13 -0
  243. package/dist/services/controller-child-observer.js +115 -0
  244. package/dist/services/controller-execution.js +88 -0
  245. package/dist/services/controller-fanout.js +202 -0
  246. package/dist/services/dashboard.js +143 -58
  247. package/dist/services/descriptor-coordinator-session.js +35 -0
  248. package/dist/services/descriptor-root-work.js +53 -0
  249. package/dist/services/descriptor-run-settlement.js +105 -0
  250. package/dist/services/descriptor-run-start.js +449 -0
  251. package/dist/services/descriptor-stage-coordinator.js +197 -0
  252. package/dist/services/descriptor-stage-settlement.js +222 -0
  253. package/dist/services/descriptor-stage-start.js +139 -0
  254. package/dist/services/discovery/agy-discovery.js +2 -0
  255. package/dist/services/discovery/codex-discovery.js +2 -0
  256. package/dist/services/discovery/droid-discovery.js +2 -0
  257. package/dist/services/discovery/grok-discovery.js +2 -0
  258. package/dist/services/discovery/opencode-discovery.js +2 -0
  259. package/dist/services/discovery/static.js +31 -0
  260. package/dist/services/discovery/zcode-discovery.js +2 -0
  261. package/dist/services/engines.js +294 -153
  262. package/dist/services/eval-snapshots.js +1215 -116
  263. package/dist/services/execution-bundles.js +263 -0
  264. package/dist/services/execution-policy.js +185 -0
  265. package/dist/services/executor-onboarding.js +56 -0
  266. package/dist/services/external-work-launch.js +308 -0
  267. package/dist/services/flow-catalog.js +21 -0
  268. package/dist/services/flow-descriptors.js +82 -0
  269. package/dist/services/frozen-engine.js +250 -0
  270. package/dist/services/generic-execution.js +164 -0
  271. package/dist/services/harness-adapter.js +355 -36
  272. package/dist/services/harness-config.js +2 -0
  273. package/dist/services/harness-discovery.js +29 -0
  274. package/dist/services/harness-doctor.js +75 -0
  275. package/dist/services/harness-probe-effects.js +51 -0
  276. package/dist/services/harness-version-probe.js +100 -0
  277. package/dist/services/hooks.js +771 -325
  278. package/dist/services/hybrid-contracts.js +483 -0
  279. package/dist/services/ids.js +29 -1
  280. package/dist/services/installation-lock.js +95 -0
  281. package/dist/services/installed-flow-source.js +183 -0
  282. package/dist/services/lanes.js +78 -65
  283. package/dist/services/lifecycle-command.js +1 -288
  284. package/dist/services/lifecycle-contract.js +1 -0
  285. package/dist/services/lifecycle-invocations.js +2007 -0
  286. package/dist/services/managed-daemon-binding.js +38 -0
  287. package/dist/services/managed-lease-monitor.js +133 -0
  288. package/dist/services/managed-processes.js +475 -55
  289. package/dist/services/memory-accepted-composition.js +77 -0
  290. package/dist/services/memory-baseline-preservation.js +65 -0
  291. package/dist/services/memory-completed-correction-wave.js +320 -0
  292. package/dist/services/memory-compose-handler.js +258 -0
  293. package/dist/services/memory-composition-acceptance.js +199 -0
  294. package/dist/services/memory-composition-graph.js +134 -0
  295. package/dist/services/memory-composition-registration.js +146 -0
  296. package/dist/services/memory-composition-work-custody.js +151 -0
  297. package/dist/services/memory-composition-work-results.js +96 -0
  298. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  299. package/dist/services/memory-corrected-review-batch.js +30 -0
  300. package/dist/services/memory-corrected-review-findings.js +106 -0
  301. package/dist/services/memory-corrected-review-inputs.js +210 -0
  302. package/dist/services/memory-corrected-review-projection.js +78 -0
  303. package/dist/services/memory-corrected-review-receipt.js +170 -0
  304. package/dist/services/memory-corrected-review-registration.js +191 -0
  305. package/dist/services/memory-correction-check-receipt.js +219 -0
  306. package/dist/services/memory-correction-input-owner.js +6 -0
  307. package/dist/services/memory-discover-handler.js +104 -0
  308. package/dist/services/memory-finish-evidence.js +435 -0
  309. package/dist/services/memory-finish-handler.js +215 -0
  310. package/dist/services/memory-finish-integration.js +280 -0
  311. package/dist/services/memory-finish-readiness.js +122 -0
  312. package/dist/services/memory-git-integration.js +151 -0
  313. package/dist/services/memory-historical-corrected-review.js +206 -0
  314. package/dist/services/memory-historical-original-review.js +130 -0
  315. package/dist/services/memory-historical-repair-registration.js +99 -0
  316. package/dist/services/memory-historical-second-review.js +128 -0
  317. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  318. package/dist/services/memory-init-contexts.js +89 -0
  319. package/dist/services/memory-init-controller-start.js +84 -0
  320. package/dist/services/memory-init-engine-release.js +223 -0
  321. package/dist/services/memory-init-git-policy.js +136 -0
  322. package/dist/services/memory-init-lint-capability.js +185 -0
  323. package/dist/services/memory-init-native-capacity.js +296 -0
  324. package/dist/services/memory-init-preflight.js +330 -0
  325. package/dist/services/memory-init-project-bootstrap.js +599 -0
  326. package/dist/services/memory-init-request-candidate.js +157 -0
  327. package/dist/services/memory-init-request-input.js +66 -0
  328. package/dist/services/memory-init-resource-storage.js +125 -0
  329. package/dist/services/memory-init-run-allocation.js +81 -0
  330. package/dist/services/memory-init-run-bundle.js +98 -0
  331. package/dist/services/memory-init-start.js +181 -0
  332. package/dist/services/memory-init-startup-custody.js +80 -0
  333. package/dist/services/memory-init-startup-input.js +240 -0
  334. package/dist/services/memory-init-storage.js +81 -0
  335. package/dist/services/memory-init-stores.js +359 -0
  336. package/dist/services/memory-init-worktrunk-capability.js +178 -0
  337. package/dist/services/memory-layer-publication.js +611 -0
  338. package/dist/services/memory-layer.js +156 -0
  339. package/dist/services/memory-lint-config.js +85 -0
  340. package/dist/services/memory-native-review-copy.js +165 -0
  341. package/dist/services/memory-normal-flow-entry.js +75 -0
  342. package/dist/services/memory-ordinary-defaults.js +112 -0
  343. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  344. package/dist/services/memory-preflight-handler.js +259 -0
  345. package/dist/services/memory-repair-current-view.js +299 -0
  346. package/dist/services/memory-repair-document-delta.js +451 -0
  347. package/dist/services/memory-repair-history.js +491 -0
  348. package/dist/services/memory-repair-physical-binding.js +312 -0
  349. package/dist/services/memory-repair-publication-files.js +152 -0
  350. package/dist/services/memory-repair-publication-intent.js +27 -0
  351. package/dist/services/memory-repair-publication-plan.js +160 -0
  352. package/dist/services/memory-repair-publication-source.js +225 -0
  353. package/dist/services/memory-repair-publication.js +794 -0
  354. package/dist/services/memory-repair-registration.js +239 -0
  355. package/dist/services/memory-repair-work-baseline.js +413 -0
  356. package/dist/services/memory-repair-work-completion.js +97 -0
  357. package/dist/services/memory-repair-work-custody.js +73 -0
  358. package/dist/services/memory-repair-work-results.js +89 -0
  359. package/dist/services/memory-repair-workspace.js +252 -0
  360. package/dist/services/memory-repair-writer-copy.js +735 -0
  361. package/dist/services/memory-result-binding.js +173 -0
  362. package/dist/services/memory-retained-review-authority.js +129 -0
  363. package/dist/services/memory-retained-review-results.js +70 -0
  364. package/dist/services/memory-retained-second-baseline.js +102 -0
  365. package/dist/services/memory-retained-second-repairs.js +156 -0
  366. package/dist/services/memory-review-acceptance.js +214 -0
  367. package/dist/services/memory-review-correction-plan.js +171 -0
  368. package/dist/services/memory-review-handler.js +585 -0
  369. package/dist/services/memory-review-inputs.js +200 -0
  370. package/dist/services/memory-review-receipt.js +180 -0
  371. package/dist/services/memory-review-registration.js +108 -0
  372. package/dist/services/memory-review-work-custody.js +124 -0
  373. package/dist/services/memory-review-work-input.js +72 -0
  374. package/dist/services/memory-review-work-results.js +73 -0
  375. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  376. package/dist/services/memory-second-correction-plan.js +76 -0
  377. package/dist/services/memory-second-repair-baseline.js +91 -0
  378. package/dist/services/memory-second-repair-launch.js +614 -0
  379. package/dist/services/memory-second-repair-registration.js +88 -0
  380. package/dist/services/memory-second-repair-source.js +187 -0
  381. package/dist/services/memory-second-review-acceptance.js +115 -0
  382. package/dist/services/memory-source-observations.js +117 -0
  383. package/dist/services/memory-stage-dispatch.js +28 -0
  384. package/dist/services/memory-stage-results.js +199 -0
  385. package/dist/services/memory-verification-acceptance.js +135 -0
  386. package/dist/services/memory-verification-checks.js +252 -0
  387. package/dist/services/memory-verification-receipt.js +159 -0
  388. package/dist/services/memory-verify-handler.js +135 -0
  389. package/dist/services/memory-work-result-preparation.js +63 -0
  390. package/dist/services/merge-queue.js +228 -149
  391. package/dist/services/merge-server.js +161 -35
  392. package/dist/services/migrations.js +37 -16
  393. package/dist/services/model-catalog.js +109 -0
  394. package/dist/services/model-discovery.js +46 -0
  395. package/dist/services/native-daemon-history.js +49 -0
  396. package/dist/services/native-session-control.js +39 -0
  397. package/dist/services/operator-profiles.js +236 -0
  398. package/dist/services/plan-runtime.js +26 -11
  399. package/dist/services/plans.js +28 -25
  400. package/dist/services/projects.js +37 -17
  401. package/dist/services/prompts.js +28 -17
  402. package/dist/services/protocols.js +163 -87
  403. package/dist/services/provider-capacity.js +385 -0
  404. package/dist/services/recovery-observation-budget.js +121 -0
  405. package/dist/services/recovery-snapshot-database.js +560 -0
  406. package/dist/services/release-archive-producer.js +52 -0
  407. package/dist/services/release-archive.js +143 -0
  408. package/dist/services/release-cache.js +142 -0
  409. package/dist/services/release-compatibility.js +18 -0
  410. package/dist/services/release-download.js +67 -0
  411. package/dist/services/release-envelope.js +104 -0
  412. package/dist/services/release-manifest.js +45 -0
  413. package/dist/services/release-payload.js +114 -0
  414. package/dist/services/release-producer.js +63 -0
  415. package/dist/services/release-provenance.js +160 -0
  416. package/dist/services/release-registry.js +66 -0
  417. package/dist/services/release-source.js +194 -0
  418. package/dist/services/repair-intents.js +42 -0
  419. package/dist/services/review-copy.js +165 -0
  420. package/dist/services/review-deferral.js +23 -0
  421. package/dist/services/run-control-receipt.js +56 -0
  422. package/dist/services/run-control-worker.js +411 -0
  423. package/dist/services/run-control.js +1056 -0
  424. package/dist/services/run-controller-adapter.js +399 -0
  425. package/dist/services/run-controller-capture.js +186 -0
  426. package/dist/services/run-controller-process.js +208 -0
  427. package/dist/services/run-controller-recovery.js +289 -0
  428. package/dist/services/run-controller-state.js +97 -0
  429. package/dist/services/run-controller.js +1205 -0
  430. package/dist/services/run-engine-bindings.js +1 -1
  431. package/dist/services/run-fork.js +147 -0
  432. package/dist/services/run-health.js +149 -0
  433. package/dist/services/run-observations.js +152 -0
  434. package/dist/services/run-projection.js +22 -64
  435. package/dist/services/run-recovery-guard.js +77 -0
  436. package/dist/services/run-recovery-runtime.js +112 -0
  437. package/dist/services/run-recovery.js +489 -0
  438. package/dist/services/run-start-custody.js +177 -0
  439. package/dist/services/runs.js +1019 -247
  440. package/dist/services/runtime-budget.js +430 -0
  441. package/dist/services/runtime-command.js +43 -0
  442. package/dist/services/runtime-scope-capture.js +52 -0
  443. package/dist/services/runtime-scope-control.js +468 -0
  444. package/dist/services/runtime-scope-resume.js +599 -0
  445. package/dist/services/runtime-scope-stop.js +210 -0
  446. package/dist/services/runtime-scope-worker.js +287 -0
  447. package/dist/services/runtime-service.js +96 -0
  448. package/dist/services/schema-validation.js +10 -8
  449. package/dist/services/scope-writer-cleanup.js +27 -0
  450. package/dist/services/sessions.js +35 -69
  451. package/dist/services/setup.js +377 -0
  452. package/dist/services/stage-blocker.js +17 -7
  453. package/dist/services/stage-context.js +193 -19
  454. package/dist/services/stage-handlers.js +74 -0
  455. package/dist/services/stage-lifecycle.js +152 -116
  456. package/dist/services/stage-pause.js +89 -75
  457. package/dist/services/stage-report-renderer.js +234 -6
  458. package/dist/services/stage-work-graph.js +52 -0
  459. package/dist/services/tooling.js +41 -30
  460. package/dist/services/usage.js +49 -101
  461. package/dist/services/verification-matrix.js +375 -0
  462. package/dist/services/vnext-code-review.js +223 -88
  463. package/dist/services/vnext-code.js +327 -226
  464. package/dist/services/vnext-contracts.js +10 -1
  465. package/dist/services/vnext-execution-profile.js +80 -5
  466. package/dist/services/vnext-fanout.js +311 -19
  467. package/dist/services/vnext-merge.js +332 -111
  468. package/dist/services/vnext-plan-review.js +279 -127
  469. package/dist/services/vnext-plan.js +228 -104
  470. package/dist/services/vnext-protocolize.js +531 -95
  471. package/dist/services/vnext-specify.js +119 -81
  472. package/dist/services/vnext-workspace-policy.js +7 -2
  473. package/dist/services/work-registry.js +1627 -199
  474. package/dist/services/workspace-bootstrap.js +52 -15
  475. package/dist/services/workspace-files.js +85 -0
  476. package/dist/services/worktrees.js +105 -48
  477. package/dist/shared/entity-references.js +8 -0
  478. package/dist/shared/errors.js +27 -0
  479. package/dist/storage/agent-output.js +26 -0
  480. package/dist/storage/database.js +891 -39
  481. package/dist/storage/foreign-key-transaction.js +39 -0
  482. package/dist/storage/immutable-input.js +45 -0
  483. package/dist/storage/import-guard.js +102 -0
  484. package/dist/storage/native-cleanup.js +52 -0
  485. package/dist/storage/paths.js +17 -4
  486. package/dist/storage/projection-lock.js +73 -0
  487. package/dist/storage/work-references.js +23 -0
  488. package/dist/storage/writer-contract.js +89 -0
  489. package/dist/storage/writer-migration.js +85 -0
  490. package/package.json +43 -13
  491. package/tools/create-canon-release.mjs +33 -0
  492. package/tools/produce-hybrid-release.mjs +41 -0
  493. package/tools/repair-paused-run-status.mjs +84 -0
@@ -0,0 +1,39 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { adapterSessionId, runHarnessAdapter } from "./harness-adapter.js";
3
+ export async function cancelControlledNativeSession(input) {
4
+ input.assertCurrent();
5
+ const reply = await runHarnessAdapter({ executable: input.executable,
6
+ args: input.observe ? ["daemon", "operation", "--operation-id", input.operationId, "--state-dir", input.stateDir, "--json"] : ["session", "cancel", "--session-id", input.sessionId, "--state-dir", input.stateDir, "--json"],
7
+ env: input.env, operationId: input.observe ? `${input.operationId}:observe` : input.operationId, timeoutMs: 30_000 });
8
+ if (input.observe && (reply.operation_id !== input.operationId || reply.operation !== "session.cancel" || reply.session_id !== input.sessionId))
9
+ throw new AppError("controller_operation_mismatch", "Cancellation observation belongs to another operation or Session", 1);
10
+ if (input.observe && reply.state === "failed") {
11
+ if (!reply.error || typeof reply.error !== "object" || Array.isArray(reply.error))
12
+ throw new AppError("controller_operation_mismatch", "Cancellation failure has no terminal error", 1);
13
+ input.assertCurrent();
14
+ return { status: "failed", receipt: reply };
15
+ }
16
+ const result = input.observe ? reply.state === "completed" ? reply.result : null : reply;
17
+ // A completed cancel command is an immutable ACK, not proof that the
18
+ // provider tree has stopped. Callers must use a separate fresh inspection.
19
+ if (!input.observe || reply.state === "completed")
20
+ assertNativeCancellationReceipt(result, input.sessionId);
21
+ input.assertCurrent();
22
+ return { status: result ? "completed" : "outcome_unknown", receipt: (result ?? reply) };
23
+ }
24
+ export function assertNativeCancellationReceipt(value, sessionId) {
25
+ const identity = adapterSessionId(value);
26
+ if (!value || typeof value !== "object" || Array.isArray(value) || identity && identity !== sessionId)
27
+ throw new AppError("controller_operation_mismatch", "Cancellation receipt has no valid result for its target Session", 1);
28
+ }
29
+ /** Native observation is shared by RUN and non-Work EVAL owners. */
30
+ export async function inspectControlledNativeSession(input) {
31
+ input.assertCurrent();
32
+ const receipt = await runHarnessAdapter({ executable: input.executable, args: ["session", "inspect", "--session-id", input.sessionId, "--state-dir", input.stateDir, ...(input.settlementObservation ? ["--no-flow"] : []), "--json"], env: input.env, operationId: input.operationId, timeoutMs: 30_000 });
33
+ if (adapterSessionId(receipt) !== input.sessionId)
34
+ throw new AppError("controller_operation_mismatch", "Session observation belongs to another target", 1);
35
+ if (input.harness && receipt.harness !== undefined && receipt.harness !== input.harness)
36
+ throw new AppError("controller_operation_mismatch", "Session observation belongs to another harness", 1);
37
+ input.assertCurrent();
38
+ return receipt;
39
+ }
@@ -0,0 +1,236 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { hybridDigest, validateHybridContract } from "./hybrid-contracts.js";
6
+ import { acquireInstallationLock } from "./installation-lock.js";
7
+ import { readBoundedReleaseFile } from "./release-source.js";
8
+ export function operatorSettingsPath(home) { return path.join(home, "profile-settings.json"); }
9
+ function profileError(code, message, exitCode = 2) {
10
+ return new AppError(code, `${message}; use dd-flow profiles --help`, exitCode, { phase: "profiles", effect: "no_effect", recoverable: true });
11
+ }
12
+ function completeSettings(input) {
13
+ const value = { ...input, profiles: input.profiles.map(entry => ({ ...entry, revision: hybridDigest(entry.profile) })) };
14
+ return { ...value, revision: hybridDigest(value) };
15
+ }
16
+ function admittedSettings(input) {
17
+ const issues = validateHybridContract("profile-settings", input);
18
+ if (issues.length)
19
+ throw profileError("profile_settings_invalid", "Profile settings violate the schema or aliases/revision contract");
20
+ const settings = input;
21
+ for (const { profile } of settings.profiles) {
22
+ if ([profile.provider, profile.model, profile.reasoning, profile.mode].some(value => !value.trim())) {
23
+ throw profileError("profile_settings_invalid", "Profile execution values must not be blank");
24
+ }
25
+ }
26
+ return settings;
27
+ }
28
+ function readRegularJson(file) {
29
+ try {
30
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readBoundedReleaseFile(file, 4 * 1024 * 1024)));
31
+ }
32
+ catch {
33
+ // Parser errors can contain submitted secrets. Never retain input text or cause.
34
+ throw profileError("profile_settings_invalid", "Profile input is unreadable or malformed JSON");
35
+ }
36
+ }
37
+ /** First successful write imports valid legacy IDs; thereafter the settings file is the sole owner. */
38
+ export function readOperatorProfiles(home) {
39
+ const file = operatorSettingsPath(home);
40
+ if (fs.existsSync(file))
41
+ return admittedSettings(readRegularJson(file));
42
+ const profiles = [];
43
+ const legacy = path.join(home, "agent-profiles");
44
+ if (fs.existsSync(legacy)) {
45
+ for (const entry of fs.readdirSync(legacy).sort()) {
46
+ if (!entry.endsWith(".json"))
47
+ continue;
48
+ const profile = readRegularJson(path.join(legacy, entry));
49
+ const id = entry.slice(0, -5);
50
+ profiles.push({ id, revision: hybridDigest(profile), profile });
51
+ }
52
+ }
53
+ return admittedSettings(completeSettings({ schema_id: "dd-flow/profile-settings@1", profiles,
54
+ aliases: {}, default_profile: null }));
55
+ }
56
+ export function resolveOperatorProfile(settings, id) {
57
+ const selected = id ?? settings.default_profile;
58
+ if (!selected)
59
+ throw profileError("agent_profile_missing", "Select an explicit profile or configure a default", 1);
60
+ const target = Object.hasOwn(settings.aliases, selected) ? settings.aliases[selected] : selected;
61
+ const entry = settings.profiles.find(item => item.id === target);
62
+ if (!entry)
63
+ throw profileError("agent_profile_missing", "Selected profile is unavailable", 1);
64
+ return structuredClone(entry.profile);
65
+ }
66
+ /** Used by explicit files and onboarding alike; extra credential fields fail before any write. */
67
+ export function prepareOperatorProfile(input) {
68
+ if (!input || typeof input !== "object" || Array.isArray(input))
69
+ throw profileError("profile_settings_invalid", "Profile must be an object");
70
+ const sample = input;
71
+ const value = completeSettings({ schema_id: "dd-flow/profile-settings@1", profiles: [
72
+ { id: sample?.id, revision: hybridDigest(input), profile: sample }
73
+ ], aliases: {}, default_profile: null });
74
+ return admittedSettings(value).profiles[0].profile;
75
+ }
76
+ export function readOperatorProfileInput(file) {
77
+ return prepareOperatorProfile(readRegularJson(file));
78
+ }
79
+ function changedSettings(current, mutation) {
80
+ const next = structuredClone(current);
81
+ if (mutation.action === "create" || mutation.action === "edit") {
82
+ if (mutation.action === "create" && mutation.setDefault !== undefined && typeof mutation.setDefault !== "boolean") {
83
+ throw profileError("profile_settings_invalid", "Default selection must be an explicit boolean");
84
+ }
85
+ const profile = prepareOperatorProfile(mutation.profile);
86
+ const index = next.profiles.findIndex(entry => entry.id === profile.id);
87
+ if (mutation.action === "create" && index >= 0)
88
+ throw profileError("profile_conflict", "Profile ID already exists", 1);
89
+ if (mutation.action === "edit" && index < 0)
90
+ throw profileError("agent_profile_missing", "Cannot edit a missing profile", 1);
91
+ const entry = { id: profile.id, revision: hybridDigest(profile), profile };
92
+ if (index >= 0)
93
+ next.profiles[index] = entry;
94
+ else
95
+ next.profiles.push(entry);
96
+ if (mutation.action === "create" && mutation.setDefault === true)
97
+ next.default_profile = profile.id;
98
+ }
99
+ else if (mutation.action === "remove") {
100
+ if (!next.profiles.some(entry => entry.id === mutation.id))
101
+ throw profileError("agent_profile_missing", "Cannot remove a missing profile", 1);
102
+ if (Object.values(next.aliases).includes(mutation.id) || next.default_profile === mutation.id
103
+ || next.default_profile !== null && next.aliases[next.default_profile] === mutation.id) {
104
+ throw profileError("profile_in_use", "Remove aliases and change the default before removing this profile", 1);
105
+ }
106
+ next.profiles = next.profiles.filter(entry => entry.id !== mutation.id);
107
+ }
108
+ else if (mutation.action === "alias") {
109
+ if (mutation.target === null) {
110
+ if (!Object.hasOwn(next.aliases, mutation.alias))
111
+ throw profileError("profile_alias_missing", "Alias is unavailable", 1);
112
+ if (next.default_profile === mutation.alias)
113
+ throw profileError("profile_in_use", "Change the default before removing its alias", 1);
114
+ delete next.aliases[mutation.alias];
115
+ }
116
+ else
117
+ Object.defineProperty(next.aliases, mutation.alias, { value: mutation.target, enumerable: true, writable: true, configurable: true });
118
+ }
119
+ else if (mutation.action === "set-default")
120
+ next.default_profile = mutation.id;
121
+ else
122
+ throw profileError("profile_settings_invalid", "Unknown profile mutation");
123
+ const { revision: _revision, ...content } = next;
124
+ void _revision;
125
+ return admittedSettings(completeSettings(content));
126
+ }
127
+ /** Pure preflight suitable for freezing into an installation request before any effects. */
128
+ export function prepareProfileCreation(settings, profile, setDefault) {
129
+ if (typeof setDefault !== "boolean")
130
+ throw profileError("profile_settings_invalid", "Default selection requires explicit consent");
131
+ const before = admittedSettings(structuredClone(settings)), admitted = prepareOperatorProfile(profile);
132
+ const after = changedSettings(before, { action: "create", profile: admitted, setDefault });
133
+ return { schema_id: "dd-flow/profile-creation-intent@1", before, after, profile: structuredClone(admitted), set_default: setDefault };
134
+ }
135
+ /** Exact frozen intent reconciliation: an already-applied write is not applied a second time.
136
+ * A later edit/default change is a conflict, never a reason to restore old settings.
137
+ */
138
+ export function validateProfileCreationIntent(input) {
139
+ if (!input || typeof input !== "object" || Array.isArray(input)
140
+ || Object.keys(input).some(key => !["schema_id", "before", "after", "profile", "set_default"].includes(key))
141
+ || ["schema_id", "before", "after", "profile", "set_default"].some(key => !Object.hasOwn(input, key))
142
+ || input.schema_id !== "dd-flow/profile-creation-intent@1")
143
+ throw profileError("profile_settings_invalid", "Invalid profile creation intent");
144
+ const expected = prepareProfileCreation(input.before, input.profile, input.set_default);
145
+ admittedSettings(input.after);
146
+ if (hybridDigest(expected) !== hybridDigest(input))
147
+ throw profileError("profile_settings_invalid", "Profile creation intent changed");
148
+ }
149
+ export function readProfileCreationIntent(file) {
150
+ try {
151
+ const input = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readBoundedReleaseFile(file, 4 * 1024 * 1024)));
152
+ validateProfileCreationIntent(input);
153
+ return input;
154
+ }
155
+ catch {
156
+ throw profileError("profile_settings_invalid", "Profile creation intent must be a bounded valid regular JSON file");
157
+ }
158
+ }
159
+ export function commitProfileCreation(home, input) {
160
+ validateProfileCreationIntent(input);
161
+ const current = readOperatorProfiles(home);
162
+ if (current.revision === input.after.revision)
163
+ return { replayed: true, settings: current };
164
+ return { replayed: false, settings: mutateOperatorProfiles(home, input.before.revision, { action: "create", profile: input.profile, setDefault: input.set_default }) };
165
+ }
166
+ /** Compare-and-swap under an exclusive owned lock; no dual writer for legacy profile files. */
167
+ export function mutateOperatorProfiles(home, expectedRevision, mutation) {
168
+ const before = readOperatorProfiles(home);
169
+ if (before.revision !== expectedRevision)
170
+ throw profileError("profile_revision_conflict", "Profile settings changed; reread before retrying", 1);
171
+ const prepared = changedSettings(before, mutation); // Fully validate before creating the home/lock.
172
+ const serialized = JSON.stringify(prepared, null, 2) + "\n";
173
+ if (Buffer.byteLength(serialized) > 4 * 1024 * 1024)
174
+ throw profileError("profile_settings_invalid", "Profile settings exceed the bounded reader limit");
175
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
176
+ const lock = path.join(home, "profile-settings.lock");
177
+ // Profiles use storage-owner identity, not a setup request ledger. A dead
178
+ // writer may be recovered by a later CAS mutation, including after commit.
179
+ let lease;
180
+ try {
181
+ lease = acquireInstallationLock(lock, hybridDigest({ resource: "profile-settings", home: path.resolve(home) }));
182
+ }
183
+ catch {
184
+ throw profileError("profile_write_conflict", "Another profile writer or unreconciled lock exists", 1);
185
+ }
186
+ const temporary = path.join(home, `.profile-settings-${randomUUID()}.tmp`);
187
+ let ownsTemporary = false;
188
+ let renameAttempted = false;
189
+ let failure;
190
+ let result;
191
+ try {
192
+ if (readOperatorProfiles(home).revision !== expectedRevision)
193
+ throw profileError("profile_revision_conflict", "Profile settings changed while acquiring the lock", 1);
194
+ const fd = fs.openSync(temporary, "wx", 0o600);
195
+ ownsTemporary = true;
196
+ try {
197
+ fs.writeFileSync(fd, serialized);
198
+ fs.fsyncSync(fd);
199
+ }
200
+ finally {
201
+ fs.closeSync(fd);
202
+ }
203
+ renameAttempted = true;
204
+ fs.renameSync(temporary, operatorSettingsPath(home));
205
+ result = readOperatorProfiles(home);
206
+ }
207
+ catch (error) {
208
+ failure = error instanceof AppError && !renameAttempted ? error : new AppError("profile_write_interrupted", "Profile write requires reconciliation; reread settings before retrying", 1, { phase: "profiles", effect: "unknown", recoverable: true,
209
+ next_action: "dd-flow profiles list --json" });
210
+ }
211
+ finally {
212
+ let cleanupFailed = false;
213
+ try {
214
+ if (ownsTemporary && fs.existsSync(temporary))
215
+ fs.unlinkSync(temporary);
216
+ }
217
+ catch {
218
+ cleanupFailed = true;
219
+ }
220
+ try {
221
+ lease.release();
222
+ }
223
+ catch {
224
+ cleanupFailed = true;
225
+ }
226
+ if (cleanupFailed) {
227
+ if (failure)
228
+ failure.details.cleanup = "not_confirmed";
229
+ else
230
+ failure = new AppError("profile_cleanup_failed", "Profile writer cleanup requires reconciliation", 1, { phase: "profiles", effect: "unknown", cleanup: "not_confirmed", next_action: "dd-flow profiles list --json" });
231
+ }
232
+ }
233
+ if (failure)
234
+ throw failure;
235
+ return result;
236
+ }
@@ -6,18 +6,24 @@ import { parseJsonObject } from "../shared/json.js";
6
6
  import { refreshRunSessionProjection } from "./run-projection.js";
7
7
  export function readCanonicalPlan(planPath, protocolId) {
8
8
  if (!fs.existsSync(planPath) || !fs.statSync(planPath).isFile()) {
9
- throw new AppError("not_found", `Canonical protocol plan is missing: ${planPath}`, 1, { plan_path: planPath });
9
+ throw new AppError("runtime_artifact_missing", `Canonical protocol plan is missing: ${planPath}`, 1, { plan_path: planPath });
10
10
  }
11
11
  const raw = fs.readFileSync(planPath, "utf8");
12
- const value = parseJsonObject(raw, planPath);
13
- const plan = validatePlan(value, protocolId);
14
- const revisionValue = value.revision;
15
- const revision = typeof revisionValue === "number" && Number.isInteger(revisionValue) && revisionValue > 0 ? revisionValue : 1;
16
- return { plan, raw, sha256: crypto.createHash("sha256").update(raw).digest("hex"), revision };
12
+ try {
13
+ const value = parseJsonObject(raw, planPath);
14
+ const plan = validatePlan(value, protocolId);
15
+ const revisionValue = value.revision;
16
+ const revision = typeof revisionValue === "number" && Number.isInteger(revisionValue) && revisionValue > 0 ? revisionValue : 1;
17
+ return { plan, raw, sha256: crypto.createHash("sha256").update(raw).digest("hex"), revision };
18
+ }
19
+ catch (error) {
20
+ if (!(error instanceof AppError) || error.code !== "validation")
21
+ throw error;
22
+ throw new AppError("runtime_artifact_invalid", "Canonical protocol plan is invalid", 1, { plan_path: planPath, cause_code: error.code });
23
+ }
17
24
  }
18
- export function boundCanonicalPlan(context, input) {
19
- const canonical = readCanonicalPlan(input.planPath, input.protocolId);
20
- const binding = context.db.get("SELECT plan_revision, plan_sha256, plan_path FROM plan_bindings WHERE project_id = ? AND protocol_id = ?", [input.projectId, input.protocolId]);
25
+ export function prepareCanonicalPlan(context, input, canonical = readCanonicalPlan(input.planPath, input.protocolId)) {
26
+ const binding = context.db.get("SELECT plan_id, plan_revision, plan_sha256, plan_path FROM plan_bindings WHERE project_id = ? AND protocol_id = ?", [input.projectId, input.protocolId]);
21
27
  if (binding && (binding.plan_revision !== canonical.revision || binding.plan_sha256 !== canonical.sha256)) {
22
28
  throw new AppError("plan_stale", "Canonical plan changed after the runtime binding was created", 1, {
23
29
  protocol_id: input.protocolId,
@@ -28,10 +34,19 @@ export function boundCanonicalPlan(context, input) {
28
34
  plan_path: input.planPath
29
35
  });
30
36
  }
37
+ return canonical;
38
+ }
39
+ export function boundCanonicalPlan(context, input, prepared) {
40
+ const canonical = prepareCanonicalPlan(context, input, prepared);
41
+ const binding = context.db.get("SELECT plan_id, plan_revision, plan_sha256, plan_path FROM plan_bindings WHERE project_id = ? AND protocol_id = ?", [input.projectId, input.protocolId]);
31
42
  const now = context.now();
32
43
  if (!binding) {
33
- context.db.run(`INSERT INTO plan_bindings (project_id, protocol_id, plan_revision, plan_sha256, plan_path, bound_at, updated_at)
34
- VALUES (?, ?, ?, ?, ?, ?, ?)`, [input.projectId, input.protocolId, canonical.revision, canonical.sha256, input.planPath, now, now]);
44
+ context.db.run(`INSERT INTO plan_bindings (project_id, protocol_id, plan_id, plan_revision, plan_sha256, plan_path, bound_at, updated_at)
45
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [input.projectId, input.protocolId, canonical.plan.plan_id, canonical.revision, canonical.sha256, input.planPath, now, now]);
46
+ }
47
+ else if (binding.plan_id === null) {
48
+ // Older bindings are upgraded when their canonical source is validated.
49
+ context.db.run("UPDATE plan_bindings SET plan_id = ?, updated_at = ? WHERE project_id = ? AND protocol_id = ? AND plan_id IS NULL", [canonical.plan.plan_id, now, input.projectId, input.protocolId]);
35
50
  }
36
51
  for (const item of canonical.plan.items) {
37
52
  context.db.run(`INSERT INTO plan_progress
@@ -4,7 +4,7 @@ import { appendAudit } from "./audit.js";
4
4
  import { persistProtocolState, readProtocolRuntimeState, requireProtocol } from "./protocols.js";
5
5
  import { requireProjectByRoot } from "./projects.js";
6
6
  import { resolveProjectRoot } from "../storage/paths.js";
7
- import { planSummary, planWithProgress, updatePlanProgress } from "./plan-runtime.js";
7
+ import { planSummary, planWithProgress, prepareCanonicalPlan, updatePlanProgress } from "./plan-runtime.js";
8
8
  export function getPlanStatus(context, input) {
9
9
  const protocol = scopedProtocol(context, input.projectRoot, input.protocolId);
10
10
  const current = planWithProgress(context, { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path }, { bind: false });
@@ -20,40 +20,43 @@ export function getPlanStatus(context, input) {
20
20
  };
21
21
  }
22
22
  export function startPlanItem(context, input) {
23
- return updatePlanItem(context, input.projectRoot, input.protocolId, input.itemId, (item, plan) => {
24
- assertDependenciesClosed(plan, item);
25
- return { ...item, status: "in_progress", summary: item.summary || "Started." };
26
- });
23
+ return updatePlanItem(context, { ...input, action: "start" });
27
24
  }
28
25
  export function completePlanItem(context, input) {
29
- return updatePlanItem(context, input.projectRoot, input.protocolId, input.itemId, (item, plan) => {
30
- assertDependenciesClosed(plan, item);
31
- return { ...item, status: "done", summary: input.summary, evidence: [...new Set([...item.evidence, ...input.evidence])] };
32
- });
26
+ return updatePlanItem(context, { ...input, action: "done" });
33
27
  }
34
28
  export function blockPlanItem(context, input) {
35
- return updatePlanItem(context, input.projectRoot, input.protocolId, input.itemId, (item) => ({
36
- ...item,
37
- status: "blocked",
38
- summary: input.reason,
39
- block_reason: input.reason,
40
- user_required: input.userRequired
41
- }));
29
+ return updatePlanItem(context, { ...input, action: "block" });
42
30
  }
43
31
  export function skipPlanItem(context, input) {
44
- return updatePlanItem(context, input.projectRoot, input.protocolId, input.itemId, (item) => ({
45
- ...item,
46
- status: "skipped",
47
- summary: input.reason
48
- }));
32
+ return updatePlanItem(context, { ...input, action: "skip" });
49
33
  }
50
- function updatePlanItem(context, projectRoot, protocolId, itemId, transform) {
34
+ export function preparePlanItemMutation(context, input) {
35
+ const { projectRoot, protocolId, itemId } = input;
36
+ const text = (value) => typeof value === "string" && value.trim().length > 0;
37
+ if (!text(itemId) || !["start", "done", "block", "skip"].includes(input.action) ||
38
+ (input.action === "done" && (!text(input.summary) || !Array.isArray(input.evidence) || !input.evidence.every(text))) ||
39
+ ((input.action === "block" || input.action === "skip") && !text(input.reason)) ||
40
+ (input.action === "block" && typeof input.userRequired !== "boolean")) {
41
+ throw new AppError("validation", "Plan item requires a valid action, item ID, nonempty summary/reason, text evidence and boolean userRequired", 2, { phase: "prepare", effect: "no_effect", recoverable: true });
42
+ }
51
43
  const protocol = scopedProtocol(context, projectRoot, protocolId);
52
- const current = planWithProgress(context, { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path });
44
+ const scope = { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path };
45
+ const current = planWithProgress(context, scope, { bind: false });
46
+ prepareCanonicalPlan(context, scope, current.canonical);
53
47
  const item = current.plan.items.find((candidate) => candidate.id === itemId);
54
48
  if (!item)
55
49
  throw new AppError("not_found", `Plan item is not found: ${itemId}`, 1);
56
- const nextItem = transform(item, current.plan);
50
+ if (input.action === "start" || input.action === "done")
51
+ assertDependenciesClosed(current.plan, item);
52
+ const nextItem = input.action === "start" ? { ...item, status: "in_progress", summary: item.summary || "Started." }
53
+ : input.action === "done" ? { ...item, status: "done", summary: input.summary, evidence: [...new Set([...item.evidence, ...input.evidence])] }
54
+ : input.action === "block" ? { ...item, status: "blocked", summary: input.reason, block_reason: input.reason, user_required: input.userRequired }
55
+ : { ...item, status: "skipped", summary: input.reason };
56
+ return { protocol, item, nextItem };
57
+ }
58
+ export function updatePlanItem(context, input) {
59
+ const { protocol, item, nextItem } = preparePlanItemMutation(context, input);
57
60
  updatePlanProgress(context, { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path, item: nextItem });
58
61
  if (nextItem.status === "done") {
59
62
  context.db.run("UPDATE flow_jobs SET status = 'done', finished_at = COALESCE(finished_at, ?), updated_at = ? WHERE project_id = ? AND protocol_id = ? AND plan_item_id = ?", [context.now(), context.now(), protocol.project_id, protocol.id, nextItem.id]);
@@ -63,7 +66,7 @@ function updatePlanItem(context, projectRoot, protocolId, itemId, transform) {
63
66
  protocolId: protocol.id,
64
67
  projectId: protocol.project_id,
65
68
  eventType: "plan.progress.updated",
66
- payload: { protocol_id: protocol.id, item_id: itemId, from: item.status, to: nextItem.status }
69
+ payload: { protocol_id: protocol.id, item_id: input.itemId, from: item.status, to: nextItem.status }
67
70
  });
68
71
  return { ok: true, protocol_id: protocol.id, item: nextItem, plan: planSummary(context, { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path }) };
69
72
  }
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { foreignKeyTransaction } from "../storage/foreign-key-transaction.js";
3
4
  import { formatFullId, isFullEntityId, isShortEntityId, parseFullEntityId, slugFromRoot } from "../domain/entity-ids.js";
4
5
  import { AppError } from "../shared/errors.js";
5
6
  import { projectRuntimeRoot, resolveProjectRoot } from "../storage/paths.js";
@@ -10,9 +11,24 @@ import { loadProjectFlowContract } from "../domain/flow-contract.js";
10
11
  import { buildStaticFlowGuidance } from "./flow-guidance.js";
11
12
  import { normalizeProtocolLifecycle } from "./protocol-lifecycle.js";
12
13
  import { buildProjectSummary, projectRegistrySummary, readPublishedProjectSummary } from "./project-summary.js";
14
+ /** Resolve filesystem identity before entering a composite SQL writer. */
15
+ export class PreparedProjectRegistration {
16
+ #root;
17
+ constructor(root) { this.#root = root; Object.freeze(this); }
18
+ static prepare(input) {
19
+ return new PreparedProjectRegistration(resolveProjectRoot(input.root));
20
+ }
21
+ root() { return this.#root; }
22
+ }
13
23
  export function registerProject(context, input) {
14
- const root = resolveProjectRoot(input.root);
15
- const existing = findProjectByRoot(context, root);
24
+ return registerPreparedProject(context, PreparedProjectRegistration.prepare(input));
25
+ }
26
+ /** Metadata-only registration; the composite caller owns its SQL transaction. */
27
+ export function registerPreparedProject(context, prepared) {
28
+ if (!(prepared instanceof PreparedProjectRegistration))
29
+ throw new AppError("validation", "Project registration must be prepared", 2);
30
+ const root = prepared.root();
31
+ const existing = context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
16
32
  const now = context.now();
17
33
  const project = existing ?? newProjectRecord(context, root, now);
18
34
  if (existing) {
@@ -37,8 +53,13 @@ export function registerProject(context, input) {
37
53
  }
38
54
  return { ok: true, project: { ...project, status: "active", updated_at: now }, db_path: context.db.path };
39
55
  }
40
- export function archiveProject(context, input) {
41
- const reason = input.reason.trim();
56
+ export function prepareProjectArchive(context, input) {
57
+ if (Boolean(input.idOrAlias) === Boolean(input.root))
58
+ throw new AppError("usage", "Project archive requires exactly one project ID/alias or --root", 2);
59
+ for (const value of [input.idOrAlias, input.root])
60
+ if (value !== undefined && (typeof value !== "string" || !value.trim()))
61
+ throw new AppError("validation", "Project archive target must be a non-empty string", 2);
62
+ const reason = typeof input.reason === "string" ? input.reason.trim() : "";
42
63
  if (!reason) {
43
64
  throw new AppError("validation", "project archive requires --reason", 2);
44
65
  }
@@ -50,6 +71,10 @@ export function archiveProject(context, input) {
50
71
  if (!project) {
51
72
  throw new AppError("not_found", "Project is not registered.", 1, { root: input.root, input: input.idOrAlias });
52
73
  }
74
+ return { project, reason };
75
+ }
76
+ export function archiveProject(context, input) {
77
+ const { project, reason } = prepareProjectArchive(context, input);
53
78
  const now = context.now();
54
79
  const result = context.db.run("UPDATE projects SET status = 'archived', updated_at = ? WHERE id = ? AND status <> 'archived'", [now, project.id]);
55
80
  if (result.changes === 1) {
@@ -139,9 +164,7 @@ export function migrateProjectIds(context, input) {
139
164
  if (conflictingProject) {
140
165
  throw new AppError("conflict", `Target project id already exists: ${target.id}`, 1, plan);
141
166
  }
142
- context.db.exec("PRAGMA foreign_keys = OFF");
143
- context.db.exec("BEGIN IMMEDIATE");
144
- try {
167
+ foreignKeyTransaction(context.db, () => {
145
168
  context.db.run("UPDATE projects SET id = ?, short_id = ?, slug = ?, state_root = ?, updated_at = ? WHERE id = ?", [target.id, target.short_id, target.slug, target.state_root, now, oldId]);
146
169
  for (const table of projectReferenceTables()) {
147
170
  context.db.run(`UPDATE ${table} SET project_id = ? WHERE project_id = ?`, [target.id, oldId]);
@@ -153,14 +176,7 @@ export function migrateProjectIds(context, input) {
153
176
  foreign_key_errors: foreignKeyErrors
154
177
  });
155
178
  }
156
- context.db.exec("COMMIT");
157
- context.db.exec("PRAGMA foreign_keys = ON");
158
- }
159
- catch (error) {
160
- context.db.exec("ROLLBACK");
161
- context.db.exec("PRAGMA foreign_keys = ON");
162
- throw error;
163
- }
179
+ });
164
180
  appendAudit(context, {
165
181
  projectId: target.id,
166
182
  eventType: "project.id_migrated",
@@ -232,9 +248,13 @@ export function findProjectByRoot(context, root) {
232
248
  return context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
233
249
  }
234
250
  export function requireProjectByRoot(context, root) {
235
- const project = findProjectByRoot(context, root);
251
+ // Callers may receive a path through a native adapter or shell. Resolve
252
+ // aliases such as macOS `/var` -> `/private/var` before comparing with the
253
+ // canonical root persisted in the project registry.
254
+ const canonical = normalizeStoredRoot(root);
255
+ const project = findProjectByRoot(context, canonical);
236
256
  if (!project) {
237
- throw new AppError("not_found", `Project is not registered: ${root}`, 1);
257
+ throw new AppError("not_found", `Project is not registered: ${canonical}`, 1);
238
258
  }
239
259
  return project;
240
260
  }
@@ -9,7 +9,8 @@ import { validatePlanItem } from "../domain/validation.js";
9
9
  import { AppError } from "../shared/errors.js";
10
10
  import { parseJsonObject } from "../shared/json.js";
11
11
  import { ensureDir, resolveProjectRoot } from "../storage/paths.js";
12
- import { planWithProgress } from "./plan-runtime.js";
12
+ import { boundCanonicalPlan, planWithProgress, prepareCanonicalPlan } from "./plan-runtime.js";
13
+ import { prepareTextFile } from "../cli/input-preparation.js";
13
14
  import { registerFlowJob } from "./sessions.js";
14
15
  const profiles = {
15
16
  code_implementation: {
@@ -22,7 +23,7 @@ const profiles = {
22
23
  static_files: [".memory-bank/dd-flow/common/worker-session.md", ".memory-bank/dd-flow/workers/verify.md"]
23
24
  }
24
25
  };
25
- export function renderWorkerPrompt(context, input) {
26
+ export function prepareWorkerPrompt(context, input) {
26
27
  const projectRoot = resolveProjectRoot(input.projectRoot);
27
28
  const project = requireProjectByRoot(context, projectRoot);
28
29
  const protocol = requireProtocol(context, protocolIdForRun(context, project.id, input.runId), project.id);
@@ -43,8 +44,10 @@ export function renderWorkerPrompt(context, input) {
43
44
  projectId: protocol.project_id,
44
45
  protocolId: protocol.id,
45
46
  planPath: protocol.plan_path
46
- });
47
+ }, { bind: false });
47
48
  const plan = planRuntime?.plan ?? null;
49
+ if (planRuntime)
50
+ prepareCanonicalPlan(context, { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path }, planRuntime.canonical);
48
51
  const genericTask = input.taskFile ? readWorkerTask(input.taskFile, runHomePath(run)) : null;
49
52
  const item = genericTask?.item ?? plan?.items.find((candidate) => candidate.id === input.planItemId);
50
53
  if (!item)
@@ -64,13 +67,6 @@ export function renderWorkerPrompt(context, input) {
64
67
  const profile = profiles[input.profile];
65
68
  if (!profile)
66
69
  throw new AppError("prompt_profile_unsupported", `Unsupported prompt profile: ${input.profile}`, 2);
67
- const job = registerFlowJob(context, {
68
- projectId: protocol.project_id,
69
- runId: run.id,
70
- protocolId: protocol.id,
71
- planItemId: item.id,
72
- groupId: item.execution_context.planned_write_areas[0] ?? null
73
- });
74
70
  const staticInputs = profile.static_files.map((file) => readStaticInput(projectRoot, file));
75
71
  const requiredRead = item.execution_context.required_read.map((file) => checkedReference(projectRoot, runHomePath(run), file, "required_read", true));
76
72
  const discoveryBoundary = item.execution_context.discovery_boundary.map((file) => checkedReference(projectRoot, runHomePath(run), file, "discovery_boundary", false));
@@ -78,7 +74,6 @@ export function renderWorkerPrompt(context, input) {
78
74
  const runHome = runHomePath(run);
79
75
  const outputDir = path.join(runHome, stage.dir, "subagents", item.id);
80
76
  assertWithin(runHome, outputDir, "output directory");
81
- ensureDir(outputDir);
82
77
  const prompt = renderPrompt({
83
78
  item,
84
79
  input,
@@ -90,6 +85,20 @@ export function renderWorkerPrompt(context, input) {
90
85
  staticInputs,
91
86
  ...(genericTask ? { handoff: genericTask.handoff } : {})
92
87
  });
88
+ return { protocol, run, item, plan, planRuntime, genericTask, workspaceRoot, staticInputs, requiredRead, discoveryBoundary, plannedWriteAreas, outputDir, prompt, canonVersion: readCanonVersion(projectRoot) };
89
+ }
90
+ export function renderWorkerPrompt(context, input, prepared = prepareWorkerPrompt(context, input)) {
91
+ const { protocol, run, item, plan, planRuntime, genericTask, workspaceRoot, staticInputs, requiredRead, discoveryBoundary, plannedWriteAreas, outputDir, prompt, canonVersion } = prepared;
92
+ if (planRuntime)
93
+ boundCanonicalPlan(context, { projectId: protocol.project_id, protocolId: protocol.id, planPath: protocol.plan_path }, planRuntime.canonical);
94
+ const job = registerFlowJob(context, {
95
+ projectId: protocol.project_id,
96
+ runId: run.id,
97
+ protocolId: protocol.id,
98
+ planItemId: item.id,
99
+ groupId: item.execution_context.planned_write_areas[0] ?? null
100
+ });
101
+ ensureDir(outputDir);
93
102
  const promptPath = path.join(outputDir, "launch-prompt.md");
94
103
  const stackPath = path.join(outputDir, "prompt-stack.json");
95
104
  const reportPath = path.join(outputDir, "render-report.json");
@@ -104,7 +113,7 @@ export function renderWorkerPrompt(context, input) {
104
113
  plan_item_id: input.taskFile ? null : item.id,
105
114
  task_id: input.taskFile ? item.id : null,
106
115
  job_id: job.job_id,
107
- canon_version: readCanonVersion(projectRoot),
116
+ canon_version: canonVersion,
108
117
  renderer_version: getCliVersionReport().cli.version,
109
118
  static_inputs: staticInputs.map(({ path: inputPath, sha256 }) => ({ path: inputPath, sha256 })),
110
119
  validation: { required_read: requiredRead, discovery_boundary: discoveryBoundary, planned_write_areas: plannedWriteAreas },
@@ -143,9 +152,7 @@ function runHomePath(run) {
143
152
  function readWorkerTask(taskFile, runHome) {
144
153
  const resolved = path.resolve(taskFile);
145
154
  assertWithin(runHome, resolved, "worker task file");
146
- if (!fs.existsSync(resolved))
147
- throw new AppError("not_found", "Worker task file is missing", 1, { path: taskFile });
148
- const task = parseJsonObject(fs.readFileSync(resolved, "utf8"), resolved);
155
+ const task = parseJsonObject(prepareTextFile(resolved, "worker task").text, resolved);
149
156
  if (task.schema_id !== "dd-flow/worker-task@1" || !task.task || typeof task.task !== "object") {
150
157
  throw new AppError("validation", "Worker task must use dd-flow/worker-task@1 with a task object", 2, { path: taskFile });
151
158
  }
@@ -180,7 +187,7 @@ function validateWorkerTaskHandoff(value, runHome) {
180
187
  const reportPath = path.resolve(report.report_path);
181
188
  assertWithin(runHome, reportPath, "predecessor report");
182
189
  if (!fs.existsSync(reportPath))
183
- throw new AppError("not_found", "Accepted predecessor report is missing", 1, { path: report.report_path });
190
+ throw new AppError("runtime_artifact_missing", "Accepted predecessor report is missing", 1, { path: report.report_path });
184
191
  return { aspect_id: report.aspect_id, verdict, report_path: reportPath };
185
192
  });
186
193
  const recovery = handoff.recovery_attempt_paths ?? [];
@@ -261,7 +268,9 @@ function assertGitFacts(workspaceRoot, expected, runId) {
261
268
  if (!expected?.head && !expected?.branch)
262
269
  return;
263
270
  const read = (args) => {
264
- const result = spawnSync("git", ["-C", workspaceRoot, ...args.split(" ")], { encoding: "utf8" });
271
+ const result = spawnSync("git", ["-C", workspaceRoot, ...args.split(" ")], { encoding: "utf8", timeout: 5000 });
272
+ if (result.error || result.signal)
273
+ throw new AppError("git_observation_unavailable", "Cannot establish RUN workspace identity", 1, { run_id: runId, effect: "unknown" });
265
274
  return result.status === 0 ? result.stdout.trim() || null : null;
266
275
  };
267
276
  const actual = { branch: read("branch --show-current"), head: read("rev-parse HEAD") };
@@ -293,6 +302,8 @@ function renderPrompt(input) {
293
302
  `- Acceptance contribution: ${item.semantic_spine?.acceptance_contribution ?? "not_applicable"}`,
294
303
  "",
295
304
  "## Hard Write Boundary",
305
+ "Before editing, change into the project workspace shown below (cd -- WORKSPACE) or use its absolute path when the tool cannot persist cwd.",
306
+ "Write only the project workspace and the named report directory; never write another RUN/native home or Git/worktree control data.",
296
307
  `- All project writes must stay under: \`${input.workspaceRoot}\``,
297
308
  "- Do not write outside this workspace, to another RUN, or into Git/worktree control data.",
298
309
  "- Accepted requirements and non-goals remain binding; planned write areas do not.",