@deksden-com/dd-flow-cli 0.9.0-beta.121 → 0.9.0-beta.122

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 (33) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/build-info.json +5 -5
  3. package/dist/cli/hook-ingress.js +4 -3
  4. package/dist/cli/native-hook-ingress.js +34 -2
  5. package/dist/harness-runtime/bin/dd-agy.mjs +6 -3
  6. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +16 -0
  7. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +60 -8
  8. package/dist/harness-runtime/lib/dd-zcode.mjs +25 -12
  9. package/dist/harness-runtime/lib/delegation-instructions.mjs +3 -3
  10. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  11. package/dist/harness-runtime/lib/lifecycle-command.mjs +218 -0
  12. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  13. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  14. package/dist/harness-runtime/lib/native-hook-command.d.mts +2 -2
  15. package/dist/harness-runtime/lib/native-hook-command.mjs +36 -5
  16. package/dist/runtime/context.js +1 -1
  17. package/dist/schemas/flow-run.schema.json +1 -0
  18. package/dist/services/codex-hook-delivery.js +6 -2
  19. package/dist/services/hooks.js +51 -59
  20. package/dist/services/lifecycle-command.js +1 -383
  21. package/dist/services/lifecycle-contract.js +1 -30
  22. package/dist/services/lifecycle-invocations.js +34 -13
  23. package/dist/services/runs.js +60 -47
  24. package/dist/services/stage-report-renderer.js +19 -0
  25. package/dist/services/verification-matrix.js +325 -0
  26. package/dist/services/vnext-code-review.js +25 -8
  27. package/dist/services/vnext-code.js +31 -10
  28. package/dist/services/vnext-merge.js +30 -7
  29. package/dist/services/vnext-plan-review.js +22 -6
  30. package/dist/services/vnext-plan.js +16 -3
  31. package/dist/services/work-registry.js +13 -1
  32. package/dist/storage/database.js +43 -9
  33. package/package.json +4 -1
@@ -17,6 +17,7 @@ import { addWorkBatch, prepareWorkBatch, bindStageCoordinatorWork, finishStageCo
17
17
  import { withStageSettlement, prepareFlowRunStageAttachment } from "./runs.js";
18
18
  import { vnextStageDirectory } from "../domain/stage-catalog.js";
19
19
  import { acceptStageReport, prepareStageReport } from "./stage-report-renderer.js";
20
+ import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix, publishVerificationMatrix } from "./verification-matrix.js";
20
21
  import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
21
22
  import { readFanoutDescriptor, writeFanoutDescriptor } from "./vnext-fanout.js";
22
23
  import { stageGraphScope } from "./stage-work-graph.js";
@@ -50,7 +51,7 @@ export function prepareVnextCodeReviewStart(context, input) {
50
51
  const common = { projectRoot, project, run, home, root, rootWork, existingPrompt };
51
52
  if (prior && fs.existsSync(existingPrompt))
52
53
  return { ...common, resumed: true, prior, prompt: fs.readFileSync(existingPrompt, "utf8"), orchestration: readFanoutDescriptor(root) };
53
- prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
54
+ const attachment = prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
54
55
  const mode = effectiveMode(home, run);
55
56
  const cycle = reviewCycle(run);
56
57
  const codeReportSha256 = sha256File(path.join(home, "05-code", "stage-report.json"));
@@ -61,7 +62,8 @@ export function prepareVnextCodeReviewStart(context, input) {
61
62
  const checks = mode.mode === "off" ? [] : acceptedCodeChecks(context, project.id, run.id).map(({ id, purpose }) => ({ id, purpose }));
62
63
  for (const name of ["review-context.json", ...(mode.mode === "off" ? ["decision.json"] : ["review-work-batch.json", "fanout.json"])])
63
64
  prepareOutputFile(path.join(root, name));
64
- return { ...common, resumed: false, mode, cycle, codeReportSha256, groups, batch, items, template, checks };
65
+ const matrix = mode.mode === "off" ? null : prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", stageAttempt: attachment.attempt });
66
+ return { ...common, resumed: false, mode, cycle, codeReportSha256, groups, batch, items, template, checks, matrix };
65
67
  }
66
68
  export function startVnextCodeReview(context, input, prepared = prepareVnextCodeReviewStart(context, input)) {
67
69
  const { projectRoot, project, run, root, rootWork, existingPrompt } = prepared;
@@ -69,6 +71,8 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
69
71
  assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
70
72
  fs.mkdirSync(root, { recursive: true });
71
73
  if (prepared.resumed) {
74
+ if (effectiveMode(prepared.home, run).mode !== "off")
75
+ resumeVerificationMatrix(context, project.id, run.id, stage);
72
76
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath: existingPrompt, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
73
77
  const binding = bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath: existingPrompt, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
74
78
  const prompt = fs.readFileSync(existingPrompt, "utf8");
@@ -76,6 +80,7 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
76
80
  return { ok: true, resumed: true, run_id: run.id, stage, stage_status: prepared.prior, id: binding.work_session_id, prompt_path: existingPrompt, worker_prompt_markdown: prompt, ...(externalContext ? { external_context: externalContext } : {}), ...(orchestration ? { orchestration } : {}), next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
77
81
  }
78
82
  const { mode, cycle, groups, batch, items, template, checks, codeReportSha256 } = prepared;
83
+ prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" }, true);
79
84
  const aspectMapChecksums = mode.mode === "off" ? [] : fs.readdirSync(path.join(run.run_root, "03-plan"), { withFileTypes: true })
80
85
  .filter(entry => entry.isDirectory() && fs.existsSync(path.join(run.run_root, "03-plan", entry.name, "aspect-map.json")))
81
86
  .map(entry => ({ path: `03-plan/${entry.name}/aspect-map.json`, sha256: sha256File(path.join(run.run_root, "03-plan", entry.name, "aspect-map.json")) }))
@@ -90,18 +95,21 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
90
95
  fs.writeFileSync(promptPath, prompt);
91
96
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
92
97
  const binding = bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
93
- attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
98
+ const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" };
99
+ attachFlowRunStage(context, attachmentInput, prepareFlowRunStageAttachment(context, attachmentInput));
94
100
  return { ok: true, run_id: run.id, stage, mode: mode.mode, mode_source: mode.source, id: binding.work_session_id, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
95
101
  }
96
102
  const batchFile = path.join(root, "review-work-batch.json");
97
103
  fs.writeFileSync(batchFile, `${JSON.stringify(batch, null, 2)}\n`);
98
- const prompt = `${orchestratorPrompt(context, { projectRoot, run, root, rootWork, mode: mode.mode, groups, template, checks })}\n${stageLifecycleInstruction}\n`;
104
+ const prompt = `${orchestratorPrompt(context, { projectRoot, run, root, rootWork, mode: mode.mode, groups, template, checks })}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, prepared.home, "reviewing whether current evidence proves the accepted obligations")}\n`;
99
105
  fs.writeFileSync(promptPath, prompt);
100
106
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
101
107
  const binding = bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
102
108
  const registered = addWorkBatch(context, { parentWorkId: rootWork.work_id, value: batch, prepared: items, stage, cycle });
103
109
  const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
104
- attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1", inputSha256: sha256Value(JSON.stringify({ batch: sha256File(batchFile), aspect_maps: aspectMapChecksums })) });
110
+ const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1", inputSha256: sha256Value(JSON.stringify({ batch: sha256File(batchFile), aspect_maps: aspectMapChecksums })) };
111
+ const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
112
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
105
113
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "code_review_started", work_id: rootWork.work_id, mode: mode.mode, mode_source: mode.source, groups, registered });
106
114
  return { ok: true, run_id: run.id, stage, mode: mode.mode, mode_source: mode.source, id: binding.work_session_id, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), orchestration, review: { groups, works: registered }, next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
107
115
  }
@@ -208,7 +216,8 @@ export async function finishVnextCodeReview(context, input) {
208
216
  fs.writeFileSync(temporary, `${JSON.stringify({ ...reviewContext, accepted_decision: decision }, null, 2)}\n`);
209
217
  fs.renameSync(temporary, contextFile);
210
218
  const repair = addVnextCodeReviewRepair(context, { projectRoot, runId: run.id, findingIds: fixIds, checkRefsByFinding, decisionReasonsByFinding: Object.fromEntries(canonical.decision.findings.map((item) => [item.finding_ref, item.reason])), objective: `Resolve accepted CODE-REVIEW findings: ${fixIds.join(", ")}` });
211
- return { ok: true, run_id: run.id, stage, outcome: "repair_required", repair, instruction: "The repair Work is already registered: do not invoke its start_command in this coordinator Session. End this Turn so the controller can launch it in a fresh child Session. When it completes, invoke next.finish_command; this review cycle uses the decision already accepted by the runtime.", next: { finish_command: finishCommand(context, run.id, projectRoot, decisionFile) } };
219
+ const retained = latestRunVerificationMatrix(context, project.id, run.id, stage, "input");
220
+ return { ok: true, run_id: run.id, stage, outcome: "repair_required", repair, ...(retained ? { verification_matrix: retained.metadata } : {}), instruction: `The repair Work is already registered: do not invoke its start_command in this coordinator Session. End this Turn so the controller can launch it in a fresh child Session. When it completes, invoke next.finish_command; this review cycle uses the decision already accepted by the runtime.\n${verificationMatrixInstruction(retained?.metadata ?? null, home, "the accepted review decision and repair")}`, next: { finish_command: finishCommand(context, run.id, projectRoot, decisionFile) } };
212
221
  }
213
222
  repairs = reviewRepairWorks(context, project.id, run.id, root, cycle);
214
223
  const incompleteRepairs = repairs.filter((work) => work.status !== "completed");
@@ -224,16 +233,21 @@ export async function finishVnextCodeReview(context, input) {
224
233
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "check_retry_requested", check_receipt_id: input.retryCheckId, stage, reason: input.retryReason ?? "operator retry" });
225
234
  }
226
235
  const receipts = await runCodeChecks(context, { projectId: project.id, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, artifactDir: stageDir, scope: "aggregate", checks: finalChecks, ...(input.retryCheckId ? { retryReceiptId: input.retryCheckId, retryReason: input.retryReason } : {}), ...(input.progress ? { progress: input.progress } : {}) });
236
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", receipts, semanticFile: decisionFile });
227
237
  const failed = receipts.filter((receipt) => receipt.status !== "passed");
228
- if (failed.length)
238
+ if (failed.length) {
239
+ context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
240
+ publishVerificationMatrix(context, matrix);
229
241
  throw new AppError("code_review_gate_failed", "CODE-REVIEW repair changed the project but the aggregate gate failed", 2, {
230
242
  failures: failed,
231
243
  outcome: "repair_required",
232
244
  retry_after_workspace_change: true,
233
245
  workspace_fingerprint: failed[0].workspace_fingerprint,
234
246
  continuation: registerAggregateCodeRepair(context, { projectRoot, runId: run.id, stage, failures: failed, checks: finalChecks, generation }),
235
- instruction: "Repair Work is already registered. End this Turn so the controller can launch a fresh child; after it finishes, repeat finish with the already accepted review decision. Do not redo review."
247
+ ...(matrix ? { verification_matrix: matrix.metadata } : {}),
248
+ instruction: `Repair Work is already registered. End this Turn so the controller can launch a fresh child; after it finishes, repeat finish with the already accepted review decision. Do not redo review.\n${verificationMatrixInstruction(matrix?.metadata ?? null, home, "the current aggregate failures and repair")}`
236
249
  });
250
+ }
237
251
  const stopTarget = executionStopTarget(run);
238
252
  const nextAction = stopTarget === "merge_completed" ? "start_merge" : "code_review_completed";
239
253
  const now = context.now();
@@ -245,6 +259,8 @@ export async function finishVnextCodeReview(context, input) {
245
259
  let mergeRequest = null;
246
260
  assertRunMutationAllowed(context, project.id, run.id, generation);
247
261
  const prepareMerge = nextAction === "start_merge" ? prepareVnextMergeRequest(context, { projectRoot, runId: run.id, acceptedPaths }) : null;
262
+ if (matrix)
263
+ Object.assign(report.semantic, { verification_matrix: matrix.metadata });
248
264
  const preparedReport = prepareStageReport(report);
249
265
  const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: report.mechanical.git };
250
266
  withStageSettlement(context, () => {
@@ -256,6 +272,7 @@ export async function finishVnextCodeReview(context, input) {
256
272
  }
257
273
  mergeRequest = prepareMerge?.() ?? null;
258
274
  const preparedCompletion = prepareFlowRunStageCompletion(context, completionInput);
275
+ acceptVerificationMatrix(context, matrix);
259
276
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
260
277
  completeFlowRunStage(context, completionInput, preparedCompletion);
261
278
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "code_review_completed", work_id: rootWork.work_id, outcome });
@@ -25,6 +25,7 @@ import { applyExternalStageContext, prepareExternalStageContext } from "./stage-
25
25
  import { readFanoutDescriptor, writeFanoutDescriptor } from "./vnext-fanout.js";
26
26
  import { assertRunMutationAllowed } from "./run-recovery.js";
27
27
  import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
28
+ import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, publishVerificationMatrix, latestRunVerificationMatrix } from "./verification-matrix.js";
28
29
  const stage = "code";
29
30
  const stageDir = vnextStageDirectory("code");
30
31
  /** A controller prepares slow, deterministic project tooling before it asks an
@@ -63,8 +64,7 @@ export function prepareVnextCodeStart(context, input) {
63
64
  if (existingStage && fs.existsSync(existingPrompt)) {
64
65
  return { ...common, resumed: true, existingStage, prompt: fs.readFileSync(existingPrompt, "utf8"), orchestration: readFanoutDescriptor(existingRoot) };
65
66
  }
66
- if (!sourceRepairAttempt)
67
- prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
67
+ const attachment = sourceRepairAttempt ? null : prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
68
68
  const batch = path.join(home, "03-plan", "code-work-batch.json");
69
69
  const handoffFailures = validateVnextCodeHandoff(context, {
70
70
  projectRoot,
@@ -86,23 +86,27 @@ export function prepareVnextCodeStart(context, input) {
86
86
  validateWorkBatchFile(batch);
87
87
  const template = fs.readFileSync(path.join(run.workspace_root, ".memory-bank", "dd-flow", "vnext", "code.md"), "utf8");
88
88
  const variables = getFlowRunVariables(context, { projectRoot, runId: run.id });
89
- return { ...common, resumed: false, template, capacity: variables.variables?.["runtime.subagent.available_slots"] };
89
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", completeness: "declared", ...(attachment ? { stageAttempt: attachment.attempt } : {}) });
90
+ return { ...common, resumed: false, template, capacity: variables.variables?.["runtime.subagent.available_slots"], matrix };
90
91
  }
91
92
  export async function startVnextCode(context, input, prepared = prepareVnextCodeStart(context, input)) {
92
93
  const { projectRoot, project, run, root, promptPath, rootWork, sourceRepairAttempt } = prepared;
93
94
  assertRunMutationAllowed(context, project.id, run.id);
94
95
  assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
95
96
  if (prepared.resumed) {
97
+ resumeVerificationMatrix(context, project.id, run.id, stage);
96
98
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
97
99
  bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
98
100
  const orchestration = prepared.orchestration ?? writeFanoutDescriptor(root, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
99
101
  return { ok: true, resumed: true, run_id: run.id, stage, stage_status: prepared.existingStage, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), orchestration, graph: codeWorkGraph(context, project.id, run.id), next: { refresh_graph_command: runtimeCommand(context, ["work", "ls", "--run", run.id, "--ready", "--project-root", projectRoot, "--json"]), finish_command: finishCommand(context, input.runId, projectRoot, path.join(root, "code-verification.json")) } };
100
102
  }
103
+ if (!sourceRepairAttempt)
104
+ prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" }, true);
101
105
  fs.mkdirSync(root, { recursive: true });
102
106
  const readiness = await ensureCodeWorkspaceReady(context, project.id, run, root, [], input.progress);
103
107
  // Graph rendering may issue managed child commands; it belongs to execution.
104
108
  const graph = codeWorkGraph(context, project.id, run.id);
105
- const prompt = `${coordinatorPrompt(context, { projectRoot, run, root, rootWork, readiness, template: prepared.template, capacity: prepared.capacity, graph })}\n${stageLifecycleInstruction}\n`;
109
+ const prompt = `${coordinatorPrompt(context, { projectRoot, run, root, rootWork, readiness, template: prepared.template, capacity: prepared.capacity, graph })}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, prepared.home, "implementation and code-verification.json")}\n`;
106
110
  fs.writeFileSync(promptPath, prompt);
107
111
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
108
112
  const binding = bindStageCoordinatorWork(context, {
@@ -114,15 +118,19 @@ export async function startVnextCode(context, input, prepared = prepareVnextCode
114
118
  ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {})
115
119
  });
116
120
  if (!sourceRepairAttempt) {
117
- attachFlowRunStage(context, {
121
+ const attachmentInput = {
118
122
  projectRoot,
119
123
  runId: input.runId,
120
124
  stage,
121
125
  dir: stageDir,
122
126
  status: "running",
123
127
  dataSchemaId: "dd-flow/stage-report@1"
124
- });
128
+ };
129
+ const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
130
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
125
131
  }
132
+ else
133
+ commitVerificationMatrixStart(context, prepared.matrix, () => undefined);
126
134
  const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
127
135
  appendFlowRunTimelineEvent(context, project.id, input.runId, { type: "code_started", work_id: rootWork.work_id, graph });
128
136
  return {
@@ -180,8 +188,10 @@ export async function finishVnextCode(context, input) {
180
188
  // Reject a malformed semantic receipt before running the expensive gate.
181
189
  const verification = input.verification ?? verificationForFinish(context, { file: input.verificationFile, projectRoot: run.workspace_root, runId: run.id, runRoot: home });
182
190
  validateSchema({ schemaName: "code-verification", file: input.verificationFile, data: verification, projectRoot: run.workspace_root, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: home });
191
+ const retainedMatrix = latestRunVerificationMatrix(context, project.id, run.id, stage);
192
+ const retainedMatrixInstruction = verificationMatrixInstruction(retainedMatrix?.metadata ?? null, home, "the current semantic verification and repair");
183
193
  if (verification.verdict === "blocked") {
184
- return { ok: true, run_id: run.id, stage, outcome: "blocked", verification, instruction: "CODE remains running. Resolve the stated external or user-input blocker in this same coordinator session, update code-verification.json, then invoke this same stage finish command again." };
194
+ return { ok: true, run_id: run.id, stage, outcome: "blocked", verification, ...(retainedMatrix ? { verification_matrix: retainedMatrix.metadata } : {}), instruction: `CODE remains running. Resolve the stated external or user-input blocker in this same coordinator session, update code-verification.json, then invoke this same stage finish command again.\n${retainedMatrixInstruction}` };
185
195
  }
186
196
  if (verification.verdict === "passed" && verification.unresolved.length > 0) {
187
197
  throw new AppError("verification_contradictory", "A passed CODE verification cannot contain unresolved obligations", 2, { unresolved: verification.unresolved });
@@ -196,7 +206,7 @@ export async function finishVnextCode(context, input) {
196
206
  verification,
197
207
  objective: verification.summary
198
208
  });
199
- return { ok: true, run_id: run.id, stage, outcome: "repair_required", verification, repair, instruction: "The semantic verification is not accepted. The repair Work is already registered: do not invoke its start_command in this coordinator Session. End this Turn so the controller can launch it in a fresh child Session. After that Work completes, update code-verification.json and invoke this same stage finish command again." };
209
+ return { ok: true, run_id: run.id, stage, outcome: "repair_required", verification, repair, ...(retainedMatrix ? { verification_matrix: retainedMatrix.metadata } : {}), instruction: `The semantic verification is not accepted. The repair Work is already registered: do not invoke its start_command in this coordinator Session. End this Turn so the controller can launch it in a fresh child Session. After that Work completes, update code-verification.json and invoke this same stage finish command again.\n${retainedMatrixInstruction}` };
200
210
  }
201
211
  const checks = finalCodeCheckDeclarations(run.workspace_root, works.flatMap((work) => packet(work)?.checks ?? []));
202
212
  if (input.retryCheckId) {
@@ -209,7 +219,11 @@ export async function finishVnextCode(context, input) {
209
219
  ...(input.progress ? { progress: input.progress } : {})
210
220
  });
211
221
  const failed = receipts.filter((receipt) => receipt.status !== "passed");
222
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", receipts, semanticFile: input.verificationFile });
223
+ const matrixInstruction = matrix ? verificationMatrixInstruction(matrix.metadata, home, "semantic verification in code-verification.json") : "";
212
224
  if (failed.length) {
225
+ context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
226
+ publishVerificationMatrix(context, matrix);
213
227
  const continuation = registerAggregateCodeRepair(context, { projectRoot, runId: run.id, stage, failures: failed, checks, generation });
214
228
  throw new AppError("code_gate_failed", "CODE remains running because the aggregate project gate failed", 2, {
215
229
  failures: failed,
@@ -217,15 +231,19 @@ export async function finishVnextCode(context, input) {
217
231
  retry_after_workspace_change: true,
218
232
  workspace_fingerprint: failed[0].workspace_fingerprint,
219
233
  continuation,
220
- instruction: "Repair Work is already registered. End this coordinator Turn; the controller will launch a fresh child. After it completes, update semantic verification and finish the Stage."
234
+ ...(matrix ? { verification_matrix: matrix.metadata, verification_matrix_instruction: matrixInstruction } : {}),
235
+ instruction: `Repair Work is already registered. End this coordinator Turn; the controller will launch a fresh child. After it completes, update semantic verification and finish the Stage.\n${matrixInstruction}`
221
236
  });
222
237
  }
223
238
  const finalReceipts = receipts;
224
239
  if (verification.verdict === "needs_checks") {
240
+ context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
241
+ publishVerificationMatrix(context, matrix);
225
242
  return { ok: true, run_id: run.id, stage, outcome: "verification_required", verification,
243
+ ...(matrix ? { verification_matrix: matrix.metadata, verification_matrix_instruction: matrixInstruction } : {}),
226
244
  receipts: finalReceipts.map(receipt => ({ id: receipt.id, receipt_path: receipt.receipt_path, input_hash: receipt.input_hash, status: receipt.status })),
227
245
  next: { kind: "verification_required", finish_command: finishCommand(context, run.id, projectRoot, input.verificationFile) },
228
- instruction: "Aggregate receipts are available. Keep CODE running. Inspect them, update code-verification.json to passed or needs_repair, then invoke only the issued successor finish_command. A material document change belongs to the registered fresh repair Work, not this coordinator Session." };
246
+ instruction: `Aggregate receipts are available. Keep CODE running. Inspect them, update code-verification.json to passed or needs_repair, then invoke only the issued successor finish_command. A material document change belongs to the registered fresh repair Work, not this coordinator Session.\n${matrixInstruction}` };
229
247
  }
230
248
  const projectedVerification = verificationProjection(works, finalReceipts, { historicalReceipts: finalReceipts, runId: run.id, runHome: home });
231
249
  const unresolvedAcceptance = (projectedVerification.acceptance ?? []).filter((item) => item.status === "unresolved");
@@ -278,6 +296,8 @@ export async function finishVnextCode(context, input) {
278
296
  let mergeRequest = null;
279
297
  assertRunMutationAllowed(context, project.id, run.id, generation);
280
298
  const prepareMerge = next === "start_merge" ? prepareVnextMergeRequest(context, { projectRoot, runId: run.id, acceptedPaths: changedPaths }) : null;
299
+ if (matrix)
300
+ Object.assign(report.semantic, { verification_matrix: matrix.metadata });
281
301
  const preparedReport = prepareStageReport(report);
282
302
  const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: report.mechanical.git };
283
303
  withStageSettlement(context, () => {
@@ -288,6 +308,7 @@ export async function finishVnextCode(context, input) {
288
308
  }
289
309
  mergeRequest = prepareMerge?.() ?? null;
290
310
  const preparedCompletion = prepareFlowRunStageCompletion(context, completionInput);
311
+ acceptVerificationMatrix(context, matrix);
291
312
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
292
313
  completeFlowRunStage(context, completionInput, preparedCompletion);
293
314
  const rootWork = requireRootWork(context, project.id, run.id);
@@ -23,6 +23,7 @@ import { ensureLaneWorkspace, prepareLaneWorkspace, heartbeatLaneLock, releaseLa
23
23
  import { assertRunMutationAllowed } from "./run-recovery.js";
24
24
  import { assertStageStartHookEvent } from "./hooks.js";
25
25
  import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
26
+ import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix, publishVerificationMatrix } from "./verification-matrix.js";
26
27
  const stage = "merge";
27
28
  const stageDir = vnextStageDirectory(stage);
28
29
  export function isVnextMergeRun(context, input) {
@@ -133,8 +134,7 @@ export function prepareVnextMergeStart(context, input) {
133
134
  if (!work || work.project_id !== project.id || work.run_id !== run.id || !["created", "running"].includes(work.status))
134
135
  throw new AppError("runtime_artifact_invalid", "MERGE executor Work is missing or not startable", 1, { work_id: request.executor_work_id });
135
136
  const index = JSON.parse(run.index_json);
136
- if (!index.stage_runs?.some(entry => entry.stage === stage))
137
- prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" });
137
+ const attachment = ["active", "action_required"].includes(request.status) && index.stage_runs?.some(entry => entry.stage === stage) ? null : prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" });
138
138
  prepareLaneWorkspace(context, { projectRoot, lane: "merge", workspacePath: request.target_workspace, branch: request.target_branch });
139
139
  const checks = effectiveMergeChecks(run, request), settings = executionSettings(run);
140
140
  const existingPrompt = ["active", "action_required"].includes(request.status) && fs.existsSync(promptPath) ? fs.readFileSync(promptPath, "utf8") : null;
@@ -142,15 +142,21 @@ export function prepareVnextMergeStart(context, input) {
142
142
  prepareOutputFile(promptPath);
143
143
  if (input.externalContext)
144
144
  prepareExternalStageContext({ stageRoot: root, loaded: input.externalContext });
145
- return { ...common, terminal: false, root, workRoot, resultPath, promptPath, checks, settings, existingPrompt };
145
+ const matrix = existingPrompt !== null ? latestRunVerificationMatrix(context, project.id, run.id, stage, "input") : prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", ...(attachment ? { stageAttempt: attachment.attempt } : {}) });
146
+ return { ...common, terminal: false, root, workRoot, resultPath, promptPath, checks, settings, existingPrompt, matrix };
146
147
  }
147
148
  export async function startVnextMerge(context, input, prepared = prepareVnextMergeStart(context, input)) {
148
149
  const { projectRoot, project, run, request, generation } = prepared;
149
150
  if (prepared.terminal)
150
151
  return requestView(context, request);
151
152
  const { root, workRoot, resultPath, promptPath, checks, settings, existingPrompt } = prepared;
153
+ if (existingPrompt !== null)
154
+ resumeVerificationMatrix(context, project.id, run.id, stage);
152
155
  assertRunMutationAllowed(context, project.id, run.id, generation);
153
156
  assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
157
+ const alreadyAttached = ["active", "action_required"].includes(request.status) && (JSON.parse(requireRun(context, project.id, run.id).index_json).stage_runs ?? []).some((item) => item.stage === stage);
158
+ if (!alreadyAttached)
159
+ prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" }, true);
154
160
  fs.mkdirSync(workRoot, { recursive: true });
155
161
  ensureLaneWorkspace(context, { projectRoot, lane: "merge", workspacePath: request.target_workspace, branch: request.target_branch });
156
162
  const lane = await waitAcquireLaneLock(context, { projectRoot, lane: "merge", workerId: request.executor_work_id, workspacePath: request.target_workspace, timeoutSeconds: 3600, pollIntervalSeconds: 15, ttlSeconds: 7200, reason: `MERGE ${request.merge_request_id}`, ...(input.progress ? { progress: input.progress } : {}) });
@@ -163,11 +169,13 @@ export async function startVnextMerge(context, input, prepared = prepareVnextMer
163
169
  const binding = pendingWork?.status === "created"
164
170
  ? startStageCoordinatorWork(context, { workId: request.executor_work_id, hookEventId: input.hookEventId, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) })
165
171
  : bindStageCoordinatorWork(context, { workId: request.executor_work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
166
- const prompt = existingPrompt ?? `${mergePrompt(context, { run, request, resultPath, projectRoot }, checks, settings)}\n${stageLifecycleInstruction}\n`;
172
+ const prompt = existingPrompt ?? `${mergePrompt(context, { run, request, resultPath, projectRoot }, checks, settings)}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, run.run_root, "MERGE source/target verification and residual-risk assessment")}\n`;
167
173
  if (existingPrompt === null)
168
174
  fs.writeFileSync(promptPath, prompt);
169
175
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
170
- ensureMergeStageAttached(context, run, projectRoot);
176
+ const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" };
177
+ const attachment = alreadyAttached ? null : prepareFlowRunStageAttachment(context, attachmentInput);
178
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachment ? attachFlowRunStage(context, attachmentInput, attachment) : undefined);
171
179
  return startPacket(context, run, request, binding, promptPath, resultPath, prompt, projectRoot, checks, externalContext);
172
180
  }
173
181
  const current = requireRequest(context, request.merge_request_id);
@@ -182,11 +190,13 @@ export async function startVnextMerge(context, input, prepared = prepareVnextMer
182
190
  if (claimed.changes !== 1 && requireRequest(context, current.merge_request_id).status !== "active")
183
191
  throw new AppError("merge_claim_conflict", "MERGE request could not acquire the integration lane", 1, { merge_request_id: current.merge_request_id });
184
192
  const started = startStageCoordinatorWork(context, { workId: current.executor_work_id, hookEventId: input.hookEventId, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
185
- const prompt = `${mergePrompt(context, { run, request: requireRequest(context, current.merge_request_id), resultPath, projectRoot }, checks, settings)}\n${stageLifecycleInstruction}\n`;
193
+ const prompt = `${mergePrompt(context, { run, request: requireRequest(context, current.merge_request_id), resultPath, projectRoot }, checks, settings)}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, run.run_root, "MERGE source/target verification and residual-risk assessment")}\n`;
186
194
  fs.writeFileSync(promptPath, prompt);
187
195
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
188
196
  context.db.run("UPDATE work_sessions SET prompt_path = ?, result_path = ?, updated_at = ? WHERE work_id = ? AND status = 'running'", [promptPath, resultPath, context.now(), current.executor_work_id]);
189
- attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" });
197
+ const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" };
198
+ const attachment = alreadyAttached ? null : prepareFlowRunStageAttachment(context, attachmentInput);
199
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachment ? attachFlowRunStage(context, attachmentInput, attachment) : undefined);
190
200
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "merge_started", merge_request_id: current.merge_request_id, work_id: current.executor_work_id, target_head: targetHead });
191
201
  return startPacket(context, run, current, started, promptPath, resultPath, prompt, projectRoot, checks, externalContext);
192
202
  }
@@ -323,6 +333,11 @@ export async function finishVnextMerge(context, input, prepared = prepareVnextMe
323
333
  verifyLocalDelivery(request);
324
334
  context.db.run("UPDATE merge_requests SET checkpoint = 'delivered', updated_at = ? WHERE merge_request_id = ?", [context.now(), request.merge_request_id]);
325
335
  request = requireRequest(context, request.merge_request_id);
336
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", receipts, semanticFile: prepared.resultPath });
337
+ // Retain exact bounded sources before deleting the feature checkout. A
338
+ // crash here recovers presentation from packets, never repeats the checks.
339
+ context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
340
+ publishVerificationMatrix(context, matrix);
326
341
  performConfiguredCleanup(run, request);
327
342
  const acceptedResult = prepared.result.text;
328
343
  const executor = context.db.get("SELECT status, result FROM works WHERE work_id = ?", [request.executor_work_id]);
@@ -338,6 +353,8 @@ export async function finishVnextMerge(context, input, prepared = prepareVnextMe
338
353
  const root = path.join(requireHome(run), stageDir);
339
354
  const retainedReport = completedMergeReport(context, run, root);
340
355
  const report = retainedReport ?? mergeReport(context, run, requireRequest(context, request.merge_request_id), semantic, receipts);
356
+ if (matrix)
357
+ Object.assign(report.semantic, { verification_matrix: matrix.metadata });
341
358
  if (!retainedReport)
342
359
  commitMergeStageReport(context, run, projectRoot, root, report);
343
360
  const rootWork = requireRootWork(context, project.id, run.id);
@@ -409,10 +426,16 @@ function completedMergeReport(context, run, root) {
409
426
  return JSON.parse(fs.readFileSync(file, "utf8"));
410
427
  }
411
428
  function commitMergeStageReport(context, run, projectRoot, root, report) {
429
+ const matrix = latestRunVerificationMatrix(context, run.project_id, run.id, stage, "output");
430
+ if (matrix)
431
+ Object.assign(report.semantic, { verification_matrix: matrix.metadata });
432
+ if (JSON.parse(run.index_json).verification_matrix_contract && !matrix)
433
+ throw new AppError("verification_matrix_packet_missing", "Completed marked MERGE lacks its retained final matrix", 1, { run_id: run.id });
412
434
  const preparedReport = prepareStageReport(report);
413
435
  const completion = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@2", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256 };
414
436
  const preparedCompletion = prepareFlowRunStageCompletion(context, completion);
415
437
  withStageSettlement(context, () => {
438
+ acceptVerificationMatrix(context, matrix);
416
439
  acceptStageReport(context, { projectId: run.project_id, runId: run.id, stage, root, prepared: preparedReport });
417
440
  completeFlowRunStage(context, completion, preparedCompletion);
418
441
  });
@@ -22,6 +22,7 @@ import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.j
22
22
  import { getStageFanoutCapacity, readFanoutDescriptor, subagentCapacityKey, writeFanoutDescriptor } from "./vnext-fanout.js";
23
23
  import { stageGraphScope } from "./stage-work-graph.js";
24
24
  import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
25
+ import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix } from "./verification-matrix.js";
25
26
  const stage = "plan-review";
26
27
  const stageDir = vnextStageDirectory(stage);
27
28
  const reviewTask = "Independently review the accepted PLAN and decide whether CODE may open.";
@@ -59,14 +60,15 @@ export function prepareVnextPlanReviewStart(context, input) {
59
60
  if (input.externalContext)
60
61
  prepareExternalStageContext({ stageRoot: root, loaded: input.externalContext });
61
62
  prepareOutputFile(path.join(root, "stage-prompt.md"));
62
- const active = { ...common, requested, protocolIds, groups, effective, workspaceRoute, batchChecksum, planChecksum };
63
+ const attachment = existing?.status === "running" ? null : prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
64
+ const matrix = existing?.status === "running" ? latestRunVerificationMatrix(context, project.id, run.id, stage, "input") : prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", ...(attachment ? { stageAttempt: attachment.attempt } : {}) });
65
+ const active = { ...common, requested, protocolIds, groups, effective, workspaceRoute, batchChecksum, planChecksum, matrix };
63
66
  if (existing?.status === "running") {
64
67
  const orchestration = readFanoutDescriptor(root);
65
68
  if (!orchestration)
66
69
  prepareOutputFile(path.join(root, "fanout.json"));
67
70
  return { ...active, state: "resumed", workId: existing.work_id, prompt: fs.readFileSync(path.join(root, "stage-prompt.md"), "utf8"), orchestration };
68
71
  }
69
- prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
70
72
  const rootWork = rootWorkId(context, project.id, run.id);
71
73
  if (effective === "off") {
72
74
  const failures = validateVnextPlanArtifacts(context, { projectRoot, workspaceRoot: run.workspace_root, runId: run.id, home, protocols: protocolIds });
@@ -85,7 +87,8 @@ export function prepareVnextPlanReviewStart(context, input) {
85
87
  }
86
88
  for (const name of ["stage-report.json", "stage-report.md", "stage-report.html"])
87
89
  prepareOutputFile(path.join(root, name));
88
- return { ...active, state: "off", rootWork, codeItems };
90
+ const outputMatrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", ...(attachment ? { stageAttempt: attachment.attempt } : {}) });
91
+ return { ...active, state: "off", rootWork, codeItems, outputMatrix };
89
92
  }
90
93
  const template = read(path.join(projectRoot, ".memory-bank", "dd-flow", "vnext", "plan-review.md"));
91
94
  const revision = currentPlanRevision(home, run.workspace_root);
@@ -103,11 +106,13 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
103
106
  }
104
107
  const { requested, effective, groups, batchChecksum, planChecksum, workspaceRoute } = prepared;
105
108
  if (prepared.state === "resumed") {
109
+ resumeVerificationMatrix(context, project.id, run.id, stage);
106
110
  const promptPath = path.join(root, "stage-prompt.md");
107
111
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
108
112
  return { ...preparedExisting(context, { projectRoot, projectId: project.id, run, root, workId: prepared.workId, hookEventId: input.hookEventId, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}), requested, effective, groups, batchChecksum, planChecksum, orchestration: prepared.orchestration }), ...(externalContext ? { external_context: externalContext } : {}) };
109
113
  }
110
114
  ensureWorkRegistry(context);
115
+ prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" }, true);
111
116
  fs.mkdirSync(root, { recursive: true });
112
117
  const now = context.now(), rootWork = prepared.rootWork;
113
118
  if (prepared.state === "off") {
@@ -115,12 +120,17 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
115
120
  fs.writeFileSync(promptPath, "PLAN-REVIEW is disabled by the frozen RUN configuration.\n");
116
121
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
117
122
  bindStageCoordinatorWork(context, { workId: rootWork, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
118
- attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
123
+ const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" };
124
+ const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
125
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
119
126
  const reportGit = gitFacts(run.workspace_root);
120
127
  const nextCommand = codeCommand(context, run.id, projectRoot);
121
128
  return withStageSettlement(context, () => {
122
129
  const registered = addWorkBatch(context, { parentWorkId: rootWork, prepared: prepared.codeItems, stage: "code" });
123
130
  const report = reportFor(context, { run, mode: effective, requested, outcome: "off", groups: [], batchChecksum, planChecksum, code: registered, now, projectRoot, flow: flowCommand(context), git: reportGit, nextCommand });
131
+ acceptVerificationMatrix(context, prepared.outputMatrix);
132
+ if (prepared.outputMatrix)
133
+ Object.assign(report.semantic, { verification_matrix: prepared.outputMatrix.metadata });
124
134
  const preparedReport = prepareStageReport(report);
125
135
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
126
136
  const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: reportGit };
@@ -133,7 +143,7 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
133
143
  const workId = nextWorkId(context, project.id, "plan-review");
134
144
  context.db.run("INSERT INTO works (work_id, project_id, run_id, parent_work_id, task, launch_policy, result_schema, depends_on_json, status, result, started_at, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, 'reuse_allowed', NULL, '[]', 'running', NULL, ?, ?, ?, NULL)", [workId, project.id, run.id, rootWork, reviewTask, now, now, now]);
135
145
  const pauseCommand = stagePauseCommand(context, { runId: run.id, stage, workId, projectRoot });
136
- const prompt = `${orchestratorPrompt(context, { projectRoot, run, home, root, workId, requested, effective, groups, batchChecksum, planChecksum, workspaceRoute, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), template: prepared.template, revision: prepared.revision, capacity: prepared.capacity })}\n${stageLifecycleInstruction}\n`;
146
+ const prompt = `${orchestratorPrompt(context, { projectRoot, run, home, root, workId, requested, effective, groups, batchChecksum, planChecksum, workspaceRoute, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), template: prepared.template, revision: prepared.revision, capacity: prepared.capacity })}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, home, "reviewing PLAN coverage, fixtures and proof limits")}\n`;
137
147
  const promptPath = path.join(root, "stage-prompt.md");
138
148
  fs.writeFileSync(promptPath, prompt);
139
149
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
@@ -146,7 +156,9 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
146
156
  fs.rmSync(promptPath, { force: true });
147
157
  throw error;
148
158
  }
149
- attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
159
+ const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" };
160
+ const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
161
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
150
162
  markReviewPending(home, groups);
151
163
  const workSessionId = String(binding.work_session_id);
152
164
  const sessionId = String(binding.session_id);
@@ -307,13 +319,17 @@ function finishPlanReviewOwned(context, input) {
307
319
  const codeItems = prepareWorkBatch(context, { parentWorkId: codeParentId, file: batch, stage: "code" });
308
320
  const reportGit = gitFacts(run.workspace_root);
309
321
  const nextCommand = codeCommand(context, run.id, projectRoot);
322
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", semanticFile: decisionPath });
310
323
  return withStageSettlement(context, () => {
311
324
  const registered = addWorkBatch(context, { parentWorkId: codeParentId, prepared: codeItems, stage: "code" });
312
325
  const now = context.now();
313
326
  settleReviewParent(context, { projectRoot, projectId: project.id, runId: run.id, parentWorkId: parent.work_id, status: "completed", result: decision.summary, reason: "plan_review_completed", resultPath: path.join(root, "stage-report.json") });
314
327
  const report = reportFor(context, { run, mode: contextFile.system?.effective_mode ?? "standard", requested: contextFile.system?.requested_mode ?? "auto", outcome: "accepted", groups, batchChecksum: contextFile.system?.batch_checksum ?? finalBatchChecksum, planChecksum: contextFile.system?.plan_checksum ?? finalPlanChecksum, finalBatchChecksum, finalPlanChecksum, initialPlanRevision: reviewedPlanRevision, finalPlanRevision: planRevision, code: registered, now, projectRoot, decision, children: latestChildren, flow: flowCommand(context), git: reportGit, nextCommand });
315
328
  applyReviewResults(home, latestChildren, decision);
329
+ if (matrix)
330
+ Object.assign(report.semantic, { verification_matrix: matrix.metadata });
316
331
  const preparedReport = prepareStageReport(report);
332
+ acceptVerificationMatrix(context, matrix);
317
333
  acceptStagePacket(context, { projectId: project.id, runId: run.id, stage, prepared: decisionReceipt });
318
334
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
319
335
  const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: reportGit };
@@ -21,6 +21,7 @@ import { acceptStageReport, prepareStageReport } from "./stage-report-renderer.j
21
21
  import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
22
22
  import { subagentCapacityKey } from "./vnext-fanout.js";
23
23
  import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
24
+ import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, verificationMatrixInstruction } from "./verification-matrix.js";
24
25
  export function isVnextPlanRun(context, input) {
25
26
  const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
26
27
  return Boolean(context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ? AND flow_kind = 'vnext_protocolize'", [project.id, input.runId]));
@@ -41,7 +42,7 @@ export function prepareVnextPlanStart(context, input) {
41
42
  const { profile: codeCheckProfile } = readCodeCheckProfile(run.workspace_root);
42
43
  if (context.db.get("SELECT 1 FROM works WHERE project_id = ? AND run_id = ? AND task = ? AND status = 'running'", [project.id, run.id, planTask]))
43
44
  throw new AppError("invalid_work_state", "PLAN already has a running Work", 1, { run_id: run.id });
44
- prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage: "plan", dir: "03-plan", status: "running", dataSchemaId: "dd-flow/protocol-plan@6" });
45
+ const attachment = prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage: "plan", dir: "03-plan", status: "running", dataSchemaId: "dd-flow/protocol-plan@6" });
45
46
  const root = path.join(home, vnextStageDirectory("plan"));
46
47
  if (input.externalContext)
47
48
  prepareExternalStageContext({ stageRoot: root, loaded: input.externalContext });
@@ -58,12 +59,16 @@ export function prepareVnextPlanStart(context, input) {
58
59
  const git = gitFacts(run.workspace_root);
59
60
  for (const file of [...planPaths, ...mapPaths, ...["stage-prompt.md", "work-context.json", "fixture-root.prompt.md"].map(name => path.join(root, name))])
60
61
  prepareOutputFile(file);
61
- return { projectRoot, project, run, home, root, rootWork, workspaceRoute, protocols, template, codeCheckProfile, planPaths, mapPaths, owned, identities, aspects, runVariables, mergeRequired, git };
62
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage: "plan", role: "input", stageAttempt: attachment.attempt });
63
+ return { projectRoot, project, run, home, root, rootWork, workspaceRoute, protocols, template, codeCheckProfile, planPaths, mapPaths, owned, identities, aspects, runVariables, mergeRequired, git, matrix };
62
64
  }
63
65
  export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(context, input)) {
64
66
  const { projectRoot, project, run, root, rootWork, workspaceRoute, protocols, template, codeCheckProfile, planPaths, mapPaths, owned, identities, aspects, runVariables, mergeRequired, git, home } = prepared;
65
67
  ensureWorkRegistry(context);
66
68
  assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage: "plan", projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
69
+ if (context.db.get("SELECT 1 FROM works WHERE project_id = ? AND run_id = ? AND task = ? AND status = 'running'", [project.id, run.id, planTask]))
70
+ throw new AppError("invalid_work_state", "PLAN already has a running Work", 1, { run_id: run.id });
71
+ prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage: "plan", dir: "03-plan", status: "running", dataSchemaId: "dd-flow/protocol-plan@6" }, true);
67
72
  fs.mkdirSync(root, { recursive: true });
68
73
  const now = context.now();
69
74
  // Imported stage fixtures have a synthetic running root with no prior agent.
@@ -113,9 +118,13 @@ export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(
113
118
  : `PLAN-REVIEW mode is ${planReviewMode}: include review groups only for genuinely applicable aspects; an empty set is allowed.`;
114
119
  fs.writeFileSync(promptPath, `${prompt}\n<review_mode_rule>${reviewModeRule}</review_mode_rule>\n<verification_rule>Every flow check must be an autonomous executable command. Do not declare external/manual proof or a human review as a check, evidence substitute, DEF, or gate. The only user interaction supported by this flow is an explicit stage pause for a material unanswered question.</verification_rule>\n${stageLifecycleInstruction}\n`);
115
120
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
121
+ if (prepared.matrix)
122
+ fs.appendFileSync(promptPath, verificationMatrixInstruction(prepared.matrix.metadata, home, "designing the source PLAN and its tests"));
116
123
  fs.writeFileSync(path.join(root, "work-context.json"), JSON.stringify({ schema_id: "dd-flow/work-context@1", system: { run_id: run.id, work_id: planWorkId, stage: "plan" }, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root }, artifacts: artifactMaterialization, input: { protocols, owned_obligations: Object.fromEntries(owned) } }, null, 2));
117
124
  const binding = bindStageCoordinatorWork(context, { workId: planWorkId, hookEventId: input.hookEventId, stage: "plan", promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
118
- attachFlowRunStage(context, { projectRoot, runId: run.id, stage: "plan", dir: "03-plan", status: "running", dataSchemaId: "dd-flow/protocol-plan@6" });
125
+ const attachmentInput = { projectRoot, runId: run.id, stage: "plan", dir: "03-plan", status: "running", dataSchemaId: "dd-flow/protocol-plan@6" };
126
+ const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
127
+ commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
119
128
  const workSessionId = String(binding.work_session_id);
120
129
  const sessionId = String(binding.session_id);
121
130
  refreshRunWorkProjection(context, project.id, run.id);
@@ -169,6 +178,9 @@ function finishVnextPlanOwned(context, input) {
169
178
  plan_checksums: planFiles.map((file) => ({ path: path.relative(run.workspace_root, file).split(path.sep).join("/"), sha256: checksum(file) })),
170
179
  aspect_map_checksums: mapFiles.map((file) => ({ path: path.relative(home, file).split(path.sep).join("/"), sha256: checksum(file) }))
171
180
  });
181
+ const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage: "plan", role: "output" });
182
+ if (matrix)
183
+ Object.assign(report.semantic, { verification_matrix: matrix.metadata });
172
184
  const preparedReport = prepareStageReport(report);
173
185
  validateSchema({ schemaName: "stage-report", file: path.join(root, "stage-report.json"), data: preparedReport.normalized, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: home });
174
186
  const completionInput = { projectRoot, runId: run.id, stage: "plan", status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: report.mechanical.git };
@@ -179,6 +191,7 @@ function finishVnextPlanOwned(context, input) {
179
191
  throw new AppError("invalid_work_state", "PLAN Work changed during finish preparation", 1);
180
192
  context.db.run("UPDATE works SET status = 'completed', result = ?, completed_at = ?, updated_at = ? WHERE work_id = ?", ["PLAN accepted; awaiting PLAN-REVIEW", now, now, work.work_id]);
181
193
  context.db.run("UPDATE work_sessions SET status = 'completed', result_path = ?, updated_at = ?, completed_at = ? WHERE id = ?", [path.join(root, "stage-report.json"), now, now, workSession.id]);
194
+ acceptVerificationMatrix(context, matrix);
182
195
  acceptStageReport(context, { projectId: project.id, runId: run.id, stage: "plan", root, prepared: preparedReport });
183
196
  completeFlowRunStage(context, completionInput, preparedCompletion);
184
197
  refreshRunWorkProjection(context, project.id, run.id);