@deksden-com/dd-flow-cli 0.9.0-beta.151 → 0.9.0-beta.155

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 +23 -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 +273 -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 +170 -25
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +40 -19
  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 +1 -0
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
  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 +77 -10
  80. package/dist/services/check-runner-proof.js +69 -1
  81. package/dist/services/cli-operation-classifier.js +6 -1
  82. package/dist/services/code-checks.js +56 -23
  83. package/dist/services/codex-hook-delivery.js +11 -2
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +9 -4
  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 +214 -24
  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 -4
  190. package/dist/services/retained-native-hook.js +245 -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 +12 -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 +209 -43
  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 +9 -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 +252 -66
  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 +4 -3
  227. package/tools/verification-jobs.mjs +342 -0
@@ -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 });
@@ -145,10 +145,11 @@ export class FrozenGenericExecution {
145
145
  this.#flow.stage(id);
146
146
  return { profile: this.profile(this.settings.stage_profiles[id]), session_mode: this.settings.stage_session_mode };
147
147
  }
148
- work(stage, profileId) {
148
+ work(stage, profileId, workerStage = stage) {
149
149
  const coordinator = this.stage(stage).profile;
150
- const id = profileId ?? coordinator.id;
151
- if (id !== coordinator.id && !this.settings.work_profiles.includes(id))
150
+ const roleProfile = this.stage(workerStage).profile;
151
+ const id = profileId ?? roleProfile.id;
152
+ if (id !== roleProfile.id && id !== coordinator.id && !this.settings.work_profiles.includes(id))
152
153
  fail("Work profile is not approved for this RUN");
153
154
  const worker = this.profile(id);
154
155
  if (this.settings.delegation.mode === "native" && !sameSessionProfile(coordinator, worker))
@@ -11,7 +11,6 @@ import { readOperationProgress, readOperationRecord, readOperationWaiting, suspe
11
11
  import { recoverDaemonReply } from "../harness-runtime/lib/daemon-observation.mjs";
12
12
  import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
13
13
  import { readBoundedReleaseFile } from "./release-source.js";
14
- const maxAdapterOutputBytes = 1_000_000;
15
14
  /** Native hooks need a real executable, not a PATH-dependent shell expression. */
16
15
  export function prepareHarnessFlowExecutable(context, stateDir, engine) {
17
16
  // Native login shells rewrite search paths. The owned SDK must execute with
@@ -132,7 +131,6 @@ function runAdapterTransport(input, stateDir, binding = {}) {
132
131
  const child = spawn(input.executable, input.args, { env, ...(input.cwd ? { cwd: input.cwd } : {}), detached, stdio: ["ignore", "pipe", "pipe"] });
133
132
  let stdout = "";
134
133
  let stderr = "";
135
- let outputTooLarge = false;
136
134
  let timedOut = false;
137
135
  let aborted = false;
138
136
  let cleanup;
@@ -159,7 +157,7 @@ function runAdapterTransport(input, stateDir, binding = {}) {
159
157
  reject(callbackError);
160
158
  }
161
159
  else
162
- reject(new AppError(outputTooLarge ? "harness_adapter_output_limit" : timedOut ? "harness_adapter_timeout" : aborted ? "harness_adapter_aborted" : "harness_adapter_cleanup_unconfirmed", "Adapter transport cleanup could not be confirmed", 1, details));
160
+ reject(new AppError(timedOut ? "harness_adapter_timeout" : aborted ? "harness_adapter_aborted" : "harness_adapter_cleanup_unconfirmed", "Adapter transport cleanup could not be confirmed", 1, details));
163
161
  });
164
162
  };
165
163
  const deliver = (callback) => {
@@ -220,14 +218,6 @@ function runAdapterTransport(input, stateDir, binding = {}) {
220
218
  else
221
219
  input.signal?.addEventListener("abort", abort, { once: true });
222
220
  const evidence = (value) => input.onEvidence?.({ operation_id: input.operationId, executable: input.executable, args: input.args, ...value, at: new Date().toISOString() });
223
- const append = (current, chunk) => {
224
- if (Buffer.byteLength(current) + Buffer.byteLength(chunk) > maxAdapterOutputBytes) {
225
- outputTooLarge = true;
226
- stopTransport();
227
- return current;
228
- }
229
- return current + chunk;
230
- };
231
221
  const observeDiagnostic = (line) => {
232
222
  if (prettyDiagnostic === null) {
233
223
  if (line.trimEnd() === "}")
@@ -281,7 +271,7 @@ function runAdapterTransport(input, stateDir, binding = {}) {
281
271
  }
282
272
  }
283
273
  };
284
- child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout = append(stdout, chunk); });
274
+ child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += chunk; });
285
275
  // Diagnostics have no lifetime quota. Stream to the existing evidence sink
286
276
  // and carry only a bounded tail in the disposable RPC receipt.
287
277
  child.stderr.setEncoding("utf8").on("data", (chunk) => {
@@ -322,8 +312,6 @@ function runAdapterTransport(input, stateDir, binding = {}) {
322
312
  deliver(() => evidence({ status, signal, timed_out: timedOut, aborted, stdout, stderr, ...(spawnError ? { error: spawnError.message } : {}) }));
323
313
  if (spawnError)
324
314
  return reject(new AppError("harness_adapter_failed", "Harness adapter call failed", 1, { cause: adapterCause(spawnError.message), ...(callbackError ? { evidence_error: String(callbackError) } : {}) }));
325
- if (outputTooLarge)
326
- return reject(new AppError("harness_adapter_output_limit", "Harness adapter exceeded the bounded reply limit", 1, { executable: input.executable, args: input.args.slice(0, 3), max_output_bytes: maxAdapterOutputBytes }));
327
315
  if (timedOut)
328
316
  return reject(Object.assign(new AppError("harness_adapter_timeout", "Harness adapter call timed out", 1, { executable: input.executable, args: input.args.slice(0, 3), timeout_ms: timeoutMs }), { observationClock: clock }));
329
317
  if (aborted)
@@ -143,7 +143,7 @@ export function memoryRepairAssignedHookCwd(context, input) {
143
143
  export function memoryNativeReviewAssignedHookCwd(context, input) {
144
144
  const work = context.db.get("SELECT status,payload_json FROM works WHERE project_id=? AND run_id=? AND work_id=?", [input.projectId, input.runId, input.workId]);
145
145
  const payload = JSON.parse(work?.payload_json ?? "null");
146
- if (payload?.kind !== "memory-review" || payload.read_only !== true)
146
+ if (!["memory-review", "interactive-review"].includes(payload?.kind) || payload?.read_only !== true)
147
147
  return null;
148
148
  const ready = context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.native_review_copy_ready' AND json_extract(payload_json,'$.intent.run_id')=? AND json_extract(payload_json,'$.intent.work_id')=?", [input.projectId, input.runId, input.workId]);
149
149
  if (!ready)