@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.150

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 (557) hide show
  1. package/CHANGELOG.md +895 -0
  2. package/README.md +140 -6
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +386 -0
  6. package/dist/cli/command-metadata.js +232 -0
  7. package/dist/cli/command-option-descriptions.js +192 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/diagnostic-invocation.js +46 -0
  11. package/dist/cli/diagnostic.js +120 -0
  12. package/dist/cli/error-output.js +52 -0
  13. package/dist/cli/executor-onboarding.js +76 -0
  14. package/dist/cli/flows.js +29 -0
  15. package/dist/cli/harnesses.js +84 -0
  16. package/dist/cli/help.js +396 -19
  17. package/dist/cli/hook-ingress.js +102 -0
  18. package/dist/cli/input-preparation.js +165 -0
  19. package/dist/cli/memory-flow-admission.js +50 -0
  20. package/dist/cli/memory-init-invocation.js +85 -0
  21. package/dist/cli/memory-init.js +249 -0
  22. package/dist/cli/memory-storage-admission.js +54 -0
  23. package/dist/cli/memory-storage-qualification.js +105 -0
  24. package/dist/cli/memory-storage-transition.js +123 -0
  25. package/dist/cli/memory-upgrade.js +189 -0
  26. package/dist/cli/models.js +39 -0
  27. package/dist/cli/native-hook-ingress.js +90 -0
  28. package/dist/cli/native-hook-preparation.js +55 -0
  29. package/dist/cli/operator-profiles.js +61 -0
  30. package/dist/cli/output-options.js +35 -0
  31. package/dist/cli/response-file.js +72 -0
  32. package/dist/cli/run-cli.js +1734 -550
  33. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  34. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  35. package/dist/cli/runtime-process.js +153 -0
  36. package/dist/cli/setup.js +150 -0
  37. package/dist/cli.js +89 -2
  38. package/dist/domain/diagnostic-aspects.js +103 -0
  39. package/dist/domain/diagnostic-flows.js +8 -0
  40. package/dist/domain/stage-catalog.js +1 -1
  41. package/dist/domain/stage-handler-identities.js +28 -0
  42. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  43. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  44. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  45. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  46. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  47. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  48. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  49. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  50. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  51. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  52. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  53. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  54. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  55. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  56. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  57. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  58. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  59. package/dist/harness-runtime/lib/daemon-operations.d.mts +2 -0
  60. package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
  61. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  62. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1096 -0
  63. package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
  64. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  65. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
  66. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  67. package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
  68. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
  69. package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
  70. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
  71. package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
  72. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +622 -0
  73. package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
  74. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
  75. package/dist/harness-runtime/lib/dd-zcode.mjs +1175 -0
  76. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  77. package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
  78. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  79. package/dist/harness-runtime/lib/driver-recovery.mjs +140 -0
  80. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  81. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  82. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  83. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  84. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  85. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  86. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  87. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  88. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  89. package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
  90. package/dist/harness-runtime/lib/managed-daemon.mjs +698 -0
  91. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  92. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  93. package/dist/harness-runtime/lib/native-children.d.mts +23 -0
  94. package/dist/harness-runtime/lib/native-children.mjs +219 -0
  95. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  96. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  97. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  98. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  99. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  100. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  101. package/dist/harness-runtime/lib/observation-clock.mjs +95 -0
  102. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  103. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  104. package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
  105. package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
  106. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  107. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  108. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  109. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  110. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  111. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +15 -0
  112. package/dist/harness-runtime/lib/provider-overload-policy.mjs +151 -0
  113. package/dist/harness-runtime/lib/runner-events.mjs +202 -0
  114. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  115. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  116. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  117. package/dist/harness-runtime/lib/setup-progress.mjs +53 -0
  118. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  119. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  120. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  121. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  133. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  134. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  135. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  136. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  137. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  138. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  139. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  140. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  141. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  142. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  143. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  144. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  145. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  146. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  147. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  148. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  153. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  154. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  155. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  156. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  157. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  158. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  159. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  160. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  161. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  162. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  163. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  164. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  165. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  166. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  167. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  172. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  173. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  174. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  175. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  176. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  177. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  178. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  179. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  180. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  181. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  182. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  183. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  184. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  185. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  186. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  187. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  188. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  189. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  190. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  191. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  192. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  193. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  194. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  195. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  196. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  197. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  198. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  199. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  200. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  201. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  202. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  203. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  204. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  205. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  206. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  207. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  208. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  209. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  210. package/dist/protocol/local-files.js +18 -9
  211. package/dist/runtime/context.js +8 -4
  212. package/dist/schemas/agent-profile.schema.json +1 -1
  213. package/dist/schemas/code-review-decision.schema.json +9 -0
  214. package/dist/schemas/code-review-result.schema.json +1 -1
  215. package/dist/schemas/code-verification.schema.json +7 -5
  216. package/dist/schemas/code-work-batch.schema.json +2 -2
  217. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  218. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  219. package/dist/schemas/discovery-result.schema.json +184 -0
  220. package/dist/schemas/execution-bundle.schema.json +211 -0
  221. package/dist/schemas/execution-settings.schema.json +162 -0
  222. package/dist/schemas/flow-descriptor.schema.json +54 -0
  223. package/dist/schemas/flow-run.schema.json +29 -0
  224. package/dist/schemas/harness-catalog.schema.json +80 -0
  225. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  226. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  227. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  228. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  229. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  230. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  231. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  232. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  233. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  234. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  235. package/dist/schemas/memory-repair-work-result.schema.json +56 -0
  236. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  237. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  238. package/dist/schemas/model-catalog.schema.json +153 -0
  239. package/dist/schemas/plan-review-decision.schema.json +12 -3
  240. package/dist/schemas/plan-review-result.schema.json +2 -2
  241. package/dist/schemas/profile-settings.schema.json +122 -0
  242. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  243. package/dist/schemas/release-impact.schema.json +3 -3
  244. package/dist/schemas/release-manifest.schema.json +291 -0
  245. package/dist/schemas/run-control-receipt.schema.json +118 -0
  246. package/dist/schemas/run-control-request.schema.json +36 -0
  247. package/dist/schemas/run-engine-binding.schema.json +1 -1
  248. package/dist/schemas/stage-start-response.schema.json +4 -0
  249. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  250. package/dist/services/accepted-intent.js +139 -0
  251. package/dist/services/branch-context.js +3 -3
  252. package/dist/services/canon.js +54 -15
  253. package/dist/services/check-environment.js +21 -0
  254. package/dist/services/check-runner-evidence.js +562 -0
  255. package/dist/services/cleanup.js +133 -40
  256. package/dist/services/cli-operation-classifier.js +26 -63
  257. package/dist/services/cli-upgrade-allowlist.js +27 -0
  258. package/dist/services/code-checks.js +1398 -119
  259. package/dist/services/codex-hook-delivery.js +32 -0
  260. package/dist/services/command-context.js +80 -0
  261. package/dist/services/compatibility-preflight.js +16 -3
  262. package/dist/services/config.js +11 -8
  263. package/dist/services/continuation-outcome.js +20 -0
  264. package/dist/services/controller-child-capacity.js +13 -0
  265. package/dist/services/controller-child-observer.js +115 -0
  266. package/dist/services/controller-execution.js +94 -0
  267. package/dist/services/controller-fanout.js +226 -0
  268. package/dist/services/controller-memory-task.js +45 -0
  269. package/dist/services/controller-memory-worker.js +49 -0
  270. package/dist/services/dashboard.js +143 -58
  271. package/dist/services/descriptor-coordinator-session.js +35 -0
  272. package/dist/services/descriptor-root-work.js +53 -0
  273. package/dist/services/descriptor-run-settlement.js +107 -0
  274. package/dist/services/descriptor-run-start.js +475 -0
  275. package/dist/services/descriptor-stage-coordinator.js +250 -0
  276. package/dist/services/descriptor-stage-settlement.js +223 -0
  277. package/dist/services/descriptor-stage-start.js +175 -0
  278. package/dist/services/diagnostic-evidence.js +73 -0
  279. package/dist/services/diagnostic-report-publication.js +72 -0
  280. package/dist/services/diagnostic-report.js +67 -0
  281. package/dist/services/diagnostic-run-bundle.js +74 -0
  282. package/dist/services/diagnostic-snapshot.js +159 -0
  283. package/dist/services/diagnostic-stage-handlers.js +108 -0
  284. package/dist/services/diagnostic-stage-results.js +132 -0
  285. package/dist/services/diagnostic-start.js +84 -0
  286. package/dist/services/diagnostic-work-batch.js +34 -0
  287. package/dist/services/diagnostic-work-collection.js +97 -0
  288. package/dist/services/diagnostic-work-registration.js +101 -0
  289. package/dist/services/diagnostic-work-results.js +93 -0
  290. package/dist/services/discovery/agy-discovery.js +2 -0
  291. package/dist/services/discovery/codex-discovery.js +2 -0
  292. package/dist/services/discovery/droid-discovery.js +2 -0
  293. package/dist/services/discovery/grok-discovery.js +2 -0
  294. package/dist/services/discovery/opencode-discovery.js +2 -0
  295. package/dist/services/discovery/static.js +31 -0
  296. package/dist/services/discovery/zcode-discovery.js +2 -0
  297. package/dist/services/engines.js +354 -163
  298. package/dist/services/eval-snapshots.js +1316 -150
  299. package/dist/services/execution-bundles.js +275 -0
  300. package/dist/services/execution-policy.js +185 -0
  301. package/dist/services/executor-onboarding.js +56 -0
  302. package/dist/services/external-work-launch.js +310 -0
  303. package/dist/services/flow-catalog.js +30 -0
  304. package/dist/services/flow-descriptors.js +82 -0
  305. package/dist/services/frozen-engine.js +387 -0
  306. package/dist/services/generic-execution.js +164 -0
  307. package/dist/services/harness-adapter.js +355 -36
  308. package/dist/services/harness-config.js +2 -0
  309. package/dist/services/harness-discovery.js +29 -0
  310. package/dist/services/harness-doctor.js +75 -0
  311. package/dist/services/harness-probe-effects.js +51 -0
  312. package/dist/services/harness-version-probe.js +100 -0
  313. package/dist/services/hook-memory-routing.js +188 -0
  314. package/dist/services/hook-recovery-guard.js +14 -0
  315. package/dist/services/hook-session-observation.js +24 -0
  316. package/dist/services/hooks.js +791 -326
  317. package/dist/services/hybrid-contracts.js +474 -0
  318. package/dist/services/hybrid-digest.js +14 -0
  319. package/dist/services/ids.js +29 -1
  320. package/dist/services/installation-lock.js +95 -0
  321. package/dist/services/installed-flow-source.js +226 -0
  322. package/dist/services/lanes.js +78 -65
  323. package/dist/services/lifecycle-command.js +1 -288
  324. package/dist/services/lifecycle-contract.js +1 -0
  325. package/dist/services/lifecycle-invocations.js +2148 -0
  326. package/dist/services/managed-daemon-binding.js +38 -0
  327. package/dist/services/managed-lease-monitor.js +133 -0
  328. package/dist/services/managed-processes.js +449 -69
  329. package/dist/services/memory-accepted-composition.js +78 -0
  330. package/dist/services/memory-baseline-preservation.js +65 -0
  331. package/dist/services/memory-completed-correction-wave.js +328 -0
  332. package/dist/services/memory-compose-handler.js +400 -0
  333. package/dist/services/memory-composition-acceptance.js +375 -0
  334. package/dist/services/memory-composition-graph.js +165 -0
  335. package/dist/services/memory-composition-registration.js +161 -0
  336. package/dist/services/memory-composition-work-custody.js +156 -0
  337. package/dist/services/memory-composition-work-results.js +108 -0
  338. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  339. package/dist/services/memory-corrected-review-batch.js +37 -0
  340. package/dist/services/memory-corrected-review-findings.js +108 -0
  341. package/dist/services/memory-corrected-review-inputs.js +215 -0
  342. package/dist/services/memory-corrected-review-projection.js +78 -0
  343. package/dist/services/memory-corrected-review-receipt.js +178 -0
  344. package/dist/services/memory-corrected-review-registration.js +192 -0
  345. package/dist/services/memory-correction-check-receipt.js +235 -0
  346. package/dist/services/memory-correction-input-owner.js +6 -0
  347. package/dist/services/memory-custody-snapshot.js +36 -0
  348. package/dist/services/memory-discover-handler.js +104 -0
  349. package/dist/services/memory-finish-evidence.js +457 -0
  350. package/dist/services/memory-finish-handler.js +229 -0
  351. package/dist/services/memory-finish-integration.js +450 -0
  352. package/dist/services/memory-finish-readiness.js +169 -0
  353. package/dist/services/memory-git-integration.js +220 -0
  354. package/dist/services/memory-historical-corrected-review.js +212 -0
  355. package/dist/services/memory-historical-original-review.js +143 -0
  356. package/dist/services/memory-historical-repair-registration.js +105 -0
  357. package/dist/services/memory-historical-second-review.js +147 -0
  358. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  359. package/dist/services/memory-init-contexts.js +131 -0
  360. package/dist/services/memory-init-controller-start.js +103 -0
  361. package/dist/services/memory-init-engine-release.js +223 -0
  362. package/dist/services/memory-init-git-policy.js +165 -0
  363. package/dist/services/memory-init-lint-capability.js +203 -0
  364. package/dist/services/memory-init-native-capacity.js +309 -0
  365. package/dist/services/memory-init-preflight.js +334 -0
  366. package/dist/services/memory-init-project-bootstrap.js +599 -0
  367. package/dist/services/memory-init-request-candidate.js +213 -0
  368. package/dist/services/memory-init-request-input.js +82 -0
  369. package/dist/services/memory-init-resource-storage.js +125 -0
  370. package/dist/services/memory-init-run-allocation.js +81 -0
  371. package/dist/services/memory-init-run-bundle.js +135 -0
  372. package/dist/services/memory-init-start.js +181 -0
  373. package/dist/services/memory-init-startup-custody.js +80 -0
  374. package/dist/services/memory-init-startup-input.js +240 -0
  375. package/dist/services/memory-init-storage.js +91 -0
  376. package/dist/services/memory-init-stores.js +359 -0
  377. package/dist/services/memory-init-worktrunk-capability.js +174 -0
  378. package/dist/services/memory-layer-publication.js +611 -0
  379. package/dist/services/memory-layer.js +221 -0
  380. package/dist/services/memory-lint-config.js +194 -0
  381. package/dist/services/memory-native-review-copy.js +103 -0
  382. package/dist/services/memory-normal-flow-entry.js +87 -0
  383. package/dist/services/memory-ordinary-defaults.js +129 -0
  384. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  385. package/dist/services/memory-preflight-handler.js +274 -0
  386. package/dist/services/memory-repair-current-view.js +299 -0
  387. package/dist/services/memory-repair-document-delta.js +488 -0
  388. package/dist/services/memory-repair-history.js +496 -0
  389. package/dist/services/memory-repair-physical-binding.js +194 -0
  390. package/dist/services/memory-repair-publication-files.js +152 -0
  391. package/dist/services/memory-repair-publication-intent.js +27 -0
  392. package/dist/services/memory-repair-publication-plan.js +162 -0
  393. package/dist/services/memory-repair-publication-source.js +225 -0
  394. package/dist/services/memory-repair-publication.js +795 -0
  395. package/dist/services/memory-repair-registration.js +240 -0
  396. package/dist/services/memory-repair-work-baseline.js +415 -0
  397. package/dist/services/memory-repair-work-completion.js +117 -0
  398. package/dist/services/memory-repair-work-custody.js +73 -0
  399. package/dist/services/memory-repair-work-results.js +108 -0
  400. package/dist/services/memory-repair-workspace.js +261 -0
  401. package/dist/services/memory-repair-writer-copy.js +736 -0
  402. package/dist/services/memory-result-binding.js +239 -0
  403. package/dist/services/memory-retained-review-authority.js +138 -0
  404. package/dist/services/memory-retained-review-results.js +70 -0
  405. package/dist/services/memory-retained-second-baseline.js +102 -0
  406. package/dist/services/memory-retained-second-repairs.js +162 -0
  407. package/dist/services/memory-review-acceptance.js +214 -0
  408. package/dist/services/memory-review-correction-plan.js +206 -0
  409. package/dist/services/memory-review-handler.js +585 -0
  410. package/dist/services/memory-review-inputs.js +246 -0
  411. package/dist/services/memory-review-limits.js +10 -0
  412. package/dist/services/memory-review-receipt.js +180 -0
  413. package/dist/services/memory-review-registration.js +117 -0
  414. package/dist/services/memory-review-work-custody.js +126 -0
  415. package/dist/services/memory-review-work-input.js +91 -0
  416. package/dist/services/memory-review-work-results.js +95 -0
  417. package/dist/services/memory-second-corrected-review-inputs.js +167 -0
  418. package/dist/services/memory-second-correction-plan.js +77 -0
  419. package/dist/services/memory-second-repair-baseline.js +91 -0
  420. package/dist/services/memory-second-repair-launch.js +614 -0
  421. package/dist/services/memory-second-repair-registration.js +88 -0
  422. package/dist/services/memory-second-repair-source.js +187 -0
  423. package/dist/services/memory-second-review-acceptance.js +115 -0
  424. package/dist/services/memory-source-observations.js +117 -0
  425. package/dist/services/memory-stage-dispatch.js +65 -0
  426. package/dist/services/memory-stage-results.js +290 -0
  427. package/dist/services/memory-tool-probe.js +25 -0
  428. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  429. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  430. package/dist/services/memory-upgrade-evidence.js +100 -0
  431. package/dist/services/memory-upgrade-inventory.js +442 -0
  432. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  433. package/dist/services/memory-upgrade-publication.js +413 -0
  434. package/dist/services/memory-upgrade-selection.js +430 -0
  435. package/dist/services/memory-upgrade-start.js +60 -0
  436. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  437. package/dist/services/memory-verification-acceptance.js +156 -0
  438. package/dist/services/memory-verification-checks.js +317 -0
  439. package/dist/services/memory-verification-receipt.js +160 -0
  440. package/dist/services/memory-verify-handler.js +137 -0
  441. package/dist/services/memory-work-result-json.js +12 -0
  442. package/dist/services/memory-work-result-preparation.js +64 -0
  443. package/dist/services/merge-queue.js +228 -149
  444. package/dist/services/merge-server.js +163 -35
  445. package/dist/services/migrations.js +164 -114
  446. package/dist/services/model-catalog.js +109 -0
  447. package/dist/services/model-discovery.js +46 -0
  448. package/dist/services/native-daemon-history.js +50 -0
  449. package/dist/services/native-session-control.js +39 -0
  450. package/dist/services/operator-profiles.js +236 -0
  451. package/dist/services/physical-startup.js +45 -0
  452. package/dist/services/plan-runtime.js +26 -11
  453. package/dist/services/plans.js +28 -25
  454. package/dist/services/project-registry.js +97 -0
  455. package/dist/services/project-summary.js +1 -1
  456. package/dist/services/projects.js +17 -87
  457. package/dist/services/prompt-delivery.js +71 -0
  458. package/dist/services/prompts.js +28 -17
  459. package/dist/services/protocols.js +163 -87
  460. package/dist/services/provider-capacity.js +455 -0
  461. package/dist/services/recovery-observation-budget.js +121 -0
  462. package/dist/services/recovery-snapshot-database.js +588 -0
  463. package/dist/services/release-archive-producer.js +52 -0
  464. package/dist/services/release-archive.js +143 -0
  465. package/dist/services/release-cache.js +150 -0
  466. package/dist/services/release-compatibility.js +18 -0
  467. package/dist/services/release-download.js +67 -0
  468. package/dist/services/release-envelope.js +104 -0
  469. package/dist/services/release-manifest.js +45 -0
  470. package/dist/services/release-payload.js +114 -0
  471. package/dist/services/release-producer.js +63 -0
  472. package/dist/services/release-provenance.js +160 -0
  473. package/dist/services/release-registry.js +66 -0
  474. package/dist/services/release-source.js +194 -0
  475. package/dist/services/repair-intents.js +31 -0
  476. package/dist/services/repair-receipt.js +13 -0
  477. package/dist/services/review-copy.js +165 -0
  478. package/dist/services/review-deferral.js +23 -0
  479. package/dist/services/run-control-receipt.js +56 -0
  480. package/dist/services/run-control-worker.js +428 -0
  481. package/dist/services/run-control.js +1092 -0
  482. package/dist/services/run-controller-adapter.js +400 -0
  483. package/dist/services/run-controller-capture.js +186 -0
  484. package/dist/services/run-controller-process.js +205 -0
  485. package/dist/services/run-controller-recovery.js +295 -0
  486. package/dist/services/run-controller-state.js +97 -0
  487. package/dist/services/run-controller.js +1326 -0
  488. package/dist/services/run-engine-bindings.js +1 -1
  489. package/dist/services/run-fork.js +147 -0
  490. package/dist/services/run-health.js +149 -0
  491. package/dist/services/run-observations.js +152 -0
  492. package/dist/services/run-projection.js +22 -64
  493. package/dist/services/run-recovery-guard.js +107 -0
  494. package/dist/services/run-recovery-runtime.js +112 -0
  495. package/dist/services/run-recovery.js +456 -0
  496. package/dist/services/run-start-custody.js +189 -0
  497. package/dist/services/runs.js +1023 -246
  498. package/dist/services/runtime-budget.js +446 -0
  499. package/dist/services/runtime-command.js +43 -0
  500. package/dist/services/runtime-scope-capture.js +52 -0
  501. package/dist/services/runtime-scope-control.js +468 -0
  502. package/dist/services/runtime-scope-resume.js +605 -0
  503. package/dist/services/runtime-scope-stop.js +211 -0
  504. package/dist/services/runtime-scope-worker.js +314 -0
  505. package/dist/services/runtime-service.js +19 -14
  506. package/dist/services/schema-validation.js +10 -8
  507. package/dist/services/scope-writer-cleanup.js +27 -0
  508. package/dist/services/sessions.js +36 -91
  509. package/dist/services/setup.js +427 -0
  510. package/dist/services/stage-blocker.js +17 -7
  511. package/dist/services/stage-context.js +193 -19
  512. package/dist/services/stage-handlers.js +89 -0
  513. package/dist/services/stage-lifecycle.js +152 -116
  514. package/dist/services/stage-pause.js +202 -91
  515. package/dist/services/stage-report-renderer.js +234 -6
  516. package/dist/services/stage-work-graph.js +52 -0
  517. package/dist/services/tooling.js +41 -30
  518. package/dist/services/usage.js +49 -101
  519. package/dist/services/verification-matrix.js +393 -0
  520. package/dist/services/version-status.js +1 -1
  521. package/dist/services/vnext-code-review.js +235 -88
  522. package/dist/services/vnext-code.js +327 -226
  523. package/dist/services/vnext-contracts.js +10 -1
  524. package/dist/services/vnext-execution-profile.js +80 -5
  525. package/dist/services/vnext-fanout.js +355 -21
  526. package/dist/services/vnext-merge.js +332 -111
  527. package/dist/services/vnext-plan-review.js +287 -132
  528. package/dist/services/vnext-plan.js +228 -104
  529. package/dist/services/vnext-protocolize.js +533 -95
  530. package/dist/services/vnext-specify.js +123 -81
  531. package/dist/services/vnext-workspace-policy.js +7 -2
  532. package/dist/services/work-registry.js +1706 -200
  533. package/dist/services/workspace-bootstrap.js +53 -15
  534. package/dist/services/workspace-files.js +85 -0
  535. package/dist/services/worktrees.js +105 -48
  536. package/dist/shared/entity-references.js +1 -0
  537. package/dist/shared/errors.js +27 -0
  538. package/dist/storage/agent-output.js +26 -0
  539. package/dist/storage/data-preservation.js +65 -0
  540. package/dist/storage/database.js +930 -40
  541. package/dist/storage/foreign-key-transaction.js +39 -0
  542. package/dist/storage/immutable-input.js +45 -0
  543. package/dist/storage/import-guard.js +159 -0
  544. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  545. package/dist/storage/native-cleanup.js +52 -0
  546. package/dist/storage/paired-writer-migration.js +424 -0
  547. package/dist/storage/paths.js +17 -4
  548. package/dist/storage/projection-lock.js +73 -0
  549. package/dist/storage/work-references.js +23 -0
  550. package/dist/storage/writer-contract.js +96 -0
  551. package/dist/storage/writer-migration.js +346 -0
  552. package/dist/storage/writer-restore.js +217 -0
  553. package/package.json +50 -13
  554. package/tools/create-canon-release.mjs +33 -0
  555. package/tools/probe-codex-history.mjs +37 -0
  556. package/tools/produce-hybrid-release.mjs +41 -0
  557. package/tools/repair-paused-run-status.mjs +84 -0
@@ -0,0 +1,211 @@
1
+ import crypto from "node:crypto";
2
+ import { nativeShutdownConfirmed } from "../harness-runtime/lib/native-children.mjs";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { createContext } from "../runtime/context.js";
7
+ import { AppError } from "../shared/errors.js";
8
+ import { runHarnessAdapter } from "./harness-adapter.js";
9
+ import { managedLeaseReleased, managedProcessBinding, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, processTreeIsAlive, resourceHome, retainedResourceHomeMatches, stopManagedProcess } from "./managed-processes.js";
10
+ import { terminalCheckSettled } from "./code-checks.js";
11
+ import { fenceRuntimeBudget, runtimeBudgetStatus, scopeLocalProcessRole } from "./runtime-budget.js";
12
+ import { retainedDaemonOperations } from "./run-control.js";
13
+ import { historicalDaemonSettlement } from "./native-daemon-history.js";
14
+ import { managedProviderBelongsToDaemon } from "./managed-daemon-binding.js";
15
+ /** Native daemons own cancellation after the observing CLI disconnects. */
16
+ export async function stopRuntimeScope(context, scopeId, requestId) {
17
+ const fence = fenceRuntimeBudget(context, scopeId, requestId);
18
+ const inventory = runtimeBudgetStatus(context, scopeId).processes;
19
+ const nodes = await Promise.all(inventory.filter(record => record.kind.endsWith("-daemon")).map(async (record) => {
20
+ try {
21
+ const historical = historicalDaemonSettlement(context, scopeId, record);
22
+ if (historical)
23
+ return historical;
24
+ const metadata = JSON.parse(record.metadata_json);
25
+ if (!metadata.state_dir || !path.isAbsolute(metadata.state_dir) || !metadata.adapter_executable || !path.isAbsolute(metadata.adapter_executable))
26
+ throw new AppError("runtime_scope_owner_unknown", "Native stop requires its retained directory and executable", 1);
27
+ const file = path.join(metadata.state_dir, "daemon.json");
28
+ const read = () => JSON.parse(fs.readFileSync(file, "utf8"));
29
+ const state = read();
30
+ const owner = state.config?.runtime_owner;
31
+ if (!state.daemon_id || !owner || owner.budget?.scope_id !== scopeId || owner.owner_id !== record.owner_id || owner.role !== metadata.role || owner.state_dir !== metadata.state_dir || owner.adapter_executable !== metadata.adapter_executable || !retainedResourceHomeMatches(context, owner.resource_home) || (owner.project_id ?? null) !== record.project_id || (owner.run_id ?? null) !== record.run_id || (owner.work_id ?? null) !== record.work_id || !record.operation_id?.startsWith(`${owner.operation_id}:`) || state.resource_process?.id !== record.id || state.resource_process.lease_token !== record.lease_token || state.pid !== record.pid)
32
+ throw new AppError("runtime_scope_owner_unknown", "Native owner does not match its registration", 1);
33
+ const closed = state.shutdown_state === "clean" && state.active_tree === false && !processTreeIsAlive(record)
34
+ ? retainedDaemonOperations(metadata.state_dir).find(operation => operation.daemon_id === state.daemon_id && operation.operation === "daemon.stop" && operation.state === "completed" && operation.result?.stopped === true && nativeShutdownConfirmed(operation.result, { daemonId: state.daemon_id ?? "" })) : undefined;
35
+ const operationId = closed ? String(closed.operation_id) : `scope-stop:${crypto.createHash("sha256").update(JSON.stringify([scopeId, record.id, state.daemon_id])).digest("hex")}`;
36
+ const requested = path.join(metadata.state_dir, "operations", crypto.createHash("sha256").update(operationId).digest("hex"), "requested.json");
37
+ const prior = fs.existsSync(requested);
38
+ const current = managedProcessStatus(context).find(item => item.id === record.id);
39
+ if (!current || current.lease_token !== record.lease_token || current.pid !== record.pid || current.pid_started_at !== record.pid_started_at)
40
+ throw new AppError("runtime_scope_owner_changed", "Native registration changed before control", 1);
41
+ if (!prior && !processIsAlive(current))
42
+ throw new AppError("runtime_scope_outcome_unknown", "Dead native owner requires retained shutdown evidence", 1);
43
+ const env = { ...context.env, DD_FLOW_HOME: owner.dd_flow_home, DD_FLOW_BIN: owner.dd_flow_bin, DD_FLOW_RESOURCE_HOME: resourceHome(context) };
44
+ delete env.DD_FLOW_CONTROL_TOKEN;
45
+ delete env.DD_FLOW_CONTROLLER_TOKEN;
46
+ const receipt = await runHarnessAdapter({ executable: metadata.adapter_executable,
47
+ args: prior ? ["daemon", "operation", "--operation-id", operationId, "--state-dir", metadata.state_dir, "--json"] : ["daemon", "stop", "--cancel-tree", "--state-dir", metadata.state_dir, "--json"],
48
+ env, operationId: prior ? `${operationId}:observe` : operationId, timeoutMs: 30_000 });
49
+ if (prior && receipt.state === "failed" && receipt.operation_id === operationId && receipt.daemon_id === state.daemon_id) {
50
+ const failure = receipt.error;
51
+ throw new AppError(failure?.code ?? "runtime_scope_control_failed", failure?.message ?? "Native close operation failed", 1, failure?.details);
52
+ }
53
+ if (prior && (receipt.operation_id !== operationId || receipt.daemon_id !== state.daemon_id || receipt.operation !== "daemon.stop" || receipt.state !== "completed"))
54
+ throw new AppError("runtime_scope_outcome_unknown", "Native close operation has not completed", 1);
55
+ const result = (prior ? receipt.result : receipt);
56
+ const after = read();
57
+ const finalOwner = managedProcessStatus(context).find(item => item.id === record.id);
58
+ if (!finalOwner || finalOwner.lease_token !== record.lease_token || finalOwner.pid !== record.pid || finalOwner.pid_started_at !== record.pid_started_at || after.resource_process?.id !== record.id || after.resource_process.lease_token !== record.lease_token || JSON.stringify(after.config?.runtime_owner) !== JSON.stringify(owner))
59
+ throw new AppError("runtime_scope_owner_changed", "Native ownership changed during control", 1);
60
+ if (result?.stopped !== true || !nativeShutdownConfirmed(result, { daemonId: state.daemon_id }) || after.daemon_id !== state.daemon_id || after.shutdown_state !== "clean" || after.active_tree !== false || processTreeIsAlive(finalOwner))
61
+ throw new AppError("runtime_scope_not_settled", "Native close has not proved whole-tree settlement", 1);
62
+ const processes = managedProcessStatus(context).filter(item => managedProviderBelongsToDaemon(item, finalOwner, after) && item.operation_id?.startsWith(`${owner.operation_id}:`) && !processTreeIsAlive(item)).map(item => ({ process_id: item.id, native_settled: true, binding: managedProcessBinding(item, resourceHome(context)) }));
63
+ return { process_id: record.id, state_dir: metadata.state_dir, settled: true, native_settled: true, physical_stopped: true, operation_id: operationId, owner_operation_id: owner.operation_id, processes };
64
+ }
65
+ catch (error) {
66
+ const failure = error;
67
+ const native = failure.details?.cause && typeof failure.details.cause === "object" ? failure.details.cause : null;
68
+ const physical = await stopVerifiedNativeProcesses(context, scopeId, record);
69
+ const stateDir = JSON.parse(record.metadata_json).state_dir;
70
+ return { process_id: record.id, ...(stateDir ? { state_dir: stateDir } : {}), settled: false, native_settled: false, physical_stopped: physical.stopped,
71
+ reason: native?.code ?? failure.code ?? "runtime_scope_control_failed", message: native?.message ?? failure.message ?? String(error),
72
+ ...(native ? { cause: { code: native.code ?? null, message: native.message ?? null, details: native.details ?? null } } : {}), ...(physical.error ? { cleanup_error: physical.error } : {}),
73
+ ...(physical.provider ? { processes: [{ process_id: physical.provider.id, native_settled: false, binding: managedProcessBinding(physical.provider, resourceHome(context)) }] } : {}) };
74
+ }
75
+ }));
76
+ const covered = new Set(nodes.map(node => node.process_id));
77
+ for (const record of runtimeBudgetStatus(context, scopeId).processes) {
78
+ if (covered.has(record.id))
79
+ continue;
80
+ if (managedLeaseReleased(record)) {
81
+ nodes.push({ process_id: record.id, settled: true });
82
+ continue;
83
+ }
84
+ const metadata = JSON.parse(record.metadata_json);
85
+ // RUN control owns active controllers/checks. Adopt only its already-finished
86
+ // physical registrations here; death alone is not a completed RUN operation.
87
+ const terminalCheck = record.kind === "check" && metadata.role === "check" && record.check_id && record.project_id && record.run_id
88
+ ? (() => {
89
+ const home = managedProcessRuntimeHome(record, context);
90
+ if (!home)
91
+ return null;
92
+ let reader;
93
+ try {
94
+ reader = createContext({ ...context.env, DD_FLOW_HOME: home }, "read_existing");
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ try {
100
+ return terminalCheckSettled(reader, record);
101
+ }
102
+ finally {
103
+ reader.db.close?.();
104
+ }
105
+ })() : null;
106
+ if ((record.kind === "run-controller" && metadata.role === "controller" && record.operation_id
107
+ || record.kind === "check" && metadata.role === "check" && record.check_id)
108
+ && record.project_id && record.run_id && ["stopped", "failed"].includes(record.state)
109
+ && record.pid && record.pid_started_at && (terminalCheck || !processTreeIsAlive(record))) {
110
+ const operationId = record.operation_id ?? record.check_id;
111
+ nodes.push({ process_id: record.id, state_dir: metadata.state_dir ?? "", settled: true, operation_id: operationId, owner_operation_id: operationId });
112
+ continue;
113
+ }
114
+ if (scopeLocalProcessRole(record) && previousBoot(record)) {
115
+ nodes.push({ process_id: record.id, settled: false, native_settled: false, physical_stopped: true, reason: "runtime_scope_outcome_unknown", message: "Physical owner predates host boot; logical outcome is retained" });
116
+ continue;
117
+ }
118
+ if (scopeLocalProcessRole(record) && record.operation_id && record.pid && record.pid_started_at) {
119
+ try {
120
+ await stopManagedProcess(context, { id: record.id, leaseToken: record.lease_token, force: false, graceMs: 500, reason: `scope-stop:${scopeId}` });
121
+ nodes.push({ process_id: record.id, state_dir: "", settled: true, operation_id: record.operation_id, owner_operation_id: record.operation_id });
122
+ }
123
+ catch (error) {
124
+ nodes.push({ process_id: record.id, settled: false, reason: "runtime_scope_resource_unsettled", message: String(error) });
125
+ }
126
+ continue;
127
+ }
128
+ const binding = JSON.stringify(managedProcessBinding(record, resourceHome(context)));
129
+ const parent = nodes.find(node => node.settled && node.state_dir === metadata.state_dir && "processes" in node && node.processes?.some(item => item.process_id === record.id && item.native_settled && JSON.stringify(item.binding) === binding));
130
+ if (parent && !processTreeIsAlive(record)) {
131
+ nodes.push({ process_id: record.id, state_dir: metadata.state_dir, settled: true, physical_stopped: true, operation_id: parent.operation_id, owner_operation_id: parent.owner_operation_id });
132
+ }
133
+ else {
134
+ const retired = nodes.find(node => node.physical_stopped === true && node.state_dir === metadata.state_dir && node.processes?.some(item => item.process_id === record.id && JSON.stringify(item.binding) === binding));
135
+ if (retired && !processTreeIsAlive(record))
136
+ nodes.push({ process_id: record.id, ...(metadata.state_dir ? { state_dir: metadata.state_dir } : {}), settled: false, native_settled: false, physical_stopped: true, ...(retired.reason ? { reason: retired.reason } : {}) });
137
+ else if (previousBoot(record))
138
+ nodes.push({ process_id: record.id, settled: false, native_settled: false, physical_stopped: true, reason: "runtime_scope_outcome_unknown", message: "Physical owner predates host boot; logical outcome is retained" });
139
+ else
140
+ nodes.push({ process_id: record.id, settled: false, reason: "runtime_scope_resource_unsettled", message: "Resource needs its own settlement proof" });
141
+ }
142
+ }
143
+ return { scope_id: scopeId, fence, settled: nodes.every(node => node.settled),
144
+ physical_stopped: nodes.every(node => node.physical_stopped === true || node.settled && node.physical_stopped !== null)
145
+ ? true : nodes.some(node => node.physical_stopped === null) ? null : false, nodes };
146
+ }
147
+ async function stopVerifiedNativeProcesses(context, scopeId, daemon) {
148
+ const metadata = JSON.parse(daemon.metadata_json);
149
+ if (!metadata.state_dir || !path.isAbsolute(metadata.state_dir))
150
+ return { stopped: null };
151
+ let state;
152
+ try {
153
+ state = JSON.parse(fs.readFileSync(path.join(metadata.state_dir, "daemon.json"), "utf8"));
154
+ }
155
+ catch {
156
+ return { stopped: null };
157
+ }
158
+ const owner = state.config?.runtime_owner;
159
+ const current = managedProcessStatus(context).find(item => item.id === daemon.id);
160
+ if (!owner || !current || current.lease_token !== daemon.lease_token || current.pid !== daemon.pid || current.pid_started_at !== daemon.pid_started_at
161
+ || !daemon.pid || !daemon.pid_started_at || owner.budget?.scope_id !== scopeId || owner.owner_id !== daemon.owner_id
162
+ || owner.state_dir !== metadata.state_dir || owner.role !== metadata.role || owner.adapter_executable !== metadata.adapter_executable
163
+ || owner.dd_flow_home !== metadata.dd_flow_home || !retainedResourceHomeMatches(context, owner.resource_home)
164
+ || state.resource_process?.id !== daemon.id || state.resource_process.lease_token !== daemon.lease_token
165
+ || (state.pid !== daemon.pid && !(state.pid == null && state.shutdown_state === "starting")))
166
+ return { stopped: null };
167
+ const registered = managedProcessStatus(context);
168
+ const providers = registered.filter(item => managedProviderBelongsToDaemon(item, daemon, state));
169
+ const unpublished = !state.provider_process && !state.provider_pid ? registered.filter(item => {
170
+ const child = JSON.parse(item.metadata_json);
171
+ return item.kind === daemon.kind.replace(/-daemon$/, "-provider") && item.owner_id === daemon.owner_id && item.project_id === daemon.project_id && item.run_id === daemon.run_id && item.work_id === daemon.work_id
172
+ && item.operation_id?.startsWith(`${owner.operation_id}:`) && item.pid && item.pid_started_at && child.owner_pid === daemon.pid && child.owner_pid_started_at === daemon.pid_started_at
173
+ && child.state_dir === metadata.state_dir && child.role === metadata.role && child.adapter_executable === metadata.adapter_executable && child.dd_flow_home === metadata.dd_flow_home
174
+ && child.dispatch_barrier === "run-generation@1" && child.budget?.scope_id === scopeId;
175
+ }) : [];
176
+ const provider = providers.length === 1 ? providers[0] : unpublished.length === 1 ? unpublished[0] : undefined;
177
+ if (provider && previousBoot(daemon) && previousBoot(provider))
178
+ return { stopped: true, provider };
179
+ // Legacy daemons did not register their provider. A host reboot proves local
180
+ // process exit, not native Session completion. Never signal a reused PID.
181
+ if (providers.length === 0 && unpublished.length === 0 && !state.provider_process && !state.provider_pid && previousBoot(daemon))
182
+ return { stopped: true };
183
+ if (providers.length > 1 || unpublished.length > 1 || state.provider_pid && !provider || !provider && state.shutdown_state !== "starting")
184
+ return { stopped: null };
185
+ try {
186
+ if (provider)
187
+ await stopManagedProcess(context, { id: provider.id, leaseToken: provider.lease_token, force: false, graceMs: 500, reason: `scope-stop:${scopeId}` });
188
+ await stopManagedProcess(context, { id: daemon.id, leaseToken: daemon.lease_token, force: false, graceMs: 500, reason: `scope-stop:${scopeId}` });
189
+ return { stopped: !provider ? null : !processTreeIsAlive(daemon) && !processTreeIsAlive(provider), ...(provider ? { provider } : {}) };
190
+ }
191
+ catch (cleanup) {
192
+ return { stopped: false, ...(provider ? { provider } : {}), error: { code: cleanup.code ?? "physical_stop_failed", message: String(cleanup) } };
193
+ }
194
+ }
195
+ function previousBoot(record) {
196
+ const metadata = JSON.parse(record.metadata_json);
197
+ // A waiting, unconfirmed launch cannot outlive the pre-boot owner that alone
198
+ // could release it. This is physical retirement, not a successful launch.
199
+ const identity = record.pid ? record : record.state === "starting" && Number.isSafeInteger(metadata.owner_pid) && metadata.owner_pid > 0 && metadata.owner_pid_started_at
200
+ ? { pid: metadata.owner_pid, pid_started_at: metadata.owner_pid_started_at, metadata_json: JSON.stringify(metadata.owner_birth ? { process_birth: metadata.owner_birth } : {}) } : null;
201
+ if (!identity || processIsAlive(identity))
202
+ return false;
203
+ // Legacy lstart has no timezone. Keep a conservative 26-hour ambiguity
204
+ // margin; recent/malformed identities must use ordinary ownership proof.
205
+ const started = Date.parse(record.started_at);
206
+ const birth = identity.pid_started_at && /^\w{3} \w{3}\s+\d{1,2} \d{2}:\d{2}:\d{2} \d{4}$/.test(identity.pid_started_at)
207
+ ? Date.parse(`${identity.pid_started_at} UTC`) : NaN;
208
+ const uptime = os.uptime();
209
+ const boundary = Date.now() - uptime * 1000 - 26 * 60 * 60 * 1000;
210
+ return Number.isFinite(uptime) && uptime > 0 && Number.isFinite(started) && Number.isFinite(birth) && started < boundary && birth < boundary;
211
+ }
@@ -0,0 +1,314 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { setTimeout as delay } from "node:timers/promises";
7
+ import { AppError, errorRecord } from "../shared/errors.js";
8
+ import { getResourceDatabase } from "../storage/database.js";
9
+ import { assertManagedProcessLease, confirmManagedProcess, finishManagedProcess, renewManagedProcess, processIsAlive, processStartedAt, registerManagedProcess, resourceHome } from "./managed-processes.js";
10
+ import { managedLeaseMonitor } from "./managed-lease-monitor.js";
11
+ import { physicalStartup } from "./physical-startup.js";
12
+ import { publicRuntimeScopeWorker, requestRuntimeScopeControl, runtimeBudgetStatus, prepareRuntimeScopeFence, parseRuntimeBudget } from "./runtime-budget.js";
13
+ import { controlRuntimeScopeRuns } from "./runtime-scope-control.js";
14
+ import { prepareRuntimeScopeResume, recordRuntimeScopeResume } from "./runtime-scope-resume.js";
15
+ import { recoveryObservationBudget, renewRecoveryObservation, withRecoveryObservation } from "./recovery-observation-budget.js";
16
+ export async function launchRuntimeScopeResume(context, input) {
17
+ const resume = recordRuntimeScopeResume(context, input);
18
+ if (!resume.current)
19
+ return { scope_id: input.scopeId, resume, current: false, settled: false };
20
+ if (resume.status === "released")
21
+ return { scope_id: input.scopeId, resume, release: resume.release, current: true, dispatch_blocked: resume.dispatch_blocked, settled: false };
22
+ const control = runtimeBudgetStatus(context, input.scopeId).control;
23
+ if (control?.generation !== input.generation)
24
+ throw new AppError("runtime_scope_control_superseded", "Scope resume source changed before owner launch", 1);
25
+ const owner = await launchRuntimeScopeControl(context, input.scopeId, control.request_id, control.requested_mode, undefined, undefined, input.requestId);
26
+ return { ...owner, resume, dispatch_blocked: true, settled: false };
27
+ }
28
+ const keyFor = (scope, generation) => crypto.createHash("sha256").update(JSON.stringify([scope, generation])).digest("hex");
29
+ function birth() {
30
+ const started = processStartedAt(process.pid);
31
+ if (!started)
32
+ throw new AppError("runtime_scope_owner_unknown", "Scope owner birth identity is unavailable", 1);
33
+ return { pid: process.pid, pid_started_at: started };
34
+ }
35
+ export function prepareRuntimeScopeReconciliation(context, scopeId, generation, requestId) {
36
+ prepareRuntimeScopeFence(scopeId, requestId);
37
+ if (!Number.isSafeInteger(generation) || generation < 1)
38
+ throw new AppError("validation", "Scope reconciliation requires a positive integer generation", 2);
39
+ renewRecoveryObservation({}, requestId);
40
+ const db = getResourceDatabase(resourceHome(context), "read_existing");
41
+ try {
42
+ const row = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control' AND resource_key = ?", [scopeId]);
43
+ const control = row ? JSON.parse(row.metadata_json) : null;
44
+ if (!control || control.generation !== generation)
45
+ throw new AppError("runtime_scope_control_superseded", "Reconciliation requires the current controlled scope generation", 1);
46
+ return control;
47
+ }
48
+ finally {
49
+ db.close?.();
50
+ }
51
+ }
52
+ export async function reconcileRuntimeScopeControl(context, scopeId, generation, requestId) {
53
+ const control = prepareRuntimeScopeReconciliation(context, scopeId, generation, requestId);
54
+ return launchRuntimeScopeControl(context, scopeId, control.request_id, control.requested_mode, undefined, requestId);
55
+ }
56
+ export async function launchRuntimeScopeControl(context, scopeId, requestId, mode, manifest, reconcileId, resumeId, prepared) {
57
+ const control = requestRuntimeScopeControl(context, scopeId, requestId, mode, manifest, prepared), db = getResourceDatabase(resourceHome(context)), key = keyFor(scopeId, control.generation);
58
+ const launcher = birth();
59
+ const publication = (() => {
60
+ for (let attempt = 0;; attempt++) {
61
+ const prior = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ?", [key]);
62
+ const existing = prior ? JSON.parse(prior.metadata_json) : null;
63
+ if (existing) {
64
+ const identity = existing.owner ?? existing.launcher;
65
+ if (!identity || !Number.isSafeInteger(identity.pid) || identity.pid < 1 || typeof identity.pid_started_at !== "string" || !identity.pid_started_at || !["starting", "running", "completed", "failed", "superseded"].includes(existing.status))
66
+ throw new AppError("runtime_scope_owner_unknown", "Retained scope owner has no verifiable birth identity", 1);
67
+ }
68
+ const existingAlive = !!existing && (existing.owner ? processIsAlive(existing.owner) : existing.status !== "failed" && processIsAlive(existing.launcher));
69
+ try {
70
+ return db.writeTransaction(() => {
71
+ const current = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control' AND resource_key = ?", [scopeId]);
72
+ if (db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ?", [key])?.metadata_json !== prior?.metadata_json)
73
+ throw new AppError("runtime_scope_owner_changed", "Scope owner changed while its physical identity was checked", 1);
74
+ if (!current || JSON.parse(current.metadata_json).generation !== control.generation || db.get("SELECT 1 FROM managed_resources WHERE resource_kind = 'provider-fence' AND resource_key = ?", [scopeId]))
75
+ return { current: false, existing };
76
+ if (existing && existingAlive)
77
+ return { current: true, existing };
78
+ if (existing?.status === "completed" && !reconcileId && !resumeId)
79
+ return { current: true, existing };
80
+ const renewal = resumeId ? `resume:${resumeId}` : reconcileId;
81
+ const snapshot = renewal ? renewRecoveryObservation(existing?.snapshot ?? {}, renewal) : existing?.snapshot;
82
+ if (existing && recoveryObservationBudget(snapshot).state().remaining_ms === 0)
83
+ return { current: true, existing };
84
+ const cleanupRequest = resumeId ? undefined : reconcileId ?? existing?.reconcile_id;
85
+ const worker = { scope_id: scopeId, generation: control.generation, request_id: requestId, mode, token: crypto.randomUUID(), status: "starting", launcher, ...(snapshot ? { snapshot } : {}), ...(cleanupRequest ? { reconcile_id: cleanupRequest } : {}) };
86
+ const now = context.now();
87
+ db.run("INSERT INTO managed_resources (resource_kind,resource_key,owner_id,lease_token,lease_expires_at,process_id,metadata_json,created_at,updated_at) VALUES ('scope-control-worker',?,?,?,?,NULL,?,?,?) ON CONFLICT(resource_kind,resource_key) DO UPDATE SET lease_token = excluded.lease_token, metadata_json = excluded.metadata_json, updated_at = excluded.updated_at", [key, scopeId, worker.token, now, JSON.stringify(worker), now, now]);
88
+ return { worker };
89
+ }, "runtimeScope.workerReserve");
90
+ }
91
+ catch (error) {
92
+ // No worker has spawned yet. Recheck physical identity after a racing update.
93
+ if (!(error instanceof AppError) || error.code !== "runtime_scope_owner_changed" || attempt >= 3)
94
+ throw error;
95
+ }
96
+ }
97
+ })();
98
+ if (!publication.worker)
99
+ return { scope_id: scopeId, control, current: publication.current, settled: false, worker: publication.existing ? publicRuntimeScopeWorker(publication.existing) : null };
100
+ const worker = publication.worker;
101
+ try {
102
+ const entry = fileURLToPath(new URL("../cli.js", import.meta.url));
103
+ if (!fs.existsSync(entry))
104
+ throw new AppError("harness_runtime_unbuilt", "Build the CLI before launching scope control", 1);
105
+ const directory = path.join(resourceHome(context), "scope-controls", key);
106
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
107
+ const log = fs.openSync(path.join(directory, "control.log"), "a", 0o600);
108
+ try {
109
+ const env = { ...context.env, DD_FLOW_HOME: context.ddFlowHome, DD_FLOW_RESOURCE_HOME: resourceHome(context), DD_FLOW_ENGINE_MODE: "1", DD_FLOW_SCOPE_CONTROL_TOKEN: worker.token };
110
+ delete env.DD_FLOW_CONTROL_TOKEN;
111
+ delete env.DD_FLOW_CONTROLLER_TOKEN;
112
+ const child = spawn(process.execPath, [entry, "runtime", "scope", "serve", "--scope-id", scopeId, "--generation", String(control.generation), "--json"], { env, detached: true, stdio: ["ignore", log, log] });
113
+ await new Promise((resolve, reject) => { child.once("spawn", resolve); child.once("error", reject); });
114
+ child.unref();
115
+ }
116
+ finally {
117
+ fs.closeSync(log);
118
+ }
119
+ }
120
+ catch (error) {
121
+ db.run("UPDATE managed_resources SET metadata_json = json_set(metadata_json, '$.status', 'failed', '$.error', ?), updated_at = ? WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ? AND json_extract(metadata_json, '$.owner') IS NULL", [String(error), context.now(), key, worker.token]);
122
+ }
123
+ const retained = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ?", [key]);
124
+ return { scope_id: scopeId, control, current: true, settled: false, worker: publicRuntimeScopeWorker(JSON.parse(retained.metadata_json)) };
125
+ }
126
+ export function prepareRuntimeScopeServing(context, scopeId, generation) {
127
+ parseRuntimeBudget(JSON.stringify({ schema_id: "dd-flow/runtime-budget@1", scope_id: scopeId, per_harness: {} }));
128
+ if (!Number.isSafeInteger(generation) || generation < 1)
129
+ throw new AppError("validation", "Scope worker requires a positive integer generation", 2);
130
+ const token = context.env.DD_FLOW_SCOPE_CONTROL_TOKEN;
131
+ if (!token)
132
+ throw new AppError("runtime_scope_owner_unknown", "Scope worker requires its launch token", 1);
133
+ const key = keyFor(scopeId, generation), db = getResourceDatabase(resourceHome(context), "read_existing");
134
+ try {
135
+ requireStartingScopeWorker(db, key, token);
136
+ }
137
+ finally {
138
+ db.close?.();
139
+ }
140
+ return { key, token };
141
+ }
142
+ function requireStartingScopeWorker(db, key, token, row = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ?", [key, token])) {
143
+ if (!row)
144
+ throw new AppError("runtime_scope_owner_unknown", "Scope worker claim changed", 1);
145
+ const worker = JSON.parse(row.metadata_json);
146
+ if (worker.owner || worker.status !== "starting")
147
+ throw new AppError("runtime_scope_already_owned", "Scope control already has a physical owner", 1);
148
+ return worker;
149
+ }
150
+ export async function serveRuntimeScopeControl(context, scopeId, generation) {
151
+ const { key, token } = prepareRuntimeScopeServing(context, scopeId, generation);
152
+ const db = getResourceDatabase(resourceHome(context));
153
+ const startup = physicalStartup(context.databaseDeadlineAt);
154
+ let claimedOwner = undefined;
155
+ const assertStartupCurrent = () => {
156
+ const control = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control' AND resource_key = ?", [scopeId]);
157
+ const fence = db.get("SELECT 1 FROM managed_resources WHERE resource_kind = 'provider-fence' AND resource_key = ?", [scopeId]);
158
+ const row = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ?", [key, token]);
159
+ const current = row ? JSON.parse(row.metadata_json) : null;
160
+ if (!control || JSON.parse(control.metadata_json).generation !== generation || fence || !current || current.token !== token || current.status !== (claimedOwner ? "running" : "starting") || current.owner?.pid !== claimedOwner?.pid || current.owner?.pid_started_at !== claimedOwner?.pid_started_at)
161
+ throw new AppError("runtime_scope_control_superseded", "Scope startup no longer owns this control", 1);
162
+ };
163
+ const starting = db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ?", [key, token]);
164
+ let worker = requireStartingScopeWorker(db, key, token, starting);
165
+ let retainedMetadata = starting.metadata_json;
166
+ let claimedByThisServe = false;
167
+ const persistWorker = () => {
168
+ const next = JSON.stringify(worker);
169
+ if (db.run("UPDATE managed_resources SET metadata_json = ?, updated_at = ? WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ? AND metadata_json = ?", [next, context.now(), key, token, retainedMetadata]).changes !== 1)
170
+ throw new AppError("runtime_scope_owner_unknown", "Scope owner changed before its retained result could be saved", 1);
171
+ retainedMetadata = next;
172
+ };
173
+ let heartbeat;
174
+ let lease;
175
+ // A fence must never be held indefinitely by an observer that cannot obtain
176
+ // fresh provider evidence. A later operator request continues the retained
177
+ // budget; this owner records why it stopped without claiming settlement.
178
+ let observationCleanup;
179
+ let cleanupFailure;
180
+ const result = await (async () => {
181
+ try {
182
+ const owner = await startup.birth(assertStartupCurrent);
183
+ const claim = db.writeTransaction(() => {
184
+ assertStartupCurrent();
185
+ const current = requireStartingScopeWorker(db, key, token);
186
+ current.owner = owner;
187
+ current.status = "running";
188
+ db.run("UPDATE managed_resources SET metadata_json = ?, updated_at = ? WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ?", [JSON.stringify(current), context.now(), key, token]);
189
+ return { worker: current, metadata: JSON.stringify(current) };
190
+ });
191
+ worker = claim.worker;
192
+ retainedMetadata = claim.metadata;
193
+ claimedOwner = owner;
194
+ claimedByThisServe = true;
195
+ const observation = observationCleanup = recoveryObservationBudget(worker.snapshot);
196
+ worker.snapshot = { ...(worker.snapshot ?? {}), recovery_observation: observation.state() };
197
+ const record = registerManagedProcess(context, { kind: "scope-control", ownerId: key, operationId: `scope-control:${key}:${token}`, ownerPid: process.pid, metadata: { role: "control", scope_id: scopeId, generation } });
198
+ worker.process_id = record.id;
199
+ worker.process_lease_token = record.lease_token;
200
+ const confirmed = await startup.observe(() => confirmManagedProcess(context, { id: record.id, leaseToken: record.lease_token, pid: process.pid, processGroupId: process.pid, ownerPid: process.pid }), assertStartupCurrent, record.lease_expires_at);
201
+ persistWorker();
202
+ lease = managedLeaseMonitor(attemptMs => renewManagedProcess({ ...context, resourceDatabaseMode: "prepared_existing", databaseDeadlineAt: Date.now() + attemptMs }, { id: record.id, leaseToken: record.lease_token }), confirmed.lease_expires_at);
203
+ let timerFailure;
204
+ const assertScopeCurrent = () => {
205
+ const latest = runtimeBudgetStatus(context, scopeId);
206
+ if (latest.control?.generation !== generation)
207
+ throw new AppError("runtime_scope_control_superseded", "Scope control was superseded during renewal", 1);
208
+ };
209
+ const ownedContext = { ...context, assertControlOwner: () => {
210
+ lease.assertNoRenewal();
211
+ assertManagedProcessLease(context, { id: record.id, leaseToken: record.lease_token, pid: process.pid });
212
+ assertScopeCurrent();
213
+ } };
214
+ heartbeat = setInterval(() => { lease.background(assertScopeCurrent, error => { timerFailure = error; }); }, 10_000);
215
+ heartbeat.unref();
216
+ for (;;) {
217
+ if (timerFailure)
218
+ throw timerFailure;
219
+ await lease.ensureConfirmed(assertScopeCurrent);
220
+ const status = runtimeBudgetStatus(context, scopeId);
221
+ const release = status.release;
222
+ if (release?.current === true && release.generation === generation) {
223
+ worker.status = "completed";
224
+ worker.snapshot = release;
225
+ break;
226
+ }
227
+ if (status.control?.generation !== generation || status.fence) {
228
+ worker.status = "superseded";
229
+ break;
230
+ }
231
+ observation.observe(status);
232
+ if (observation.exhausted()) {
233
+ const final = runtimeBudgetStatus(context, scopeId);
234
+ const terminal = final.release;
235
+ if (terminal?.current === true && terminal.generation === generation) {
236
+ worker.status = "completed";
237
+ worker.snapshot = terminal;
238
+ break;
239
+ }
240
+ if (final.control?.generation !== generation || final.fence) {
241
+ worker.status = "superseded";
242
+ break;
243
+ }
244
+ worker.status = "failed";
245
+ worker.error = "recovery_observation_budget_exhausted";
246
+ worker.snapshot = {
247
+ ...(worker.snapshot ?? {}),
248
+ status: "recovery_blocked",
249
+ settled: false,
250
+ pending_reasons: ["recovery_observation_budget_exhausted"],
251
+ error: { code: "recovery_observation_budget_exhausted", message: "Scope recovery observation exceeded its 120 second budget" },
252
+ recovery_observation: observation.state(),
253
+ };
254
+ return { scope_id: scopeId, generation, status: "recovery_blocked", settled: false, error: worker.error };
255
+ }
256
+ const resume = status.resume;
257
+ const outcome = await withRecoveryObservation(observation, async () => (worker.reconcile_id ? null : await prepareRuntimeScopeResume(ownedContext, scopeId, generation, resume?.generation === generation ? resume.request_id : undefined))
258
+ ?? await controlRuntimeScopeRuns(ownedContext, scopeId, worker.request_id, worker.mode, worker.reconcile_id));
259
+ await lease.ensureConfirmed(assertScopeCurrent);
260
+ observation.observe(outcome);
261
+ worker.snapshot = { ...outcome, recovery_observation: observation.state() };
262
+ persistWorker();
263
+ if (worker.snapshot.drain?.physical_settled === true) {
264
+ worker.status = "completed";
265
+ break;
266
+ }
267
+ await delay(observation.nextDelay());
268
+ }
269
+ return { scope_id: scopeId, generation, status: worker.status };
270
+ }
271
+ catch (error) {
272
+ if (error instanceof AppError && error.code === "runtime_scope_control_superseded") {
273
+ const release = runtimeBudgetStatus(context, scopeId).release;
274
+ worker.status = release?.current === true && release.generation === generation ? "completed" : "superseded";
275
+ if (worker.status === "completed")
276
+ worker.snapshot = release;
277
+ return { scope_id: scopeId, generation, status: worker.status };
278
+ }
279
+ worker.status = "failed";
280
+ worker.error = String(error);
281
+ worker.snapshot = { ...(worker.snapshot ?? {}), error: errorRecord(error) };
282
+ throw error;
283
+ }
284
+ finally {
285
+ lease?.close();
286
+ if (heartbeat)
287
+ clearInterval(heartbeat);
288
+ observationCleanup?.exhausted();
289
+ worker.snapshot = { ...(worker.snapshot ?? {}), ...(observationCleanup ? { recovery_observation: observationCleanup.state() } : {}) };
290
+ let persistenceFailure;
291
+ try {
292
+ persistWorker();
293
+ }
294
+ catch (error) {
295
+ persistenceFailure = error;
296
+ process.stderr.write(`${JSON.stringify({ primary: worker.error, primary_error: worker.snapshot.error, phase: "persist_scope_owner", secondary: errorRecord(error) })}\n`);
297
+ }
298
+ try {
299
+ const current = claimedByThisServe && db.get("SELECT metadata_json FROM managed_resources WHERE resource_kind = 'scope-control-worker' AND resource_key = ? AND lease_token = ?", [key, token]);
300
+ if (current && current.metadata_json === retainedMetadata && !persistenceFailure && worker.process_id && worker.process_lease_token)
301
+ finishManagedProcess(context, { id: worker.process_id, leaseToken: worker.process_lease_token, state: worker.status === "failed" ? "failed" : "stopped", reason: "scope_owner_exited" });
302
+ }
303
+ catch (error) {
304
+ cleanupFailure = error;
305
+ process.stderr.write(`${JSON.stringify({ primary: worker.error ?? String(persistenceFailure), primary_error: worker.snapshot.error ?? (persistenceFailure === undefined ? undefined : errorRecord(persistenceFailure)), phase: "finish_scope_owner", secondary: errorRecord(error) })}\n`);
306
+ }
307
+ if (persistenceFailure)
308
+ cleanupFailure = persistenceFailure;
309
+ }
310
+ })();
311
+ if (cleanupFailure !== undefined && !worker.error)
312
+ throw cleanupFailure;
313
+ return result;
314
+ }
@@ -2,25 +2,29 @@ import fs from "node:fs";
2
2
  import crypto from "node:crypto";
3
3
  import path from "node:path";
4
4
  import { AppError } from "../shared/errors.js";
5
+ import { validateObservationDuration } from "../harness-runtime/lib/observation-clock.mjs";
6
+ import { assertRunMutationAllowed } from "./run-recovery.js";
5
7
  import { requireProjectByRoot } from "./projects.js";
6
8
  import { resolveRun } from "./runs.js";
7
9
  import { codeExecutionEnvironment, runCheck } from "./code-checks.js";
8
- import { confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessStatus, processTreeIsAlive, registerManagedProcess, reservePorts, resourceHome, stopManagedProcess } from "./managed-processes.js";
9
- /** Foreground supervisor: the tool may yield while this command owns the service.
10
- * Its ready receipt is emitted immediately; a separate stop ends this command.
11
- * Readiness is infrastructure evidence, never a successful scenario verdict. */
12
- export async function startRuntimeService(context, input) {
13
- if (!Number.isFinite(input.timeoutMs) || input.timeoutMs <= 0)
14
- throw new AppError("validation", "Readiness timeout must be positive and finite", 2);
15
- if (!input.command.trim() || !input.ports.length || input.ports.some(name => !/^[a-z][a-z0-9_]*$/.test(name)) || new Set(input.ports).size !== input.ports.length || !input.ports.includes(input.readyPort) || !input.readyPath.startsWith("/") || input.readyPath.startsWith("//"))
10
+ import { retainedBrowserEnvironment } from "./check-environment.js";
11
+ import { confirmManagedProcess, finishManagedProcess, renewManagedProcess, managedProcessStatus, processTreeIsAlive, registerManagedProcess, reservePorts, resourceHome, stopManagedProcess } from "./managed-processes.js";
12
+ export function prepareRuntimeService(context, input) {
13
+ validateObservationDuration(input.timeoutMs);
14
+ if (typeof input.command !== "string" || !input.command.trim() || !Array.isArray(input.ports) || !input.ports.length || input.ports.some(name => typeof name !== "string" || !/^[a-z][a-z0-9_]*$/.test(name)) || new Set(input.ports).size !== input.ports.length || !input.ports.includes(input.readyPort) || typeof input.readyPath !== "string" || !input.readyPath.startsWith("/") || input.readyPath.startsWith("//"))
16
15
  throw new AppError("validation", "Declare unique lowercase port names, a ready-port from that list and a local /ready-path", 2);
17
16
  const project = requireProjectByRoot(context, path.resolve(input.projectRoot));
18
17
  const run = resolveRun(context, project.id, input.runId);
19
18
  if (!run?.run_root || !run.workspace_root)
20
19
  throw new AppError("not_found", "Registered RUN with a materialized workspace is required", 2);
20
+ const generation = assertRunMutationAllowed(context, project.id, run.id);
21
+ return { project, run, runHome: run.run_root, generation };
22
+ }
23
+ export async function startRuntimeService(context, input) {
24
+ const { project, run, runHome, generation } = prepareRuntimeService(context, input);
21
25
  input = { ...input, runId: run.id };
22
26
  const id = `PROC-${crypto.randomUUID()}`;
23
- const directory = path.join(run.run_root, "runtime", id);
27
+ const directory = path.join(runHome, "runtime", id);
24
28
  fs.mkdirSync(directory, { recursive: true });
25
29
  const record = registerManagedProcess(context, { id, kind: "runtime-service", ownerId: `run:${input.runId}`, ownerPid: process.pid, runId: input.runId, projectId: project.id, stdoutPath: path.join(directory, "stdout.log"), stderrPath: path.join(directory, "stderr.log"), metadata: { command: input.command } });
26
30
  const receiptPath = path.join(directory, "service.json");
@@ -30,22 +34,23 @@ export async function startRuntimeService(context, input) {
30
34
  const save = () => { const temporary = `${receiptPath}.${process.pid}.tmp`; fs.writeFileSync(temporary, JSON.stringify(receipt, null, 2)); fs.renameSync(temporary, receiptPath); };
31
35
  try {
32
36
  const allocation = await reservePorts(context, { ownerId: record.owner_id, processId: record.id, names: input.ports });
33
- const environment = { ...codeExecutionEnvironment(run.workspace_root), ...Object.fromEntries(Object.entries(allocation.ports).map(([name, port]) => [`DD_FLOW_PORT_${name.toUpperCase()}`, String(port)])), DD_FLOW_EVIDENCE_DIR: directory, DD_FLOW_CHECK_COMPLETION_FILE: path.join(directory, "completion.json") };
37
+ const environment = { ...codeExecutionEnvironment(run.workspace_root, retainedBrowserEnvironment(context.env)), ...Object.fromEntries(Object.entries(allocation.ports).map(([name, port]) => [`DD_FLOW_PORT_${name.toUpperCase()}`, String(port)])), DD_FLOW_EVIDENCE_DIR: directory, DD_FLOW_CHECK_COMPLETION_FILE: path.join(directory, "completion.json") };
34
38
  const quoted = (value) => `'${value.replaceAll("'", "'\\''")}'`;
35
39
  Object.assign(receipt, { ports: allocation.ports, environment: Object.fromEntries(Object.entries(environment).filter(([key]) => key.startsWith("DD_FLOW_PORT_"))), stop_command: `DD_FLOW_HOME=${quoted(context.ddFlowHome)} DD_FLOW_RESOURCE_HOME=${quoted(resourceHome(context))} dd-flow runtime process stop --id ${record.id} --lease-token ${record.lease_token} --json` });
36
40
  save();
41
+ input.progress?.(`service ${record.id} preparing; receipt: ${receiptPath}`);
42
+ assertRunMutationAllowed(context, project.id, run.id, generation);
37
43
  stdout = fs.openSync(String(receipt.stdout_path), "a");
38
44
  stderr = fs.openSync(String(receipt.stderr_path), "a");
39
- const running = runCheck(input.command, run.workspace_root, environment, stdout, stderr, elapsed => input.progress?.(`service ${record.id} alive (${elapsed}s); receipt: ${receiptPath}`), pid => confirmManagedProcess(context, { id: record.id, leaseToken: record.lease_token, pid, processGroupId: process.platform === "win32" ? null : pid }), () => heartbeatManagedProcess(context, { id: record.id, leaseToken: record.lease_token }));
45
+ const running = runCheck(input.command, run.workspace_root, environment, stdout, stderr, elapsed => input.progress?.(`service ${record.id} alive (${elapsed}s); receipt: ${receiptPath}`), pid => confirmManagedProcess(context, { id: record.id, leaseToken: record.lease_token, pid, processGroupId: process.platform === "win32" ? null : pid }), attemptMs => renewManagedProcess({ ...context, resourceDatabaseMode: "prepared_existing", databaseDeadlineAt: Date.now() + attemptMs }, { id: record.id, leaseToken: record.lease_token }), { inactivityTimeoutMs: input.timeoutMs, phase: () => ready ? "serving" : "startup" });
40
46
  void running.then(() => { settled = true; });
41
- const deadline = performance.now() + input.timeoutMs;
42
- while (!settled && performance.now() < deadline) {
47
+ while (!settled) {
43
48
  try {
44
49
  const response = await fetch(`http://127.0.0.1:${allocation.ports[input.readyPort]}${input.readyPath}`, { signal: AbortSignal.timeout(1_000), redirect: "error" });
45
50
  await response.body?.cancel();
46
51
  ready = response.ok;
47
52
  }
48
- catch { /* Startup is observed until the declared deadline. */ }
53
+ catch { /* Only actual startup output advances the owned command clock. */ }
49
54
  if (ready)
50
55
  break;
51
56
  await new Promise(resolve => setTimeout(resolve, 100));