@deksden-com/dd-flow-cli 0.9.0-beta.1 → 0.9.0-beta.11
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 +77 -0
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +3 -3
- package/dist/cli/run-cli.js +127 -8
- package/dist/runtime/context.js +3 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +1 -1
- package/dist/schemas/harness-config.schema.json +23 -0
- package/dist/schemas/plan-review-decision.schema.json +1 -1
- package/dist/schemas/vnext-protocol-plan.schema.json +1 -1
- package/dist/services/cleanup.js +18 -8
- package/dist/services/code-checks.js +194 -44
- package/dist/services/engines.js +4 -4
- package/dist/services/eval-snapshots.js +10 -5
- package/dist/services/harness-config.js +66 -0
- package/dist/services/hooks.js +62 -28
- package/dist/services/lanes.js +1 -0
- package/dist/services/managed-processes.js +169 -0
- package/dist/services/merge-server.js +8 -2
- package/dist/services/portable-refs.js +57 -0
- package/dist/services/prompts.js +4 -2
- package/dist/services/run-engine-bindings.js +19 -61
- package/dist/services/run-projection.js +10 -8
- package/dist/services/runs.js +71 -9
- package/dist/services/schema-validation.js +11 -11
- package/dist/services/session-identity.js +19 -0
- package/dist/services/sessions.js +26 -11
- package/dist/services/stage-lifecycle.js +15 -8
- package/dist/services/stage-pause.js +35 -20
- package/dist/services/usage.js +74 -42
- package/dist/services/vnext-code-review.js +82 -41
- package/dist/services/vnext-code.js +98 -34
- package/dist/services/vnext-fanout.js +5 -12
- package/dist/services/vnext-merge.js +144 -65
- package/dist/services/vnext-plan-review.js +50 -35
- package/dist/services/vnext-plan.js +69 -21
- package/dist/services/vnext-protocolize.js +6 -6
- package/dist/services/vnext-specify.js +6 -6
- package/dist/services/work-registry.js +150 -40
- package/dist/storage/database.js +128 -2
- package/package.json +1 -1
- package/tools/audit-runtime-fix-boundaries.mjs +96 -0
|
@@ -4,7 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { AppError } from "../shared/errors.js";
|
|
6
6
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
7
|
-
import { aggregateCheckDeclarations, checkReceipts, codeExecutionEnvironment, finalCodeCheckDeclarations, runCodeChecks, unchangedFinalGateFailures, workspaceFingerprint } from "./code-checks.js";
|
|
7
|
+
import { aggregateCheckDeclarations, checkReceipts, codeExecutionEnvironment, finalCodeCheckDeclarations, runCodeChecks, unchangedFinalGateFailures, workspaceChangedPaths, workspaceFingerprint } from "./code-checks.js";
|
|
8
8
|
import { requireProjectByRoot } from "./projects.js";
|
|
9
9
|
import { appendFlowRunTimelineEvent, advanceFlowRun, attachFlowRunStage, completeFlowRunStage, completeFlowRun, getFlowRunVariables, gitFacts } from "./runs.js";
|
|
10
10
|
import { validateSchema } from "./schema-validation.js";
|
|
@@ -53,7 +53,8 @@ export async function startVnextCode(context, input) {
|
|
|
53
53
|
file: batch,
|
|
54
54
|
projectRoot: run.workspace_root,
|
|
55
55
|
ddFlowHome: context.ddFlowHome,
|
|
56
|
-
runId: input.runId
|
|
56
|
+
runId: input.runId,
|
|
57
|
+
runRoot: home
|
|
57
58
|
});
|
|
58
59
|
validateWorkBatchFile(batch);
|
|
59
60
|
const root = path.join(home, stageDir);
|
|
@@ -134,9 +135,23 @@ export async function finishVnextCode(context, input) {
|
|
|
134
135
|
throw new AppError("obligation_coverage_incomplete", "CODE graph does not cover every accepted obligation", 2, { missing });
|
|
135
136
|
}
|
|
136
137
|
// Reject a malformed semantic receipt before running the expensive gate.
|
|
137
|
-
const verification = verificationForFinish(context, { file: input.verificationFile, projectRoot: run.workspace_root, runId: run.id });
|
|
138
|
-
if (verification.verdict
|
|
139
|
-
|
|
138
|
+
const verification = verificationForFinish(context, { file: input.verificationFile, projectRoot: run.workspace_root, runId: run.id, runRoot: home });
|
|
139
|
+
if (verification.verdict === "blocked") {
|
|
140
|
+
return { ok: true, run_id: run.id, stage, outcome: "blocked", verification, instruction: "CODE remains running. Resolve the stated external or user-input blocker in this same coordinator session, update code-verification.json, then invoke this same stage finish command again." };
|
|
141
|
+
}
|
|
142
|
+
if (verification.verdict === "passed" && verification.unresolved.length > 0) {
|
|
143
|
+
throw new AppError("verification_contradictory", "A passed CODE verification cannot contain unresolved obligations", 2, { unresolved: verification.unresolved });
|
|
144
|
+
}
|
|
145
|
+
if (verification.verdict === "needs_repair") {
|
|
146
|
+
const repair = addVnextCodeRepair(context, {
|
|
147
|
+
projectRoot,
|
|
148
|
+
runId: run.id,
|
|
149
|
+
originWorkIds: works.filter((work) => work.status === "completed").map((work) => work.work_id),
|
|
150
|
+
semanticUnresolved: verification.unresolved.length ? verification.unresolved : [verification.summary],
|
|
151
|
+
verificationPath: input.verificationFile,
|
|
152
|
+
objective: verification.summary
|
|
153
|
+
});
|
|
154
|
+
return { ok: true, run_id: run.id, stage, outcome: "repair_required", verification, repair, instruction: "The semantic verification is not accepted. Run the returned repair Work, then update code-verification.json and invoke this same stage finish command again." };
|
|
140
155
|
}
|
|
141
156
|
const checks = finalCodeCheckDeclarations(run.workspace_root, works.flatMap((work) => packet(work)?.checks ?? []));
|
|
142
157
|
const unchangedFailures = unchangedFinalGateFailures(context, { projectId: project.id, runId: run.id, workspaceRoot: run.workspace_root, declarations: checks });
|
|
@@ -174,18 +189,26 @@ export async function finishVnextCode(context, input) {
|
|
|
174
189
|
}
|
|
175
190
|
const allReceipts = checkReceipts(context, { projectId: project.id, runId: run.id });
|
|
176
191
|
const finalReceipts = latestReceiptsByCommand(allReceipts);
|
|
192
|
+
const projectedVerification = verificationProjection(works, finalReceipts, { historicalReceipts: allReceipts, runId: run.id, runHome: home });
|
|
193
|
+
const unresolvedAcceptance = (projectedVerification.acceptance ?? []).filter((item) => item.status === "unresolved");
|
|
194
|
+
if (unresolvedAcceptance.length)
|
|
195
|
+
throw new AppError("code_acceptance_unresolved", "CODE cannot finish until every due acceptance criterion has current checks and evidence", 2, { unresolved: unresolvedAcceptance });
|
|
177
196
|
const now = context.now();
|
|
178
197
|
const timing = stageTiming(home, stage, now);
|
|
179
|
-
const
|
|
198
|
+
const reportedPaths = [...new Set(works.flatMap((work) => resultPaths(work.result)))];
|
|
199
|
+
const observedChangedPaths = workspaceChangedPaths(run.workspace_root);
|
|
200
|
+
const changedPaths = observedChangedPaths ?? reportedPaths;
|
|
201
|
+
const missingReportedPaths = observedChangedPaths === null ? [] : reportedPaths.filter((item) => !observedChangedPaths.includes(item));
|
|
202
|
+
if (missingReportedPaths.length)
|
|
203
|
+
throw new AppError("changed_path_not_materialized", "CODE Work reported paths that are not changed in the accepted workspace", 2, { paths: missingReportedPaths });
|
|
180
204
|
const next = nextAction(run, changedPaths);
|
|
181
|
-
writeProtocolFlowStatus(run.workspace_root, home, run.id, next === "start_code_review" ? "CODE complete; CODE-REVIEW is next." : next === "start_merge" ? "CODE complete; MERGE is queued." : "CODE complete; this RUN reached its configured terminal boundary.");
|
|
182
205
|
const report = {
|
|
183
206
|
schema_id: "dd-flow/stage-report@1",
|
|
184
207
|
run_id: run.id,
|
|
185
208
|
stage,
|
|
186
209
|
generated_at: now,
|
|
187
210
|
verdict: "done",
|
|
188
|
-
verification:
|
|
211
|
+
verification: projectedVerification,
|
|
189
212
|
semantic: {
|
|
190
213
|
result: `Completed ${works.length} CODE Work item${works.length === 1 ? "" : "s"}; ${finalReceipts.filter((receipt) => receipt.status === "passed").length} current final check receipt${finalReceipts.length === 1 ? "" : "s"} passed.`,
|
|
191
214
|
acceptance: [...expected],
|
|
@@ -213,6 +236,10 @@ export async function finishVnextCode(context, input) {
|
|
|
213
236
|
artifacts: { json: "stage-report.json", markdown: "stage-report.md", html: "stage-report.html", summary: "stage-report.md" },
|
|
214
237
|
validation: { permission_scope: "known_targets_only", memory_bank_scope: "changed_files_and_links_only", status: "passed" }
|
|
215
238
|
};
|
|
239
|
+
// Creating the MERGE request is part of accepting this terminal handoff.
|
|
240
|
+
// Do it before materialising a terminal stage report or changing RUN state,
|
|
241
|
+
// so an invalid merge policy leaves CODE safely running and retryable.
|
|
242
|
+
const mergeRequest = next === "start_merge" ? ensureVnextMergeRequest(context, { projectRoot, runId: run.id, acceptedPaths: changedPaths }) : null;
|
|
216
243
|
writeReport(root, report);
|
|
217
244
|
completeFlowRunStage(context, {
|
|
218
245
|
projectRoot,
|
|
@@ -237,7 +264,6 @@ export async function finishVnextCode(context, input) {
|
|
|
237
264
|
else {
|
|
238
265
|
advanceFlowRun(context, { projectRoot, runId: run.id, status: "running", verdict: "code_completed", nextAction: next });
|
|
239
266
|
}
|
|
240
|
-
const mergeRequest = next === "start_merge" ? ensureVnextMergeRequest(context, { projectRoot, runId: run.id }) : null;
|
|
241
267
|
return {
|
|
242
268
|
ok: true,
|
|
243
269
|
run_id: run.id,
|
|
@@ -255,8 +281,8 @@ export function addVnextCodeRepair(context, input) {
|
|
|
255
281
|
const home = requireHome(run);
|
|
256
282
|
if (!input.objective.trim())
|
|
257
283
|
throw new AppError("validation", "Repair objective must not be empty", 2);
|
|
258
|
-
if (!input.checkReceiptId && !input.
|
|
259
|
-
throw new AppError("validation", "Repair needs
|
|
284
|
+
if (!input.checkReceiptId && !input.reviewFindings?.length && !input.semanticUnresolved?.length) {
|
|
285
|
+
throw new AppError("validation", "Repair needs failed check evidence, a CODE-REVIEW finding, or an unresolved CODE verification", 2);
|
|
260
286
|
}
|
|
261
287
|
const receipt = input.checkReceiptId ? context.db.get("SELECT id, declaration_id, run_id, command, status, receipt_path, stdout_path, stderr_path FROM check_receipts WHERE project_id = ? AND id = ?", [
|
|
262
288
|
project.id,
|
|
@@ -269,11 +295,19 @@ export function addVnextCodeRepair(context, input) {
|
|
|
269
295
|
}
|
|
270
296
|
const origins = [...new Set(input.originWorkIds)].map((id) => requireCodeWork(context, project.id, run.id, id));
|
|
271
297
|
const packets = origins.map((work) => packet(work));
|
|
272
|
-
const invariantPackets = receipt
|
|
298
|
+
const invariantPackets = receipt || input.semanticUnresolved?.length
|
|
273
299
|
? codeWorks(context, project.id, run.id).map((work) => packet(work)).filter((value) => !value.repair)
|
|
274
300
|
: packets;
|
|
275
301
|
const first = packets[0];
|
|
276
|
-
|
|
302
|
+
// A later repair may be caused by any accepted aggregate declaration, not
|
|
303
|
+
// only the checks copied into its immediate repair parent. Otherwise a
|
|
304
|
+
// repair that fixes one failed aggregate check can make a second, already
|
|
305
|
+
// declared aggregate failure impossible to repair. The immutable source
|
|
306
|
+
// of repair eligibility is the original CODE graph.
|
|
307
|
+
const declaredChecks = finalCodeCheckDeclarations(run.workspace_root, codeWorks(context, project.id, run.id)
|
|
308
|
+
.map((work) => packet(work))
|
|
309
|
+
.filter((value) => !value.repair)
|
|
310
|
+
.flatMap((value) => value.checks));
|
|
277
311
|
const failedCheck = receipt ? declaredChecks.find((check) => check.id === receipt.declaration_id) : undefined;
|
|
278
312
|
if (receipt && !failedCheck) {
|
|
279
313
|
throw new AppError("repair_check_declaration_missing", "Repair receipt is not backed by an accepted CODE check declaration", 2, {
|
|
@@ -281,7 +315,8 @@ export function addVnextCodeRepair(context, input) {
|
|
|
281
315
|
declaration_id: receipt.declaration_id
|
|
282
316
|
});
|
|
283
317
|
}
|
|
284
|
-
const
|
|
318
|
+
const semanticRepair = Boolean(input.semanticUnresolved?.length);
|
|
319
|
+
const key = input.reviewFindings?.length ? "code-review-repair" : semanticRepair ? "code-verification-repair" : "code-gate-repair";
|
|
285
320
|
const receiptWriteScope = receipt ? receiptRepairPaths(run.workspace_root, receipt) : [];
|
|
286
321
|
const repair = {
|
|
287
322
|
schema_id: "dd-flow/code-work-packet@5",
|
|
@@ -291,22 +326,23 @@ export function addVnextCodeRepair(context, input) {
|
|
|
291
326
|
repair: {
|
|
292
327
|
origin_work_ids: origins.map((work) => work.work_id),
|
|
293
328
|
...(receipt ? { check_receipt_id: receipt.id, failure_receipt_path: receipt.receipt_path } : {}),
|
|
294
|
-
...(input.
|
|
329
|
+
...(input.reviewFindings?.length ? { review_findings: input.reviewFindings } : {}),
|
|
330
|
+
...(semanticRepair ? { semantic_unresolved: unique(input.semanticUnresolved ?? []), verification_path: input.verificationPath } : {})
|
|
295
331
|
},
|
|
296
332
|
task: input.objective,
|
|
297
333
|
semantic_spine: {
|
|
298
|
-
user_outcome: receipt ? `Restore the accepted behavior after aggregate failure: ${input.objective}` : `Resolve accepted CODE-REVIEW finding: ${input.objective}`,
|
|
334
|
+
user_outcome: receipt ? `Restore the accepted behavior after aggregate failure: ${input.objective}` : semanticRepair ? `Close the unresolved CODE verification: ${input.objective}` : `Resolve accepted CODE-REVIEW finding: ${input.objective}`,
|
|
299
335
|
component_responsibility: "Diagnose and repair the failed accepted CODE result without changing unrelated behavior.",
|
|
300
336
|
must_preserve: unique(invariantPackets.flatMap((value) => value.semantic_spine.must_preserve)),
|
|
301
337
|
non_goals: unique(invariantPackets.flatMap((value) => value.semantic_spine.non_goals)),
|
|
302
|
-
acceptance_contribution: receipt ? `Make failed check pass: ${receipt.command}` : `Resolve CODE-REVIEW finding(s): ${input.
|
|
338
|
+
acceptance_contribution: receipt ? `Make failed check pass: ${receipt.command}` : semanticRepair ? `Resolve CODE verification gap(s): ${input.semanticUnresolved.join("; ")}` : `Resolve CODE-REVIEW finding(s): ${input.reviewFindings.map((finding) => finding.finding_ref).join(", ")}`
|
|
303
339
|
},
|
|
304
340
|
requirements: uniqueBy(invariantPackets.flatMap((value) => value.requirements), (value) => value.id),
|
|
305
341
|
acceptance: uniqueBy(invariantPackets.flatMap((value) => value.acceptance), (value) => JSON.stringify(value)),
|
|
306
342
|
// A CODE-REVIEW repair changes delivered code/evidence, never the
|
|
307
343
|
// already accepted PLAN or its ownership declaration.
|
|
308
344
|
document_updates: [],
|
|
309
|
-
required_read: unique([...(receipt ? [receipt.receipt_path] : input.
|
|
345
|
+
required_read: unique([...(receipt ? [receipt.receipt_path] : input.reviewFindings?.flatMap((finding) => finding.evidence_refs) ?? []), ...(semanticRepair && input.verificationPath ? [input.verificationPath] : []), ...packets.flatMap((value) => value.required_read)]),
|
|
310
346
|
discovery_boundary: unique(packets.flatMap((value) => value.discovery_boundary)),
|
|
311
347
|
// These are collision-avoidance hints. Receipt paths enrich the coordinator
|
|
312
348
|
// picture but never restrict the repair's project-local edits.
|
|
@@ -314,7 +350,7 @@ export function addVnextCodeRepair(context, input) {
|
|
|
314
350
|
// Receipts record the resolved shell command. A repair must retain the
|
|
315
351
|
// accepted declaration (including its immutable @check alias) and only
|
|
316
352
|
// change when it runs, so work finish can validate it again.
|
|
317
|
-
checks: selectRepairChecks({ ...(failedCheck ? { failedCheck } : {}), ...(input.reviewChecks ? { reviewChecks: input.reviewChecks } : {}) }),
|
|
353
|
+
checks: selectRepairChecks({ ...(failedCheck ? { failedCheck } : {}), ...(input.reviewChecks ? { reviewChecks: input.reviewChecks } : {}), ...(semanticRepair ? { semanticChecks: declaredChecks } : {}) }),
|
|
318
354
|
provides_checks: [],
|
|
319
355
|
stop_conditions: unique([
|
|
320
356
|
...invariantPackets.flatMap((value) => value.stop_conditions),
|
|
@@ -339,16 +375,18 @@ export function addVnextCodeRepair(context, input) {
|
|
|
339
375
|
type: "code_repair_created",
|
|
340
376
|
work_id: id,
|
|
341
377
|
origin_work_ids: input.originWorkIds,
|
|
342
|
-
...(receipt ? { check_receipt_id: receipt.id } : { review_finding_ids: input.
|
|
378
|
+
...(receipt ? { check_receipt_id: receipt.id } : input.reviewFindings?.length ? { review_finding_ids: input.reviewFindings.map((finding) => finding.finding_ref) } : { semantic_unresolved: input.semanticUnresolved })
|
|
343
379
|
});
|
|
344
380
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
345
381
|
return { ok: true, run_id: run.id, repair_work_id: id, start_command: workStartCommand(context, work) };
|
|
346
382
|
}
|
|
347
|
-
/**
|
|
383
|
+
/** Retain the causal declaration for worker context; run_at keeps aggregate gates at stage scope. */
|
|
348
384
|
export function selectRepairChecks(input) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
385
|
+
// Gate placement belongs to the accepted PLAN. `runCodeChecks` executes only
|
|
386
|
+
// work-scoped entries, while retaining the aggregate declaration tells the
|
|
387
|
+
// worker exactly what will be rerun by the coordinator.
|
|
388
|
+
const candidates = input.failedCheck ? [input.failedCheck] : input.reviewChecks ?? input.semanticChecks ?? [];
|
|
389
|
+
const checks = candidates.map((check) => ({ ...check, purpose: `${input.semanticChecks ? "Prove the CODE verification repair" : "Prove the CODE-REVIEW repair"}: ${check.purpose}` }));
|
|
352
390
|
return uniqueBy(checks, (check) => check.id);
|
|
353
391
|
}
|
|
354
392
|
function receiptRepairPaths(workspaceRoot, receipt) {
|
|
@@ -494,19 +532,35 @@ function processAlive(pid) {
|
|
|
494
532
|
return false;
|
|
495
533
|
}
|
|
496
534
|
}
|
|
497
|
-
|
|
535
|
+
/**
|
|
536
|
+
* The immutable Work result is a repair history. The stage report is a current
|
|
537
|
+
* acceptance projection, so a registered receipt from an older check epoch
|
|
538
|
+
* must never stand in for the receipt that currently proves the declaration.
|
|
539
|
+
*/
|
|
540
|
+
export function verificationProjection(works, receipts, options = {}) {
|
|
498
541
|
const packets = works.map((work) => packet(work)).filter((value) => value !== null);
|
|
499
542
|
const declarations = uniqueBy(packets.flatMap((value) => value.checks), (value) => value.id);
|
|
500
543
|
const receiptByDeclaration = new Map(receipts.flatMap((receipt) => receipt.check_refs.map((ref) => [ref, receipt])));
|
|
501
|
-
const
|
|
544
|
+
const rawEvidence = new Map();
|
|
502
545
|
for (const work of works) {
|
|
503
546
|
const result = readJsonResult(work.result);
|
|
504
547
|
for (const item of result.evidence ?? [])
|
|
505
548
|
if (item.criterion_id)
|
|
506
|
-
|
|
549
|
+
rawEvidence.set(item.criterion_id, unique([...(rawEvidence.get(item.criterion_id) ?? []), ...(item.refs ?? [])]));
|
|
507
550
|
}
|
|
508
551
|
const acceptance = uniqueBy(packets.flatMap((value) => value.acceptance), (value) => value.criterion_id);
|
|
509
|
-
const
|
|
552
|
+
const historicalReceiptRefs = new Set((options.historicalReceipts ?? receipts).flatMap((receipt) => receiptReferences(receipt, options)));
|
|
553
|
+
const acceptanceEvidence = new Map(acceptance.map((item) => {
|
|
554
|
+
const reported = rawEvidence.get(item.criterion_id) ?? [];
|
|
555
|
+
const current = unique(item.check_refs.flatMap((checkRef) => {
|
|
556
|
+
const receipt = receiptByDeclaration.get(checkRef);
|
|
557
|
+
return receipt?.status === "passed" ? [currentReceiptRef(receipt, options)] : [];
|
|
558
|
+
}));
|
|
559
|
+
const evidenceRefs = unique([...reported.filter((ref) => !historicalReceiptRefs.has(ref)), ...current]);
|
|
560
|
+
const historical = reported.filter((ref) => historicalReceiptRefs.has(ref) && !current.includes(ref));
|
|
561
|
+
return [item.criterion_id, { evidenceRefs, historical }];
|
|
562
|
+
}));
|
|
563
|
+
const hasEvidence = (criterionId) => (acceptanceEvidence.get(criterionId)?.evidenceRefs.length ?? 0) > 0;
|
|
510
564
|
const isPassed = (checkId, criterionId) => receiptByDeclaration.get(checkId)?.status === "passed" || (declarations.find((check) => check.id === checkId)?.run_at === "external" && hasEvidence(criterionId));
|
|
511
565
|
return {
|
|
512
566
|
checks: declarations.map((check) => {
|
|
@@ -517,10 +571,20 @@ export function verificationProjection(works, receipts) {
|
|
|
517
571
|
acceptance: acceptance.map((item) => {
|
|
518
572
|
const external = item.check_refs.some((id) => declarations.find((check) => check.id === id)?.run_at === "external");
|
|
519
573
|
const confirmed = item.check_refs.every((id) => isPassed(id, item.criterion_id)) && hasEvidence(item.criterion_id);
|
|
520
|
-
|
|
574
|
+
const evidence = acceptanceEvidence.get(item.criterion_id) ?? { evidenceRefs: [], historical: [] };
|
|
575
|
+
return { criterion_id: item.criterion_id, gate: item.gate, status: confirmed ? (external ? "confirmed_with_external_evidence" : "confirmed") : ["work", "code", "readiness"].includes(item.gate) ? "unresolved" : "not_due", check_refs: item.check_refs, evidence_refs: evidence.evidenceRefs, ...(evidence.historical.length ? { reported_evidence_refs: evidence.historical } : {}) };
|
|
521
576
|
})
|
|
522
577
|
};
|
|
523
578
|
}
|
|
579
|
+
function currentReceiptRef(receipt, options) {
|
|
580
|
+
if (!options.runId || !options.runHome)
|
|
581
|
+
return receipt.receipt_path;
|
|
582
|
+
const relative = path.relative(options.runHome, receipt.receipt_path).split(path.sep).join("/");
|
|
583
|
+
return relative && !relative.startsWith("../") ? `run://${options.runId}/${relative}` : receipt.receipt_path;
|
|
584
|
+
}
|
|
585
|
+
function receiptReferences(receipt, options) {
|
|
586
|
+
return unique([receipt.receipt_path, currentReceiptRef(receipt, options)]);
|
|
587
|
+
}
|
|
524
588
|
function readJsonResult(value) { try {
|
|
525
589
|
const parsed = JSON.parse(value ?? "{}");
|
|
526
590
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -572,7 +636,7 @@ function requireRootWork(context, projectId, runId) {
|
|
|
572
636
|
return work;
|
|
573
637
|
}
|
|
574
638
|
function requireRun(context, projectId, runId) {
|
|
575
|
-
const run = context.db.get("SELECT id, project_id,
|
|
639
|
+
const run = context.db.get("SELECT id, project_id, run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
|
|
576
640
|
if (!run)
|
|
577
641
|
throw new AppError("not_found", "RUN is not registered", 1);
|
|
578
642
|
return run;
|
|
@@ -630,16 +694,16 @@ function readReadiness(file) {
|
|
|
630
694
|
}
|
|
631
695
|
}
|
|
632
696
|
function requireHome(run) {
|
|
633
|
-
if (!run.
|
|
634
|
-
throw new AppError("runtime_missing", "RUN
|
|
635
|
-
return run.
|
|
697
|
+
if (!run.run_root)
|
|
698
|
+
throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1);
|
|
699
|
+
return run.run_root;
|
|
636
700
|
}
|
|
637
701
|
function finishCommand(context, runId, projectRoot, verificationFile) {
|
|
638
702
|
return `${flowCommand(context)} stage finish ${runId} --stage code --verification-file ${JSON.stringify(verificationFile)} --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl`;
|
|
639
703
|
}
|
|
640
704
|
function readVerification(context, input) {
|
|
641
705
|
const file = path.resolve(input.file);
|
|
642
|
-
validateSchema({ schemaName: "code-verification", file, projectRoot: input.projectRoot, ddFlowHome: context.ddFlowHome, runId: input.runId });
|
|
706
|
+
validateSchema({ schemaName: "code-verification", file, projectRoot: input.projectRoot, ddFlowHome: context.ddFlowHome, runId: input.runId, runRoot: input.runRoot });
|
|
643
707
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
644
708
|
}
|
|
645
709
|
function verificationForFinish(context, input) {
|
|
@@ -7,12 +7,6 @@ import { getFlowRunVariables } from "./runs.js";
|
|
|
7
7
|
import { listWorks } from "./work-registry.js";
|
|
8
8
|
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
9
9
|
export const subagentCapacityKey = "runtime.subagent.available_slots";
|
|
10
|
-
export const capacityProbe = {
|
|
11
|
-
fanout_size: 15,
|
|
12
|
-
probe_hold_seconds: 60,
|
|
13
|
-
cleanup_deadline_seconds: 180,
|
|
14
|
-
completion_token: "AGENT-NN"
|
|
15
|
-
};
|
|
16
10
|
export function fanoutState(input) {
|
|
17
11
|
if (!input.hasWork)
|
|
18
12
|
return input.dispatch === "none" ? "coordinator_required" : "dispatch_required";
|
|
@@ -42,10 +36,10 @@ export function readFanoutDescriptor(stageRoot) {
|
|
|
42
36
|
export function getVnextFanoutStatus(context, input) {
|
|
43
37
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
44
38
|
const project = requireProjectByRoot(context, projectRoot);
|
|
45
|
-
const run = context.db.get("SELECT
|
|
46
|
-
if (!run?.
|
|
47
|
-
throw new AppError("not_found", "RUN
|
|
48
|
-
const stageRoot = path.join(run.
|
|
39
|
+
const run = context.db.get("SELECT run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
|
|
40
|
+
if (!run?.run_root)
|
|
41
|
+
throw new AppError("not_found", "RUN artifact root is unavailable", 1, { run_id: input.runId });
|
|
42
|
+
const stageRoot = path.join(run.run_root, vnextStageDirectory(input.stage));
|
|
49
43
|
const descriptor = readFanoutDescriptor(stageRoot);
|
|
50
44
|
if (!descriptor)
|
|
51
45
|
return { ok: true, run_id: input.runId, stage: input.stage, orchestration: null };
|
|
@@ -70,8 +64,7 @@ export function getVnextFanoutStatus(context, input) {
|
|
|
70
64
|
state: fanoutState({ dispatch: descriptor.dispatch, hasWork, capacityRequired: descriptor.capacity_required, capacityKnown, created: counts.created ?? 0, running: counts.running ?? 0 }),
|
|
71
65
|
capacity: {
|
|
72
66
|
run_key: subagentCapacityKey,
|
|
73
|
-
available_slots: capacityKnown ? available : null
|
|
74
|
-
...(descriptor.capacity_required && !capacityKnown ? { probe: capacityProbe } : {})
|
|
67
|
+
available_slots: capacityKnown ? available : null
|
|
75
68
|
},
|
|
76
69
|
works: { ...counts, ready: ready.works.map((work) => ({ work_id: work.work_id, task: work.task, start_command: work.start_command })) }
|
|
77
70
|
}
|