@deksden-com/dd-flow-cli 0.9.0-beta.18 → 0.9.0-beta.21
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 +18 -0
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +5 -3
- package/dist/cli/run-cli.js +69 -12
- package/dist/services/dashboard.js +9 -0
- package/dist/services/engines.js +4 -6
- package/dist/services/eval-snapshots.js +230 -16
- package/dist/services/hooks.js +107 -20
- package/dist/services/lifecycle-command.js +11 -1
- package/dist/services/merge-server.js +27 -7
- package/dist/services/run-recovery.js +254 -0
- package/dist/services/runs.js +15 -0
- package/dist/services/stage-lifecycle.js +3 -0
- package/dist/services/vnext-fanout.js +40 -0
- package/dist/services/vnext-merge.js +34 -9
- package/dist/services/work-registry.js +105 -12
- package/dist/storage/database.js +36 -1
- package/package.json +13 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Validate every selected engine artifact and execute its pinned snapshot even when its version matches the router. Retain a new recovery interruption before root acknowledgement.
|
|
8
|
+
|
|
9
|
+
## 0.9.0-beta.20
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Verify recovery payloads and workspace drift before resuming, preserve staged Git changes, publish snapshots through a temporary directory, and allow explicit recovery restoration into fresh targets. Fence RUN mutations and late asynchronous Work completion while recovery is active.
|
|
14
|
+
|
|
15
|
+
## 0.9.0-beta.19
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Require a fresh trusted hook receipt from the physical Work owner before terminal stage or Work lifecycle commands can mutate a vNext RUN. Record terminal native-child observations at their originating Stage so unbound delegation is retained as a diagnostic instead of contaminating a later fan-out wave.
|
|
20
|
+
|
|
3
21
|
## 0.9.0-beta.18
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/build-info.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
3
|
+
"cli_version": "0.9.0-beta.21",
|
|
4
|
+
"cli_commit": "2350e9cbeddba31e283d27384163b2bb44690d46",
|
|
5
|
+
"built_at": "2026-09-06T23:59:54.226Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
|
-
"version": "4.0.
|
|
8
|
-
"commit": "
|
|
7
|
+
"version": "4.0.6",
|
|
8
|
+
"commit": "2aafb30d23e3f646b736b7ead25438cab97b16bf",
|
|
9
9
|
"flow_contract": "dd-flow-canonical-2026-08",
|
|
10
10
|
"repo_root": "/Users/deksden/Documents/_Projects/dd-memorybank",
|
|
11
11
|
"memorybank_root": "/Users/deksden/Documents/_Projects/dd-memorybank/.memory-bank",
|
package/dist/cli/help.js
CHANGED
|
@@ -155,8 +155,9 @@ Usage:
|
|
|
155
155
|
dd-flow run status <RUN-ID|RUN-short-id> --project-root <root> --json
|
|
156
156
|
dd-flow run list --project-root <root> --json
|
|
157
157
|
dd-flow run prepare-vnext-specify --project-root <root> --slug <slug> --json
|
|
158
|
-
dd-flow run
|
|
159
|
-
dd-flow run snapshot
|
|
158
|
+
dd-flow run recovery inspect|begin|seal|capture|resume <RUN-ID|RUN-short-id> --project-root <root> --json
|
|
159
|
+
dd-flow run snapshot create <RUN-ID|RUN-short-id> (--stage-entry <stage>|--candidate|--incomplete|--recovery-id <RCV-ID>) --project-root <root> --output <directory> --json
|
|
160
|
+
dd-flow run snapshot restore --snapshot <directory> --project-root <prepared-root> [--recovery-id <RCV-ID>] --json
|
|
160
161
|
dd-flow run snapshot bootstrap create --project-root <root> --output <directory> --json
|
|
161
162
|
dd-flow run snapshot bootstrap restore --snapshot <directory> --project-root <prepared-root> --json
|
|
162
163
|
dd-flow run timeline <RUN-ID|RUN-short-id> --project-root <root> --json
|
|
@@ -170,7 +171,7 @@ Usage:
|
|
|
170
171
|
|
|
171
172
|
RUN-* is the execution envelope for one concrete flow launch. Semantic truth remains in protocol, experiment, DEF, scenario, evidence, and Memory Bank documents. New run artifacts are stored under DD_FLOW_HOME/projects/<PRJ-ID-slug>/runs/<RUN-ID-slug>/ with one run.json projection and an append-only timeline.jsonl. Timeline is the compact full run report: summary is always present, while --hide may suppress bulky sections. Use \`dd-flow stat usage --run …\` to reread registered local Codex transcript counters and tool-call facts and refresh the current usage projection; unavailable data is explicit rather than zero.
|
|
172
173
|
|
|
173
|
-
run prepare-vnext-specify allocates an otherwise unstarted vNext RUN for the canonical SPECIFY entry checkpoint. run snapshot is the eval-only checkpoint boundary: it snapshots one quiescent RUN, its dedicated DD_FLOW_HOME, and the exact project tree including its Git repository, then restores them into a fresh dedicated runtime. It is not a general runtime backup command and fails closed for a shared runtime.
|
|
174
|
+
run prepare-vnext-specify allocates an otherwise unstarted vNext RUN for the canonical SPECIFY entry checkpoint. run recovery makes an interrupted RUN read-only, requires an explicit settled-tree receipt, captures immutable recovery evidence, then explicitly returns delegated Work to the ready graph. run snapshot is the eval-only checkpoint boundary: it snapshots one quiescent RUN, its dedicated DD_FLOW_HOME, and the exact project tree including its Git repository, then restores them into a fresh dedicated runtime. It is not a general runtime backup command and fails closed for a shared runtime.
|
|
174
175
|
|
|
175
176
|
Examples:
|
|
176
177
|
dd-flow run start --project-root "$PWD" --flow-kind mb_sdlc --subject-type protocol --subject-id PRT-001-demo --slug demo --json
|
|
@@ -192,6 +193,7 @@ Usage:
|
|
|
192
193
|
dd-flow stage fanout status <RUN-ID|RUN-short-id> --project-root <root> --stage <name> --json
|
|
193
194
|
dd-flow stage fanout dispatch <RUN-ID|RUN-short-id> --project-root <root> --stage <name> --json
|
|
194
195
|
dd-flow stage fanout reconcile <RUN-ID> --project-root <root> --stage <name> --observations-file <native-observations.json> --json
|
|
196
|
+
dd-flow stage native observe <RUN-ID> --project-root <root> --stage <name> --observations-file <native-observations.json> --json
|
|
195
197
|
dd-flow stage finish <RUN-ID|RUN-short-id> --project-root <root> --stage <name> [--semantic-file <RUN-local-json>] --json
|
|
196
198
|
dd-flow stage finish <RUN-ID|RUN-short-id> --project-root <root> --stage specify (--result-file <RUN-local-specify.json>|--result-stdin) --outcome <specified|failed|cancelled> --json
|
|
197
199
|
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -41,14 +41,15 @@ import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services
|
|
|
41
41
|
import { addVnextCodeReviewRepair, finishVnextCodeReview, isVnextCodeReviewRun, startVnextCodeReview } from "../services/vnext-code-review.js";
|
|
42
42
|
import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
43
43
|
import { mergeServerStatus, serveMergeRequests, stopMergeServer } from "../services/merge-server.js";
|
|
44
|
-
import { getVnextFanoutStatus, reconcileVnextFanout } from "../services/vnext-fanout.js";
|
|
44
|
+
import { getVnextFanoutStatus, observeVnextNativeChildren, reconcileVnextFanout } from "../services/vnext-fanout.js";
|
|
45
45
|
import { pauseStageForUser, resumeStageAfterUser } from "../services/stage-pause.js";
|
|
46
46
|
import { blockStageForRuntime, unblockStageAfterRuntimeRepair } from "../services/stage-blocker.js";
|
|
47
47
|
import { classifyCliOperation, requiresExplicitUpgradeAuthorization } from "../services/cli-operation-classifier.js";
|
|
48
48
|
import { renderWorkerPrompt } from "../services/prompts.js";
|
|
49
49
|
import { finishStage, startStage } from "../services/stage-lifecycle.js";
|
|
50
|
-
import { addWorkBatch, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
50
|
+
import { addWorkBatch, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
51
51
|
import { createEvalBootstrapSnapshot, createEvalRunSnapshot, prepareVnextSpecifyRun, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
|
|
52
|
+
import { acceptRunRecovery, beginRunRecovery, inspectRunRecovery, recordRecoveryCapture, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
|
|
52
53
|
import { loadExternalStageContext } from "../services/stage-context.js";
|
|
53
54
|
import { stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessStatus, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
|
|
54
55
|
import { startRuntimeService } from "../services/runtime-service.js";
|
|
@@ -670,6 +671,11 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
670
671
|
}
|
|
671
672
|
throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch|reconcile> <RUN> --stage <stage> --project-root <path>; reconcile requires --observations-file <native-observations.json>", 2);
|
|
672
673
|
}
|
|
674
|
+
if (family === "stage" && command === "native") {
|
|
675
|
+
if (requiredPosition(parsed, 0, "native action") !== "observe")
|
|
676
|
+
throw new AppError("usage", "Use: dd-flow stage native observe <RUN> --stage <stage> --observations-file <native-observations.json> --project-root <path>", 2);
|
|
677
|
+
return observeVnextNativeChildren(context, { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "stage native observe"), runId: requiredPosition(parsed, 1, "run-id"), stage: requiredOption(parsed, "stage"), observations: JSON.parse(fs.readFileSync(requiredOption(parsed, "observations-file"), "utf8")) });
|
|
678
|
+
}
|
|
673
679
|
if (family === "run" && command === "capacity") {
|
|
674
680
|
if (requiredPosition(parsed, 0, "capacity action") !== "record")
|
|
675
681
|
throw new AppError("usage", "Use: dd-flow run capacity record <RUN> --available-slots <0..15> --project-root <path>", 2);
|
|
@@ -757,19 +763,27 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
757
763
|
return deleteWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
758
764
|
if (family === "work" && command === "start") {
|
|
759
765
|
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
766
|
+
const recoveryId = optionalOption(parsed, "recovery-id");
|
|
760
767
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
|
|
761
|
-
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), projectRoot });
|
|
768
|
+
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot });
|
|
762
769
|
}
|
|
763
770
|
if (family === "work" && command === "finish") {
|
|
764
771
|
const resultFile = optionalOption(parsed, "result-file");
|
|
765
772
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
766
773
|
if (Boolean(resultFile) === resultStdin)
|
|
767
774
|
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
775
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
776
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work finish");
|
|
777
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_finish", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
768
778
|
const result = resultFile ? fs.readFileSync(path.resolve(resultFile), "utf8") : await readStdin(io.stdin);
|
|
769
|
-
return await finishWork(context,
|
|
779
|
+
return await finishWork(context, workId, result, commandProgress(io, output));
|
|
780
|
+
}
|
|
781
|
+
if (family === "work" && command === "fail") {
|
|
782
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
783
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work fail");
|
|
784
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_fail", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
785
|
+
return failWork(context, workId, requiredOption(parsed, "reason"));
|
|
770
786
|
}
|
|
771
|
-
if (family === "work" && command === "fail")
|
|
772
|
-
return failWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
773
787
|
if (family === "work" && command === "cancel")
|
|
774
788
|
return cancelWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
775
789
|
if (family === "work" && command === "retry")
|
|
@@ -882,8 +896,17 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
882
896
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
883
897
|
const retryCheckId = optionalOption(parsed, "retry-check");
|
|
884
898
|
const retryReason = optionalOption(parsed, "reason");
|
|
899
|
+
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
885
900
|
if (retryCheckId && !retryReason?.trim())
|
|
886
901
|
throw new AppError("usage", "--retry-check requires --reason explaining the recovered environment", 2);
|
|
902
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
903
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
904
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
905
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
906
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
907
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
908
|
+
if (protectedLifecycle)
|
|
909
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, operation: "stage_finish", ...(hookEventId ? { hookEventId } : {}) });
|
|
887
910
|
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
888
911
|
const outcome = optionalOption(parsed, "outcome");
|
|
889
912
|
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome)) {
|
|
@@ -949,11 +972,23 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
949
972
|
if (family === "stage" && command === "pause") {
|
|
950
973
|
if (!hasOption(parsed, "question-stdin"))
|
|
951
974
|
throw new AppError("usage", "stage pause requires --question-stdin", 2);
|
|
975
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
976
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
977
|
+
const stage = requiredOption(parsed, "stage");
|
|
978
|
+
const workId = requiredOption(parsed, "work");
|
|
979
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
980
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
981
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
982
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
983
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
984
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
985
|
+
if (protectedLifecycle)
|
|
986
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, workId, operation: "stage_pause", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
952
987
|
return pauseStageForUser(context, {
|
|
953
|
-
projectRoot
|
|
954
|
-
runId
|
|
955
|
-
stage
|
|
956
|
-
workId
|
|
988
|
+
projectRoot,
|
|
989
|
+
runId,
|
|
990
|
+
stage,
|
|
991
|
+
workId,
|
|
957
992
|
question: await readStdin(io.stdin)
|
|
958
993
|
});
|
|
959
994
|
}
|
|
@@ -1429,6 +1464,24 @@ function dispatchSession(context, command, parsed) {
|
|
|
1429
1464
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
1430
1465
|
}
|
|
1431
1466
|
function dispatchRun(context, command, parsed) {
|
|
1467
|
+
if (command === "recovery") {
|
|
1468
|
+
const action = requiredPosition(parsed, 0, "recovery action");
|
|
1469
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1470
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
1471
|
+
if (action === "inspect")
|
|
1472
|
+
return inspectRunRecovery(context, { projectRoot, runId });
|
|
1473
|
+
if (action === "begin")
|
|
1474
|
+
return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") });
|
|
1475
|
+
if (action === "seal")
|
|
1476
|
+
return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") });
|
|
1477
|
+
if (action === "capture")
|
|
1478
|
+
return recordRecoveryCapture(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") });
|
|
1479
|
+
if (action === "resume")
|
|
1480
|
+
return resumeRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), daemonId: requiredOption(parsed, "daemon-id"), harness: requiredOption(parsed, "harness"), nativeSessionId: requiredOption(parsed, "native-session-id") });
|
|
1481
|
+
if (action === "accept")
|
|
1482
|
+
return acceptRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1483
|
+
throw new AppError("usage", "Use: dd-flow run recovery inspect|begin|seal|capture|resume|accept", 2);
|
|
1484
|
+
}
|
|
1432
1485
|
if (command === "snapshot") {
|
|
1433
1486
|
const action = requiredPosition(parsed, 0, "snapshot action");
|
|
1434
1487
|
if (action === "bootstrap") {
|
|
@@ -1448,9 +1501,10 @@ function dispatchRun(context, command, parsed) {
|
|
|
1448
1501
|
if (action === "create") {
|
|
1449
1502
|
const candidate = parsed.options.has("candidate");
|
|
1450
1503
|
const incomplete = parsed.options.has("incomplete");
|
|
1504
|
+
const recoveryId = optionalOption(parsed, "recovery-id");
|
|
1451
1505
|
const stageEntry = optionalOption(parsed, "stage-entry");
|
|
1452
|
-
if (Number(candidate) + Number(incomplete) + Number(stageEntry !== undefined) !== 1)
|
|
1453
|
-
throw new AppError("usage", "Use exactly one of --candidate, --incomplete or --stage-entry", 2);
|
|
1506
|
+
if (Number(candidate) + Number(incomplete) + Number(stageEntry !== undefined) + Number(recoveryId !== undefined) !== 1)
|
|
1507
|
+
throw new AppError("usage", "Use exactly one of --candidate, --incomplete, --recovery-id or --stage-entry", 2);
|
|
1454
1508
|
const input = {
|
|
1455
1509
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
1456
1510
|
runId: requiredPosition(parsed, 1, "run-id"),
|
|
@@ -1458,6 +1512,8 @@ function dispatchRun(context, command, parsed) {
|
|
|
1458
1512
|
};
|
|
1459
1513
|
if (incomplete)
|
|
1460
1514
|
return createEvalRunSnapshot(context, { ...input, incomplete: true });
|
|
1515
|
+
if (recoveryId)
|
|
1516
|
+
return createEvalRunSnapshot(context, { ...input, recoveryId });
|
|
1461
1517
|
return candidate
|
|
1462
1518
|
? createEvalRunSnapshot(context, { ...input, candidate: true })
|
|
1463
1519
|
: createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
|
|
@@ -1465,6 +1521,7 @@ function dispatchRun(context, command, parsed) {
|
|
|
1465
1521
|
if (action === "restore")
|
|
1466
1522
|
return restoreEvalRunSnapshot(context, {
|
|
1467
1523
|
snapshot: requiredOption(parsed, "snapshot"),
|
|
1524
|
+
...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}),
|
|
1468
1525
|
projectRoot: requiredOption(parsed, "project-root")
|
|
1469
1526
|
});
|
|
1470
1527
|
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore | dd-flow run snapshot create <RUN> (--stage-entry <stage>|--candidate) --project-root <path> --output <directory> | dd-flow run snapshot restore --snapshot <directory> --project-root <path>", 2);
|
|
@@ -19,6 +19,8 @@ export function getCmuxStatus(context, input) {
|
|
|
19
19
|
}
|
|
20
20
|
export function renderDashboard(context, input) {
|
|
21
21
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
22
|
+
if (projectRecoveryGuarded(context, project.id))
|
|
23
|
+
throw new AppError("run_recovery_guarded", "Project dashboard is frozen while its RUN is retained for recovery", 1);
|
|
22
24
|
const format = parseDashboardFormat(input.format);
|
|
23
25
|
if (format === "html") {
|
|
24
26
|
if (input.protocol) {
|
|
@@ -170,6 +172,10 @@ export function refreshAllDashboards(context, input = {}) {
|
|
|
170
172
|
}
|
|
171
173
|
export function autoRefreshDashboards(context, input) {
|
|
172
174
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
175
|
+
// Hooks and usage ingestion continue to record receipts after capture.
|
|
176
|
+
// Their derived timestamp must not mutate the captured workspace.
|
|
177
|
+
if (projectRecoveryGuarded(context, project.id))
|
|
178
|
+
return { ok: true, skipped: true, reason: "run_recovery_guarded" };
|
|
173
179
|
const config = readProjectConfig(context, project.id);
|
|
174
180
|
if (!config.dashboard.auto_refresh) {
|
|
175
181
|
return { ok: true, skipped: true, reason: "dashboard_auto_refresh_disabled" };
|
|
@@ -196,6 +202,9 @@ export function autoRefreshDashboards(context, input) {
|
|
|
196
202
|
}
|
|
197
203
|
return result;
|
|
198
204
|
}
|
|
205
|
+
function projectRecoveryGuarded(context, projectId) {
|
|
206
|
+
return Boolean(context.db.get("SELECT 1 FROM run_recovery_guards WHERE project_id = ? AND status IN ('draining', 'sealed', 'resuming') LIMIT 1", [projectId]));
|
|
207
|
+
}
|
|
199
208
|
export function dashboardSummary(context, projectId, projectRoot) {
|
|
200
209
|
const config = readProjectConfig(context, projectId);
|
|
201
210
|
return {
|
package/dist/services/engines.js
CHANGED
|
@@ -174,7 +174,9 @@ export function routeArgsThroughEngine(context, args, io, stdin, env, resolvedPr
|
|
|
174
174
|
if (!selection.selected)
|
|
175
175
|
return Promise.resolve(null);
|
|
176
176
|
const current = getCliBuildInfo();
|
|
177
|
-
|
|
177
|
+
const currentRoot = findPackageRoot(path.dirname(sourceFile));
|
|
178
|
+
if (selection.selected.package_name === current.package_name && selection.selected.package_version === current.version
|
|
179
|
+
&& currentRoot !== null && fs.realpathSync(selection.selected.snapshot_root) === fs.realpathSync(currentRoot)) {
|
|
178
180
|
return Promise.resolve({ routed: true, exitCode: 0, inProcess: true });
|
|
179
181
|
}
|
|
180
182
|
const entrypoint = resolveManifestEntrypoint(selection.selected);
|
|
@@ -383,11 +385,7 @@ export function selectEngine(context, input, options = {}, classification = inpu
|
|
|
383
385
|
const manifests = readInstalledManifests(engineStoreRoot(context.ddFlowHome))
|
|
384
386
|
.filter((manifest) => manifest.package_name === packageName)
|
|
385
387
|
.filter((manifest) => requiredRange !== null && satisfiesRange(manifest.package_version, requiredRange))
|
|
386
|
-
.filter((manifest) =>
|
|
387
|
-
&& manifest.package_name === build.package_name
|
|
388
|
-
&& manifest.package_version === build.version
|
|
389
|
-
? true
|
|
390
|
-
: manifestHealthy(manifest))
|
|
388
|
+
.filter((manifest) => manifestHealthy(manifest))
|
|
391
389
|
.sort((a, b) => {
|
|
392
390
|
const versionOrder = compareSemverStrings(b.package_version, a.package_version);
|
|
393
391
|
if (versionOrder !== 0)
|