@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.139

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 (283) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +9 -0
  3. package/dist/build-info.json +6 -6
  4. package/dist/cli/argument-values.js +5 -5
  5. package/dist/cli/command-inputs.js +89 -20
  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 +240 -6
  15. package/dist/cli/input-preparation.js +54 -5
  16. package/dist/cli/memory-flow-admission.js +44 -0
  17. package/dist/cli/memory-init-invocation.js +78 -0
  18. package/dist/cli/memory-init.js +240 -0
  19. package/dist/cli/memory-storage-admission.js +53 -0
  20. package/dist/cli/models.js +39 -0
  21. package/dist/cli/native-hook-ingress.js +1 -1
  22. package/dist/cli/operator-profiles.js +61 -0
  23. package/dist/cli/output-options.js +35 -0
  24. package/dist/cli/response-file.js +71 -0
  25. package/dist/cli/run-cli.js +283 -160
  26. package/dist/cli/runtime-process.js +1 -1
  27. package/dist/cli/setup.js +149 -0
  28. package/dist/cli.js +56 -2
  29. package/dist/domain/stage-handler-identities.js +20 -0
  30. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  31. package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
  32. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +53 -22
  33. package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
  34. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +12 -6
  35. package/dist/harness-runtime/lib/dd-codex.mjs +24 -12
  36. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
  37. package/dist/harness-runtime/lib/dd-droid.mjs +32 -9
  38. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +11 -3
  39. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +100 -15
  40. package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
  41. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +22 -10
  42. package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
  43. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  44. package/dist/harness-runtime/lib/delegation-instructions.mjs +16 -3
  45. package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
  46. package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
  47. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
  48. package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
  49. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  50. package/dist/harness-runtime/lib/native-children.d.mts +2 -0
  51. package/dist/harness-runtime/lib/native-children.mjs +20 -1
  52. package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
  53. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  54. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  55. package/dist/harness-runtime/lib/opencode-plugin.mjs +32 -9
  56. package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
  57. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  58. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  59. package/dist/runtime/context.js +1 -1
  60. package/dist/schemas/discovery-result.schema.json +184 -0
  61. package/dist/schemas/execution-bundle.schema.json +205 -0
  62. package/dist/schemas/execution-settings.schema.json +162 -0
  63. package/dist/schemas/flow-descriptor.schema.json +54 -0
  64. package/dist/schemas/flow-run.schema.json +26 -0
  65. package/dist/schemas/harness-catalog.schema.json +80 -0
  66. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  67. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  68. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  69. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  70. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  71. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  72. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  73. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  74. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  75. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  76. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  77. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  78. package/dist/schemas/model-catalog.schema.json +153 -0
  79. package/dist/schemas/profile-settings.schema.json +122 -0
  80. package/dist/schemas/release-manifest.schema.json +291 -0
  81. package/dist/schemas/run-engine-binding.schema.json +1 -1
  82. package/dist/services/check-runner-evidence.js +51 -4
  83. package/dist/services/cli-operation-classifier.js +8 -47
  84. package/dist/services/cli-upgrade-allowlist.js +27 -0
  85. package/dist/services/code-checks.js +387 -34
  86. package/dist/services/controller-child-capacity.js +13 -0
  87. package/dist/services/controller-child-observer.js +18 -2
  88. package/dist/services/controller-execution.js +88 -0
  89. package/dist/services/controller-fanout.js +71 -12
  90. package/dist/services/dashboard.js +8 -2
  91. package/dist/services/descriptor-coordinator-session.js +35 -0
  92. package/dist/services/descriptor-root-work.js +53 -0
  93. package/dist/services/descriptor-run-settlement.js +105 -0
  94. package/dist/services/descriptor-run-start.js +449 -0
  95. package/dist/services/descriptor-stage-coordinator.js +197 -0
  96. package/dist/services/descriptor-stage-settlement.js +222 -0
  97. package/dist/services/descriptor-stage-start.js +139 -0
  98. package/dist/services/discovery/agy-discovery.js +2 -0
  99. package/dist/services/discovery/codex-discovery.js +2 -0
  100. package/dist/services/discovery/droid-discovery.js +2 -0
  101. package/dist/services/discovery/grok-discovery.js +2 -0
  102. package/dist/services/discovery/opencode-discovery.js +2 -0
  103. package/dist/services/discovery/static.js +31 -0
  104. package/dist/services/discovery/zcode-discovery.js +2 -0
  105. package/dist/services/engines.js +39 -22
  106. package/dist/services/eval-snapshots.js +83 -25
  107. package/dist/services/execution-bundles.js +263 -0
  108. package/dist/services/execution-policy.js +20 -16
  109. package/dist/services/executor-onboarding.js +56 -0
  110. package/dist/services/external-work-launch.js +93 -15
  111. package/dist/services/flow-catalog.js +21 -0
  112. package/dist/services/flow-descriptors.js +82 -0
  113. package/dist/services/frozen-engine.js +250 -0
  114. package/dist/services/generic-execution.js +164 -0
  115. package/dist/services/harness-adapter.js +28 -2
  116. package/dist/services/harness-discovery.js +29 -0
  117. package/dist/services/harness-doctor.js +75 -0
  118. package/dist/services/harness-probe-effects.js +51 -0
  119. package/dist/services/harness-version-probe.js +100 -0
  120. package/dist/services/hooks.js +24 -6
  121. package/dist/services/hybrid-contracts.js +483 -0
  122. package/dist/services/ids.js +29 -1
  123. package/dist/services/installation-lock.js +95 -0
  124. package/dist/services/installed-flow-source.js +183 -0
  125. package/dist/services/lifecycle-invocations.js +257 -78
  126. package/dist/services/managed-processes.js +31 -10
  127. package/dist/services/memory-accepted-composition.js +77 -0
  128. package/dist/services/memory-baseline-preservation.js +65 -0
  129. package/dist/services/memory-completed-correction-wave.js +320 -0
  130. package/dist/services/memory-compose-handler.js +258 -0
  131. package/dist/services/memory-composition-acceptance.js +199 -0
  132. package/dist/services/memory-composition-graph.js +134 -0
  133. package/dist/services/memory-composition-registration.js +146 -0
  134. package/dist/services/memory-composition-work-custody.js +151 -0
  135. package/dist/services/memory-composition-work-results.js +96 -0
  136. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  137. package/dist/services/memory-corrected-review-batch.js +30 -0
  138. package/dist/services/memory-corrected-review-findings.js +106 -0
  139. package/dist/services/memory-corrected-review-inputs.js +210 -0
  140. package/dist/services/memory-corrected-review-projection.js +78 -0
  141. package/dist/services/memory-corrected-review-receipt.js +170 -0
  142. package/dist/services/memory-corrected-review-registration.js +191 -0
  143. package/dist/services/memory-correction-check-receipt.js +219 -0
  144. package/dist/services/memory-correction-input-owner.js +6 -0
  145. package/dist/services/memory-discover-handler.js +104 -0
  146. package/dist/services/memory-finish-evidence.js +435 -0
  147. package/dist/services/memory-finish-handler.js +215 -0
  148. package/dist/services/memory-finish-integration.js +280 -0
  149. package/dist/services/memory-finish-readiness.js +122 -0
  150. package/dist/services/memory-git-integration.js +151 -0
  151. package/dist/services/memory-historical-corrected-review.js +206 -0
  152. package/dist/services/memory-historical-original-review.js +130 -0
  153. package/dist/services/memory-historical-repair-registration.js +99 -0
  154. package/dist/services/memory-historical-second-review.js +128 -0
  155. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  156. package/dist/services/memory-init-contexts.js +89 -0
  157. package/dist/services/memory-init-controller-start.js +84 -0
  158. package/dist/services/memory-init-engine-release.js +223 -0
  159. package/dist/services/memory-init-git-policy.js +136 -0
  160. package/dist/services/memory-init-lint-capability.js +185 -0
  161. package/dist/services/memory-init-native-capacity.js +296 -0
  162. package/dist/services/memory-init-preflight.js +330 -0
  163. package/dist/services/memory-init-project-bootstrap.js +599 -0
  164. package/dist/services/memory-init-request-candidate.js +157 -0
  165. package/dist/services/memory-init-request-input.js +66 -0
  166. package/dist/services/memory-init-resource-storage.js +125 -0
  167. package/dist/services/memory-init-run-allocation.js +81 -0
  168. package/dist/services/memory-init-run-bundle.js +98 -0
  169. package/dist/services/memory-init-start.js +181 -0
  170. package/dist/services/memory-init-startup-custody.js +80 -0
  171. package/dist/services/memory-init-startup-input.js +240 -0
  172. package/dist/services/memory-init-storage.js +81 -0
  173. package/dist/services/memory-init-stores.js +359 -0
  174. package/dist/services/memory-init-worktrunk-capability.js +178 -0
  175. package/dist/services/memory-layer-publication.js +611 -0
  176. package/dist/services/memory-layer.js +156 -0
  177. package/dist/services/memory-lint-config.js +85 -0
  178. package/dist/services/memory-native-review-copy.js +165 -0
  179. package/dist/services/memory-normal-flow-entry.js +75 -0
  180. package/dist/services/memory-ordinary-defaults.js +112 -0
  181. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  182. package/dist/services/memory-preflight-handler.js +259 -0
  183. package/dist/services/memory-repair-current-view.js +299 -0
  184. package/dist/services/memory-repair-document-delta.js +451 -0
  185. package/dist/services/memory-repair-history.js +491 -0
  186. package/dist/services/memory-repair-physical-binding.js +312 -0
  187. package/dist/services/memory-repair-publication-files.js +152 -0
  188. package/dist/services/memory-repair-publication-intent.js +27 -0
  189. package/dist/services/memory-repair-publication-plan.js +160 -0
  190. package/dist/services/memory-repair-publication-source.js +225 -0
  191. package/dist/services/memory-repair-publication.js +794 -0
  192. package/dist/services/memory-repair-registration.js +239 -0
  193. package/dist/services/memory-repair-work-baseline.js +413 -0
  194. package/dist/services/memory-repair-work-completion.js +97 -0
  195. package/dist/services/memory-repair-work-custody.js +73 -0
  196. package/dist/services/memory-repair-work-results.js +89 -0
  197. package/dist/services/memory-repair-workspace.js +252 -0
  198. package/dist/services/memory-repair-writer-copy.js +735 -0
  199. package/dist/services/memory-result-binding.js +173 -0
  200. package/dist/services/memory-retained-review-authority.js +129 -0
  201. package/dist/services/memory-retained-review-results.js +70 -0
  202. package/dist/services/memory-retained-second-baseline.js +102 -0
  203. package/dist/services/memory-retained-second-repairs.js +156 -0
  204. package/dist/services/memory-review-acceptance.js +214 -0
  205. package/dist/services/memory-review-correction-plan.js +171 -0
  206. package/dist/services/memory-review-handler.js +585 -0
  207. package/dist/services/memory-review-inputs.js +200 -0
  208. package/dist/services/memory-review-receipt.js +180 -0
  209. package/dist/services/memory-review-registration.js +108 -0
  210. package/dist/services/memory-review-work-custody.js +124 -0
  211. package/dist/services/memory-review-work-input.js +72 -0
  212. package/dist/services/memory-review-work-results.js +73 -0
  213. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  214. package/dist/services/memory-second-correction-plan.js +76 -0
  215. package/dist/services/memory-second-repair-baseline.js +91 -0
  216. package/dist/services/memory-second-repair-launch.js +614 -0
  217. package/dist/services/memory-second-repair-registration.js +88 -0
  218. package/dist/services/memory-second-repair-source.js +187 -0
  219. package/dist/services/memory-second-review-acceptance.js +115 -0
  220. package/dist/services/memory-source-observations.js +117 -0
  221. package/dist/services/memory-stage-dispatch.js +28 -0
  222. package/dist/services/memory-stage-results.js +199 -0
  223. package/dist/services/memory-verification-acceptance.js +135 -0
  224. package/dist/services/memory-verification-checks.js +252 -0
  225. package/dist/services/memory-verification-receipt.js +159 -0
  226. package/dist/services/memory-verify-handler.js +135 -0
  227. package/dist/services/memory-work-result-preparation.js +63 -0
  228. package/dist/services/merge-server.js +2 -4
  229. package/dist/services/model-catalog.js +109 -0
  230. package/dist/services/model-discovery.js +46 -0
  231. package/dist/services/operator-profiles.js +236 -0
  232. package/dist/services/projects.js +17 -2
  233. package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
  234. package/dist/services/recovery-snapshot-database.js +417 -2
  235. package/dist/services/release-archive-producer.js +52 -0
  236. package/dist/services/release-archive.js +143 -0
  237. package/dist/services/release-cache.js +142 -0
  238. package/dist/services/release-compatibility.js +18 -0
  239. package/dist/services/release-download.js +67 -0
  240. package/dist/services/release-envelope.js +104 -0
  241. package/dist/services/release-manifest.js +45 -0
  242. package/dist/services/release-payload.js +114 -0
  243. package/dist/services/release-producer.js +63 -0
  244. package/dist/services/release-provenance.js +160 -0
  245. package/dist/services/release-registry.js +66 -0
  246. package/dist/services/release-source.js +194 -0
  247. package/dist/services/repair-intents.js +1 -1
  248. package/dist/services/review-copy.js +10 -2
  249. package/dist/services/run-control.js +80 -9
  250. package/dist/services/run-controller-adapter.js +1 -1
  251. package/dist/services/run-controller-capture.js +10 -3
  252. package/dist/services/run-controller-process.js +21 -0
  253. package/dist/services/run-controller-recovery.js +5 -5
  254. package/dist/services/run-controller-state.js +1 -1
  255. package/dist/services/run-controller.js +158 -64
  256. package/dist/services/run-recovery-guard.js +5 -3
  257. package/dist/services/run-recovery-runtime.js +3 -3
  258. package/dist/services/run-recovery.js +145 -5
  259. package/dist/services/run-start-custody.js +177 -0
  260. package/dist/services/runs.js +413 -30
  261. package/dist/services/setup.js +377 -0
  262. package/dist/services/stage-context.js +9 -1
  263. package/dist/services/stage-handlers.js +74 -0
  264. package/dist/services/stage-report-renderer.js +63 -0
  265. package/dist/services/stage-work-graph.js +17 -0
  266. package/dist/services/vnext-code-review.js +1 -1
  267. package/dist/services/vnext-code.js +6 -6
  268. package/dist/services/vnext-execution-profile.js +2 -2
  269. package/dist/services/vnext-fanout.js +73 -11
  270. package/dist/services/vnext-merge.js +8 -3
  271. package/dist/services/vnext-plan-review.js +7 -5
  272. package/dist/services/vnext-plan.js +3 -3
  273. package/dist/services/vnext-protocolize.js +20 -6
  274. package/dist/services/vnext-workspace-policy.js +7 -2
  275. package/dist/services/work-registry.js +616 -71
  276. package/dist/services/workspace-files.js +1 -1
  277. package/dist/storage/database.js +84 -6
  278. package/dist/storage/writer-contract.js +11 -7
  279. package/package.json +13 -3
  280. package/tools/create-canon-release.mjs +33 -0
  281. package/tools/produce-hybrid-release.mjs +41 -0
  282. package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
  283. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
@@ -93,8 +93,8 @@ export function validateVnextExecutionProfile(value) {
93
93
  || profile.merge_delivery?.strategy !== "local"
94
94
  || !["retain", "delete_after_success"].includes(String(profile.merge_cleanup?.source))
95
95
  || !["code_completed", "code_review_completed", "merge_completed"].includes(String(profile.stop_target))
96
- || !profile.code_bootstrap || typeof profile.code_bootstrap.command !== "string" || !profile.code_bootstrap.command.trim()
97
- || typeof profile.code_bootstrap.policy_ref !== "string" || !profile.code_bootstrap.policy_ref.trim()
96
+ || (profile.code_bootstrap !== undefined && (!profile.code_bootstrap || typeof profile.code_bootstrap.command !== "string" || !profile.code_bootstrap.command.trim()
97
+ || typeof profile.code_bootstrap.policy_ref !== "string" || !profile.code_bootstrap.policy_ref.trim()))
98
98
  || (profile.execution === undefined ? profile.schema_id === "dd-flow/project-execution@3" : !isExecutionRouting(profile.execution))) {
99
99
  throw new AppError("execution_profile_invalid", "Project execution profile does not match its supported dd-flow schema", 1);
100
100
  }
@@ -3,14 +3,17 @@ import path from "node:path";
3
3
  import { AppError } from "../shared/errors.js";
4
4
  import { resolveProjectRoot } from "../storage/paths.js";
5
5
  import { requireProjectByRoot } from "./projects.js";
6
- import { getFlowRunVariables, appendFlowRunTimelineEvent } from "./runs.js";
6
+ import { getFlowRunVariables, appendFlowRunTimelineEvent, readFlowRunExecutionBundle } from "./runs.js";
7
7
  import { resolveExecutionPolicy } from "./execution-policy.js";
8
- import { listWorks, failWork, cancelWork } from "./work-registry.js";
8
+ import { listWorks, failWork, cancelWork, readWorkBatchCustody } from "./work-registry.js";
9
9
  import { storageSessionId } from "./session-identity.js";
10
10
  import { reconcileUnfinishedChecks } from "./code-checks.js";
11
11
  import { vnextStageDirectory } from "../domain/stage-catalog.js";
12
12
  import { stageWorkIds, stageGraphScope } from "./stage-work-graph.js";
13
- import { eligibleCodexChildOverload } from "./codex-capacity.js";
13
+ import { eligibleProviderChildOverload } from "./provider-capacity.js";
14
+ import { renderNativeChildFollowupInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
15
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
16
+ import { selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
14
17
  import { safeNativeFailure } from "../harness-runtime/lib/native-children.mjs";
15
18
  export const subagentCapacityKey = "runtime.subagent.available_slots";
16
19
  /** External concurrency is policy, never evidence of native child qualification. */
@@ -20,17 +23,29 @@ export function getStageFanoutCapacity(context, input) {
20
23
  const run = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
21
24
  if (!run)
22
25
  throw new AppError("not_found", "RUN is unavailable", 1);
23
- const frozen = JSON.parse(run.index_json).execution_profile;
24
- if (frozen?.settings.execution) {
26
+ const index = JSON.parse(run.index_json);
27
+ const bundle = index.execution_bundle ? readFlowRunExecutionBundle(context, { projectRoot, runId: input.runId }) : null;
28
+ const generic = bundle?.execution.work(input.stage).delegation;
29
+ if (generic?.mode === "external")
30
+ return input.nativeOnly
31
+ ? { run_key: null, available_slots: null, source: "harness_qualification" }
32
+ : { run_key: null, available_slots: generic.max_parallel, source: "external_policy" };
33
+ // Generic native admission already requires a scoped capacity receipt whose
34
+ // observed count covers this frozen limit. Mutable legacy variables cannot
35
+ // replace that receipt or silently prevent its productive dispatch.
36
+ if (generic?.mode === "native")
37
+ return { run_key: null, available_slots: generic.max_parallel, source: "harness_qualification" };
38
+ const frozen = index.execution_profile;
39
+ if (!bundle && frozen?.settings.execution) {
25
40
  if (!frozen.agent_profiles)
26
41
  throw new AppError("execution_profile_not_frozen", "Fan-out needs the RUN's frozen profiles", 1);
27
42
  const resolved = resolveExecutionPolicy({ ddFlowHome: context.ddFlowHome, policy: frozen.settings.execution, profiles: frozen.agent_profiles, stage: input.stage });
28
- if (resolved.delegation.mode === "external")
43
+ if (resolved.delegation.mode === "external" && !input.nativeOnly)
29
44
  return { run_key: null, available_slots: resolved.delegation.max_parallel, source: "external_policy" };
30
45
  }
31
46
  const variables = getFlowRunVariables(context, { projectRoot, runId: input.runId });
32
47
  const available = variables.variables[subagentCapacityKey];
33
- return { run_key: subagentCapacityKey, available_slots: typeof available === "number" && Number.isInteger(available) && available >= 0 ? available : null, source: "harness_qualification" };
48
+ return { run_key: subagentCapacityKey, available_slots: typeof available === "number" && Number.isInteger(available) && available >= 0 ? Math.min(available, generic?.max_parallel ?? available) : null, source: "harness_qualification" };
34
49
  }
35
50
  function nativeFailureController(context, projectId, runId, stage, parent) {
36
51
  if (!context.db.get("SELECT 1 FROM sqlite_master WHERE type='table' AND name='run_controllers'"))
@@ -113,17 +128,64 @@ export function readFanoutDescriptor(stageRoot) {
113
128
  }
114
129
  return value;
115
130
  }
131
+ /** Memory graphs are SQL-owned; a mutable/missing fanout.json is not their
132
+ * dispatch authority. Domain registrations and Work launch owners still fence
133
+ * productive assignments, including reviewer/correction waves.
134
+ */
135
+ export function memoryStageFanoutDescriptor(context, input) {
136
+ if (!["compose", "review"].includes(input.stage))
137
+ return null;
138
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
139
+ const run = context.db.get("SELECT flow_kind,index_json FROM runs WHERE project_id=? AND id=?", [project.id, input.runId]);
140
+ if (run?.flow_kind !== "mb-init")
141
+ return null;
142
+ const bundle = readFlowRunExecutionBundle(context, input);
143
+ if (!bundle || bundle.descriptor.descriptor.id !== "mb-init")
144
+ throw new AppError("stage_graph_compatibility_required", "Memory graph requires its admitted RUN bundle", 1);
145
+ const index = JSON.parse(run.index_json);
146
+ if (index.current_stage !== input.stage || !index.stage_runs.some(stage => stage.stage === input.stage && stage.status === "running"))
147
+ return null;
148
+ const root = readDescriptorRootWork(context, { projectId: project.id, runId: input.runId }, bundle);
149
+ const scope = stageGraphScope(context, project.id, input.runId, input.stage), members = stageWorkIds(context, project.id, input.runId, input.stage);
150
+ if (root.status !== "running" || !members.has(root.workId))
151
+ throw new AppError("stage_graph_conflict", "Memory graph lost its active coordinator", 1);
152
+ for (const id of members) {
153
+ if (id === root.workId)
154
+ continue;
155
+ const work = context.db.get("SELECT parent_work_id,result_schema FROM works WHERE project_id=? AND run_id=? AND work_id=?", [project.id, input.runId, id]);
156
+ const schemas = input.stage === "compose" ? ["dd-flow/memory-composition-work-result@1"] : ["dd-flow/memory-review-work-result@1", "dd-flow/memory-repair-work-result@1"];
157
+ if (!work || work.parent_work_id !== root.workId || !schemas.includes(work.result_schema ?? ""))
158
+ throw new AppError("stage_graph_conflict", "Memory graph contains an unassigned Work", 1);
159
+ const custody = readWorkBatchCustody(context, id);
160
+ if (custody.scope.project_id !== scope.project_id || custody.scope.run_id !== scope.run_id || custody.scope.attempt !== scope.attempt
161
+ || custody.scope.cycle !== scope.cycle || custody.scope.stage !== input.stage || custody.scope.parent_work_id !== root.workId)
162
+ throw new AppError("stage_graph_conflict", "Memory graph contains foreign allocation custody", 1);
163
+ }
164
+ if (input.stage === "compose") {
165
+ const children = context.db.all("SELECT work_id FROM works WHERE project_id=? AND run_id=? AND parent_work_id=?", [project.id, input.runId, root.workId]).filter(work => members.has(work.work_id));
166
+ const registered = context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.cycle')=? LIMIT 1", [project.id, input.runId, scope.attempt, scope.cycle]);
167
+ if (children.length || registered) {
168
+ const registration = selectMemoryCompositionRegistration(context, { ...scope, stage: "compose", parent_work_id: root.workId });
169
+ const ids = new Set(Object.values(registration.work_ids));
170
+ if (ids.size !== children.length || children.some(work => !ids.has(work.work_id)))
171
+ throw new AppError("stage_graph_conflict", "Memory composition differs from its registered graph", 1);
172
+ }
173
+ }
174
+ return { schema_id: "dd-flow/work-fanout@2", kind: "work_fanout", scope, stage: input.stage, parent_work_id: root.workId, dispatch: "none", capacity_required: true };
175
+ }
116
176
  /** Read-only projection for an external orchestrator. dd-flow keeps the graph authority. */
117
177
  export function getVnextFanoutStatus(context, input) {
118
178
  const projectRoot = resolveProjectRoot(input.projectRoot);
119
179
  const project = requireProjectByRoot(context, projectRoot);
120
- const run = context.db.get("SELECT run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
180
+ const run = context.db.get("SELECT run_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
121
181
  if (!run)
122
182
  throw new AppError("not_found", "RUN is not registered", 1, { run_id: input.runId });
123
183
  if (!run.run_root)
124
184
  throw new AppError("runtime_artifact_missing", "RUN artifact root is unavailable", 1, { run_id: input.runId });
125
- const stageRoot = path.join(run.run_root, vnextStageDirectory(input.stage));
126
- let descriptor = readFanoutDescriptor(stageRoot);
185
+ const index = JSON.parse(run.index_json);
186
+ const bundle = index.execution_bundle ? readFlowRunExecutionBundle(context, { projectRoot, runId: input.runId }) : null;
187
+ const stageRoot = path.join(run.run_root, bundle ? bundle.descriptor.stage(input.stage).directory : vnextStageDirectory(input.stage));
188
+ let descriptor = memoryStageFanoutDescriptor(context, input) ?? readFanoutDescriptor(stageRoot);
127
189
  // Registration is authoritative even if projection publication was interrupted,
128
190
  // or an otherwise review-off stage acquired an aggregate repair graph.
129
191
  if (!descriptor && context.db.get("SELECT 1 FROM sqlite_master WHERE name = 'repair_intents'")) {
@@ -201,7 +263,7 @@ export async function reconcileVnextFanout(context, input) {
201
263
  continue;
202
264
  }
203
265
  const link = active[0];
204
- if (value.harness_id === "codex-desktop" && eligibleCodexChildOverload(child, value.parent_session_id)) {
266
+ if (renderNativeChildFollowupInstruction(String(value.harness_id), String(child.session_id)) && eligibleProviderChildOverload(child, value.parent_session_id)) {
205
267
  issues.push({ code: "child_capacity_pending", work_id: link.work_id, session_id: child.session_id });
206
268
  continue;
207
269
  }
@@ -232,6 +232,7 @@ export function applyVnextMerge(context, input) {
232
232
  throw new AppError("merge_apply_receipt_invalid", "Existing merge apply receipt does not match the active request", 1, { receipt_path: receiptFile });
233
233
  return receipt;
234
234
  }
235
+ requireBootstrapCommand(requireRun(context, request.project_id, request.run_id));
235
236
  heartbeatLaneLock(context, { projectRoot: input.projectRoot, lane: "merge", workerId: request.executor_work_id, workspacePath: request.target_workspace, ttlSeconds: 7200 });
236
237
  assertExecutionBaseline(request);
237
238
  input.progress?.(`applying ${request.source_commit} to ${request.target_branch}`);
@@ -316,7 +317,7 @@ export async function finishVnextMerge(context, input, prepared = prepareVnextMe
316
317
  if (failed.length) {
317
318
  context.db.run("UPDATE merge_requests SET status = 'action_required', last_error_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "merge_gate_failed", failures: failed }), context.now(), request.merge_request_id]);
318
319
  const sourceRepair = failed.every(receipt => checkRepairDisposition(receipt) === "source_repair");
319
- throw new AppError("merge_gate_failed", "Integrated target checks failed. Only assertion-backed failures permit source repair; tooling or unknown evidence requires diagnosis/settlement first.", 2, { failures: failed, ...(sourceRepair ? { repair_command: repairCommand(context, request, projectRoot) } : { outcome: "blocked", recoverable: false }), continuation: { ...recoveryEvidenceInput(run.run_root, failed[0].id), command_after_input: finishCommand(context, run.id, request, projectRoot, failed[0].id) } });
320
+ throw new AppError("merge_gate_failed", "Integrated target checks failed. Only candidate-source-backed failures permit source repair; tooling or unknown evidence requires diagnosis/settlement first.", 2, { failures: failed, ...(sourceRepair ? { repair_command: repairCommand(context, request, projectRoot), continuation: { ...recoveryEvidenceInput(run.run_root, failed[0].id), command_after_input: finishCommand(context, run.id, request, projectRoot, failed[0].id) } } : { outcome: "blocked", recoverable: false }) });
320
321
  }
321
322
  const passedRefs = new Set(receipts.flatMap((receipt) => receipt.check_refs));
322
323
  const missingRefs = frozenGateRefs(gate).filter((ref) => !passedRefs.has(ref));
@@ -567,10 +568,14 @@ function protocolIds(home) { const root = path.join(home, "03-plan"); if (!fs.ex
567
568
  function workspaceRoute(home) { const file = path.join(home, "02-protocolize", "workspace-route.json"); const value = JSON.parse(fs.readFileSync(file, "utf8")); if (!value.policy?.integration_branch)
568
569
  throw new AppError("workspace_route_missing", "MERGE requires the frozen integration branch", 1, { file }); return { integration_branch: value.policy.integration_branch }; }
569
570
  function executionSettings(run) { return JSON.parse(run.index_json).execution_profile?.settings ?? {}; }
570
- async function runBootstrap(context, run, cwd, mergeId, progress) {
571
+ function requireBootstrapCommand(run) {
571
572
  const command = executionSettings(run).code_bootstrap?.command;
572
573
  if (!command)
573
- throw new AppError("execution_profile_invalid", "MERGE requires a frozen workspace bootstrap command", 2);
574
+ throw new AppError("execution_profile_invalid", "MERGE requires an evidenced bootstrap command in the frozen RUN profile; configure code_bootstrap before starting a code-ready RUN", 2);
575
+ return command;
576
+ }
577
+ async function runBootstrap(context, run, cwd, mergeId, progress) {
578
+ const command = requireBootstrapCommand(run);
574
579
  const result = await runWorkspaceBootstrap(context, { projectId: run.project_id, runId: run.id, runHome: requireHome(run), workspaceRoot: cwd, command, artifactDir: `${stageDir}/${mergeId}/readiness`, progress });
575
580
  if (result.exit_code !== 0 || result.error)
576
581
  throw new AppError("merge_bootstrap_failed", "Integrated target bootstrap failed; inspect its retained logs", 2, { command, ...result });
@@ -1,6 +1,6 @@
1
1
  import { managedLifecycleCommand } from "./lifecycle-invocations.js";
2
2
  import crypto from "node:crypto";
3
- import { withStageSettlement, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion } from "./runs.js";
3
+ import { withStageSettlement, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, readFlowRunExecutionBundle } from "./runs.js";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { AppError } from "../shared/errors.js";
@@ -50,7 +50,7 @@ export function prepareVnextPlanReviewStart(context, input) {
50
50
  return { ...common, state: "terminal", report, outcome };
51
51
  throw new AppError("invalid_work_state", "PLAN-REVIEW is already terminal; corrections are applied in the same review Work and are not re-reviewed automatically", 2, { run_id: run.id, outcome });
52
52
  }
53
- const workspaceRoute = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan-review" });
53
+ const workspaceRoute = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan-review" });
54
54
  if (!fs.existsSync(path.join(planRoot, "stage-report.json")) || !fs.existsSync(batch))
55
55
  throw new AppError("runtime_artifact_missing", "PLAN-REVIEW requires the accepted PLAN report and proposed CODE batch", 1);
56
56
  const requested = requestedMode(run);
@@ -132,6 +132,7 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
132
132
  commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
133
133
  const reportGit = gitFacts(run.workspace_root);
134
134
  const next = codeSuccessor(context, run.id, projectRoot);
135
+ const completionBundle = readFlowRunExecutionBundle(context, { projectRoot, runId: run.id });
135
136
  return withStageSettlement(context, () => {
136
137
  const registered = addWorkBatch(context, { parentWorkId: rootWork, prepared: prepared.codeItems, stage: "code" });
137
138
  const report = reportFor(context, { run, mode: effective, requested, outcome: "off", groups: [], batchChecksum, planChecksum, code: registered, now, projectRoot, flow: flowCommand(context), git: reportGit, ...(next.command ? { nextCommand: next.command } : {}) });
@@ -141,7 +142,7 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
141
142
  const preparedReport = prepareStageReport(report);
142
143
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
143
144
  const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: reportGit };
144
- completeFlowRunStage(context, completionInput, prepareFlowRunStageCompletion(context, completionInput));
145
+ completeFlowRunStage(context, completionInput, prepareFlowRunStageCompletion(context, completionInput, completionBundle));
145
146
  advanceFlowRun(context, { settlementStage: "plan-review", projectRoot, runId: run.id, status: "running", verdict: "plan_review_off", nextAction: "start_code" });
146
147
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "plan_review_opened", mode: effective, outcome: "off" });
147
148
  return { ok: true, run_id: run.id, stage, outcome: "review_off", ...(externalContext ? { external_context: externalContext } : {}), plan_review: { requested_mode: requested, effective_mode: effective, policy_source: policySource(run), reason: "No applicable independent review group." }, next };
@@ -209,7 +210,7 @@ export function dispatchVnextPlanReview(context, input) {
209
210
  }
210
211
  const created = context.db.all("SELECT work_id, task, status, launch_policy, created_at FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id = ?", [project.id, run.id, parent.work_id]);
211
212
  const reviews = groups.map((group) => { const work = created.find((item) => item.task === group.task); if (!work)
212
- throw new AppError("runtime_missing", "Reviewer Work was not created", 1, { group: group.key }); const ready = work.status === "created" && isReviewWorkReady(context, work.work_id); return { group: group.key, work_id: work.work_id, status: work.status, depends_on: group.depends_on, ready, ...(ready ? { start_command: workStartCommand(context, { ...work, project_id: project.id, run_id: run.id }) } : {}) }; });
213
+ throw new AppError("runtime_missing", "Reviewer Work was not created", 1, { group: group.key }); const ready = work.status === "created" && isReviewWorkReady(context, work.work_id); return { group: group.key, work_id: work.work_id, status: work.status, depends_on: group.depends_on, ready, ...(ready ? { start_command: workStartCommand(context, { ...work, project_id: project.id, run_id: run.id }, { preview: true }) } : {}) }; });
213
214
  refreshRunWorkProjection(context, project.id, run.id);
214
215
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "plan_review_dispatched", work_id: parent.work_id, groups: reviews.map((item) => ({ group: item.group, work_id: item.work_id })) });
215
216
  const waves = plannedReviewWaves(groups, capacity.availableSlots);
@@ -329,6 +330,7 @@ function finishPlanReviewOwned(context, input) {
329
330
  const reportGit = gitFacts(run.workspace_root);
330
331
  const next = codeSuccessor(context, run.id, projectRoot);
331
332
  const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", semanticFile: decisionPath });
333
+ const completionBundle = readFlowRunExecutionBundle(context, { projectRoot, runId: run.id });
332
334
  return withStageSettlement(context, () => {
333
335
  const registered = addWorkBatch(context, { parentWorkId: codeParentId, prepared: codeItems, stage: "code" });
334
336
  const now = context.now();
@@ -343,7 +345,7 @@ function finishPlanReviewOwned(context, input) {
343
345
  acceptStagePacket(context, { projectId: project.id, runId: run.id, stage, prepared: decisionReceipt });
344
346
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
345
347
  const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: reportGit };
346
- completeFlowRunStage(context, completionInput, prepareFlowRunStageCompletion(context, completionInput));
348
+ completeFlowRunStage(context, completionInput, prepareFlowRunStageCompletion(context, completionInput, completionBundle));
347
349
  refreshRunWorkProjection(context, project.id, run.id);
348
350
  advanceFlowRun(context, { settlementStage: "plan-review", projectRoot, runId: run.id, status: "running", verdict: "plan_review_accepted", nextAction: "start_code" });
349
351
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "plan_review_accepted", work_id: parent.work_id, code: registered });
@@ -33,7 +33,7 @@ export function prepareVnextPlanStart(context, input) {
33
33
  const project = requireProjectByRoot(context, projectRoot);
34
34
  const run = requireRun(context, projectRoot, input.runId);
35
35
  const home = requireHome(run);
36
- const workspaceRoute = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
36
+ const workspaceRoute = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
37
37
  const protocols = protocolIds(home);
38
38
  if (!protocols.length)
39
39
  throw new AppError("runtime_artifact_missing", "PLAN requires accepted PROTOCOLIZE protocols", 1);
@@ -136,7 +136,7 @@ export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(
136
136
  export function prepareVnextPlanFinish(context, input) {
137
137
  const projectRoot = resolveProjectRoot(input.projectRoot);
138
138
  const run = requireRun(context, projectRoot, input.runId), home = requireHome(run);
139
- requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
139
+ requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
140
140
  const protocols = protocolIds(home);
141
141
  assertProtocolWorkspace(run.workspace_root, protocols);
142
142
  const prepared = prepareVnextPlanArtifacts(context, { projectRoot, workspaceRoot: run.workspace_root, runId: run.id, home, protocols });
@@ -153,7 +153,7 @@ function finishVnextPlanOwned(context, input) {
153
153
  const run = requireRun(context, projectRoot, input.runId);
154
154
  const home = requireHome(run);
155
155
  const root = path.join(home, "03-plan");
156
- requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
156
+ requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
157
157
  const protocols = protocolIds(home);
158
158
  if (!protocols.length)
159
159
  throw new AppError("runtime_artifact_missing", "PLAN requires accepted PROTOCOLIZE protocols", 1);
@@ -10,7 +10,7 @@ import { projectFeatureWorktreePath, resolveProjectRoot } from "../storage/paths
10
10
  import { previewNextEntityId } from "./ids.js";
11
11
  import { registerProtocol } from "./protocols.js";
12
12
  import { requireProjectByRoot } from "./projects.js";
13
- import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, withStageSettlement, gitFacts, rebindFlowRunWorkspace } from "./runs.js";
13
+ import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, withStageSettlement, gitFacts, rebindFlowRunWorkspace, publishRunProjections } from "./runs.js";
14
14
  import { resolveStageTransition, stageExecutionCwd } from "./execution-policy.js";
15
15
  import { validateSchema } from "./schema-validation.js";
16
16
  import { assertStageStartHookEvent, hookSessionIdentity } from "./hooks.js";
@@ -21,6 +21,8 @@ import { loadVnextWorkspacePolicy, requireVnextWorkspaceRoute, vnextRunBranch }
21
21
  import { readVnextSpecifyResult } from "./vnext-specify.js";
22
22
  import { flowCommand, stagePauseCommand, stagePauseCommandTemplate } from "./stage-pause.js";
23
23
  import { acceptStageReport, prepareStageReport, publishStageReport } from "./stage-report-renderer.js";
24
+ import { hybridDigest } from "./hybrid-contracts.js";
25
+ import { assertRunMutationAllowed } from "./run-recovery.js";
24
26
  import { currentStagePrompt, applyExternalStageContext, prepareExternalStageContext, renderRunOperationalContext } from "./stage-context.js";
25
27
  import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
26
28
  import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
@@ -202,7 +204,7 @@ export async function finishVnextProtocolize(context, input) {
202
204
  throw new AppError("protocolize_intent_conflict", "Completed PROTOCOLIZE result differs from accepted input", 1, { run_id: run.id });
203
205
  const currentStage = acceptedIndex.current_stage ?? [...acceptedIndex.stage_runs].reverse().find(item => item.status === "running" || item.status === "done")?.stage;
204
206
  const activeReplay = currentStage === stage && acceptedIndex.status === "running";
205
- const resolvedPolicy = activeReplay ? requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" }) : accepted.mechanical?.workspace_route;
207
+ const resolvedPolicy = activeReplay ? requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" }) : accepted.mechanical?.workspace_route;
206
208
  if (activeReplay) {
207
209
  if (acceptedIndex.verdict !== "protocolized")
208
210
  advanceFlowRun(context, { settlementStage: "protocolize", projectRoot, runId: run.id, status: "running", verdict: "protocolized", nextAction: "start_plan" });
@@ -212,7 +214,7 @@ export async function finishVnextProtocolize(context, input) {
212
214
  return { ok: true, outcome: "protocolized", run_id: run.id, protocol_ids: accepted.semantic.acceptance, workspace_root: run.workspace_root, git_policy: resolvedPolicy, next_action: activeReplay ? "await_plan" : acceptedIndex.next_action, ...("command" in next && next.command ? { next_command: next.command } : {}), next,
213
215
  artifacts: { result: resultFile, report_json: file, report_markdown: path.join(root, "stage-report.md"), report_html: path.join(root, "stage-report.html"), protocols: accepted.semantic.acceptance.map(id => path.join(run.workspace_root, ".memory-bank/protocol", id, "summary.md")) } };
214
216
  }
215
- const resolvedPolicy = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" });
217
+ const resolvedPolicy = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" });
216
218
  const acceptedObligations = acceptedSpecifyObligations(context, projectRoot, run.id, path.join(requireRunHome(run), "01-specify", "specify.json"));
217
219
  let obligations;
218
220
  try {
@@ -699,6 +701,11 @@ function renderPrompt(input) {
699
701
  return ["<work_context>", `- RUN: ${input.runId}`, `- Work: ${input.workId}`, `- Accepted SPECIFY JSON: ${input.specifyPath}`, `- Handoff: ${input.handoff.stage_handoff.effective}`, "</work_context>", "", "<accepted_obligations>", obligationList, "</accepted_obligations>", "", "<frozen_git_policy>", `- route: ${input.policy.route}`, `- integration branch: ${input.policy.integration_branch ?? "not applicable"}`, `- base ref: ${input.policy.base_ref ?? "not applicable"}`, `- feature branch: ${input.policy.feature_branch ?? "not applicable"}`, `- service worktree: ${input.policy.worktree_path ?? "not applicable"}`, `- bootstrap: ${input.policy.bootstrap_status}`, `- decision: ${input.policy.reason}`, "The CLI already created and bootstrapped this workspace at PROTOCOLIZE start. Do not create branches, worktrees, protocol files or Git commits yourself. This transition Work may run from the stable session; edit only the supplied RUN result file.", "</frozen_git_policy>", "", "<catalog>", input.catalog.length ? input.catalog.map((x) => `- ${x}`).join("\n") : "- inactive", "</catalog>", "", "<stage_instructions>", input.template.trim(), "</stage_instructions>", "", "<output_contract>", `Edit only ${input.resultPath}. Keep this exact JSON shape; CLI allocates ids and writes PRT/PSET/feature documents in the already provisioned workspace:\n\n\`\`\`json\n${JSON.stringify(resultTemplate(input.obligations), null, 2)}\n\`\`\``, "obligation_id values and their one-per-obligation entries are prefilled by the CLI. Preserve those IDs exactly; change only member_keys when semantic ownership requires it. The CLI rejects unknown, duplicate or missing obligations, and every member must own at least one AC-*.", "If PROTOCOLIZE itself exposes a material user decision with no reasonable default, do not finish and do not return to SPECIFY. Write the question packet to the named text file, then run the separate standalone lifecycle command below. Do not combine file creation and dd-flow in one shell command:", "```sh", input.pauseCommandTemplate, "```", "Ask the returned user_message and stop. Resume this same PROTOCOLIZE Work using the exact command returned by pause.", `When the answer is incorporated and the result is protocolized, finish:\n\n${input.finishCommand}`, stageLifecycleInstruction, "</output_contract>", ""].join("\n");
700
702
  }
701
703
  function prepareWorkspaceForProtocolize(context, projectRoot, projectId, run, stageRoot) {
704
+ // A committed rebind may have survived a projection crash. Repair only from
705
+ // its SQL authority before considering physical creation again.
706
+ const stored = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, run.id]);
707
+ if (stored && JSON.parse(stored.index_json).workspace_route)
708
+ publishRunProjections(context, projectId, run.id);
702
709
  const receiptPath = path.join(stageRoot, "workspace-route.json");
703
710
  if (fs.existsSync(receiptPath))
704
711
  return { kind: "retained", policy: readWorkspaceRouteReceipt(stageRoot).policy };
@@ -727,7 +734,9 @@ function prepareWorkspaceForProtocolize(context, projectRoot, projectId, run, st
727
734
  throw error;
728
735
  }
729
736
  prepareManagedWorktreeBootstrapSources(projectRoot);
730
- return { kind: "feature", receiptPath, config, stableFacts, sessionMode, branch, create };
737
+ return { kind: "feature", receiptPath, config, stableFacts, sessionMode, branch, create,
738
+ expectedRevision: stored ? JSON.parse(stored.index_json).runtime_revision ?? 0 : 0,
739
+ expectedGeneration: assertRunMutationAllowed(context, projectId, run.id) };
731
740
  }
732
741
  async function provisionWorkspaceForProtocolize(context, projectRoot, run, prepared) {
733
742
  if (prepared.kind === "retained")
@@ -740,9 +749,14 @@ async function provisionWorkspaceForProtocolize(context, projectRoot, run, prepa
740
749
  const { config, stableFacts, sessionMode, branch, create } = prepared;
741
750
  const created = await createManagedWorktree(context, create, create);
742
751
  const bootstrap = await bootstrapManagedWorktree(context, { projectRoot, worktreePath: created.worktreePath });
743
- rebindFlowRunWorkspace(context, { projectRoot, runId: run.id, workspaceRoot: created.worktreePath, reason: "PROTOCOLIZE start provisioned the configured feature-worktree route" });
744
752
  const policy = { route: "feature_worktree", integration_branch: config.workspace.integration_branch, base_ref: stableFacts.head, feature_branch: branch, worktree_path: created.worktreePath, reason: "Project workspace configuration requires a managed feature worktree before durable protocol documents exist.", provision_stage: "protocolize_start", stage_session_mode: sessionMode, provisioned: true, bootstrap_status: bootstrap.status };
745
- writeJson(receiptPath, { schema_id: "dd-flow/vnext-workspace-route@1", policy, created, bootstrap });
753
+ const receipt = { schema_id: "dd-flow/vnext-workspace-route@1", policy,
754
+ created: { worktreePath: created.worktreePath, worktrunk: created.worktrunk, command: [], result: { exit_code: 0, stdout: "", stderr: "" }, response: { workspace_path: created.worktreePath } },
755
+ bootstrap: { status: bootstrap.status, commands: [] },
756
+ proof: { creation_sha256: hybridDigest(created), bootstrap_sha256: hybridDigest(bootstrap) } };
757
+ rebindFlowRunWorkspace(context, { projectRoot, runId: run.id, workspaceRoot: created.worktreePath, routeReceipt: receipt,
758
+ expectedRevision: prepared.expectedRevision, expectedGeneration: prepared.expectedGeneration,
759
+ reason: "PROTOCOLIZE start provisioned the configured feature-worktree route" });
746
760
  return policy;
747
761
  }
748
762
  function readWorkspaceRouteReceipt(root) {
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { AppError } from "../shared/errors.js";
5
- import { gitFacts } from "./runs.js";
5
+ import { gitFacts, publishRunProjections } from "./runs.js";
6
6
  const configRelativePath = path.join(".memory-bank", "dd-flow", "project-workspace.json");
7
7
  /** The runtime reads explicit configuration; it never attempts to infer route from policy prose. */
8
8
  export function loadVnextWorkspacePolicy(projectRoot) {
@@ -53,8 +53,13 @@ export function vnextRunBranch(policy, run) {
53
53
  * The route receipt is the single source of truth after PROTOCOLIZE provisions
54
54
  * the workspace. Every later mutating vNext stage calls this before it opens.
55
55
  */
56
- export function requireVnextWorkspaceRoute(input) {
56
+ export function requireVnextWorkspaceRoute(context, input) {
57
57
  const file = path.join(input.runHome, "02-protocolize", "workspace-route.json");
58
+ const stored = context.db.get("SELECT project_id,index_json FROM runs WHERE project_root = ? AND id = ?", [input.projectRoot, input.runId]);
59
+ // Productive lifecycle readback repairs only SQL-committed projections. The
60
+ // status command never calls this path and cannot mutate historical files.
61
+ if (stored && JSON.parse(stored.index_json).workspace_route)
62
+ publishRunProjections(context, stored.project_id, input.runId);
58
63
  let receipt;
59
64
  try {
60
65
  receipt = JSON.parse(fs.readFileSync(file, "utf8"));