@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Bind a native Codex subagent Work to the child Thread identifier supplied as
|
|
8
|
+
`agent_id`, rather than Codex's inherited root hook session identifier.
|
|
9
|
+
|
|
10
|
+
## 0.9.0-beta.10
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 4858f6d: Preserve complete CODE review findings through repair and require one evidenced resolution per assigned finding.
|
|
15
|
+
|
|
16
|
+
## 0.9.0-beta.9
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- f156ad8: Use harness-qualified native child capacity for PLAN-REVIEW, CODE and
|
|
21
|
+
CODE-REVIEW fan-out instead of creating flow-owned capacity probes.
|
|
22
|
+
|
|
23
|
+
## 0.9.0-beta.8
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- 5f17e79: Keep CODE worker and reviewer prompts aligned on authorization/lifecycle write
|
|
28
|
+
boundaries, and project current check receipts without overwriting repair
|
|
29
|
+
history.
|
|
30
|
+
|
|
31
|
+
## 0.9.0-beta.7
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- 4621387: Expose public Session receipts and cleanup actions as native
|
|
36
|
+
harness/Session pairs without SQLite storage keys. Keep a freshly reserved
|
|
37
|
+
check receipt owned while its process is initializing, so a concurrent caller
|
|
38
|
+
cannot incorrectly abort it.
|
|
39
|
+
|
|
40
|
+
## 0.9.0-beta.6
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 781274c: Materialize a causal CODE repair Work when the coordinator's semantic
|
|
45
|
+
verification lists unresolved accepted obligations, rather than leaving CODE
|
|
46
|
+
running after a rejected finish.
|
|
47
|
+
|
|
48
|
+
## 0.9.0-beta.5
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- Keep every accepted original CODE check eligible for a later causal repair, so
|
|
53
|
+
a prior repair cannot make another failed aggregate check impossible to fix.
|
|
54
|
+
|
|
55
|
+
## 0.9.0-beta.4
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- Persist an atomic check completion marker so a later CLI invocation can recover
|
|
60
|
+
the actual terminal result after its original caller exits.
|
|
61
|
+
|
|
62
|
+
## 0.9.0-beta.3
|
|
63
|
+
|
|
64
|
+
### Patch Changes
|
|
65
|
+
|
|
66
|
+
- 0bd32f6: Expose a durable host resource registry for managed processes, preserve public
|
|
67
|
+
native Session identity separately from internal storage keys, and safely
|
|
68
|
+
reconcile interrupted check receipts.
|
|
69
|
+
|
|
70
|
+
## 0.9.0-beta.2
|
|
71
|
+
|
|
72
|
+
### Patch Changes
|
|
73
|
+
|
|
74
|
+
- Make CODE and CODE-REVIEW terminal transitions create their MERGE request
|
|
75
|
+
before persisting a completed stage, and validate MERGE prerequisites before
|
|
76
|
+
freezing the source commit. Upgrade project check profiles to
|
|
77
|
+
`dd-flow/code-check-profile@6`: aliases may declare named local ports, which
|
|
78
|
+
the CLI allocates per check invocation and records in its receipt.
|
|
79
|
+
|
|
3
80
|
## 0.9.0-beta.1
|
|
4
81
|
|
|
5
82
|
### 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.11",
|
|
4
|
+
"cli_commit": "67fea9fdb3fbcc04cc0a45de9afc5f7120c13a08",
|
|
5
|
+
"built_at": "2026-09-04T20:59:42.597Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
|
-
"version": "4.0.
|
|
8
|
-
"commit": "
|
|
7
|
+
"version": "4.0.2",
|
|
8
|
+
"commit": "fccdb9fe7359f2ba321eebace328fd18557dcd25",
|
|
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
|
@@ -204,9 +204,9 @@ Usage:
|
|
|
204
204
|
dd-flow work show <WORK-ID> --json
|
|
205
205
|
dd-flow work start <WORK-ID> --project-root <root> --json
|
|
206
206
|
dd-flow work finish <WORK-ID> (--result-file <path>|--result-stdin) --project-root <root> --json
|
|
207
|
-
dd-flow work repair add --run <RUN-ID> --from-check <CHECK-ID> --origin-work <WORK-ID>... --task-stdin --project-root <root> --json
|
|
207
|
+
dd-flow work repair add --run <RUN-ID> (--from-check <CHECK-ID>|--from-unresolved <gap> --verification-file <file>) --origin-work <WORK-ID>... --task-stdin --project-root <root> --json
|
|
208
208
|
|
|
209
|
-
CODE workers receive their full accepted packet from work start. work finish validates the result and executes the packet's focused checks before completion. A failed aggregate CODE gate
|
|
209
|
+
CODE workers receive their full accepted packet from work start. work finish validates the result and executes the packet's focused checks before completion. A failed aggregate CODE gate or unresolved semantic verification creates a repair Work from the selected completed origin context and retained evidence. Its planned write areas are coordination hints only; the repair may change any necessary project file inside its RUN workspace.`
|
|
210
210
|
],
|
|
211
211
|
[
|
|
212
212
|
"id",
|
|
@@ -477,7 +477,7 @@ Dashboards are rendered views of dd-flow state. Markdown remains the compatibili
|
|
|
477
477
|
`dd-flow schema - validate canonical dd-flow data contracts
|
|
478
478
|
|
|
479
479
|
Usage:
|
|
480
|
-
dd-flow schema validate --schema <name> --file <json-file> [--project-root <root>] [--schema-dir <dir>] --json
|
|
480
|
+
dd-flow schema validate --schema <name> --file <json-file> [--project-root <root>] [--run <RUN>] [--schema-dir <dir>] --json
|
|
481
481
|
|
|
482
482
|
Schema names resolve to <name>.schema.json. Lookup order is --schema-dir, then <project-root>/.memory-bank/dd-flow/schemas/, then <project-root>/dd-flow/schemas/ for canonical checkouts, then bundled CLI schemas. Flow-contract validation runs structural checks first and the runtime's shared semantic normalizer second. If --project-root is omitted, the current working directory is used. JSON mode writes valid results to stdout and structured validation/usage errors to stderr.`
|
|
483
483
|
],
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -50,6 +50,7 @@ import { finishStage, startStage } from "../services/stage-lifecycle.js";
|
|
|
50
50
|
import { addWorkBatch, 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
52
|
import { loadExternalStageContext } from "../services/stage-context.js";
|
|
53
|
+
import { confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessStatus, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
|
|
53
54
|
const defaultIo = {
|
|
54
55
|
stdout: process.stdout,
|
|
55
56
|
stderr: process.stderr,
|
|
@@ -88,7 +89,8 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
88
89
|
heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
|
|
89
90
|
}
|
|
90
91
|
const scopeProjectRoot = resolveProjectRootBeforeRouting(output.args, env);
|
|
91
|
-
const
|
|
92
|
+
const routedArgs = output.responseFile ? [...output.args, "--response-file", output.responseFile] : output.args;
|
|
93
|
+
const routed = await routeArgsThroughEngine(routerContext, output.json ? [...routedArgs, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : routedArgs, io, io.stdin, env, scopeProjectRoot);
|
|
92
94
|
if (routed?.routed && !routed.inProcess) {
|
|
93
95
|
if (heartbeat)
|
|
94
96
|
clearInterval(heartbeat);
|
|
@@ -108,8 +110,9 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
108
110
|
writeProgressJsonl(io, "done", "Command finished");
|
|
109
111
|
emitHumanProgress(io, output, progress, "done");
|
|
110
112
|
const payload = attachProgress(resultWithWarnings, progress, output);
|
|
113
|
+
const visible = writeResponseFile(payload, output);
|
|
111
114
|
if (output.json)
|
|
112
|
-
writeJson(io.stdout,
|
|
115
|
+
writeJson(io.stdout, visible);
|
|
113
116
|
else
|
|
114
117
|
io.stdout.write(renderHumanResult(output.args, payload));
|
|
115
118
|
return completedCommandExitCode(payload);
|
|
@@ -124,10 +127,11 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
124
127
|
if (output.progressJsonl)
|
|
125
128
|
writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
|
|
126
129
|
const errorPayload = attachProgress(payload, progress, output);
|
|
130
|
+
const visible = writeResponseFile(errorPayload, output);
|
|
127
131
|
if (output.json && output.progressJsonl)
|
|
128
132
|
writeJson(io.stdout, errorPayload);
|
|
129
133
|
else if (output.json)
|
|
130
|
-
writeJson(io.stderr,
|
|
134
|
+
writeJson(io.stderr, visible);
|
|
131
135
|
else
|
|
132
136
|
io.stderr.write(renderHumanError(payload));
|
|
133
137
|
return isAppError(error) ? error.exitCode : 1;
|
|
@@ -189,7 +193,11 @@ function parseOutputOptions(args) {
|
|
|
189
193
|
const stripped = [];
|
|
190
194
|
let json = false;
|
|
191
195
|
let progressJsonl = false;
|
|
192
|
-
|
|
196
|
+
let responseFile = null;
|
|
197
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
198
|
+
const arg = args[index];
|
|
199
|
+
if (arg === undefined)
|
|
200
|
+
continue;
|
|
193
201
|
if (arg === "--json") {
|
|
194
202
|
json = true;
|
|
195
203
|
continue;
|
|
@@ -198,10 +206,44 @@ function parseOutputOptions(args) {
|
|
|
198
206
|
progressJsonl = true;
|
|
199
207
|
continue;
|
|
200
208
|
}
|
|
209
|
+
if (arg === "--response-file") {
|
|
210
|
+
const file = args[++index];
|
|
211
|
+
if (!file)
|
|
212
|
+
throw new AppError("usage", "--response-file requires a path", 2);
|
|
213
|
+
responseFile = file;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
201
216
|
stripped.push(arg);
|
|
202
217
|
}
|
|
218
|
+
if (responseFile && !json)
|
|
219
|
+
throw new AppError("usage", "--response-file requires --json", 2);
|
|
203
220
|
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
204
|
-
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly };
|
|
221
|
+
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
|
|
222
|
+
}
|
|
223
|
+
/** Persist the complete machine response for harnesses that cap tool output.
|
|
224
|
+
* The visible response keeps routing fields but omits the potentially large
|
|
225
|
+
* rendered prompt; an agent can safely read the named file without rerunning
|
|
226
|
+
* the lifecycle command. */
|
|
227
|
+
function writeResponseFile(payload, output) {
|
|
228
|
+
if (!output.responseFile)
|
|
229
|
+
return payload;
|
|
230
|
+
const file = path.resolve(output.responseFile);
|
|
231
|
+
try {
|
|
232
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
233
|
+
const temporary = `${file}.tmp-${process.pid}-${crypto.randomUUID()}`;
|
|
234
|
+
fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
235
|
+
fs.renameSync(temporary, file);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
// The lifecycle mutation may already be accepted. Do not retry it just
|
|
239
|
+
// because its optional full-response file could not be written.
|
|
240
|
+
return { ...(payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true, result: payload }), response_file: file, response_file_error: String(error) };
|
|
241
|
+
}
|
|
242
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
243
|
+
return { ok: true, response_file: file };
|
|
244
|
+
const visible = { ...payload, response_file: file };
|
|
245
|
+
delete visible.worker_prompt_markdown;
|
|
246
|
+
return visible;
|
|
205
247
|
}
|
|
206
248
|
function progressForCommand(args) {
|
|
207
249
|
const [family, command, subcommand] = args;
|
|
@@ -474,6 +516,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
474
516
|
if (family === "version") {
|
|
475
517
|
return getCliVersionReport();
|
|
476
518
|
}
|
|
519
|
+
if (family === "runtime") {
|
|
520
|
+
return await dispatchRuntime(context, command, parsed);
|
|
521
|
+
}
|
|
477
522
|
if (family === "canon") {
|
|
478
523
|
return dispatchCanon(context, command, parsed);
|
|
479
524
|
}
|
|
@@ -662,8 +707,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
662
707
|
throw new AppError("usage", "work repair add requires --task-stdin", 2);
|
|
663
708
|
const origins = parsed.options.get("origin-work") ?? [];
|
|
664
709
|
const findings = parsed.options.get("from-finding") ?? [];
|
|
710
|
+
const unresolved = parsed.options.get("from-unresolved") ?? [];
|
|
665
711
|
if (!origins.length && !findings.length)
|
|
666
|
-
throw new AppError("usage", "work repair add requires --from-check with --origin-work or --from-
|
|
712
|
+
throw new AppError("usage", "work repair add requires --from-check with --origin-work, --from-finding, or --from-unresolved with --origin-work", 2);
|
|
667
713
|
if (findings.length)
|
|
668
714
|
return addVnextCodeReviewRepair(context, {
|
|
669
715
|
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
@@ -672,14 +718,30 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
672
718
|
checkRefsByFinding: Object.fromEntries(findings.map((finding) => [finding, parsed.options.get("check-ref") ?? []])),
|
|
673
719
|
objective: await readStdin(io.stdin)
|
|
674
720
|
});
|
|
721
|
+
if (unresolved.length) {
|
|
722
|
+
if (!origins.length)
|
|
723
|
+
throw new AppError("usage", "semantic repair requires at least one --origin-work", 2);
|
|
724
|
+
const verificationPath = requiredOption(parsed, "verification-file");
|
|
725
|
+
return addVnextCodeRepair(context, {
|
|
726
|
+
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
727
|
+
runId: requiredOption(parsed, "run"),
|
|
728
|
+
originWorkIds: origins,
|
|
729
|
+
semanticUnresolved: unresolved,
|
|
730
|
+
verificationPath,
|
|
731
|
+
objective: await readStdin(io.stdin)
|
|
732
|
+
});
|
|
733
|
+
}
|
|
675
734
|
if (!hasOption(parsed, "from-check"))
|
|
676
735
|
throw new AppError("usage", "check repair requires --from-check", 2);
|
|
677
736
|
if (!origins.length)
|
|
678
737
|
throw new AppError("usage", "check repair requires at least one --origin-work", 2);
|
|
738
|
+
const receipts = parsed.options.get("from-check") ?? [];
|
|
739
|
+
if (receipts.length !== 1)
|
|
740
|
+
throw new AppError("usage", "check repair accepts exactly one --from-check; create one causal repair Work per failed receipt", 2);
|
|
679
741
|
return addVnextCodeRepair(context, {
|
|
680
742
|
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
681
743
|
runId: requiredOption(parsed, "run"),
|
|
682
|
-
checkReceiptId:
|
|
744
|
+
checkReceiptId: receipts[0],
|
|
683
745
|
originWorkIds: origins,
|
|
684
746
|
objective: await readStdin(io.stdin)
|
|
685
747
|
});
|
|
@@ -1519,6 +1581,42 @@ function dispatchRun(context, command, parsed) {
|
|
|
1519
1581
|
}
|
|
1520
1582
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1521
1583
|
}
|
|
1584
|
+
async function dispatchRuntime(context, command, parsed) {
|
|
1585
|
+
if (command !== "process")
|
|
1586
|
+
throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
|
|
1587
|
+
const action = requiredPosition(parsed, 0, "runtime process action");
|
|
1588
|
+
if (action === "register") {
|
|
1589
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1590
|
+
const process = registerManagedProcess(context, {
|
|
1591
|
+
kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
|
|
1592
|
+
projectId: optionalOption(parsed, "project-id") ?? null, runId: optionalOption(parsed, "run") ?? null,
|
|
1593
|
+
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
1594
|
+
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
1595
|
+
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
|
|
1596
|
+
});
|
|
1597
|
+
return { ok: true, process };
|
|
1598
|
+
}
|
|
1599
|
+
if (action === "confirm") {
|
|
1600
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1601
|
+
const processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
|
|
1602
|
+
return { ok: true, process: confirmManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(processGroupId ? { processGroupId } : {}), ...(leaseMs ? { leaseMs } : {}) }) };
|
|
1603
|
+
}
|
|
1604
|
+
if (action === "heartbeat") {
|
|
1605
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1606
|
+
return { ok: heartbeatManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs ? { leaseMs } : {}) }) };
|
|
1607
|
+
}
|
|
1608
|
+
if (action === "finish") {
|
|
1609
|
+
const state = requiredOption(parsed, "state");
|
|
1610
|
+
if (state !== "stopped" && state !== "failed")
|
|
1611
|
+
throw new AppError("validation", "--state must be stopped or failed", 2);
|
|
1612
|
+
return { ok: finishManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state: state, reason: optionalOption(parsed, "reason") ?? null }) };
|
|
1613
|
+
}
|
|
1614
|
+
if (action === "status")
|
|
1615
|
+
return { ok: true, processes: managedProcessStatus(context) };
|
|
1616
|
+
if (action === "reconcile")
|
|
1617
|
+
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalPositiveNumber(parsed, "grace-ms", true)) };
|
|
1618
|
+
throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
|
|
1619
|
+
}
|
|
1522
1620
|
function dispatchStat(context, command, parsed) {
|
|
1523
1621
|
if (command === "usage") {
|
|
1524
1622
|
return getStoredFlowRunUsage(context, {
|
|
@@ -1699,16 +1797,37 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1699
1797
|
if (command === "validate") {
|
|
1700
1798
|
const schemaDir = optionalOption(parsed, "schema-dir");
|
|
1701
1799
|
const projectRoot = optionalOption(parsed, "project-root");
|
|
1800
|
+
const file = requiredOption(parsed, "file");
|
|
1801
|
+
// A generic validation command can still bind to the exact RUN without
|
|
1802
|
+
// searching project directories: run_id is a structural field in the
|
|
1803
|
+
// submitted artifact and the database owns its materialized run_root.
|
|
1804
|
+
const explicitRunId = optionalOption(parsed, "run");
|
|
1805
|
+
const declaredRunId = explicitRunId ?? readRunId(file);
|
|
1806
|
+
const fileRunRoot = path.basename(file) === "run.json" ? path.dirname(path.resolve(file)) : undefined;
|
|
1807
|
+
const bound = projectRoot
|
|
1808
|
+
? context.db.get("SELECT r.id, r.run_root FROM runs r JOIN projects p ON p.id = r.project_id WHERE p.root = ? AND (r.id = ? OR r.run_root = ?) LIMIT 1", [fs.realpathSync(projectRoot), declaredRunId ?? "", fileRunRoot ?? ""])
|
|
1809
|
+
: undefined;
|
|
1702
1810
|
return validateSchema({
|
|
1703
1811
|
schemaName: requiredOption(parsed, "schema"),
|
|
1704
|
-
file
|
|
1812
|
+
file,
|
|
1705
1813
|
...(schemaDir ? { schemaDir } : {}),
|
|
1706
1814
|
...(projectRoot ? { projectRoot } : {}),
|
|
1815
|
+
...(declaredRunId || bound?.id ? { runId: declaredRunId ?? bound.id } : {}),
|
|
1816
|
+
...(bound?.run_root ? { runRoot: bound.run_root } : {}),
|
|
1707
1817
|
ddFlowHome: context.ddFlowHome
|
|
1708
1818
|
});
|
|
1709
1819
|
}
|
|
1710
1820
|
throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
|
|
1711
1821
|
}
|
|
1822
|
+
function readRunId(file) {
|
|
1823
|
+
try {
|
|
1824
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
1825
|
+
return typeof value.run_id === "string" && value.run_id.trim() ? value.run_id : undefined;
|
|
1826
|
+
}
|
|
1827
|
+
catch {
|
|
1828
|
+
return undefined;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1712
1831
|
async function dispatchLane(context, command, parsed) {
|
|
1713
1832
|
if (command === "status") {
|
|
1714
1833
|
return getLaneStatus(context, { projectRoot: requiredOption(parsed, "project-root"), lane: optionalOption(parsed, "lane") });
|
package/dist/runtime/context.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getDatabase } from "../storage/database.js";
|
|
2
2
|
import { resolveDdFlowHome } from "../storage/paths.js";
|
|
3
3
|
export function createRouterContext(env) {
|
|
4
|
+
const ddFlowHome = resolveDdFlowHome(env);
|
|
4
5
|
return {
|
|
5
|
-
ddFlowHome
|
|
6
|
+
ddFlowHome,
|
|
7
|
+
db: getDatabase(ddFlowHome, "read_existing"),
|
|
6
8
|
now: () => new Date().toISOString()
|
|
7
9
|
};
|
|
8
10
|
}
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"verdict": {"enum": ["pass", "findings", "blocked"]},
|
|
10
10
|
"summary": {"type": "string", "minLength": 1},
|
|
11
11
|
"aspects": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["aspect_id", "verdict", "summary", "evidence_refs"], "properties": {"aspect_id": {"type": "string", "minLength": 1}, "verdict": {"enum": ["pass", "findings", "blocked"]}, "summary": {"type": "string", "minLength": 1}, "evidence_refs": {"type": "array", "items": {"type": "string"}}}}},
|
|
12
|
-
"findings": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["finding_id", "aspect_id", "priority", "problem", "impact", "evidence_refs", "obligation_refs"], "properties": {"finding_id": {"type": "string", "pattern": "^FIND-[0-9]{3}$"}, "aspect_id": {"type": "string", "minLength": 1}, "priority": {"enum": ["p0", "p1", "p2", "p3"]}, "problem": {"type": "string", "minLength": 1}, "impact": {"type": "string", "minLength": 1}, "evidence_refs": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, "obligation_refs": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}}}}
|
|
12
|
+
"findings": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["finding_id", "aspect_id", "priority", "problem", "impact", "required_outcome", "evidence_refs", "obligation_refs"], "properties": {"finding_id": {"type": "string", "pattern": "^FIND-[0-9]{3}$"}, "aspect_id": {"type": "string", "minLength": 1}, "priority": {"enum": ["p0", "p1", "p2", "p3"]}, "problem": {"type": "string", "minLength": 1}, "impact": {"type": "string", "minLength": 1}, "required_outcome": {"type": "string", "minLength": 1}, "evidence_refs": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, "obligation_refs": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}}}}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
"spine": {"type": "object", "additionalProperties": false, "required": ["user_outcome", "component_responsibility", "must_preserve", "non_goals", "acceptance_contribution"], "properties": {"user_outcome": {"type": "string", "minLength": 1}, "component_responsibility": {"type": "string", "minLength": 1}, "must_preserve": {"$ref": "#/$defs/strings"}, "non_goals": {"$ref": "#/$defs/stringList"}, "acceptance_contribution": {"type": "string", "minLength": 1}}},
|
|
17
17
|
"obligation": {"type": "object", "additionalProperties": false, "required": ["id", "statement"], "properties": {"id": {"type": "string", "minLength": 1}, "statement": {"type": "string", "minLength": 1}}},
|
|
18
18
|
"acceptance": {"type": "object", "additionalProperties": false, "required": ["criterion_id", "path", "environment", "fixtures", "cleanup", "check_refs", "expected_evidence", "proof_limits", "gate"], "properties": {"criterion_id": {"type": "string", "pattern": "^AC-[0-9]+$"}, "path": {"type": "string", "minLength": 1}, "environment": {"type": "string", "minLength": 1}, "fixtures": {"$ref": "#/$defs/stringList"}, "cleanup": {"type": "string", "minLength": 1}, "check_refs": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "expected_evidence": {"$ref": "#/$defs/strings"}, "proof_limits": {"$ref": "#/$defs/strings"}, "gate": {"type": "string", "minLength": 1}}},
|
|
19
|
-
"check": {"type": "object", "additionalProperties": false, "required": ["id", "command", "purpose", "run_at", "availability"], "properties": {"id": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}, "command": {"type": "string", "minLength": 1}, "purpose": {"type": "string", "minLength": 1}, "run_at": {"enum": ["work", "code", "readiness", "merge", "release", "external"]}, "availability": {"enum": ["available", "planned"]}, "provided_by": {"type": "string", "minLength": 1}, "definition": {"type": "string", "minLength": 1}, "required_artifacts": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}, "allOf": [{"if": {"properties": {"availability": {"const": "planned"}}, "required": ["availability"]}, "then": {"required": ["provided_by", "definition"], "properties": {"command": {"pattern": "^@check/"}}}}]},
|
|
19
|
+
"check": {"type": "object", "additionalProperties": false, "required": ["id", "command", "purpose", "run_at", "availability"], "properties": {"id": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}, "command": {"type": "string", "minLength": 1}, "purpose": {"type": "string", "minLength": 1}, "run_at": {"enum": ["work", "code", "readiness", "merge", "release", "external"]}, "availability": {"enum": ["available", "planned"]}, "provided_by": {"type": "string", "minLength": 1}, "definition": {"type": "string", "minLength": 1}, "required_artifacts": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "ports": {"type": "array", "uniqueItems": true, "items": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]*$"}}}, "allOf": [{"if": {"properties": {"availability": {"const": "planned"}}, "required": ["availability"]}, "then": {"required": ["provided_by", "definition"], "properties": {"command": {"pattern": "^@check/"}}}}]},
|
|
20
20
|
"documentUpdate": {"type": "object", "additionalProperties": false, "required": ["path", "action", "owner", "reason", "baseline_sha256"], "properties": {"path": {"type": "string", "minLength": 1}, "action": {"enum": ["create", "update"]}, "owner": {"type": "string", "minLength": 1}, "reason": {"type": "string", "minLength": 1}, "baseline_sha256": {"type": ["string", "null"], "pattern": "^[a-f0-9]{64}$"}}},
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"reviewFinding": {"type": "object", "additionalProperties": false, "required": ["finding_ref", "priority", "problem", "impact", "required_outcome", "evidence_refs", "obligation_refs", "decision_reason", "check_refs"], "properties": {"finding_ref": {"type": "string", "minLength": 1}, "priority": {"enum": ["p0", "p1", "p2", "p3"]}, "problem": {"type": "string", "minLength": 1}, "impact": {"type": "string", "minLength": 1}, "required_outcome": {"type": "string", "minLength": 1}, "evidence_refs": {"$ref": "#/$defs/strings"}, "obligation_refs": {"$ref": "#/$defs/strings"}, "decision_reason": {"type": "string", "minLength": 1}, "check_refs": {"$ref": "#/$defs/strings"}}},
|
|
22
|
+
"repair": {"type": "object", "additionalProperties": false, "required": ["origin_work_ids"], "anyOf": [{"required": ["check_receipt_id", "failure_receipt_path"]}, {"required": ["review_findings"]}, {"required": ["semantic_unresolved", "verification_path"]}], "properties": {"origin_work_ids": {"$ref": "#/$defs/strings"}, "check_receipt_id": {"type": "string", "minLength": 1}, "failure_receipt_path": {"type": "string", "minLength": 1}, "review_findings": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/reviewFinding"}}, "semantic_unresolved": {"$ref": "#/$defs/strings"}, "verification_path": {"type": "string", "minLength": 1}}},
|
|
23
|
+
"work": {"type": "object", "additionalProperties": false, "required": ["schema_id", "key", "launch_policy", "source", "task", "semantic_spine", "requirements", "acceptance", "document_updates", "required_read", "discovery_boundary", "planned_write_areas", "checks", "provides_checks", "stop_conditions", "depends_on", "result_schema"], "properties": {"schema_id": {"const": "dd-flow/code-work-packet@5"}, "key": {"type": "string", "minLength": 1}, "launch_policy": {"const": "fresh_agent_required"}, "source": {"$ref": "#/$defs/source"}, "repair": {"$ref": "#/$defs/repair"}, "task": {"type": "string", "minLength": 1}, "semantic_spine": {"$ref": "#/$defs/spine"}, "requirements": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/obligation"}}, "acceptance": {"type": "array", "items": {"$ref": "#/$defs/acceptance"}}, "document_updates": {"type": "array", "items": {"$ref": "#/$defs/documentUpdate"}}, "required_read": {"$ref": "#/$defs/strings", "description": "Mandatory starting sources, not a read allowlist."}, "discovery_boundary": {"$ref": "#/$defs/strings", "description": "Likely discovery areas; additional project-local reads are allowed."}, "planned_write_areas": {"type": "array", "items": {"type": "string", "minLength": 1}, "description": "Soft coordination hints only. A worker may change any necessary project file inside the RUN workspace."}, "checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "provides_checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "stop_conditions": {"$ref": "#/$defs/strings", "description": "Hard semantic or external stop conditions; path prediction drift is not a stop condition."}, "depends_on": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "result_schema": {"const": "dd-flow/code-work-result@2"}}}
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
"evidence": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["criterion_id", "refs"], "properties": {"criterion_id": {"type": "string", "minLength": 1}, "refs": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}}},
|
|
12
12
|
"deviations": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
|
13
13
|
"blockers": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
|
14
|
-
"
|
|
14
|
+
"resolutions": {"type": "array", "uniqueItems": true, "items": {"type": "object", "additionalProperties": false, "required": ["finding_ref", "summary", "evidence_refs"], "properties": {"finding_ref": {"type": "string", "minLength": 1}, "summary": {"type": "string", "minLength": 1}, "evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}}}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "dd-flow/harness-config@1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"required": ["schema_id", "harnesses"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema_id": { "const": "dd-flow/harness-config@1" },
|
|
9
|
+
"harnesses": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": ["adapter_command", "runtime_command"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"adapter_command": { "type": "string", "minLength": 1 },
|
|
17
|
+
"runtime_command": { "type": "string", "minLength": 1 },
|
|
18
|
+
"allow_path_discovery": { "type": "boolean" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"required": ["schema_id", "outcome", "summary", "finding_decisions", "correction"],
|
|
7
7
|
"properties": {
|
|
8
8
|
"schema_id": {"const": "dd-flow/plan-review-decision@3"},
|
|
9
|
-
"outcome": {"enum": ["accepted", "
|
|
9
|
+
"outcome": {"enum": ["accepted", "failed", "cancelled"]},
|
|
10
10
|
"summary": {"type": "string", "minLength": 1},
|
|
11
11
|
"finding_decisions": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["finding_ref", "decision", "reason"], "properties": {"finding_ref": {"type": "string", "pattern": "^WRK-[^/]+/FIND-[0-9]{3}$"}, "decision": {"enum": ["accepted_fix", "rejected", "deferred_as_DEF", "requires_user", "duplicate"]}, "reason": {"type": "string", "minLength": 1}}}},
|
|
12
12
|
"correction": {"type": "object", "additionalProperties": false, "required": ["status", "previous_plan_revision", "changed_paths", "summary"], "properties": {"status": {"enum": ["applied", "not_required"]}, "previous_plan_revision": {"type": "integer", "minimum": 1}, "changed_paths": {"type": "array", "items": {"type": "string", "minLength": 1}}, "summary": {"type": "string", "minLength": 1}}}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"documentUpdate": {"type": "object", "additionalProperties": false, "required": ["path", "action", "owner", "reason"], "properties": {"path": {"type": "string", "minLength": 1}, "action": {"enum": ["create", "update"]}, "owner": {"type": "string", "minLength": 1}, "reason": {"type": "string", "minLength": 1}}},
|
|
30
30
|
"spine": {"type": "object", "additionalProperties": false, "required": ["user_outcome", "component_responsibility", "must_preserve", "non_goals", "acceptance_contribution"], "properties": {"user_outcome": {"type": "string", "minLength": 1}, "component_responsibility": {"type": "string", "minLength": 1}, "must_preserve": {"$ref": "#/$defs/strings"}, "non_goals": {"type": "array", "items": {"type": "string", "minLength": 1}}, "acceptance_contribution": {"type": "string", "minLength": 1}}},
|
|
31
31
|
"execution": {"type": "object", "additionalProperties": false, "required": ["required_read", "discovery_boundary", "planned_write_areas", "stop_conditions"], "properties": {"required_read": {"$ref": "#/$defs/strings", "description": "Mandatory starting sources. This is not a read allowlist."}, "discovery_boundary": {"$ref": "#/$defs/strings", "description": "Likely discovery areas. A worker may inspect other project-local sources when necessary."}, "planned_write_areas": {"type": "array", "items": {"type": "string", "minLength": 1}, "description": "Optional file or directory hints used only to coordinate concurrent Works. They never grant or deny write permission."}, "stop_conditions": {"$ref": "#/$defs/strings", "description": "Semantic contradictions or external blockers that require the worker to stop."}}},
|
|
32
|
-
"check": {"type": "object", "additionalProperties": false, "required": ["id", "command", "purpose", "run_at", "availability"], "properties": {"id": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}, "command": {"type": "string", "minLength": 1}, "purpose": {"type": "string", "minLength": 1}, "run_at": {"enum": ["work", "code", "readiness", "merge", "release", "external"]}, "availability": {"enum": ["available", "planned"]}, "provided_by": {"type": "string", "pattern": "^P[0-9]+$"}, "definition": {"type": "string", "minLength": 1}, "required_artifacts": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}, "allOf": [{"if": {"properties": {"availability": {"const": "planned"}}, "required": ["availability"]}, "then": {"required": ["provided_by", "definition"], "properties": {"command": {"pattern": "^@check/"}}}}, {"if": {"properties": {"command": {"pattern": "^@check/"}}}, "then": {"required": ["definition"]}}]},
|
|
32
|
+
"check": {"type": "object", "additionalProperties": false, "required": ["id", "command", "purpose", "run_at", "availability"], "properties": {"id": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}, "command": {"type": "string", "minLength": 1}, "purpose": {"type": "string", "minLength": 1}, "run_at": {"enum": ["work", "code", "readiness", "merge", "release", "external"]}, "availability": {"enum": ["available", "planned"]}, "provided_by": {"type": "string", "pattern": "^P[0-9]+$"}, "definition": {"type": "string", "minLength": 1}, "required_artifacts": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "ports": {"type": "array", "uniqueItems": true, "items": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]*$"}}}, "allOf": [{"if": {"properties": {"availability": {"const": "planned"}}, "required": ["availability"]}, "then": {"required": ["provided_by", "definition"], "properties": {"command": {"pattern": "^@check/"}}}}, {"if": {"properties": {"command": {"pattern": "^@check/"}}}, "then": {"required": ["definition"]}}]},
|
|
33
33
|
"verification": {"type": "object", "additionalProperties": false, "required": ["check_refs"], "properties": {"check_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}}}},
|
|
34
34
|
"item": {"type": "object", "additionalProperties": false, "required": ["id", "title", "summary", "details", "depends_on", "requirement_refs", "semantic_spine", "execution_context", "verification"], "properties": {"id": {"type": "string", "pattern": "^P[0-9]+$"}, "title": {"type": "string", "minLength": 1}, "summary": {"type": "string", "minLength": 1}, "details": {"type": "string", "minLength": 1}, "depends_on": {"type": "array", "uniqueItems": true, "items": {"type": "string", "pattern": "^P[0-9]+$"}}, "requirement_refs": {"$ref": "#/$defs/strings"}, "semantic_spine": {"$ref": "#/$defs/spine"}, "execution_context": {"$ref": "#/$defs/execution"}, "verification": {"$ref": "#/$defs/verification"}}},
|
|
35
35
|
"acceptance": {"type": "object", "additionalProperties": false, "required": ["criterion_id", "plan_item_ids", "changed_surfaces", "path", "environment", "fixtures", "cleanup", "check_refs", "expected_evidence", "proof_limits", "gate"], "properties": {"criterion_id": {"type": "string", "pattern": "^AC-[0-9]+$"}, "plan_item_ids": {"type": "array", "minItems": 1, "items": {"type": "string", "pattern": "^P[0-9]+$"}}, "changed_surfaces": {"$ref": "#/$defs/strings"}, "path": {"type": "string", "minLength": 1}, "environment": {"type": "string", "minLength": 1}, "fixtures": {"type": "array", "items": {"type": "string", "minLength": 1}}, "cleanup": {"type": "string", "minLength": 1}, "check_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}}, "expected_evidence": {"$ref": "#/$defs/strings"}, "proof_limits": {"$ref": "#/$defs/strings"}, "gate": {"enum": ["work", "code", "readiness", "merge", "release", "external"]}}}
|
package/dist/services/cleanup.js
CHANGED
|
@@ -8,6 +8,7 @@ import { projectCheckoutRoot, resolveProjectRoot } from "../storage/paths.js";
|
|
|
8
8
|
import { appendAudit } from "./audit.js";
|
|
9
9
|
import { requireProjectByRoot } from "./projects.js";
|
|
10
10
|
import { protocolRunDiagnostics, readProtocolRuntimeState, requireProtocol } from "./protocols.js";
|
|
11
|
+
import { publicSessionIdentity } from "./session-identity.js";
|
|
11
12
|
const actionKinds = new Set([
|
|
12
13
|
"repair_protocol_state",
|
|
13
14
|
"cancel_queue_job",
|
|
@@ -68,7 +69,7 @@ export function cleanupScan(context, input) {
|
|
|
68
69
|
action: { kind: "cancel_queue_job", project_id: project.id, protocol_id: job.protocol_id }
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
|
-
const staleSessions = context.db.all(`SELECT session_id, protocol_id, workspace_path FROM sessions
|
|
72
|
+
const staleSessions = context.db.all(`SELECT session_id, harness, provider_session_id, protocol_id, workspace_path FROM sessions
|
|
72
73
|
WHERE project_id = ? AND status IN ('pending', 'active', 'waiting_user', 'blocked', 'stopping')
|
|
73
74
|
ORDER BY updated_at ASC, session_id ASC`, [project.id]);
|
|
74
75
|
for (const session of staleSessions) {
|
|
@@ -79,11 +80,11 @@ export function cleanupScan(context, input) {
|
|
|
79
80
|
const terminalProtocol = Boolean(protocol && (["closed", "cancelled"].includes(protocol.status) || ["closed", "cancelled"].includes(protocol.stage)));
|
|
80
81
|
if (missingWorkspace || terminalProtocol) {
|
|
81
82
|
pushFinding(findings, actions, "stale_flow_session", "warning", {
|
|
82
|
-
|
|
83
|
+
session: publicSessionIdentity(session),
|
|
83
84
|
protocol_id: session.protocol_id,
|
|
84
85
|
missing_workspace: missingWorkspace,
|
|
85
86
|
terminal_protocol: terminalProtocol,
|
|
86
|
-
action: { kind: "close_flow_session", project_id: project.id,
|
|
87
|
+
action: { kind: "close_flow_session", project_id: project.id, session: publicSessionIdentity(session) }
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
}
|
|
@@ -241,6 +242,12 @@ function validateCleanupPlan(value, project) {
|
|
|
241
242
|
actual_project_id: action.project_id
|
|
242
243
|
});
|
|
243
244
|
}
|
|
245
|
+
if (action.kind === "close_flow_session") {
|
|
246
|
+
const session = action.session;
|
|
247
|
+
if (!session || typeof session.harness_id !== "string" || !session.harness_id || typeof session.session_id !== "string" || !session.session_id) {
|
|
248
|
+
throw new AppError("validation", "close_flow_session requires a native session identity", 2);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
244
251
|
return action;
|
|
245
252
|
});
|
|
246
253
|
return { project_root: project.root, project_id: project.id, actions };
|
|
@@ -305,20 +312,23 @@ function applyAction(context, action, reason, force, now) {
|
|
|
305
312
|
});
|
|
306
313
|
return { kind: action.kind, changed: true, protocol_id: action.protocol_id };
|
|
307
314
|
}
|
|
308
|
-
if (action.kind === "close_flow_session" && action.
|
|
315
|
+
if (action.kind === "close_flow_session" && action.session) {
|
|
316
|
+
const stored = context.db.get("SELECT session_id FROM sessions WHERE project_id = ? AND harness = ? AND COALESCE(provider_session_id, session_id) = ?", [action.project_id, action.session.harness_id, action.session.session_id]);
|
|
317
|
+
if (!stored)
|
|
318
|
+
return { kind: action.kind, changed: false, skipped: true, reason: "session_not_found", session: action.session };
|
|
309
319
|
const result = context.db.run(`UPDATE sessions SET status = 'stopped', stop_reason = ?, updated_at = ?, stopped_at = ?
|
|
310
|
-
WHERE project_id = ? AND session_id = ? AND status IN ('pending', 'active', 'waiting_user', 'blocked', 'stopping')`, [reason, now, now, action.project_id,
|
|
320
|
+
WHERE project_id = ? AND session_id = ? AND status IN ('pending', 'active', 'waiting_user', 'blocked', 'stopping')`, [reason, now, now, action.project_id, stored.session_id]);
|
|
311
321
|
if (result.changes !== 1) {
|
|
312
|
-
return { kind: action.kind, changed: false, skipped: true, reason: "already_stopped",
|
|
322
|
+
return { kind: action.kind, changed: false, skipped: true, reason: "already_stopped", session: action.session };
|
|
313
323
|
}
|
|
314
324
|
appendAudit(context, {
|
|
315
325
|
projectId: action.project_id,
|
|
316
326
|
eventType: "flow_session.stopped",
|
|
317
327
|
reason,
|
|
318
328
|
forced: force,
|
|
319
|
-
payload: { session_id:
|
|
329
|
+
payload: { session_id: stored.session_id, source: "cleanup.apply" }
|
|
320
330
|
});
|
|
321
|
-
return { kind: action.kind, changed: true,
|
|
331
|
+
return { kind: action.kind, changed: true, session: action.session };
|
|
322
332
|
}
|
|
323
333
|
if (action.kind === "discard_stale_run" && action.run_id) {
|
|
324
334
|
const run = context.db.get("SELECT status, runtime_path, run_dir, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [action.project_id, action.run_id]);
|