@deksden-com/dd-flow-cli 0.9.0-beta.121 → 0.9.0-beta.123
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 +17 -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/cli/run-cli.js +3 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +6 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +68 -6
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +21 -5
- package/dist/harness-runtime/lib/dd-codex.mjs +5 -1
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +60 -8
- package/dist/harness-runtime/lib/dd-zcode.mjs +40 -16
- 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/managed-daemon.mjs +3 -2
- package/dist/harness-runtime/lib/model-observations.mjs +16 -9
- 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/harness-runtime/lib/tool-observations.d.mts +1 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +52 -11
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/flow-run.schema.json +1 -0
- package/dist/services/codex-capacity.js +131 -12
- package/dist/services/codex-hook-delivery.js +6 -2
- package/dist/services/eval-snapshots.js +13 -8
- package/dist/services/external-work-launch.js +27 -9
- 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/merge-server.js +26 -9
- package/dist/services/review-copy.js +21 -0
- package/dist/services/run-controller-adapter.js +25 -3
- package/dist/services/run-controller-recovery.js +9 -2
- package/dist/services/run-controller.js +59 -20
- package/dist/services/run-observations.js +7 -2
- package/dist/services/runs.js +60 -47
- package/dist/services/stage-report-renderer.js +44 -2
- package/dist/services/verification-matrix.js +345 -0
- package/dist/services/vnext-code-review.js +30 -11
- package/dist/services/vnext-code.js +31 -10
- package/dist/services/vnext-merge.js +30 -7
- package/dist/services/vnext-plan-review.js +28 -7
- package/dist/services/vnext-plan.js +16 -3
- package/dist/services/work-registry.js +15 -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";
|
|
@@ -24,6 +25,7 @@ import { prepareVnextMergeRequest } from "./vnext-merge.js";
|
|
|
24
25
|
import { validateVnextCodeHandoff } from "./vnext-plan.js";
|
|
25
26
|
import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
26
27
|
import { prepareJsonFile, prepareOutputFile } from "../cli/input-preparation.js";
|
|
28
|
+
import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
|
|
27
29
|
const stage = "code-review";
|
|
28
30
|
const stageDir = vnextStageDirectory(stage);
|
|
29
31
|
const baselineAspects = ["goal_traceability", "coding_standards_design_review", "verification_evidence_review"];
|
|
@@ -50,18 +52,20 @@ export function prepareVnextCodeReviewStart(context, input) {
|
|
|
50
52
|
const common = { projectRoot, project, run, home, root, rootWork, existingPrompt };
|
|
51
53
|
if (prior && fs.existsSync(existingPrompt))
|
|
52
54
|
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" });
|
|
55
|
+
const attachment = prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
|
|
54
56
|
const mode = effectiveMode(home, run);
|
|
55
57
|
const cycle = reviewCycle(run);
|
|
56
58
|
const codeReportSha256 = sha256File(path.join(home, "05-code", "stage-report.json"));
|
|
57
59
|
const groups = mode.mode === "off" ? [] : reviewGroups(home);
|
|
58
|
-
const
|
|
60
|
+
const reviewerInputs = mode.mode === "off" ? [] : reviewerSourceInputs(run.workspace_root, groups.flatMap(group => group.aspect_ids));
|
|
61
|
+
const batch = { works: groups.map((group, index) => ({ key: `code-review-${index + 1}`, task: `${reviewerTask(home, group)}\n${reviewerSourceInstruction(run.workspace_root, group.aspect_ids)}`, launch_policy: "fresh_agent_required", result_schema: "dd-flow/code-review-result@1", payload: { kind: "code-review", review_cycle: cycle, group, read_only: true } })) };
|
|
59
62
|
const items = mode.mode === "off" ? [] : prepareWorkBatch(context, { parentWorkId: rootWork.work_id, value: batch });
|
|
60
63
|
const template = mode.mode === "off" ? "" : read(path.join(run.workspace_root, ".memory-bank", "dd-flow", "vnext", "code-review.md"));
|
|
61
64
|
const checks = mode.mode === "off" ? [] : acceptedCodeChecks(context, project.id, run.id).map(({ id, purpose }) => ({ id, purpose }));
|
|
62
65
|
for (const name of ["review-context.json", ...(mode.mode === "off" ? ["decision.json"] : ["review-work-batch.json", "fanout.json"])])
|
|
63
66
|
prepareOutputFile(path.join(root, name));
|
|
64
|
-
|
|
67
|
+
const matrix = mode.mode === "off" ? null : prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", stageAttempt: attachment.attempt });
|
|
68
|
+
return { ...common, resumed: false, mode, cycle, codeReportSha256, groups, batch, items, template, checks, matrix, reviewerInputs };
|
|
65
69
|
}
|
|
66
70
|
export function startVnextCodeReview(context, input, prepared = prepareVnextCodeReviewStart(context, input)) {
|
|
67
71
|
const { projectRoot, project, run, root, rootWork, existingPrompt } = prepared;
|
|
@@ -69,6 +73,8 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
|
|
|
69
73
|
assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
|
|
70
74
|
fs.mkdirSync(root, { recursive: true });
|
|
71
75
|
if (prepared.resumed) {
|
|
76
|
+
if (effectiveMode(prepared.home, run).mode !== "off")
|
|
77
|
+
resumeVerificationMatrix(context, project.id, run.id, stage);
|
|
72
78
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath: existingPrompt, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
73
79
|
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
80
|
const prompt = fs.readFileSync(existingPrompt, "utf8");
|
|
@@ -76,6 +82,7 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
|
|
|
76
82
|
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
83
|
}
|
|
78
84
|
const { mode, cycle, groups, batch, items, template, checks, codeReportSha256 } = prepared;
|
|
85
|
+
prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" }, true);
|
|
79
86
|
const aspectMapChecksums = mode.mode === "off" ? [] : fs.readdirSync(path.join(run.run_root, "03-plan"), { withFileTypes: true })
|
|
80
87
|
.filter(entry => entry.isDirectory() && fs.existsSync(path.join(run.run_root, "03-plan", entry.name, "aspect-map.json")))
|
|
81
88
|
.map(entry => ({ path: `03-plan/${entry.name}/aspect-map.json`, sha256: sha256File(path.join(run.run_root, "03-plan", entry.name, "aspect-map.json")) }))
|
|
@@ -83,25 +90,28 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
|
|
|
83
90
|
freezeFlowRunReviewMode(context, { projectRoot, runId: run.id, review: "code", mode: mode.mode, source: mode.source, reason: mode.reason });
|
|
84
91
|
const promptPath = path.join(root, "stage-prompt.md");
|
|
85
92
|
const reviewContextPath = path.join(root, "review-context.json");
|
|
86
|
-
fs.writeFileSync(reviewContextPath, `${JSON.stringify({ schema_id: "dd-flow/code-review-context@1", cycle, mode: mode.mode, mode_source: mode.source, mode_reason: mode.reason, ...(mode.mode !== "off" ? { read_only_input: { parent_work_id: rootWork.work_id, version: 3 } } : {}), code_report_sha256: codeReportSha256, review_batch_sha256: sha256Value(`${JSON.stringify(batch, null, 2)}\n`), aspect_map_checksums: aspectMapChecksums }, null, 2)}\n`);
|
|
93
|
+
fs.writeFileSync(reviewContextPath, `${JSON.stringify({ schema_id: "dd-flow/code-review-context@1", reviewer_inputs: prepared.reviewerInputs, cycle, mode: mode.mode, mode_source: mode.source, mode_reason: mode.reason, ...(mode.mode !== "off" ? { read_only_input: { parent_work_id: rootWork.work_id, version: 3 } } : {}), code_report_sha256: codeReportSha256, review_batch_sha256: sha256Value(`${JSON.stringify(batch, null, 2)}\n`), aspect_map_checksums: aspectMapChecksums }, null, 2)}\n`);
|
|
87
94
|
if (mode.mode === "off") {
|
|
88
95
|
fs.writeFileSync(path.join(root, "decision.json"), `${JSON.stringify(disabledReviewDecision(), null, 2)}\n`);
|
|
89
96
|
const prompt = `<stage_identity>\n- RUN: ${run.id}\n- stage: code-review\n- mode: off\n</stage_identity>\n\nCODE-REVIEW is disabled by the frozen RUN configuration. Finish with: ${finishCommand(context, run.id, projectRoot, path.join(root, "decision.json"))}\n${stageLifecycleInstruction}\n`;
|
|
90
97
|
fs.writeFileSync(promptPath, prompt);
|
|
91
98
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
92
99
|
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
|
-
|
|
100
|
+
const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" };
|
|
101
|
+
attachFlowRunStage(context, attachmentInput, prepareFlowRunStageAttachment(context, attachmentInput));
|
|
94
102
|
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
103
|
}
|
|
96
104
|
const batchFile = path.join(root, "review-work-batch.json");
|
|
97
105
|
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`;
|
|
106
|
+
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
107
|
fs.writeFileSync(promptPath, prompt);
|
|
100
108
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
101
109
|
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
110
|
const registered = addWorkBatch(context, { parentWorkId: rootWork.work_id, value: batch, prepared: items, stage, cycle });
|
|
103
111
|
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
|
-
|
|
112
|
+
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 })) };
|
|
113
|
+
const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
|
|
114
|
+
commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
|
|
105
115
|
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
116
|
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
117
|
}
|
|
@@ -208,7 +218,8 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
208
218
|
fs.writeFileSync(temporary, `${JSON.stringify({ ...reviewContext, accepted_decision: decision }, null, 2)}\n`);
|
|
209
219
|
fs.renameSync(temporary, contextFile);
|
|
210
220
|
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
|
-
|
|
221
|
+
const retained = latestRunVerificationMatrix(context, project.id, run.id, stage, "input");
|
|
222
|
+
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
223
|
}
|
|
213
224
|
repairs = reviewRepairWorks(context, project.id, run.id, root, cycle);
|
|
214
225
|
const incompleteRepairs = repairs.filter((work) => work.status !== "completed");
|
|
@@ -224,16 +235,21 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
224
235
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "check_retry_requested", check_receipt_id: input.retryCheckId, stage, reason: input.retryReason ?? "operator retry" });
|
|
225
236
|
}
|
|
226
237
|
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 } : {}) });
|
|
238
|
+
const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", receipts, semanticFile: decisionFile });
|
|
227
239
|
const failed = receipts.filter((receipt) => receipt.status !== "passed");
|
|
228
|
-
if (failed.length)
|
|
240
|
+
if (failed.length) {
|
|
241
|
+
context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
|
|
242
|
+
publishVerificationMatrix(context, matrix);
|
|
229
243
|
throw new AppError("code_review_gate_failed", "CODE-REVIEW repair changed the project but the aggregate gate failed", 2, {
|
|
230
244
|
failures: failed,
|
|
231
245
|
outcome: "repair_required",
|
|
232
246
|
retry_after_workspace_change: true,
|
|
233
247
|
workspace_fingerprint: failed[0].workspace_fingerprint,
|
|
234
248
|
continuation: registerAggregateCodeRepair(context, { projectRoot, runId: run.id, stage, failures: failed, checks: finalChecks, generation }),
|
|
235
|
-
|
|
249
|
+
...(matrix ? { verification_matrix: matrix.metadata } : {}),
|
|
250
|
+
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
251
|
});
|
|
252
|
+
}
|
|
237
253
|
const stopTarget = executionStopTarget(run);
|
|
238
254
|
const nextAction = stopTarget === "merge_completed" ? "start_merge" : "code_review_completed";
|
|
239
255
|
const now = context.now();
|
|
@@ -245,6 +261,8 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
245
261
|
let mergeRequest = null;
|
|
246
262
|
assertRunMutationAllowed(context, project.id, run.id, generation);
|
|
247
263
|
const prepareMerge = nextAction === "start_merge" ? prepareVnextMergeRequest(context, { projectRoot, runId: run.id, acceptedPaths }) : null;
|
|
264
|
+
if (matrix)
|
|
265
|
+
Object.assign(report.semantic, { verification_matrix: matrix.metadata });
|
|
248
266
|
const preparedReport = prepareStageReport(report);
|
|
249
267
|
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
268
|
withStageSettlement(context, () => {
|
|
@@ -256,6 +274,7 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
256
274
|
}
|
|
257
275
|
mergeRequest = prepareMerge?.() ?? null;
|
|
258
276
|
const preparedCompletion = prepareFlowRunStageCompletion(context, completionInput);
|
|
277
|
+
acceptVerificationMatrix(context, matrix);
|
|
259
278
|
acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
|
|
260
279
|
completeFlowRunStage(context, completionInput, preparedCompletion);
|
|
261
280
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "code_review_completed", work_id: rootWork.work_id, outcome });
|
|
@@ -335,7 +354,7 @@ export function assertCodeReviewCoverage(groups, reviewers) {
|
|
|
335
354
|
throw new AppError("reviewer_coverage_invalid", "CODE-REVIEW requires every expected reviewer group", 2, { missing_groups: missing });
|
|
336
355
|
}
|
|
337
356
|
export function reviewerTask(home, group) { return `Read-only independent CODE review for ${group.key}. Assess every assigned aspect exactly once: ${group.aspect_ids.join(", ")}. Read the accepted PLAN, ${path.join(home, "05-code", "stage-report.json")}, and any files needed under the bounded CODE evidence root ${path.join(home, "05-code")}. Verify that universal and exclusive constraints (only, any, all, never, whole) and stated exceptions were not narrowed. For every changed mutation guarded by membership, ownership, authorization or parent lifecycle state, trace the decision to the write boundary: the predicate must remain in the write statement or the guard and write must share one explicit transaction with the needed lock. A separate earlier read is not proof of current authority or lifecycle state; report a material finding when this invariant is broken. Evidence closes a claim only when it exercises the named failure mechanism; a sequential negative test does not prove a concurrency race, and proof limits cannot waive an accepted obligation. Report only material, evidenced defects: a violated obligation or rule, direct evidence, impact, and minimum required outcome. Do not report taste, cosmetics, or untargeted refactoring. Use local finding ids FIND-001, FIND-002, and so on; dd-flow adds the Work-qualified canonical reference. Return dd-flow/code-review-result@1.`; }
|
|
338
|
-
function orchestratorPrompt(context, input) { const { template, checks } = input; const decision = path.join(input.root, "decision.json"); return ["<stage_identity>", `- RUN: ${input.run.id}`, `- root Work: ${input.rootWork.work_id}`, `- stage: ${stage}`, `- mode: ${input.mode}`, "</stage_identity>", "", "<trusted_runtime_context>", `- project root: ${input.projectRoot}`, `- immutable write workspace: ${input.run.workspace_root}`, `- stage workspace: ${input.root}`, `- bounded CODE evidence root: ${path.join(input.run.run_root, "05-code")}`, "CODE
|
|
357
|
+
function orchestratorPrompt(context, input) { const { template, checks } = input; const decision = path.join(input.root, "decision.json"); return ["<stage_identity>", `- RUN: ${input.run.id}`, `- root Work: ${input.rootWork.work_id}`, `- stage: ${stage}`, `- mode: ${input.mode}`, "</stage_identity>", "", "<trusted_runtime_context>", `- project root: ${input.projectRoot}`, `- immutable write workspace: ${input.run.workspace_root}`, `- stage workspace: ${input.root}`, `- bounded CODE evidence root: ${path.join(input.run.run_root, "05-code")}`, "CODE passed its declared checks; each check proves only its named contour and proof limits, not complete semantic acceptance. Conduct independent quality review and do not manually rerun every declared check.", "</trusted_runtime_context>", "", "<review_groups>", ...input.groups.map((group) => `- ${group.key}: ${group.aspect_ids.join(", ")}`), "</review_groups>", "", "<execution_commands>", "Do not choose a provider delegation tool from this stage prompt. At the Work-graph boundary, return immediately; the shared controller supplies the selected adapter's native tool, exact parameters, child task, and wait contract. Each reviewer Work must run in one fresh child session. The coordinator must never invoke a Work start_command itself: a child runs its exact start_command after native delegation. Reviewers are read-only and must not create subagents. Do not interrupt a quiet worker.", "Ready reviewer Works: " + runtimeCommand(context, ["work", "ls", "--run", input.run.id, "--ready", "--project-root", input.projectRoot, "--json"]), `Write the decision to this exact absolute path, not a relative look-alike: ${decision}`, `Finish only after every reviewer Work settles and that exact file exists: ${finishCommand(context, input.run.id, input.projectRoot, decision)}`, "Reviewer results use local FIND-NNN ids. Classify them by the canonical WRK-.../FIND-NNN finding_ref returned by dd-flow. Fix P0/P1. Fix bounded safe P2 by default; defer only a legitimate P2 with a named DEF. P3 is an observation or a reasoned rejection, not automatic repair/DEF. For every disposition fix, check_refs is mandatory: choose the one or more causal checks from the accepted list below that the repair must rerun. Do not guess a CHK id or copy every check. The first successful Finish freezes this decision and creates one repair Work when needed. Do not invoke that Work's start_command from the coordinator Session: end this Turn so the controller can launch a fresh child, then call the returned next.finish_command after it completes. Internal invocation IDs are runtime-owned and must not be copied or reused. Review is not repeated.", "If the aggregate gate fails with repair_required, its repair Work is already registered with all causal checks. End this Turn so the controller launches a fresh child. Do not copy receipt IDs, create another repair, invoke its start_command in the coordinator, or edit invisibly in the root orchestrator.", `Accepted repair checks: ${JSON.stringify(checks)}`, "```json", JSON.stringify({ schema_id: "dd-flow/code-review-decision@3", summary: "Evidence-backed conclusion.", findings: [{ finding_ref: "WRK-001-review/FIND-001", disposition: "fix | defer | reject | duplicate", reason: "Why this classification is correct.", check_refs: ["CHK-CAUSAL-CHECK only when disposition is fix"], def_id: "DEF-0001 only for an allowed P2 deferral", duplicate_of: "canonical finding_ref only for duplicate" }] }, null, 2), "```", "</execution_commands>", "", "<stage_instructions>", template, "</stage_instructions>", ""].join("\n"); }
|
|
339
358
|
function canonicalReviewEvidence(decision, reviewers) {
|
|
340
359
|
const known = new Set(canonicalCodeFindings(reviewers).map((item) => item.finding_ref));
|
|
341
360
|
const items = new Map(decision.findings.map((item) => [item.finding_ref, item]));
|
|
@@ -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,8 @@ 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";
|
|
26
|
+
import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
|
|
25
27
|
const stage = "plan-review";
|
|
26
28
|
const stageDir = vnextStageDirectory(stage);
|
|
27
29
|
const reviewTask = "Independently review the accepted PLAN and decide whether CODE may open.";
|
|
@@ -54,19 +56,24 @@ export function prepareVnextPlanReviewStart(context, input) {
|
|
|
54
56
|
assertReviewWorkspace(run.workspace_root, protocolIds);
|
|
55
57
|
const groups = reviewGroups(home, run.workspace_root);
|
|
56
58
|
const effective = resolveMode(requested, groups, home, run.workspace_root);
|
|
59
|
+
const reviewerInputs = effective === "off" ? [] : reviewerSourceInputs(run.workspace_root, groups.flatMap(group => group.aspect_ids));
|
|
60
|
+
if (effective !== "off")
|
|
61
|
+
for (const group of groups)
|
|
62
|
+
group.task += `\n${reviewerSourceInstruction(run.workspace_root, group.aspect_ids)}`;
|
|
57
63
|
const existing = context.db.get("SELECT work_id, status FROM works WHERE project_id = ? AND run_id = ? AND task = ? ORDER BY created_at DESC LIMIT 1", [project.id, run.id, reviewTask]);
|
|
58
64
|
const batchChecksum = checksum(batch), planChecksum = planSetChecksum(home, run.workspace_root);
|
|
59
65
|
if (input.externalContext)
|
|
60
66
|
prepareExternalStageContext({ stageRoot: root, loaded: input.externalContext });
|
|
61
67
|
prepareOutputFile(path.join(root, "stage-prompt.md"));
|
|
62
|
-
const
|
|
68
|
+
const attachment = existing?.status === "running" ? null : prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
|
|
69
|
+
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 } : {}) });
|
|
70
|
+
const active = { ...common, requested, protocolIds, groups, effective, workspaceRoute, batchChecksum, planChecksum, matrix, reviewerInputs };
|
|
63
71
|
if (existing?.status === "running") {
|
|
64
72
|
const orchestration = readFanoutDescriptor(root);
|
|
65
73
|
if (!orchestration)
|
|
66
74
|
prepareOutputFile(path.join(root, "fanout.json"));
|
|
67
75
|
return { ...active, state: "resumed", workId: existing.work_id, prompt: fs.readFileSync(path.join(root, "stage-prompt.md"), "utf8"), orchestration };
|
|
68
76
|
}
|
|
69
|
-
prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
|
|
70
77
|
const rootWork = rootWorkId(context, project.id, run.id);
|
|
71
78
|
if (effective === "off") {
|
|
72
79
|
const failures = validateVnextPlanArtifacts(context, { projectRoot, workspaceRoot: run.workspace_root, runId: run.id, home, protocols: protocolIds });
|
|
@@ -85,7 +92,8 @@ export function prepareVnextPlanReviewStart(context, input) {
|
|
|
85
92
|
}
|
|
86
93
|
for (const name of ["stage-report.json", "stage-report.md", "stage-report.html"])
|
|
87
94
|
prepareOutputFile(path.join(root, name));
|
|
88
|
-
|
|
95
|
+
const outputMatrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", ...(attachment ? { stageAttempt: attachment.attempt } : {}) });
|
|
96
|
+
return { ...active, state: "off", rootWork, codeItems, outputMatrix };
|
|
89
97
|
}
|
|
90
98
|
const template = read(path.join(projectRoot, ".memory-bank", "dd-flow", "vnext", "plan-review.md"));
|
|
91
99
|
const revision = currentPlanRevision(home, run.workspace_root);
|
|
@@ -103,11 +111,13 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
|
|
|
103
111
|
}
|
|
104
112
|
const { requested, effective, groups, batchChecksum, planChecksum, workspaceRoute } = prepared;
|
|
105
113
|
if (prepared.state === "resumed") {
|
|
114
|
+
resumeVerificationMatrix(context, project.id, run.id, stage);
|
|
106
115
|
const promptPath = path.join(root, "stage-prompt.md");
|
|
107
116
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
108
117
|
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
118
|
}
|
|
110
119
|
ensureWorkRegistry(context);
|
|
120
|
+
prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" }, true);
|
|
111
121
|
fs.mkdirSync(root, { recursive: true });
|
|
112
122
|
const now = context.now(), rootWork = prepared.rootWork;
|
|
113
123
|
if (prepared.state === "off") {
|
|
@@ -115,12 +125,17 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
|
|
|
115
125
|
fs.writeFileSync(promptPath, "PLAN-REVIEW is disabled by the frozen RUN configuration.\n");
|
|
116
126
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
117
127
|
bindStageCoordinatorWork(context, { workId: rootWork, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
|
|
118
|
-
|
|
128
|
+
const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" };
|
|
129
|
+
const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
|
|
130
|
+
commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
|
|
119
131
|
const reportGit = gitFacts(run.workspace_root);
|
|
120
132
|
const nextCommand = codeCommand(context, run.id, projectRoot);
|
|
121
133
|
return withStageSettlement(context, () => {
|
|
122
134
|
const registered = addWorkBatch(context, { parentWorkId: rootWork, prepared: prepared.codeItems, stage: "code" });
|
|
123
135
|
const report = reportFor(context, { run, mode: effective, requested, outcome: "off", groups: [], batchChecksum, planChecksum, code: registered, now, projectRoot, flow: flowCommand(context), git: reportGit, nextCommand });
|
|
136
|
+
acceptVerificationMatrix(context, prepared.outputMatrix);
|
|
137
|
+
if (prepared.outputMatrix)
|
|
138
|
+
Object.assign(report.semantic, { verification_matrix: prepared.outputMatrix.metadata });
|
|
124
139
|
const preparedReport = prepareStageReport(report);
|
|
125
140
|
acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
|
|
126
141
|
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 +148,7 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
|
|
|
133
148
|
const workId = nextWorkId(context, project.id, "plan-review");
|
|
134
149
|
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
150
|
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`;
|
|
151
|
+
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
152
|
const promptPath = path.join(root, "stage-prompt.md");
|
|
138
153
|
fs.writeFileSync(promptPath, prompt);
|
|
139
154
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
@@ -146,12 +161,14 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
|
|
|
146
161
|
fs.rmSync(promptPath, { force: true });
|
|
147
162
|
throw error;
|
|
148
163
|
}
|
|
149
|
-
|
|
164
|
+
const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" };
|
|
165
|
+
const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
|
|
166
|
+
commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
|
|
150
167
|
markReviewPending(home, groups);
|
|
151
168
|
const workSessionId = String(binding.work_session_id);
|
|
152
169
|
const sessionId = String(binding.session_id);
|
|
153
170
|
const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: workId, dispatch: "plan-review", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
|
|
154
|
-
writeJson(path.join(root, "work-context.json"), { schema_id: "dd-flow/plan-review-context@1", read_only_input: { parent_work_id: workId, version: 3 }, system: { run_id: run.id, work_id: workId, stage, requested_mode: requested, effective_mode: effective, plan_revision: prepared.revision, plan_checksum: planChecksum, batch_checksum: batchChecksum }, groups, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root, plan_root: planRoot } });
|
|
171
|
+
writeJson(path.join(root, "work-context.json"), { schema_id: "dd-flow/plan-review-context@1", reviewer_inputs: prepared.reviewerInputs, read_only_input: { parent_work_id: workId, version: 3 }, system: { run_id: run.id, work_id: workId, stage, requested_mode: requested, effective_mode: effective, plan_revision: prepared.revision, plan_checksum: planChecksum, batch_checksum: batchChecksum }, groups, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root, plan_root: planRoot } });
|
|
155
172
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
156
173
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "work_session_started", stage, work_id: workId, id: workSessionId, session_id: sessionId });
|
|
157
174
|
return { ok: true, run_id: run.id, stage, outcome: "review_required", work_id: workId, id: workSessionId, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), orchestration, plan_review: { requested_mode: requested, effective_mode: effective, policy_source: policySource(run), groups }, next: { dispatch_command: dispatchCommand(context, run.id, projectRoot), finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
|
|
@@ -307,13 +324,17 @@ function finishPlanReviewOwned(context, input) {
|
|
|
307
324
|
const codeItems = prepareWorkBatch(context, { parentWorkId: codeParentId, file: batch, stage: "code" });
|
|
308
325
|
const reportGit = gitFacts(run.workspace_root);
|
|
309
326
|
const nextCommand = codeCommand(context, run.id, projectRoot);
|
|
327
|
+
const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", semanticFile: decisionPath });
|
|
310
328
|
return withStageSettlement(context, () => {
|
|
311
329
|
const registered = addWorkBatch(context, { parentWorkId: codeParentId, prepared: codeItems, stage: "code" });
|
|
312
330
|
const now = context.now();
|
|
313
331
|
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
332
|
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
333
|
applyReviewResults(home, latestChildren, decision);
|
|
334
|
+
if (matrix)
|
|
335
|
+
Object.assign(report.semantic, { verification_matrix: matrix.metadata });
|
|
316
336
|
const preparedReport = prepareStageReport(report);
|
|
337
|
+
acceptVerificationMatrix(context, matrix);
|
|
317
338
|
acceptStagePacket(context, { projectId: project.id, runId: run.id, stage, prepared: decisionReceipt });
|
|
318
339
|
acceptStageReport(context, { projectId: project.id, runId: run.id, stage, root, prepared: preparedReport });
|
|
319
340
|
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 };
|