@deksden-com/dd-flow-cli 0.9.0-beta.119 → 0.9.0-beta.121
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 +7 -0
- package/dist/build-info.json +3 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +3 -2
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +100 -23
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +12 -2
- package/dist/harness-runtime/lib/dd-opencode.mjs +2 -2
- package/dist/harness-runtime/lib/dd-zcode.mjs +5 -3
- package/dist/services/code-checks.js +40 -0
- package/dist/services/codex-capacity.js +71 -0
- package/dist/services/controller-fanout.js +13 -1
- package/dist/services/external-work-launch.js +37 -3
- package/dist/services/managed-processes.js +42 -6
- package/dist/services/merge-server.js +34 -4
- package/dist/services/run-control.js +7 -1
- package/dist/services/run-controller-adapter.js +20 -2
- package/dist/services/run-controller-recovery.js +47 -3
- package/dist/services/run-controller.js +105 -10
- package/dist/services/run-recovery-runtime.js +43 -2
- package/dist/services/runtime-scope-stop.js +23 -2
- package/dist/services/vnext-code.js +7 -1
- package/dist/services/vnext-plan.js +29 -14
- package/package.json +1 -1
|
@@ -201,6 +201,7 @@ export function prepareVnextPlanArtifacts(context, input) {
|
|
|
201
201
|
const batch = path.join(input.home, "03-plan", "code-work-batch.json");
|
|
202
202
|
const ownership = protocolOwnership(input.home, input.protocols);
|
|
203
203
|
const obligations = acceptedObligations(input.home);
|
|
204
|
+
const mergeEnding = runEndsAtMerge(context, input.projectRoot, input.runId);
|
|
204
205
|
const frozenDocumentBaselines = input.publishBatch === false && fs.existsSync(batch)
|
|
205
206
|
? new Map(readJson(batch).works?.flatMap((work) => typeof work.key === "string" ? (work.document_updates ?? []).flatMap((update) => typeof update.path === "string" && (typeof update.baseline_sha256 === "string" || update.baseline_sha256 === null)
|
|
206
207
|
? [[`${work.key}\u0000${update.path}`, update.baseline_sha256]]
|
|
@@ -214,7 +215,7 @@ export function prepareVnextPlanArtifacts(context, input) {
|
|
|
214
215
|
try {
|
|
215
216
|
const { value } = prepareJsonSchema(file, "PLAN", { schemaName: "vnext-protocol-plan", projectRoot: workspaceRoot, ddFlowHome: context.ddFlowHome, runId: input.runId, runRoot: input.home });
|
|
216
217
|
assertPlanIdentity(value, planIdentity(input.home, input.runId, protocolId, ownership.get(protocolId) ?? []), file);
|
|
217
|
-
validatePlanSemantics(file, value, new Set(ownership.get(protocolId) ?? []), obligations);
|
|
218
|
+
validatePlanSemantics(file, value, new Set(ownership.get(protocolId) ?? []), obligations, mergeEnding);
|
|
218
219
|
validateCodeCheckCommands(workspaceRoot, value.checks.filter((check) => check.availability === "available").map((check) => check.command));
|
|
219
220
|
for (const check of value.checks.filter((item) => item.availability === "available"))
|
|
220
221
|
validateCheckDeclaration(workspaceRoot, check);
|
|
@@ -481,7 +482,7 @@ function normalizedAspectMapRefs(file, bytes, projectRoot, runHome, runId) {
|
|
|
481
482
|
aspect.planned_artifact_refs = aspect.planned_artifact_refs.map(normalize);
|
|
482
483
|
return changed ? `${JSON.stringify(map, null, 2)}\n` : bytes;
|
|
483
484
|
}
|
|
484
|
-
function projectCodeWorkBatch(input) {
|
|
485
|
+
export function projectCodeWorkBatch(input) {
|
|
485
486
|
const obligationMap = acceptedObligationMap(input.home);
|
|
486
487
|
const orientation = projectOrientation(input.workspaceRoot);
|
|
487
488
|
const works = input.plans.flatMap(({ protocolId, file, value }) => value.items.map((item) => {
|
|
@@ -516,10 +517,9 @@ function projectCodeWorkBatch(input) {
|
|
|
516
517
|
])],
|
|
517
518
|
discovery_boundary: item.execution_context.discovery_boundary,
|
|
518
519
|
planned_write_areas: [...new Set([...item.execution_context.planned_write_areas, ...documentPaths])],
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
checks: value.checks.filter((check) => itemCheckRefs(item, value.acceptance).includes(check.id)),
|
|
520
|
+
// CODE still needs acceptance-only aggregate declarations in its packets;
|
|
521
|
+
// work checks belong only to the item that explicitly verifies them.
|
|
522
|
+
checks: value.checks.filter((check) => itemCheckRefs(item, value.acceptance, value.checks).includes(check.id)),
|
|
523
523
|
provides_checks: value.checks.filter((check) => check.availability === "planned" && check.provided_by === item.id),
|
|
524
524
|
stop_conditions: item.execution_context.stop_conditions,
|
|
525
525
|
depends_on: item.depends_on.map((dependency) => `${protocolId}:${dependency}`),
|
|
@@ -548,10 +548,15 @@ function projectCodeWorkBatch(input) {
|
|
|
548
548
|
const ordered = [...works].sort((a, b) => a.key.localeCompare(b.key));
|
|
549
549
|
return { schema_id: "dd-flow/code-work-batch@5", sources: input.plans.map(({ protocolId, file, value }) => ({ plan_id: value.plan_id, protocol_id: protocolId, revision: value.revision, sha256: checksum(file) })), works: ordered };
|
|
550
550
|
}
|
|
551
|
-
function itemCheckRefs(item, acceptance) {
|
|
552
|
-
|
|
551
|
+
function itemCheckRefs(item, acceptance, catalog) {
|
|
552
|
+
const aggregate = new Set(catalog.filter((check) => check.run_at !== "work").map((check) => check.id));
|
|
553
|
+
return [...new Set([...item.verification.check_refs, ...acceptance.filter((entry) => entry.plan_item_ids.includes(item.id)).flatMap((entry) => entry.check_refs.filter((ref) => aggregate.has(ref)))])];
|
|
554
|
+
}
|
|
555
|
+
function renderCodeTask(item, catalog, acceptance = []) {
|
|
556
|
+
const local = catalog.filter((check) => check.run_at === "work" && item.verification.check_refs.includes(check.id));
|
|
557
|
+
const aggregate = catalog.filter((check) => check.run_at !== "work" && itemCheckRefs(item, acceptance, catalog).includes(check.id));
|
|
558
|
+
return [item.title, item.summary, item.details, `Preserve: ${item.semantic_spine.must_preserve.join("; ")}`, `Work checks: ${local.map((check) => `${check.id}:${check.command}`).join("; ")}`, `Later aggregate checks (CODE/MERGE; do not run in this Work): ${aggregate.map((check) => `${check.id}:${check.command}`).join("; ")}`, `Stop: ${item.execution_context.stop_conditions.join("; ")}`].join("\n\n");
|
|
553
559
|
}
|
|
554
|
-
function renderCodeTask(item, catalog, acceptance = []) { const checks = catalog.filter((check) => itemCheckRefs(item, acceptance).includes(check.id)); return [item.title, item.summary, item.details, `Preserve: ${item.semantic_spine.must_preserve.join("; ")}`, `Checks: ${checks.map((check) => `${check.id}:${check.command}`).join("; ")}`, `Stop: ${item.execution_context.stop_conditions.join("; ")}`].join("\n\n"); }
|
|
555
560
|
function validateProjectedPaths(batch, workspaceRoot, home, runId) {
|
|
556
561
|
for (const work of batch.works) {
|
|
557
562
|
for (const write of work.planned_write_areas)
|
|
@@ -595,7 +600,7 @@ function resolvePortablePath(value, workspaceRoot, home, runId) {
|
|
|
595
600
|
}
|
|
596
601
|
function writeJson(file, value) { const temporary = `${file}.${crypto.randomUUID()}.tmp`; fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`); fs.renameSync(temporary, file); }
|
|
597
602
|
function runRef(runId, runHome, file) { return `run://${runId}/${path.relative(runHome, file).split(path.sep).join("/")}`; }
|
|
598
|
-
function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs) {
|
|
603
|
+
function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs, mergeEnding) {
|
|
599
604
|
const items = plan.items ?? [];
|
|
600
605
|
const ids = new Set(items.map((item) => item.id).filter((id) => Boolean(id)));
|
|
601
606
|
if (ids.size !== items.length)
|
|
@@ -608,6 +613,8 @@ function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs) {
|
|
|
608
613
|
for (const check of plan.checks ?? []) {
|
|
609
614
|
if (checks.has(check.id))
|
|
610
615
|
throw new AppError("validation", "PLAN check ids must be unique", 2, { file, check_id: check.id });
|
|
616
|
+
if (mergeEnding && check.run_at === "release")
|
|
617
|
+
throw new AppError("check_gate_unreachable", "A RUN ending at MERGE cannot execute RELEASE checks", 2, { file, check_id: check.id });
|
|
611
618
|
if (check.availability === "planned" && (!check.provided_by || !ids.has(check.provided_by)))
|
|
612
619
|
throw new AppError("planned_check_provider_missing", "A planned check must name a PLAN item that provides it", 2, { file, check_id: check.id, provided_by: check.provided_by ?? null });
|
|
613
620
|
if (check.availability === "planned" && !check.command.startsWith("@check/"))
|
|
@@ -616,6 +623,8 @@ function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs) {
|
|
|
616
623
|
throw new AppError("planned_check_definition_missing", "A planned check must declare its exact alias definition", 2, { file, check_id: check.id });
|
|
617
624
|
checks.set(check.id, check);
|
|
618
625
|
}
|
|
626
|
+
const localChecks = new Set(items.flatMap((item) => item.verification.check_refs));
|
|
627
|
+
const acceptanceIds = new Set();
|
|
619
628
|
const ancestors = (itemId, seen = new Set()) => {
|
|
620
629
|
for (const dependency of items.find((item) => item.id === itemId)?.depends_on ?? [])
|
|
621
630
|
if (!seen.has(dependency)) {
|
|
@@ -639,7 +648,7 @@ function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs) {
|
|
|
639
648
|
const check = checks.get(checkId);
|
|
640
649
|
if (!check)
|
|
641
650
|
throw new AppError("check_reference_unknown", "PLAN item references an unknown check", 2, { file, item: item.id, check_id: checkId });
|
|
642
|
-
if (check.availability === "planned" && check.provided_by !== item.id && !ancestors(item.id).has(check.provided_by))
|
|
651
|
+
if (check.run_at === "work" && check.availability === "planned" && check.provided_by !== item.id && !ancestors(item.id).has(check.provided_by))
|
|
643
652
|
throw new AppError("check_consumer_not_ordered_after_provider", "A Work may consume a planned check only after its provider", 2, { file, item: item.id, check_id: checkId, provider: check.provided_by });
|
|
644
653
|
}
|
|
645
654
|
}
|
|
@@ -655,6 +664,11 @@ function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs) {
|
|
|
655
664
|
for (const acceptance of plan.acceptance ?? []) {
|
|
656
665
|
if (!acceptance.criterion_id || !acceptance.criterion_id.startsWith("AC-") || !ownedRefs.has(acceptance.criterion_id))
|
|
657
666
|
throw new AppError("validation", "PLAN acceptance references an AC-* owned by this protocol", 2, { file, criterion_id: acceptance.criterion_id });
|
|
667
|
+
if (acceptanceIds.has(acceptance.criterion_id))
|
|
668
|
+
throw new AppError("duplicate_plan_criterion", "PLAN acceptance criterion ids must be unique within a protocol", 2, { file, criterion_id: acceptance.criterion_id });
|
|
669
|
+
acceptanceIds.add(acceptance.criterion_id);
|
|
670
|
+
if (mergeEnding && acceptance.gate === "release")
|
|
671
|
+
throw new AppError("check_gate_unreachable", "A RUN ending at MERGE cannot prove RELEASE acceptance", 2, { file, criterion_id: acceptance.criterion_id });
|
|
658
672
|
for (const id of acceptance.plan_item_ids ?? [])
|
|
659
673
|
if (!ids.has(id))
|
|
660
674
|
throw new AppError("validation", "PLAN acceptance references an unknown item", 2, { file, criterion_id: acceptance.criterion_id, plan_item_id: id });
|
|
@@ -662,9 +676,10 @@ function validatePlanSemantics(file, plan, ownedRefs, acceptedRefs) {
|
|
|
662
676
|
const check = checks.get(id);
|
|
663
677
|
if (!check)
|
|
664
678
|
throw new AppError("check_reference_unknown", "PLAN acceptance references an unknown check", 2, { file, criterion_id: acceptance.criterion_id, check_id: id });
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
679
|
+
if ((acceptance.gate !== "release" && check.run_at === "release") || (!["merge", "release"].includes(acceptance.gate) && check.run_at === "merge"))
|
|
680
|
+
throw new AppError("check_gate_unreachable", "PLAN acceptance references a check after its gate", 2, { file, criterion_id: acceptance.criterion_id, check_id: id, gate: acceptance.gate, run_at: check.run_at });
|
|
681
|
+
if (check.run_at === "work" && !localChecks.has(id))
|
|
682
|
+
throw new AppError("work_check_unassigned", "A work check referenced by acceptance needs an explicit verifying PLAN item", 2, { file, criterion_id: acceptance.criterion_id, check_id: id });
|
|
668
683
|
}
|
|
669
684
|
}
|
|
670
685
|
for (const obligation of ownedRefs)
|