@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.
- package/CHANGELOG.md +9 -0
- package/dist/build-info.json +5 -5
- package/dist/cli/hook-ingress.js +4 -3
- package/dist/cli/native-hook-ingress.js +34 -2
- package/dist/harness-runtime/bin/dd-agy.mjs +6 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +16 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +60 -8
- package/dist/harness-runtime/lib/dd-zcode.mjs +25 -12
- package/dist/harness-runtime/lib/delegation-instructions.mjs +3 -3
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +218 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +2 -2
- package/dist/harness-runtime/lib/native-hook-command.mjs +36 -5
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/flow-run.schema.json +1 -0
- package/dist/services/codex-hook-delivery.js +6 -2
- package/dist/services/hooks.js +51 -59
- package/dist/services/lifecycle-command.js +1 -383
- package/dist/services/lifecycle-contract.js +1 -30
- package/dist/services/lifecycle-invocations.js +34 -13
- package/dist/services/runs.js +60 -47
- package/dist/services/stage-report-renderer.js +19 -0
- package/dist/services/verification-matrix.js +325 -0
- package/dist/services/vnext-code-review.js +25 -8
- package/dist/services/vnext-code.js +31 -10
- package/dist/services/vnext-merge.js +30 -7
- package/dist/services/vnext-plan-review.js +22 -6
- package/dist/services/vnext-plan.js +16 -3
- package/dist/services/work-registry.js +13 -1
- package/dist/storage/database.js +43 -9
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|