@deksden-com/dd-flow-cli 0.9.0-beta.152 → 0.9.0-beta.156

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 (227) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +274 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +115 -17
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +2 -1
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +15 -5
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +45 -8
  80. package/dist/services/cli-operation-classifier.js +6 -1
  81. package/dist/services/code-checks.js +46 -29
  82. package/dist/services/codex-hook-delivery.js +11 -2
  83. package/dist/services/controller-child-repair.js +95 -0
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +19 -9
  86. package/dist/services/controller-memory-worker.js +6 -4
  87. package/dist/services/controller-result-authority.js +122 -0
  88. package/dist/services/dashboard.js +4 -2
  89. package/dist/services/delivery-access.js +123 -0
  90. package/dist/services/delivery-approval.js +68 -0
  91. package/dist/services/delivery-artifact.js +86 -0
  92. package/dist/services/delivery-checks.js +178 -0
  93. package/dist/services/delivery-execution.js +181 -0
  94. package/dist/services/delivery-intents.js +212 -0
  95. package/dist/services/delivery-operation.js +81 -0
  96. package/dist/services/delivery-policy.js +91 -0
  97. package/dist/services/delivery-procedures.js +43 -0
  98. package/dist/services/delivery-request.js +99 -0
  99. package/dist/services/delivery-result.js +144 -0
  100. package/dist/services/delivery-stage-commands.js +26 -0
  101. package/dist/services/delivery-stage-entry.js +56 -0
  102. package/dist/services/delivery-stage-handlers.js +135 -0
  103. package/dist/services/delivery-stage-results.js +94 -0
  104. package/dist/services/descriptor-coordinator-session.js +3 -1
  105. package/dist/services/descriptor-run-start.js +24 -4
  106. package/dist/services/descriptor-stage-coordinator.js +78 -10
  107. package/dist/services/descriptor-stage-settlement.js +59 -14
  108. package/dist/services/diagnostic-repair-intake.js +123 -0
  109. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  110. package/dist/services/diagnostic-report.js +30 -0
  111. package/dist/services/diagnostic-run-bundle.js +48 -9
  112. package/dist/services/diagnostic-snapshot.js +2 -2
  113. package/dist/services/diagnostic-stage-handlers.js +3 -2
  114. package/dist/services/diagnostic-stage-results.js +26 -0
  115. package/dist/services/diagnostic-start.js +56 -6
  116. package/dist/services/diagnostic-work-collection.js +3 -3
  117. package/dist/services/distill-document-publication.js +280 -0
  118. package/dist/services/distill-promotion.js +197 -0
  119. package/dist/services/distill-publication-custody.js +162 -0
  120. package/dist/services/distill-stage-handlers.js +249 -0
  121. package/dist/services/distill-stage-results.js +105 -0
  122. package/dist/services/distill-user-inputs.js +4 -0
  123. package/dist/services/distill-work-batch.js +42 -0
  124. package/dist/services/distill-work-collection.js +74 -0
  125. package/dist/services/distill-work-registration.js +93 -0
  126. package/dist/services/distill-work-results.js +98 -0
  127. package/dist/services/eval-snapshots.js +17 -7
  128. package/dist/services/external-work-launch.js +8 -2
  129. package/dist/services/flow-catalog.js +24 -0
  130. package/dist/services/frozen-engine.js +19 -2
  131. package/dist/services/generic-execution.js +4 -3
  132. package/dist/services/harness-adapter.js +2 -14
  133. package/dist/services/hook-memory-routing.js +1 -1
  134. package/dist/services/hooks.js +31 -6
  135. package/dist/services/hybrid-contracts.js +6 -1
  136. package/dist/services/installed-flow-source.js +37 -2
  137. package/dist/services/interactive-attached-session.js +215 -0
  138. package/dist/services/interactive-baseline-custody.js +54 -0
  139. package/dist/services/interactive-before-versions.js +148 -0
  140. package/dist/services/interactive-bundle-data.js +85 -0
  141. package/dist/services/interactive-checks.js +179 -0
  142. package/dist/services/interactive-execution.js +33 -0
  143. package/dist/services/interactive-finish-integration.js +236 -0
  144. package/dist/services/interactive-git-baseline.js +230 -0
  145. package/dist/services/interactive-git-dispatch.js +278 -0
  146. package/dist/services/interactive-input-delivery.js +82 -0
  147. package/dist/services/interactive-mutation-scope.js +231 -0
  148. package/dist/services/interactive-request.js +287 -0
  149. package/dist/services/interactive-run-bundle.js +109 -0
  150. package/dist/services/interactive-stage-entry.js +41 -0
  151. package/dist/services/interactive-stage-handlers.js +289 -0
  152. package/dist/services/interactive-stage-observation.js +136 -0
  153. package/dist/services/interactive-stage-results.js +111 -0
  154. package/dist/services/interactive-user-inputs.js +142 -0
  155. package/dist/services/interactive-work-batch.js +43 -0
  156. package/dist/services/interactive-work-collection.js +119 -0
  157. package/dist/services/interactive-work-completion.js +180 -0
  158. package/dist/services/interactive-work-context.js +249 -0
  159. package/dist/services/interactive-work-history.js +112 -0
  160. package/dist/services/interactive-work-registration.js +124 -0
  161. package/dist/services/interactive-work-results.js +115 -0
  162. package/dist/services/interactive-work-start.js +155 -0
  163. package/dist/services/lifecycle-invocations.js +250 -33
  164. package/dist/services/managed-daemon-binding.js +52 -1
  165. package/dist/services/memory-compose-handler.js +2 -22
  166. package/dist/services/memory-finish-integration.js +58 -17
  167. package/dist/services/memory-fix-finish-integration.js +129 -0
  168. package/dist/services/memory-fix-git-dispatch.js +139 -0
  169. package/dist/services/memory-fix-intake-custody.js +11 -0
  170. package/dist/services/memory-fix-intake-settlement.js +127 -0
  171. package/dist/services/memory-fix-publication.js +196 -0
  172. package/dist/services/memory-fix-repair-admission.js +116 -0
  173. package/dist/services/memory-fix-request.js +141 -0
  174. package/dist/services/memory-fix-run-bundle.js +83 -0
  175. package/dist/services/memory-fix-scope.js +78 -0
  176. package/dist/services/memory-fix-sources.js +85 -0
  177. package/dist/services/memory-fix-stage-entry.js +24 -0
  178. package/dist/services/memory-fix-stage-handlers.js +282 -0
  179. package/dist/services/memory-fix-stage-results.js +106 -0
  180. package/dist/services/memory-fix-workspace.js +127 -0
  181. package/dist/services/memory-flow-user-inputs.js +88 -0
  182. package/dist/services/memory-init-contexts.js +55 -5
  183. package/dist/services/memory-init-controller-start.js +20 -4
  184. package/dist/services/memory-layer.js +7 -3
  185. package/dist/services/memory-native-review-copy.js +27 -3
  186. package/dist/services/memory-repair-document-delta.js +1 -0
  187. package/dist/services/project-flow-source.js +195 -0
  188. package/dist/services/project-git-integration.js +92 -0
  189. package/dist/services/repair-intents.js +2 -7
  190. package/dist/services/retained-native-hook.js +274 -0
  191. package/dist/services/review-fix-handoff.js +82 -0
  192. package/dist/services/review-fix-intake-custody.js +17 -0
  193. package/dist/services/review-fix-stage-entry.js +43 -0
  194. package/dist/services/review-fix-stage-handlers.js +176 -0
  195. package/dist/services/review-fix-stage-results.js +86 -0
  196. package/dist/services/run-control-worker.js +42 -1
  197. package/dist/services/run-control.js +40 -5
  198. package/dist/services/run-controller-adapter.js +37 -3
  199. package/dist/services/run-controller-process.js +25 -4
  200. package/dist/services/run-controller-recovery.js +46 -6
  201. package/dist/services/run-controller-state.js +3 -2
  202. package/dist/services/run-controller.js +257 -44
  203. package/dist/services/run-recovery.js +31 -2
  204. package/dist/services/run-timeline.js +59 -0
  205. package/dist/services/runs.js +18 -31
  206. package/dist/services/sessions.js +2 -1
  207. package/dist/services/stage-handlers.js +55 -3
  208. package/dist/services/stage-lifecycle.js +4 -3
  209. package/dist/services/stage-pause.js +26 -9
  210. package/dist/services/stage-report-renderer.js +2 -6
  211. package/dist/services/technical-notes.js +31 -0
  212. package/dist/services/verification-matrix.js +1 -1
  213. package/dist/services/version-status.js +1 -1
  214. package/dist/services/vnext-code-review.js +74 -5
  215. package/dist/services/vnext-code.js +8 -32
  216. package/dist/services/vnext-fanout.js +53 -8
  217. package/dist/services/vnext-merge.js +10 -5
  218. package/dist/services/vnext-plan-review.js +1 -1
  219. package/dist/services/vnext-plan.js +1 -1
  220. package/dist/services/vnext-protocolize.js +1 -1
  221. package/dist/services/vnext-specify.js +10 -5
  222. package/dist/services/work-registry.js +255 -69
  223. package/dist/services/worktrees.js +7 -4
  224. package/dist/storage/database.js +2 -1
  225. package/dist/verification-components.json +82 -0
  226. package/package.json +3 -2
  227. package/tools/verification-jobs.mjs +35 -10
@@ -0,0 +1,105 @@
1
+ import path from "node:path";
2
+ import { Ajv2020 } from "ajv/dist/2020.js";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { distillAspects } from "../domain/distill-flow.js";
5
+ import { hybridDigest } from "./hybrid-contracts.js";
6
+ import { verifyDiagnosticCoverage, verifyDiagnosticSources } from "./diagnostic-evidence.js";
7
+ function refuse(reason) {
8
+ throw new AppError("distill_stage_result_invalid", "Distillation stage result lacks its exact input, reviewed scope or publication proof", 2, { reason, effect: "no_effect", ...(reason === "invalid_schema" ? { phase: "prepare", recoverable: true } : {}) });
9
+ }
10
+ const portable = (value) => Boolean(value) && !path.posix.isAbsolute(value) && !path.win32.isAbsolute(value)
11
+ && !value.includes("\\") && !value.includes("\0") && value.split("/").every(part => Boolean(part) && ![".", "..", ".git", "node_modules"].includes(part));
12
+ /** Shape/evidence checks do not establish independent Work or publication
13
+ * custody. The registered handler must prepare those bindings and fence them
14
+ * again in its settlement transaction. */
15
+ export function validateDistillStageResult(bytes, schemaBytes, binding) {
16
+ if (!bytes.length || bytes.length > 1024 * 1024 || !schemaBytes.length || schemaBytes.length > 1024 * 1024)
17
+ refuse("result_exceeds_bound");
18
+ const validate = (() => {
19
+ try {
20
+ const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(schemaBytes));
21
+ if (schema.$id !== "dd-flow/distill-stage-result@1")
22
+ refuse("frozen_schema_invalid");
23
+ return new Ajv2020({ strict: false, validateFormats: false }).compile(schema);
24
+ }
25
+ catch {
26
+ return refuse("frozen_schema_invalid");
27
+ }
28
+ })();
29
+ let result;
30
+ try {
31
+ result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
32
+ }
33
+ catch {
34
+ return refuse("invalid_schema");
35
+ }
36
+ if (!validate(result))
37
+ refuse("invalid_schema");
38
+ if (result.flow_id !== "mb-distill" || result.run_id !== binding.runId || result.stage !== binding.stage
39
+ || result.input_sha256 !== binding.inputSha256 || result.snapshot_sha256 !== binding.snapshotSha256
40
+ || hybridDigest(result.predecessors) !== hybridDigest(binding.predecessors))
41
+ refuse("foreign_stage_binding");
42
+ const details = result.details;
43
+ verifyDiagnosticCoverage(binding.snapshotRoot, distillAspects.map(aspect => aspect.id), details.coverage);
44
+ const ids = new Set();
45
+ const baseline = (refs) => {
46
+ if (refs.some(ref => !binding.support.some(source => source.path === ref.path && source.sha256 === ref.sha256)))
47
+ refuse("foreign_baseline");
48
+ };
49
+ for (const candidate of details.candidates) {
50
+ if (ids.has(candidate.id) || !details.coverage.some(row => row.id === candidate.aspect_id && row.applicability === "applicable"))
51
+ refuse("candidate_outside_selection");
52
+ ids.add(candidate.id);
53
+ baseline(candidate.baseline_refs);
54
+ verifyDiagnosticSources(binding.snapshotRoot, [...candidate.source_refs, ...candidate.usage_refs]);
55
+ }
56
+ const decided = new Set();
57
+ for (const decision of details.decisions) {
58
+ if (!ids.has(decision.candidate_id) || decided.has(decision.candidate_id))
59
+ refuse("foreign_or_duplicate_decision");
60
+ decided.add(decision.candidate_id);
61
+ baseline(decision.baseline_refs);
62
+ verifyDiagnosticSources(binding.snapshotRoot, decision.source_refs);
63
+ }
64
+ if (!["preflight", "extract"].includes(binding.stage) && decided.size !== ids.size)
65
+ refuse("unresolved_candidate");
66
+ if (hybridDigest(details.write_scope) !== hybridDigest(binding.writeScope)
67
+ || hybridDigest(details.promotion_receipt) !== hybridDigest(binding.promotionReceipt))
68
+ refuse("unowned_publication_claim");
69
+ const scope = binding.writeScope;
70
+ if (!["verify", "finish"].includes(binding.stage) && details.verification.length)
71
+ refuse("verification_before_publication_review");
72
+ if (scope) {
73
+ if (!["promote", "verify", "finish"].includes(binding.stage) || !path.isAbsolute(scope.destination_root)
74
+ || path.resolve(scope.destination_root) !== scope.destination_root || scope.paths.some(value => !portable(value))
75
+ || scope.candidate_ids.some(id => !details.decisions.some(decision => decision.candidate_id === id && ["adopt", "adapt"].includes(decision.disposition))))
76
+ refuse("invalid_write_scope");
77
+ verifyDiagnosticSources(binding.runRoot, [scope.approval_source]);
78
+ }
79
+ if (binding.promotionReceipt)
80
+ verifyDiagnosticSources(binding.runRoot, [binding.promotionReceipt]);
81
+ if (binding.stage === "promote" && details.decisions.some(decision => ["adopt", "adapt"].includes(decision.disposition))
82
+ && (!scope || !binding.promotionReceipt))
83
+ refuse("promotion_requires_accepted_scope_and_receipt");
84
+ const verified = new Set();
85
+ for (const observation of details.verification) {
86
+ if (!scope?.candidate_ids.includes(observation.candidate_id) || verified.has(observation.candidate_id))
87
+ refuse("foreign_verification");
88
+ verified.add(observation.candidate_id);
89
+ for (const evidence of observation.evidence)
90
+ verifyDiagnosticSources(evidence.root === "run" ? binding.runRoot : scope.destination_root, [evidence]);
91
+ }
92
+ if (["verify", "finish"].includes(binding.stage) && details.decisions.some(decision => ["adopt", "adapt"].includes(decision.disposition))
93
+ && (!scope || !binding.promotionReceipt || scope.candidate_ids.some(id => !verified.has(id))
94
+ || details.verification.some(observation => observation.verdict !== "verified")))
95
+ refuse("unverified_promotion");
96
+ return result;
97
+ }
98
+ export function assertDistillStageScope(result, prior) {
99
+ if (hybridDigest(result.details.coverage) !== hybridDigest(prior.details.coverage)
100
+ || prior.details.limitations.some(value => !result.details.limitations.includes(value))
101
+ || (prior.stage !== "preflight" && hybridDigest(result.details.candidates) !== hybridDigest(prior.details.candidates))
102
+ || (!["preflight", "extract"].includes(prior.stage) && hybridDigest(result.details.decisions) !== hybridDigest(prior.details.decisions))
103
+ || (prior.details.write_scope && hybridDigest(result.details.write_scope) !== hybridDigest(prior.details.write_scope)))
104
+ refuse("accepted_scope_changed");
105
+ }
@@ -0,0 +1,4 @@
1
+ import { readMemoryFlowUserInputs } from "./memory-flow-user-inputs.js";
2
+ export function readDistillUserInputs(context, input, retainedBundle) {
3
+ return readMemoryFlowUserInputs(context, input, "mb-distill", retainedBundle);
4
+ }
@@ -0,0 +1,42 @@
1
+ import { distillAspects } from "../domain/distill-flow.js";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { hybridDigest } from "./hybrid-contracts.js";
4
+ function refuse(reason) {
5
+ throw new AppError("distill_work_batch_invalid", "Distillation requires its accepted complete aspect selection", 2, { reason, effect: "no_effect" });
6
+ }
7
+ /** One selected aspect is one independent semantic unit. Capacity changes
8
+ * scheduling in the existing Work runtime, never selection or dependencies. */
9
+ export function distillWorkBatch(input) {
10
+ const ids = input.coverage.map(row => row.id);
11
+ if (input.flow_id !== "mb-distill" || !["extract", "review"].includes(input.stage)
12
+ || ids.length !== distillAspects.length || new Set(ids).size !== ids.length
13
+ || distillAspects.some(aspect => !ids.includes(aspect.id))
14
+ || input.coverage.some(row => !row.reason.trim() || !["applicable", "not_applicable", "blocked", "out_of_scope"].includes(row.applicability)))
15
+ refuse("invalid_coverage");
16
+ const selected = distillAspects.filter(aspect => input.coverage.some(row => row.id === aspect.id && row.applicability === "applicable"));
17
+ if (input.stage === "extract" && input.candidates.length)
18
+ refuse("extract_cannot_accept_candidates");
19
+ if (new Set(input.candidates.map(candidate => candidate.id)).size !== input.candidates.length
20
+ || input.candidates.some(candidate => !selected.some(aspect => aspect.id === candidate.aspect_id)))
21
+ refuse("candidate_outside_selection");
22
+ for (const aspect of selected)
23
+ if (!input.support.some(ref => ref.path === aspect.criterion))
24
+ refuse("missing_selected_criterion");
25
+ const review = input.stage === "review";
26
+ const assignments = review
27
+ ? [{ id: "novelty", criterion: null, role: "critic" }, { id: "portability", criterion: null, role: "critic" }]
28
+ : selected.map(aspect => ({ id: aspect.id, criterion: aspect.criterion, role: "aspect" }));
29
+ return { works: assignments.map((assignment, index) => {
30
+ const key = `${assignment.role}-${String(index + 1).padStart(3, "0")}`;
31
+ const packet = { schema_id: "dd-flow/distill-work-input@1", ...structuredClone(input), key,
32
+ aspect_id: assignment.id, role: assignment.role, criterion: assignment.criterion };
33
+ return { key, depends_on: [], launch_policy: "fresh_agent_required",
34
+ result_schema: "dd-flow/distill-work-result@1",
35
+ task: (review
36
+ ? `Independently review every candidate through the ${assignment.id} lens. Compare exact project usage and frozen canon baseline. Preserve observe/duplicate/reject decisions and disagreements. Adopt/adapt requires transferable applicability and sufficient evidence, not popularity or missing project compliance. Return one decision per candidate. Do not replace candidates, promote documents, change scope, allocate Work or claim another critic's review.`
37
+ : `Extract transferable practices only for ${assignment.id}. Read its frozen criterion and canon baseline, then actual assigned project sources. Do not audit project compliance. Explain novelty, usage, applicability, cost and complexity; distinguish established practice from plans. Keep domain and process lenses separate. Return an explicit checked/no-candidate explanation when nothing substantial is found. Decide whether external research can change the assessment and disclose use and sources. Do not mutate project/canon, promote documents, allocate Work or self-accept candidates.`)
38
+ + " Read support references relative to run_home/execution-bundle; preserve their supplied support paths and hashes in baseline_refs. Project source_refs/usage_refs are relative to the assigned snapshot workspace with exact snapshot-file hashes, never canon, intake or RUN files. Old manual lifecycle/dispatch snippets in criteria are reference data, not execution authority.",
39
+ payload: { kind: review ? "distill-critic" : "distill-aspect", read_only: true,
40
+ distill: { ...packet, packet_sha256: hybridDigest(packet) } } };
41
+ }) };
42
+ }
@@ -0,0 +1,74 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
3
+ import { requireProjectByRoot } from "./projects.js";
4
+ import { resolveProjectRoot } from "../storage/paths.js";
5
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
6
+ import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
7
+ import { readRegisteredDistillGraph } from "./distill-work-registration.js";
8
+ import { distillWorkBatch } from "./distill-work-batch.js";
9
+ import { validateDistillWorkResult } from "./distill-work-results.js";
10
+ import { assertDiagnosticSessionIndependence, readCompletedResearchWork } from "./diagnostic-work-collection.js";
11
+ import { hybridDigest } from "./hybrid-contracts.js";
12
+ function refuse(reason) {
13
+ throw new AppError("distill_work_collection_invalid", "Distillation requires the complete independently settled registered graph", 2, { reason, effect: "no_effect" });
14
+ }
15
+ function collectSql(context, scope, rootId) {
16
+ const graph = readRegisteredDistillGraph(context, scope);
17
+ if (!graph || graph.parent_work_id !== rootId)
18
+ refuse("registration_required");
19
+ const batch = distillWorkBatch(graph.input);
20
+ const works = batch.works.map(item => readCompletedResearchWork(context, scope, graph.work_ids[item.key]));
21
+ const coordinators = context.db.all("SELECT s.harness,s.provider_session_id FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.run_id=? AND s.session_id=ws.session_id WHERE ws.work_id=?", [scope.projectId, scope.runId, rootId]);
22
+ const authorGraph = scope.stage === "review" ? readRegisteredDistillGraph(context, { ...scope, stage: "extract" }) : null;
23
+ if (scope.stage === "review" && !authorGraph)
24
+ refuse("author_graph_missing");
25
+ const authors = authorGraph ? Object.values(authorGraph.work_ids).map(id => readCompletedResearchWork(context, scope, id)) : [];
26
+ assertDiagnosticSessionIndependence(works.map(work => work.session), [...coordinators, ...authors.map(work => work.session)]);
27
+ return { graph, works, authors, coordinators };
28
+ }
29
+ export class PreparedDistillWorkCollection {
30
+ #scope;
31
+ #rootId;
32
+ #sql;
33
+ #snapshot;
34
+ #results;
35
+ constructor(scope, rootId, sql, snapshot, results) {
36
+ this.#scope = scope;
37
+ this.#rootId = rootId;
38
+ this.#sql = sql;
39
+ this.#snapshot = snapshot;
40
+ this.#results = results;
41
+ Object.freeze(this);
42
+ }
43
+ static prepare(context, input) {
44
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), bundle = readFlowRunExecutionBundle(context, input);
45
+ if (!bundle || bundle.manifest.flow_id !== "mb-distill")
46
+ refuse("distill_bundle_required");
47
+ const scope = { projectId: project.id, runId: input.runId, stage: input.stage }, frozen = bundle.execution.snapshot().scope;
48
+ const snapshot = readDiagnosticSnapshot(context, { projectId: project.id, projectRoot: frozen.projectRoot,
49
+ sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId: "mb-distill" }, input.runId);
50
+ if (!snapshot || snapshot.root !== frozen.workspace)
51
+ refuse("snapshot_required");
52
+ const root = readDescriptorRootWork(context, scope, bundle);
53
+ const sql = context.db.readSnapshot(() => {
54
+ assertPreparedRunBundleSqlAuthority(context, scope, bundle);
55
+ return collectSql(context, scope, root.workId);
56
+ });
57
+ if (sql.graph.bundle_sha256 !== bundle.sha256 || sql.graph.input.snapshot_sha256 !== snapshot.source_sha256)
58
+ refuse("foreign_graph");
59
+ const batch = distillWorkBatch(sql.graph.input), schema = bundle.blob("schemas/distill-work-result.schema.json");
60
+ const results = sql.works.map((work, index) => ({ workId: work.workId,
61
+ result: validateDistillWorkResult(Buffer.from(work.result), schema, diagnosticSnapshotPaths(context, snapshot, input.runId).root, sql.graph.input, {
62
+ workId: work.workId, aspectId: batch.works[index].payload.distill.aspect_id, role: batch.works[index].payload.distill.role
63
+ }) }));
64
+ return new PreparedDistillWorkCollection(scope, root.workId, sql, snapshot, results);
65
+ }
66
+ results() { return structuredClone(this.#results); }
67
+ assertCustody(context) {
68
+ const s = this.#snapshot;
69
+ if (hybridDigest(collectSql(context, this.#scope, this.#rootId)) !== hybridDigest(this.#sql)
70
+ || hybridDigest(readDiagnosticSnapshotCustody(context, { projectId: s.projectId, projectRoot: s.projectRoot,
71
+ sourceRoot: s.sourceRoot, requestId: s.requestId, flowId: s.flowId }, this.#scope.runId)) !== hybridDigest(s))
72
+ refuse("collection_changed");
73
+ }
74
+ }
@@ -0,0 +1,93 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
3
+ import { requireProjectByRoot } from "./projects.js";
4
+ import { resolveProjectRoot } from "../storage/paths.js";
5
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
6
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
7
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
8
+ import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
9
+ import { validateDistillStageResult } from "./distill-stage-results.js";
10
+ import { distillWorkBatch } from "./distill-work-batch.js";
11
+ import { prepareWorkBatch, addWorkBatch, readWorkBatchCustody } from "./work-registry.js";
12
+ import { stageGraphScope } from "./stage-work-graph.js";
13
+ import { appendAudit } from "./audit.js";
14
+ import { hybridDigest } from "./hybrid-contracts.js";
15
+ const event = "distill.work_registration";
16
+ function refuse(reason) {
17
+ throw new AppError("distill_work_registration_invalid", "Distillation graph requires its accepted predecessor and frozen stage", 2, { reason, effect: "no_effect" });
18
+ }
19
+ export function readRegisteredDistillGraph(context, scope) {
20
+ const graph = stageGraphScope(context, scope.projectId, scope.runId, scope.stage);
21
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? AND json_extract(payload_json,'$.attempt')=? LIMIT 2", [scope.projectId, event, scope.runId, scope.stage, graph.attempt]);
22
+ if (!rows.length)
23
+ return null;
24
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
25
+ refuse("ambiguous_registration");
26
+ const receipt = JSON.parse(rows[0].payload_json);
27
+ if (receipt.schema_id !== "dd-flow/distill-work-registration@1" || receipt.run_id !== scope.runId || receipt.stage !== scope.stage
28
+ || receipt.attempt !== graph.attempt || !receipt.work_ids || receipt.input.run_id !== scope.runId
29
+ || receipt.input.stage !== scope.stage || receipt.input.input_sha256 !== receipt.input_sha256)
30
+ refuse("foreign_registration");
31
+ const batch = distillWorkBatch(receipt.input);
32
+ if (receipt.batch_sha256 !== hybridDigest(batch.works) || batch.works.length !== Object.keys(receipt.work_ids).length
33
+ || new Set(Object.values(receipt.work_ids)).size !== batch.works.length)
34
+ refuse("changed_batch");
35
+ for (const item of batch.works) {
36
+ const id = receipt.work_ids[item.key];
37
+ if (!id)
38
+ refuse("missing_work");
39
+ const custody = readWorkBatchCustody(context, id);
40
+ if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
41
+ || hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids)
42
+ || hybridDigest(custody.scope) !== hybridDigest({ ...graph, stage: scope.stage, parent_work_id: receipt.parent_work_id, request_key: "distill-graph" }))
43
+ refuse("changed_work_custody");
44
+ }
45
+ return receipt;
46
+ }
47
+ export function registerDistillStageGraph(context, input) {
48
+ const bundle = readFlowRunExecutionBundle(context, input), project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
49
+ if (!bundle || bundle.manifest.flow_id !== "mb-distill" || !["extract", "review"].includes(input.stage))
50
+ refuse("distill_stage_required");
51
+ const frozen = bundle.execution.snapshot().scope, scope = { projectId: project.id, runId: input.runId, stage: input.stage };
52
+ const selection = readDescriptorStageInput(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
53
+ const snapshotScope = { projectId: project.id, projectRoot: frozen.projectRoot, sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId: "mb-distill" };
54
+ const snapshot = readDiagnosticSnapshot(context, snapshotScope, input.runId);
55
+ if (!snapshot || snapshot.root !== frozen.workspace)
56
+ refuse("snapshot_required");
57
+ const previousStage = input.stage === "extract" ? "preflight" : "extract";
58
+ if (selection.input.predecessor?.stage !== previousStage)
59
+ refuse("predecessor_required");
60
+ const previousScope = { ...scope, stage: previousStage }, accepted = readAcceptedStageData(context, previousScope);
61
+ const priorInput = readDescriptorStageInput(context, previousScope, bundle, true, true);
62
+ const support = bundle.manifest.inventory.filter(ref => ref.path.startsWith("support/"));
63
+ const previous = validateDistillStageResult(accepted.bytes, bundle.descriptor.stageBlob(previousStage, "result_schema"), {
64
+ runId: input.runId, stage: previousStage, inputSha256: priorInput.inputSha256, snapshotSha256: snapshot.source_sha256,
65
+ snapshotRoot: diagnosticSnapshotPaths(context, snapshot, input.runId).root, runRoot: selection.runRoot, support, writeScope: null, promotionReceipt: null,
66
+ predecessors: priorInput.input.predecessor ? [{ stage: priorInput.input.predecessor.stage, ...priorInput.input.predecessor.result }] : []
67
+ });
68
+ const workInput = { flow_id: "mb-distill", run_id: input.runId, stage: input.stage,
69
+ input_sha256: selection.inputSha256, snapshot_sha256: snapshot.source_sha256, instruction: bundle.descriptor.stageBlob(input.stage, "instruction").toString("utf8"),
70
+ coverage: previous.details.coverage, candidates: input.stage === "review" ? previous.details.candidates : [], support };
71
+ const batch = distillWorkBatch(workInput), batchInput = { parentWorkId: root.workId, stage: input.stage, requestKey: "distill-graph", value: batch };
72
+ const prepared = batch.works.length ? prepareWorkBatch(context, batchInput) : [];
73
+ return context.db.writeTransaction(() => {
74
+ assertPreparedRunBundleSqlAuthority(context, scope, bundle);
75
+ if (hybridDigest(readDiagnosticSnapshotCustody(context, snapshotScope, input.runId)) !== hybridDigest(snapshot)
76
+ || hybridDigest(readDescriptorStageInput(context, scope, bundle)) !== hybridDigest(selection)
77
+ || hybridDigest(readAcceptedStageData(context, previousScope)) !== hybridDigest(accepted)
78
+ || readDescriptorRootWork(context, scope, bundle).workId !== root.workId)
79
+ refuse("stage_authority_changed");
80
+ const expected = { schema_id: "dd-flow/distill-work-registration@1", run_id: input.runId, stage: input.stage, attempt: selection.attempt,
81
+ parent_work_id: root.workId, bundle_sha256: bundle.sha256, input_sha256: selection.inputSha256, input: workInput, batch_sha256: hybridDigest(batch.works) };
82
+ const prior = readRegisteredDistillGraph(context, scope);
83
+ if (prior) {
84
+ if (hybridDigest({ ...prior, work_ids: undefined }) !== hybridDigest(expected))
85
+ refuse("registration_changed");
86
+ return prior;
87
+ }
88
+ const workIds = batch.works.length ? addWorkBatch(context, { ...batchInput, prepared }).work_ids : {};
89
+ const receipt = { ...expected, work_ids: workIds };
90
+ appendAudit(context, { projectId: project.id, eventType: event, payload: receipt });
91
+ return readRegisteredDistillGraph(context, scope);
92
+ });
93
+ }
@@ -0,0 +1,98 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { verifyDiagnosticSources } from "./diagnostic-evidence.js";
4
+ function refuse(reason) {
5
+ throw new AppError("distill_work_result_invalid", "Distillation result does not match its assigned aspect, evidence and baseline", 2, { reason, effect: "no_effect", ...(reason === "invalid_schema" ? { phase: "prepare", recoverable: true } : {}) });
6
+ }
7
+ /** Semantic judgments remain agent-owned. Mechanically verify the assigned
8
+ * unit, exact project evidence and frozen canon baseline before Work admission. */
9
+ export function validateDistillWorkResult(bytes, schemaBytes, snapshotRoot, input, assignment) {
10
+ if (!bytes.length || bytes.length > 1024 * 1024 || !schemaBytes.length || schemaBytes.length > 1024 * 1024)
11
+ refuse("result_exceeds_bound");
12
+ const validate = (() => {
13
+ try {
14
+ const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(schemaBytes));
15
+ if (schema.$id !== "dd-flow/distill-work-result@1")
16
+ refuse("frozen_schema_invalid");
17
+ return new Ajv2020({ strict: false, validateFormats: false }).compile(schema);
18
+ }
19
+ catch {
20
+ return refuse("frozen_schema_invalid");
21
+ }
22
+ })();
23
+ let result;
24
+ try {
25
+ result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
26
+ }
27
+ catch {
28
+ return refuse("invalid_schema");
29
+ }
30
+ if (!validate(result))
31
+ refuse("invalid_schema");
32
+ if (result.flow_id !== input.flow_id || result.run_id !== input.run_id || result.stage !== input.stage
33
+ || result.work_id !== assignment.workId || result.aspect_id !== assignment.aspectId)
34
+ refuse("foreign_assignment");
35
+ const baseline = (refs) => {
36
+ if (new Set(refs.map(ref => ref.path)).size !== refs.length
37
+ || refs.some(ref => !input.support.some(source => source.path === ref.path && source.sha256 === ref.sha256)))
38
+ refuse("foreign_baseline");
39
+ };
40
+ const external = result.external_research;
41
+ if (!external.used && (external.sources.length || external.questions.length || external.signal !== "none" || external.effect !== "neutral"))
42
+ refuse("unperformed_research_claim");
43
+ if (external.signal !== "none" && !external.sources.length)
44
+ refuse("external_signal_without_sources");
45
+ for (const source of external.sources) {
46
+ try {
47
+ const url = new URL(source.url);
48
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
49
+ refuse("invalid_external_source");
50
+ }
51
+ catch {
52
+ refuse("invalid_external_source");
53
+ }
54
+ }
55
+ if (assignment.role === "critic") {
56
+ const decisions = result.decisions.map(decision => decision.candidate_id);
57
+ if (input.stage !== "review" || result.candidates.length || new Set(decisions).size !== decisions.length
58
+ || decisions.length !== input.candidates.length || input.candidates.some(candidate => !decisions.includes(candidate.id)))
59
+ refuse("incomplete_critic_coverage");
60
+ for (const decision of result.decisions) {
61
+ if (!decision.source_refs.length && !decision.baseline_refs.length)
62
+ refuse("decision_without_evidence");
63
+ verifyDiagnosticSources(snapshotRoot, decision.source_refs);
64
+ baseline(decision.baseline_refs);
65
+ }
66
+ }
67
+ else {
68
+ if (input.stage !== "extract" || result.decisions.length
69
+ || !input.coverage.some(row => row.id === assignment.aspectId && row.applicability === "applicable"))
70
+ refuse("foreign_aspect");
71
+ const ids = new Set();
72
+ for (const candidate of result.candidates) {
73
+ if (candidate.aspect_id !== assignment.aspectId || ids.has(candidate.id))
74
+ refuse("foreign_or_duplicate_candidate");
75
+ ids.add(candidate.id);
76
+ verifyDiagnosticSources(snapshotRoot, candidate.source_refs);
77
+ verifyDiagnosticSources(snapshotRoot, candidate.usage_refs);
78
+ baseline(candidate.baseline_refs);
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ /** Collect admitted Work only; keep local IDs and all rejected/observed data. */
84
+ export function collectDistillCandidates(results) {
85
+ const candidates = [], origins = [];
86
+ const owners = new Set();
87
+ for (const [workIndex, work] of results.entries()) {
88
+ if (owners.has(work.workId) || work.workId !== work.result.work_id || work.result.stage !== "extract")
89
+ refuse("foreign_or_duplicate_work");
90
+ owners.add(work.workId);
91
+ for (const [index, candidate] of work.result.candidates.entries()) {
92
+ const id = `DST-${String(workIndex + 1).padStart(3, "0")}-${String(index + 1).padStart(3, "0")}`;
93
+ candidates.push({ ...structuredClone(candidate), id });
94
+ origins.push({ id, work_id: work.workId, original_id: candidate.id });
95
+ }
96
+ }
97
+ return { candidates, origins };
98
+ }
@@ -22,15 +22,25 @@ import { prepareOutputFile } from "../cli/input-preparation.js";
22
22
  import { localEnvironmentAllowed, projectServicePathFilter } from "./workspace-files.js";
23
23
  import { cleanupScopeWriters } from "./scope-writer-cleanup.js";
24
24
  import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
25
+ import { PreparedProjectFlowSource } from "./project-flow-source.js";
25
26
  import { readSetupStatus } from "./setup.js";
26
- import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
27
+ import { isSourceSnapshotFlow } from "../domain/source-snapshot-flows.js";
28
+ import { isDeliveryFlow } from "../domain/delivery-flows.js";
27
29
  import { isObservationOperation, nativeInventoryVersion, retainedDaemonOperations } from "./run-control.js";
28
30
  /** Capture the only flow boundary which exists before `stage start --bootstrap` creates a RUN. */
29
31
  export function prepareEvalBootstrapSnapshot(context, input) {
30
32
  let source;
31
- if (input.flow !== undefined && input.flow !== "sdlc") {
32
- if (input.flow !== "mb-init" && input.flow !== "mb-upgrade" && !isDiagnosticFlow(input.flow))
33
+ if (input.flow === "interactive") {
34
+ const projectRoot = resolveProjectRoot(input.projectRoot);
35
+ source = PreparedProjectFlowSource.fromProject({ projectRoot, workspace: projectRoot, flowId: "interactive" });
36
+ if (source.resolveRegisteredFlow().descriptor.stages[0]?.id !== "preflight")
37
+ throw new AppError("snapshot_flow_unsupported", "Interactive bootstrap requires its preflight entry", 2, { phase: "prepare", effect: "no_effect" });
38
+ }
39
+ else if (input.flow !== undefined && input.flow !== "sdlc") {
40
+ if (input.flow !== "mb-init" && input.flow !== "mb-upgrade" && !isSourceSnapshotFlow(input.flow) && !isDeliveryFlow(input.flow))
33
41
  throw new AppError("snapshot_flow_unsupported", "Bootstrap snapshot flow is not registered", 2, { phase: "prepare", effect: "no_effect" });
42
+ if (input.flow === "review-fix")
43
+ throw new AppError("snapshot_flow_unsupported", "Review-fix starts at intake, not the preflight bootstrap boundary", 2, { phase: "prepare", effect: "no_effect" });
34
44
  const home = context.env.DD_FLOW_CONFIG_HOME ?? context.ddFlowHome;
35
45
  const selected = readSetupStatus(home);
36
46
  if (!selected.installation)
@@ -223,7 +233,7 @@ export function prepareEvalRunSnapshotCreation(context, input) {
223
233
  throw new AppError("snapshot_output_inside_project", "Snapshot output must be outside the project and RUN workspace trees", 1, { output });
224
234
  prepareOutputFile(output);
225
235
  prepareSnapshotProjectGit(projectRoot, recovery && status.run.flow_kind === "mb-init");
226
- prepareSnapshotWorkspace(workspaceRoot, projectRoot, isDiagnosticFlow(status.run.flow_kind));
236
+ prepareSnapshotWorkspace(workspaceRoot, projectRoot, isSourceSnapshotFlow(status.run.flow_kind));
227
237
  return { projectRoot, project, status, recovery, output, workspaceRoot, activeChildren };
228
238
  }
229
239
  function captureEvalRunSnapshot(context, input) {
@@ -241,7 +251,7 @@ function captureEvalRunSnapshot(context, input) {
241
251
  }
242
252
  }
243
253
  else
244
- copyRuntimeTree(context.ddFlowHome, path.join(output, "runtime"), false, [...databaseFiles, ...codexExclusions, ...(isDiagnosticFlow(status.run.flow_kind) ? [workspaceRoot] : [])], grokProviderHomes(context.ddFlowHome), input.observationCuts);
254
+ copyRuntimeTree(context.ddFlowHome, path.join(output, "runtime"), false, [...databaseFiles, ...codexExclusions, ...(isSourceSnapshotFlow(status.run.flow_kind) ? [workspaceRoot] : [])], grokProviderHomes(context.ddFlowHome), input.observationCuts);
245
255
  for (const [file, cut] of input.journalCuts ?? []) {
246
256
  const target = path.join(output, "runtime", path.relative(context.ddFlowHome, file));
247
257
  fs.mkdirSync(path.dirname(target), { recursive: true });
@@ -281,7 +291,7 @@ function captureEvalRunSnapshot(context, input) {
281
291
  filter: projectPayloadFilter(projectRoot)
282
292
  });
283
293
  const projectGit = snapshotProjectGit(projectRoot, output, recovery && status.run.flow_kind === "mb-init");
284
- const workspace = snapshotWorkspace(workspaceRoot, projectRoot, output, isDiagnosticFlow(status.run.flow_kind));
294
+ const workspace = snapshotWorkspace(workspaceRoot, projectRoot, output, isSourceSnapshotFlow(status.run.flow_kind));
285
295
  // Accepted Work results are captured in SQLite. Their file representations
286
296
  // are retained evidence, not a second authority or a checkpoint gate.
287
297
  if (recovery) {
@@ -1546,7 +1556,7 @@ function readBootstrapManifest(snapshot) {
1546
1556
  throw new AppError("snapshot_missing", "Bootstrap snapshot manifest is missing", 1, { snapshot });
1547
1557
  const value = JSON.parse(fs.readFileSync(file, "utf8"));
1548
1558
  const validBoundary = value.schema_id === "dd-eval/bootstrap-snapshot@1" ? value.stage === "specify" && value.flow === undefined
1549
- : value.schema_id === "dd-eval/bootstrap-snapshot@2" && (value.flow?.id === "mb-init" || value.flow?.id === "mb-upgrade" || isDiagnosticFlow(value.flow?.id)) && value.stage === "preflight"
1559
+ : value.schema_id === "dd-eval/bootstrap-snapshot@2" && (value.flow?.id === "mb-init" || value.flow?.id === "mb-upgrade" || value.flow?.id === "interactive" || isSourceSnapshotFlow(value.flow?.id) || isDeliveryFlow(value.flow?.id)) && value.stage === "preflight"
1550
1560
  && Object.keys(value.flow).sort().join() === "descriptor_sha256,id,source_sha256"
1551
1561
  && /^[a-f0-9]{64}$/.test(value.flow.source_sha256) && /^[a-f0-9]{64}$/.test(value.flow.descriptor_sha256);
1552
1562
  if (!validBoundary || !value.project_root || !value.project_sha256 || !value.project_git?.branch || !value.project_git.head || !value.project_git.bundle_sha256)
@@ -22,6 +22,7 @@ import { readFlowRunExecutionBundle } from "./runs.js";
22
22
  import { stageWorkIds } from "./stage-work-graph.js";
23
23
  import { hybridDigest } from "./hybrid-contracts.js";
24
24
  import { prepareMemoryRepairDispatchFromStage, prepareMemorySecondRepairDispatchFromStage, prepareExternalMemoryRepairCopyFromStage, prepareMemorySecondRepairLaunchFromStage } from "./memory-review-handler.js";
25
+ import { readRegisteredInteractiveWork } from "./interactive-work-registration.js";
25
26
  /**
26
27
  * Creates an external provider root for one already-created Work. The launch
27
28
  * is not a Work claim: only the worker's trusted `work start` hook binds it.
@@ -60,7 +61,12 @@ export async function launchExternalWork(context, input) {
60
61
  }
61
62
  if (override?.delegation !== undefined)
62
63
  throw new AppError("generic_execution_invalid", "Work cannot replace the RUN's frozen delegation", 2);
63
- const route = bundle.execution.work(stage, override?.agent_profile_id);
64
+ const interactive = bundle.manifest.flow_id === "interactive"
65
+ ? readRegisteredInteractiveWork(context, { projectId: work.project_id, runId: work.run_id }, bundle, work.work_id).packet : null;
66
+ if (interactive && interactive.owner_stage !== stage)
67
+ throw new AppError("execution_stage_mismatch", "Interactive role differs from its owning Stage", 2);
68
+ const route = interactive ? bundle.execution.work(stage, override?.agent_profile_id, interactive.stage)
69
+ : bundle.execution.work(stage, override?.agent_profile_id);
64
70
  const selected = { coordinator: route.coordinator, worker: route.worker,
65
71
  delegation: { mode: route.delegation.mode, max_parallel: route.delegation.mode === "external" ? route.delegation.max_parallel : null } };
66
72
  resolved = { ...selected, profile_hash: crypto.createHash("sha256").update(JSON.stringify(selected)).digest("hex") };
@@ -182,7 +188,7 @@ export async function launchExternalWork(context, input) {
182
188
  if (reviewCopy && reviewSourceHash(run.workspace_root, reviewInputs, reviewBase) !== reviewCopy.source_sha256)
183
189
  throw new AppError("read_only_input_changed", "Authoritative review workspace changed after the execution copy was made", 2, { work_id: work.work_id });
184
190
  const promptFile = path.join(root, "launch.md");
185
- fs.writeFileSync(promptFile, `Start the assigned Work now. Your first tool call must be this exact standalone command:\n\n${workStartCommand(runtimeContext, work)}\n\nUse the packet returned by that command. Do not claim another Work or run this command more than once.\n\n${lifecycleRetryInstruction}${isolatedWorkspace ? `\n\nThe review copy has no .git. Read the captured status/diff or gitless note in ${path.join(isolatedWorkspace, ".dd-flow", "review-evidence")}; do not run Git inside the copy.\n` : "\n"}`);
191
+ fs.writeFileSync(promptFile, `Start the assigned Work now. Your first tool call must be this exact standalone command:\n\n${workStartCommand(runtimeContext, work)}\n\nUse the packet returned by that command. Do not claim another Work or run this command more than once.\n\nAfter the issued Work finish succeeds, end this Turn immediately. The coordinator, not you, continues the Stage. Do not then read or follow other runtime/coordinator prompts, run Stage checks, write Stage proposals, or invoke stage start/finish/resume.\n\n${lifecycleRetryInstruction}${isolatedWorkspace ? `\n\nThe review copy has no .git. Read the captured status/diff or gitless note in ${path.join(isolatedWorkspace, ".dd-flow", "review-evidence")}; do not run Git inside the copy.\n` : "\n"}`);
186
192
  const promptOperationId = `${operationId}:session.prompt`;
187
193
  context.db.run("UPDATE work_launches SET adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'launching'", [JSON.stringify({ create: created, progress_policy: "native-inactivity@1" }), context.now(), operationId]);
188
194
  let prompted;
@@ -2,6 +2,11 @@ import { vnextStages } from "../domain/stage-catalog.js";
2
2
  import { sdlcHandlerIdentities, memoryInitHandlerIdentities, memoryUpgradeHandlerIdentities } from "../domain/stage-handler-identities.js";
3
3
  import { AppError } from "../shared/errors.js";
4
4
  import { diagnosticFlowStages } from "../domain/diagnostic-flows.js";
5
+ import { distillStages } from "../domain/distill-flow.js";
6
+ import { memoryFixStages } from "../domain/memory-fix-flow.js";
7
+ import { reviewFixStages } from "../domain/review-fix-flow.js";
8
+ import { interactiveStages } from "../domain/interactive-flow.js";
9
+ import { deliveryFlowStages } from "../domain/delivery-flows.js";
5
10
  /** Engine-owned catalog, not descriptor discovery or permission to execute a RUN. */
6
11
  export function listBundledFlows() {
7
12
  return [
@@ -16,6 +21,25 @@ export function listBundledFlows() {
16
21
  edges: stages.slice(0, -1).flatMap((handler, index) => [{ from: handler.stage, to: stages[index + 1].stage, on: "accepted" },
17
22
  ...(handler.stage === "verify" ? [{ from: handler.stage, to: "review", on: "needs_repair" }] : [])]) };
18
23
  }),
24
+ { id: "mb-distill", handler_status: "registered", entry_stage: "preflight", terminal_stages: ["finish"],
25
+ stages: distillStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`, entry_handler: "distill-stage-entry", finish_handler: "distill-stage-finish" })),
26
+ edges: distillStages.slice(0, -1).map((from, index) => ({ from, to: distillStages[index + 1], on: "accepted" })) },
27
+ { id: "mb-fix", handler_status: "registered", entry_stage: "intake", terminal_stages: ["finish"],
28
+ stages: memoryFixStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`,
29
+ entry_handler: "memory-fix-stage-entry", finish_handler: "memory-fix-stage-finish" })),
30
+ edges: memoryFixStages.slice(0, -1).map((from, index) => ({ from, to: memoryFixStages[index + 1], on: "accepted" })) },
31
+ { id: "review-fix", handler_status: "registered", entry_stage: "intake", terminal_stages: ["handoff"],
32
+ stages: reviewFixStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`,
33
+ entry_handler: "review-fix-stage-entry", finish_handler: "review-fix-stage-finish" })),
34
+ edges: reviewFixStages.slice(0, -1).map((from, index) => ({ from, to: reviewFixStages[index + 1], on: "accepted" })) },
35
+ { id: "interactive", handler_status: "registered", entry_stage: "preflight", terminal_stages: ["integration"],
36
+ stages: interactiveStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`,
37
+ entry_handler: "interactive-stage-entry", finish_handler: "interactive-stage-finish" })),
38
+ edges: interactiveStages.slice(0, -1).map((from, index) => ({ from, to: interactiveStages[index + 1], on: "accepted" })) },
39
+ ...Object.entries(deliveryFlowStages).map(([id, stages]) => ({ id, handler_status: "registered", entry_stage: "preflight", terminal_stages: ["finish"],
40
+ stages: stages.map((stage, index) => ({ id: stage, directory: `${String(index + 1).padStart(2, "0")}-${stage}`,
41
+ entry_handler: "delivery-stage-entry", finish_handler: "delivery-stage-finish" })),
42
+ edges: stages.slice(0, -1).map((from, index) => ({ from, to: stages[index + 1], on: "accepted" })) })),
19
43
  ...Object.entries(diagnosticFlowStages).map(([id, stages]) => ({ id, handler_status: "registered", entry_stage: "preflight", terminal_stages: ["report"],
20
44
  stages: stages.map((stage, index) => ({ id: stage, directory: `${String(index + 1).padStart(2, "0")}-${stage}`,
21
45
  entry_handler: "diagnostic-stage-entry", finish_handler: "diagnostic-stage-finish" })),
@@ -12,6 +12,7 @@ import { isMemoryInitResourceStorageFacts } from "./memory-init-resource-storage
12
12
  import { memoryInitStartupInputSchemas } from "./memory-init-startup-custody.js";
13
13
  import { SETUP_STARTUP_MS } from "../harness-runtime/lib/setup-progress.mjs";
14
14
  import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
15
+ import { isDeliveryFlow } from "../domain/delivery-flows.js";
15
16
  function storageProbeRefuse(reason, probe) {
16
17
  const summary = probe ? ` (${probe.error_code ?? probe.signal ?? `exit ${probe.status}`})` : "";
17
18
  throw new AppError("frozen_engine_storage_incompatible", `Selected frozen engine cannot admit memory-flow storage${summary}`, 1, { reason, effect: "no_effect", ...(probe ? { probe } : {}) });
@@ -160,6 +161,22 @@ export class AdmittedFrozenEngine {
160
161
  admitDiagnosticFlow(source, flowId) {
161
162
  return this.#admitMemoryFlow(source, flowId);
162
163
  }
164
+ admitDistillFlow(source) {
165
+ return this.#admitMemoryFlow(source, "mb-distill");
166
+ }
167
+ admitMemoryFixFlow(source) {
168
+ return this.#admitMemoryFlow(source, "mb-fix");
169
+ }
170
+ admitReviewFixFlow(source) {
171
+ return this.#admitMemoryFlow(source, "review-fix");
172
+ }
173
+ admitInteractiveFlow(source) {
174
+ source.assertEngineCompatibility(this.#manifest);
175
+ return this.#admitMemoryFlow(source, "interactive");
176
+ }
177
+ admitDeliveryFlow(source, flowId) {
178
+ return this.#admitMemoryFlow(source, flowId);
179
+ }
163
180
  #admitMemoryFlow(source, flowId, startupInputSchema) {
164
181
  const fail = (reason, details = {}) => {
165
182
  throw new AppError("frozen_engine_flow_incompatible", `Selected frozen engine cannot admit ${flowId} handlers`, 1, { ...details, reason, phase: "prepare", effect: "no_effect" });
@@ -191,7 +208,7 @@ export class AdmittedFrozenEngine {
191
208
  const flow = source.resolveRegisteredFlow();
192
209
  if (flow.descriptor.id !== flowId)
193
210
  fail("foreign_flow_source");
194
- const expected = { schema_id: isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
211
+ const expected = { schema_id: isDeliveryFlow(flowId) ? "dd-flow/delivery-engine-flow@1" : flowId === "interactive" ? "dd-flow/interactive-engine-flow@1" : flowId === "review-fix" ? "dd-flow/review-fix-engine-flow@1" : flowId === "mb-fix" ? "dd-flow/memory-fix-engine-flow@1" : flowId === "mb-distill" ? "dd-flow/distill-engine-flow@1" : isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
195
212
  : startupInputSchema === undefined ? "dd-flow/memory-init-engine-flow@1" : "dd-flow/memory-init-engine-flow@2",
196
213
  ...(startupInputSchema === undefined ? {} : { startup_input_schema: startupInputSchema }),
197
214
  flow_id: flowId, source_sha256: source.sha256,
@@ -199,7 +216,7 @@ export class AdmittedFrozenEngine {
199
216
  revalidate();
200
217
  const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !["NODE_OPTIONS", "NODE_PATH"].includes(key) && !key.startsWith("DD_FLOW_")));
201
218
  const started = performance.now();
202
- const result = spawnSync(this.nodeExecutable, [this.entrypoint, "runtime", "flow", "admit", "--operation", isDiagnosticFlow(flowId) ? flowId : flowId === "mb-init" ? "memory-init" : "memory-upgrade", "--json"], { input, cwd: this.#manifest.snapshot_root, env: { ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: this.#manifest.snapshot_root },
219
+ const result = spawnSync(this.nodeExecutable, [this.entrypoint, "runtime", "flow", "admit", "--operation", isDeliveryFlow(flowId) || isDiagnosticFlow(flowId) || flowId === "interactive" || flowId === "mb-distill" || flowId === "mb-fix" || flowId === "review-fix" ? flowId : flowId === "mb-init" ? "memory-init" : "memory-upgrade", "--json"], { input, cwd: this.#manifest.snapshot_root, env: { ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: this.#manifest.snapshot_root },
203
220
  // This is cold startup of a selected CLI, not an already-loaded handler.
204
221
  // Admission is bounded local validation; no provider operation is started.
205
222
  timeout: SETUP_STARTUP_MS, maxBuffer: 1024 * 1024, shell: false, windowsHide: true });