@deksden-com/dd-flow-cli 0.1.0 → 0.3.0
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/README.md +13 -0
- package/dist/build-info.json +15 -0
- package/dist/cli/help.js +133 -14
- package/dist/cli/run-cli.js +242 -8
- package/dist/schemas/archived-flow-manifest.schema.json +112 -0
- package/dist/schemas/compatibility.schema.json +26 -0
- package/dist/schemas/flow-guidance.schema.json +73 -0
- package/dist/schemas/flow-run-index.schema.json +30 -4
- package/dist/schemas/global-dashboard-data.schema.json +68 -0
- package/dist/schemas/mb-sdlc-review-report.schema.json +242 -0
- package/dist/schemas/merge-stage-report.schema.json +61 -1
- package/dist/schemas/plan-stage-report.schema.json +255 -0
- package/dist/schemas/project-dashboard-data.schema.json +98 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +127 -0
- package/dist/schemas/protocol-dashboard-data.schema.json +89 -0
- package/dist/schemas/status-report.schema.json +186 -0
- package/dist/schemas/version-report.schema.json +22 -0
- package/dist/services/build-info.js +114 -0
- package/dist/services/canon.js +298 -0
- package/dist/services/cleanup.js +14 -1
- package/dist/services/config.js +25 -0
- package/dist/services/dashboard.js +655 -10
- package/dist/services/flow-guidance.js +214 -0
- package/dist/services/ids.js +106 -0
- package/dist/services/lanes.js +6 -2
- package/dist/services/merge-queue.js +68 -4
- package/dist/services/merge-worker.js +177 -0
- package/dist/services/projects.js +7 -1
- package/dist/services/protocols.js +648 -17
- package/dist/services/runs.js +98 -21
- package/dist/services/schema-validation.js +88 -9
- package/dist/services/sessions.js +3 -2
- package/dist/services/status.js +306 -0
- package/dist/services/version-status.js +284 -0
- package/dist/storage/database.js +13 -0
- package/dist/storage/paths.js +21 -0
- package/package.json +2 -2
package/dist/services/runs.js
CHANGED
|
@@ -2,12 +2,16 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { formatFullId, isFullEntityId, isShortEntityId, parseFullEntityId } from "../domain/entity-ids.js";
|
|
4
4
|
import { AppError } from "../shared/errors.js";
|
|
5
|
-
import { ensureDir,
|
|
5
|
+
import { ensureDir, projectRunHome, projectRunIndexPath, projectRunJsonPath, resolveProjectRoot } from "../storage/paths.js";
|
|
6
6
|
import { appendAudit } from "./audit.js";
|
|
7
7
|
import { registerProject, requireProjectByRoot } from "./projects.js";
|
|
8
|
-
|
|
8
|
+
import { buildRunFlowGuidance } from "./flow-guidance.js";
|
|
9
|
+
import { readProtocolRuntimeState, requireProtocol } from "./protocols.js";
|
|
10
|
+
const runSchemaId = "dd-flow/flow-run-index@2";
|
|
11
|
+
const legacyRunSchemaId = "dd-flow/flow-run-index@1";
|
|
9
12
|
const runIdType = "RUN";
|
|
10
13
|
const allowedRunFlowKinds = [
|
|
14
|
+
"mb_sdlc",
|
|
11
15
|
"coding",
|
|
12
16
|
"experiment",
|
|
13
17
|
"mb-init",
|
|
@@ -15,6 +19,8 @@ const allowedRunFlowKinds = [
|
|
|
15
19
|
"mb-audit",
|
|
16
20
|
"mb-distill",
|
|
17
21
|
"mb-upgrade-review",
|
|
22
|
+
"mb-sdlc-review",
|
|
23
|
+
"review",
|
|
18
24
|
"custom"
|
|
19
25
|
];
|
|
20
26
|
export function startFlowRun(context, input) {
|
|
@@ -27,10 +33,10 @@ export function startFlowRun(context, input) {
|
|
|
27
33
|
const runId = nextRunId(context, slug);
|
|
28
34
|
const { shortId } = parseFullEntityId(runId);
|
|
29
35
|
const now = context.now();
|
|
30
|
-
const
|
|
31
|
-
const runIndexAbsolute =
|
|
32
|
-
const runDirRelative = path.
|
|
33
|
-
const runtimePath =
|
|
36
|
+
const runHome = projectRunHome(context.ddFlowHome, project.id, runId);
|
|
37
|
+
const runIndexAbsolute = projectRunIndexPath(context.ddFlowHome, project.id, runId);
|
|
38
|
+
const runDirRelative = path.posix.join("runs", runId);
|
|
39
|
+
const runtimePath = projectRunJsonPath(context.ddFlowHome, project.id, runId);
|
|
34
40
|
const index = {
|
|
35
41
|
schema_id: runSchemaId,
|
|
36
42
|
run_id: runId,
|
|
@@ -48,6 +54,18 @@ export function startFlowRun(context, input) {
|
|
|
48
54
|
root: workspaceRoot,
|
|
49
55
|
run_dir: runDirRelative
|
|
50
56
|
},
|
|
57
|
+
run_home: {
|
|
58
|
+
storage: "dd_flow_home",
|
|
59
|
+
path: runHome,
|
|
60
|
+
relative_path: runDirRelative
|
|
61
|
+
},
|
|
62
|
+
execution: {
|
|
63
|
+
project_root: project.root,
|
|
64
|
+
workspace_root: workspaceRoot
|
|
65
|
+
},
|
|
66
|
+
legacy: {
|
|
67
|
+
project_tasks_run_dir: null
|
|
68
|
+
},
|
|
51
69
|
stage_runs: [],
|
|
52
70
|
sessions: [],
|
|
53
71
|
artifacts: [],
|
|
@@ -58,13 +76,14 @@ export function startFlowRun(context, input) {
|
|
|
58
76
|
updated_at: now
|
|
59
77
|
};
|
|
60
78
|
ensureDir(path.dirname(runtimePath));
|
|
61
|
-
ensureDir(
|
|
79
|
+
ensureDir(runHome);
|
|
62
80
|
writeJsonFile(runtimePath, index);
|
|
63
81
|
writeJsonFile(runIndexAbsolute, index);
|
|
64
82
|
context.db.run(`INSERT INTO flow_runs
|
|
65
83
|
(id, short_id, slug, project_id, project_root, workspace_root, flow_kind, subject_type, subject_id,
|
|
66
|
-
status, verdict, next_action, runtime_path, run_dir, run_index_path,
|
|
67
|
-
|
|
84
|
+
status, verdict, next_action, runtime_path, run_dir, run_index_path, run_home_path, layout_version, artifact_root_kind,
|
|
85
|
+
index_json, created_at, updated_at, completed_at)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`, [
|
|
68
87
|
runId,
|
|
69
88
|
shortId,
|
|
70
89
|
slug,
|
|
@@ -78,8 +97,11 @@ export function startFlowRun(context, input) {
|
|
|
78
97
|
index.verdict,
|
|
79
98
|
index.next_action,
|
|
80
99
|
runtimePath,
|
|
81
|
-
|
|
100
|
+
runHome,
|
|
82
101
|
runIndexAbsolute,
|
|
102
|
+
runHome,
|
|
103
|
+
"home_run_v2",
|
|
104
|
+
"dd_flow_home",
|
|
83
105
|
JSON.stringify(index),
|
|
84
106
|
now,
|
|
85
107
|
now
|
|
@@ -87,14 +109,15 @@ export function startFlowRun(context, input) {
|
|
|
87
109
|
appendAudit(context, {
|
|
88
110
|
projectId: project.id,
|
|
89
111
|
eventType: "flow_run.started",
|
|
90
|
-
payload: { run_id: runId, flow_kind: flowKind, subject: index.subject, workspace_root: workspaceRoot }
|
|
112
|
+
payload: { run_id: runId, flow_kind: flowKind, subject: index.subject, workspace_root: workspaceRoot, run_home: runHome }
|
|
91
113
|
});
|
|
92
114
|
return { ok: true, run: flowRunSummary(requireRunById(context, project.id, runId)), index };
|
|
93
115
|
}
|
|
94
116
|
export function getFlowRunStatus(context, input) {
|
|
95
117
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
96
118
|
const run = resolveRun(context, project.id, input.runId);
|
|
97
|
-
|
|
119
|
+
const index = parseRunIndex(run.index_json);
|
|
120
|
+
return { ok: true, run: flowRunSummary(run), index, flow_guidance: guidanceForRun(context, run, index) };
|
|
98
121
|
}
|
|
99
122
|
export function listFlowRuns(context, input) {
|
|
100
123
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
@@ -114,6 +137,9 @@ export function attachFlowRunStage(context, input) {
|
|
|
114
137
|
const dir = requiredStageDir(input.dir);
|
|
115
138
|
const status = parseStageStatus(input.status);
|
|
116
139
|
const existing = index.stage_runs.find((item) => item.stage === stage);
|
|
140
|
+
if (status === "running" && existing) {
|
|
141
|
+
archiveExistingStageAttempt(runArtifactRoot(run), dir);
|
|
142
|
+
}
|
|
117
143
|
const stageRun = {
|
|
118
144
|
...(existing ?? { order: index.stage_runs.length + 1 }),
|
|
119
145
|
stage,
|
|
@@ -130,7 +156,8 @@ export function attachFlowRunStage(context, input) {
|
|
|
130
156
|
eventType: "flow_run.stage_attached",
|
|
131
157
|
payload: { run_id: run.id, stage, dir, status }
|
|
132
158
|
});
|
|
133
|
-
|
|
159
|
+
const updatedRun = requireRunById(context, project.id, run.id);
|
|
160
|
+
return { ok: true, run: flowRunSummary(updatedRun), stage_run: stageRun, index, flow_guidance: guidanceForRun(context, updatedRun, index) };
|
|
134
161
|
}
|
|
135
162
|
export function completeFlowRunStage(context, input) {
|
|
136
163
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
@@ -161,7 +188,8 @@ export function completeFlowRunStage(context, input) {
|
|
|
161
188
|
eventType: "flow_run.stage_completed",
|
|
162
189
|
payload: { run_id: run.id, stage, status, stage_report: input.stageReport ?? null, data: input.data ?? null }
|
|
163
190
|
});
|
|
164
|
-
|
|
191
|
+
const updatedRun = requireRunById(context, project.id, run.id);
|
|
192
|
+
return { ok: true, run: flowRunSummary(updatedRun), stage_run: stageRun, index, flow_guidance: guidanceForRun(context, updatedRun, index) };
|
|
165
193
|
}
|
|
166
194
|
export function completeFlowRun(context, input) {
|
|
167
195
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
@@ -182,11 +210,32 @@ export function completeFlowRun(context, input) {
|
|
|
182
210
|
eventType: "flow_run.completed",
|
|
183
211
|
payload: { run_id: run.id, status, verdict: index.verdict, next_action: index.next_action }
|
|
184
212
|
});
|
|
185
|
-
|
|
213
|
+
const updatedRun = requireRunById(context, project.id, run.id);
|
|
214
|
+
return { ok: true, run: flowRunSummary(updatedRun), index, flow_guidance: guidanceForRun(context, updatedRun, index) };
|
|
215
|
+
}
|
|
216
|
+
function guidanceForRun(context, run, index) {
|
|
217
|
+
if (run.subject_type === "protocol") {
|
|
218
|
+
try {
|
|
219
|
+
const protocol = requireProtocol(context, run.subject_id);
|
|
220
|
+
const state = readProtocolRuntimeState(context, protocol).state;
|
|
221
|
+
return buildRunFlowGuidance({
|
|
222
|
+
stageRuns: index.stage_runs,
|
|
223
|
+
protocolStage: state.stage,
|
|
224
|
+
...(state.flow_contract ? { contract: state.flow_contract } : {}),
|
|
225
|
+
runId: run.id,
|
|
226
|
+
runDir: index.run_home?.relative_path ?? index.workspace.run_dir
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (!(error instanceof AppError && error.code === "not_found"))
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return buildRunFlowGuidance({ stageRuns: index.stage_runs, runId: run.id, runDir: index.run_home?.relative_path ?? index.workspace.run_dir });
|
|
186
235
|
}
|
|
187
236
|
function persistRunIndex(context, project, run, index) {
|
|
188
|
-
const runtimePath =
|
|
189
|
-
const runIndexPath =
|
|
237
|
+
const runtimePath = run.runtime_path || projectRunJsonPath(context.ddFlowHome, project.id, run.id);
|
|
238
|
+
const runIndexPath = run.run_index_path || projectRunIndexPath(context.ddFlowHome, project.id, run.id);
|
|
190
239
|
ensureDir(path.dirname(runtimePath));
|
|
191
240
|
ensureDir(path.dirname(runIndexPath));
|
|
192
241
|
writeJsonFile(runtimePath, index);
|
|
@@ -281,6 +330,9 @@ function flowRunSummary(run) {
|
|
|
281
330
|
runtime_path: run.runtime_path,
|
|
282
331
|
run_dir: run.run_dir,
|
|
283
332
|
run_index_path: run.run_index_path,
|
|
333
|
+
run_home_path: run.run_home_path ?? null,
|
|
334
|
+
layout_version: run.layout_version ?? null,
|
|
335
|
+
artifact_root_kind: run.artifact_root_kind ?? null,
|
|
284
336
|
created_at: run.created_at,
|
|
285
337
|
updated_at: run.updated_at,
|
|
286
338
|
completed_at: run.completed_at
|
|
@@ -288,19 +340,20 @@ function flowRunSummary(run) {
|
|
|
288
340
|
}
|
|
289
341
|
function parseRunIndex(text) {
|
|
290
342
|
const index = JSON.parse(text);
|
|
291
|
-
if (index.schema_id !== runSchemaId) {
|
|
343
|
+
if (index.schema_id !== runSchemaId && index.schema_id !== legacyRunSchemaId) {
|
|
292
344
|
throw new AppError("validation", `Invalid run index schema: ${String(index.schema_id)}`, 2);
|
|
293
345
|
}
|
|
294
346
|
return index;
|
|
295
347
|
}
|
|
296
348
|
function parseRunFlowKind(value) {
|
|
297
|
-
|
|
349
|
+
const normalized = value === "mb-sdlc" ? "mb_sdlc" : value;
|
|
350
|
+
if (!allowedRunFlowKinds.includes(normalized)) {
|
|
298
351
|
throw new AppError("validation", "flow-kind is not supported for run start", 2, {
|
|
299
352
|
flow_kind: value,
|
|
300
353
|
allowed: allowedRunFlowKinds
|
|
301
354
|
});
|
|
302
355
|
}
|
|
303
|
-
return
|
|
356
|
+
return normalized;
|
|
304
357
|
}
|
|
305
358
|
function parseRunStatus(value) {
|
|
306
359
|
if (!["running", "done", "blocked", "cancelled", "failed"].includes(value)) {
|
|
@@ -354,6 +407,30 @@ function resolveWorkspaceRoot(workspaceRoot) {
|
|
|
354
407
|
}
|
|
355
408
|
return fs.realpathSync(absolute);
|
|
356
409
|
}
|
|
410
|
+
function archiveExistingStageAttempt(runRoot, stageDir) {
|
|
411
|
+
const currentStageDir = path.join(runRoot, stageDir);
|
|
412
|
+
if (!fs.existsSync(currentStageDir) || !fs.statSync(currentStageDir).isDirectory()) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const entries = fs.readdirSync(currentStageDir).filter((entry) => !/^try-\d{3}$/.test(entry));
|
|
416
|
+
if (entries.length === 0) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
let attempt = 1;
|
|
420
|
+
while (fs.existsSync(path.join(currentStageDir, `try-${String(attempt).padStart(3, "0")}`))) {
|
|
421
|
+
attempt += 1;
|
|
422
|
+
}
|
|
423
|
+
const archiveDir = path.join(currentStageDir, `try-${String(attempt).padStart(3, "0")}`);
|
|
424
|
+
ensureDir(archiveDir);
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
fs.renameSync(path.join(currentStageDir, entry), path.join(archiveDir, entry));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
357
429
|
function writeJsonFile(file, value) {
|
|
358
|
-
|
|
430
|
+
const tmpFile = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
431
|
+
fs.writeFileSync(tmpFile, `${JSON.stringify(value, null, 2)}\n`);
|
|
432
|
+
fs.renameSync(tmpFile, file);
|
|
433
|
+
}
|
|
434
|
+
function runArtifactRoot(run) {
|
|
435
|
+
return path.dirname(run.run_index_path);
|
|
359
436
|
}
|
|
@@ -16,6 +16,20 @@ const mandatoryMbUpgradeReviewAspectIds = [
|
|
|
16
16
|
"09-report-quality",
|
|
17
17
|
"10-agent-process-quality"
|
|
18
18
|
];
|
|
19
|
+
const mandatoryMbSdlcReviewAspectIds = [
|
|
20
|
+
"structure_navigation_review",
|
|
21
|
+
"feature_epic_layer_review",
|
|
22
|
+
"spec_conformance_review",
|
|
23
|
+
"architecture_harmonization_review",
|
|
24
|
+
"adr_decision_review",
|
|
25
|
+
"scenario_evidence_review",
|
|
26
|
+
"contract_traceability_review",
|
|
27
|
+
"frontmatter_crosslink_review",
|
|
28
|
+
"engineering_standards_review",
|
|
29
|
+
"operations_policy_review",
|
|
30
|
+
"protocol_delivery_trace_review",
|
|
31
|
+
"def_followup_review"
|
|
32
|
+
];
|
|
19
33
|
export function validateSchema(options) {
|
|
20
34
|
if (!schemaNamePattern.test(options.schemaName)) {
|
|
21
35
|
throw new AppError("usage", "--schema must be a schema name such as mb-upgrade-review-data", 2);
|
|
@@ -50,11 +64,10 @@ function resolveSchema(options) {
|
|
|
50
64
|
if (options.schemaDir) {
|
|
51
65
|
candidates.push({ path: path.join(path.resolve(options.schemaDir), fileName), source: "schema_dir" });
|
|
52
66
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
67
|
+
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
68
|
+
candidates.push({ path: path.join(projectRoot, ".memory-bank", "dd-flow", "schemas", fileName), source: "project" });
|
|
69
|
+
candidates.push({ path: path.join(projectRoot, "dd-flow", "schemas", fileName), source: "canonical" });
|
|
70
|
+
candidates.push({ path: path.join(bundledSchemaDir(), fileName), source: "bundled" });
|
|
58
71
|
const found = candidates.find((candidate) => fs.existsSync(candidate.path));
|
|
59
72
|
if (!found) {
|
|
60
73
|
throw new AppError("schema_not_found", `Schema not found: ${options.schemaName}`, 2, {
|
|
@@ -98,14 +111,17 @@ function formatAjvError(error) {
|
|
|
98
111
|
};
|
|
99
112
|
}
|
|
100
113
|
function validateSemanticSchema(schemaName, data) {
|
|
101
|
-
if (schemaName !== "mb-upgrade-review-data") {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
114
|
const root = asRecord(data);
|
|
105
115
|
if (!root) {
|
|
106
116
|
return [];
|
|
107
117
|
}
|
|
108
|
-
|
|
118
|
+
if (schemaName === "mb-upgrade-review-data") {
|
|
119
|
+
return validateMbUpgradeReviewData(root);
|
|
120
|
+
}
|
|
121
|
+
if (schemaName === "mb-sdlc-review-report") {
|
|
122
|
+
return validateMbSdlcReviewReport(root);
|
|
123
|
+
}
|
|
124
|
+
return [];
|
|
109
125
|
}
|
|
110
126
|
function validateMbUpgradeReviewData(root) {
|
|
111
127
|
const errors = [];
|
|
@@ -160,6 +176,69 @@ function validateMbUpgradeReviewData(root) {
|
|
|
160
176
|
});
|
|
161
177
|
return errors;
|
|
162
178
|
}
|
|
179
|
+
function validateMbSdlcReviewReport(root) {
|
|
180
|
+
const errors = [];
|
|
181
|
+
const aspects = arrayValue(root, "aspect_coverage").filter(isRecord);
|
|
182
|
+
const findings = arrayValue(root, "findings_register").filter(isRecord);
|
|
183
|
+
const conformance = recordValue(root, "conformance_summary");
|
|
184
|
+
const verdict = conformance ? stringValue(conformance, "overall_verdict") : undefined;
|
|
185
|
+
const aspectIds = aspects.map((aspect) => stringValue(aspect, "aspect_id")).filter(isString);
|
|
186
|
+
for (const id of mandatoryMbSdlcReviewAspectIds) {
|
|
187
|
+
const count = aspectIds.filter((value) => value === id).length;
|
|
188
|
+
if (count !== 1) {
|
|
189
|
+
errors.push({
|
|
190
|
+
path: "/aspect_coverage",
|
|
191
|
+
message: `mandatory aspect ${id} must be present exactly once`,
|
|
192
|
+
keyword: "dd-flow/aspect-id"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const findingIds = new Set(findings.map((finding) => stringValue(finding, "id")).filter(isString));
|
|
197
|
+
aspects.forEach((aspect, aspectIndex) => {
|
|
198
|
+
const refs = arrayValue(aspect, "findings").filter(isString);
|
|
199
|
+
refs.forEach((ref, refIndex) => {
|
|
200
|
+
if (!findingIds.has(ref)) {
|
|
201
|
+
errors.push({
|
|
202
|
+
path: `/aspect_coverage/${aspectIndex}/findings/${refIndex}`,
|
|
203
|
+
message: `finding reference does not exist: ${ref}`,
|
|
204
|
+
keyword: "dd-flow/finding-ref"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
const decisions = recordValue(root, "critic_pass")
|
|
210
|
+
? arrayValue(recordValue(root, "critic_pass"), "decisions").filter(isRecord)
|
|
211
|
+
: [];
|
|
212
|
+
decisions.forEach((decision, decisionIndex) => {
|
|
213
|
+
const findingId = stringValue(decision, "finding_id");
|
|
214
|
+
const disposition = stringValue(decision, "disposition");
|
|
215
|
+
if (findingId && !findingIds.has(findingId)) {
|
|
216
|
+
errors.push({
|
|
217
|
+
path: `/critic_pass/decisions/${decisionIndex}/finding_id`,
|
|
218
|
+
message: `critic decision finding_id does not exist: ${findingId}`,
|
|
219
|
+
keyword: "dd-flow/finding-ref"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (disposition === "accepted" && !findingId) {
|
|
223
|
+
errors.push({
|
|
224
|
+
path: `/critic_pass/decisions/${decisionIndex}/finding_id`,
|
|
225
|
+
message: "accepted critic decision must reference a final finding_id",
|
|
226
|
+
keyword: "dd-flow/accepted-finding-ref"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
findings.forEach((finding, findingIndex) => {
|
|
231
|
+
const severity = stringValue(finding, "severity");
|
|
232
|
+
if ((verdict === "accepted" || verdict === "accepted_with_findings") && severity === "blocking") {
|
|
233
|
+
errors.push({
|
|
234
|
+
path: `/findings_register/${findingIndex}/severity`,
|
|
235
|
+
message: "accepted review verdict cannot coexist with blocking findings",
|
|
236
|
+
keyword: "dd-flow/accepted-blocking"
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return errors;
|
|
241
|
+
}
|
|
163
242
|
function asRecord(value) {
|
|
164
243
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
165
244
|
}
|
|
@@ -60,8 +60,9 @@ export function stoppedMergeWorkerState(context, projectId, workerId) {
|
|
|
60
60
|
WHERE project_id = ? AND worker_id = ? AND flow_kind = 'merge_worker'
|
|
61
61
|
ORDER BY updated_at DESC, rowid DESC LIMIT 1`, [projectId, workerId]);
|
|
62
62
|
return {
|
|
63
|
-
stopped: latest
|
|
64
|
-
reason: latest?.stop_reason ?? null
|
|
63
|
+
stopped: latest ? ["stopped", "stopping"].includes(latest.status) : false,
|
|
64
|
+
reason: latest?.stop_reason ?? null,
|
|
65
|
+
status: latest?.status ?? null
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
export function activeFlowSessionsForProject(context, projectId) {
|