chainlesschain 0.37.10 → 0.37.12
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 +166 -10
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/init.js +184 -0
- package/src/commands/lowcode.js +320 -0
- package/src/commands/plugin.js +55 -2
- package/src/commands/sandbox.js +366 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +44 -0
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/plugin-manager.js +118 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +117 -112
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Engine — DAG-based workflow creation, execution, and management.
|
|
3
|
+
* Supports stages with action/approval types, pause/resume, rollback, and built-in templates.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ensure workflow tables exist in the database.
|
|
10
|
+
*/
|
|
11
|
+
export function ensureWorkflowTables(db) {
|
|
12
|
+
db.exec(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS workflows (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
description TEXT,
|
|
17
|
+
dag TEXT NOT NULL,
|
|
18
|
+
status TEXT DEFAULT 'active',
|
|
19
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
20
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
db.exec(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS workflow_executions (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
workflow_id TEXT NOT NULL,
|
|
27
|
+
status TEXT DEFAULT 'pending',
|
|
28
|
+
input TEXT,
|
|
29
|
+
log TEXT DEFAULT '[]',
|
|
30
|
+
current_stage TEXT,
|
|
31
|
+
started_at TEXT,
|
|
32
|
+
completed_at TEXT,
|
|
33
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
34
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
35
|
+
)
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate a DAG (directed acyclic graph) of stages using DFS cycle detection.
|
|
41
|
+
* @param {Array} stages - Array of { id, type, name, next }
|
|
42
|
+
* @returns {boolean} true if the DAG is valid (no cycles), false otherwise
|
|
43
|
+
*/
|
|
44
|
+
export function validateDAG(stages) {
|
|
45
|
+
if (!Array.isArray(stages) || stages.length === 0) return false;
|
|
46
|
+
|
|
47
|
+
const stageMap = new Map();
|
|
48
|
+
for (const stage of stages) {
|
|
49
|
+
if (!stage.id || !stage.name) return false;
|
|
50
|
+
stageMap.set(stage.id, stage);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check all next references point to valid stages
|
|
54
|
+
for (const stage of stages) {
|
|
55
|
+
if (stage.next) {
|
|
56
|
+
for (const nextId of stage.next) {
|
|
57
|
+
if (!stageMap.has(nextId)) return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// DFS cycle detection
|
|
63
|
+
const WHITE = 0,
|
|
64
|
+
GRAY = 1,
|
|
65
|
+
BLACK = 2;
|
|
66
|
+
const color = new Map();
|
|
67
|
+
for (const stage of stages) {
|
|
68
|
+
color.set(stage.id, WHITE);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function dfs(nodeId) {
|
|
72
|
+
color.set(nodeId, GRAY);
|
|
73
|
+
const node = stageMap.get(nodeId);
|
|
74
|
+
const neighbors = node.next || [];
|
|
75
|
+
for (const nextId of neighbors) {
|
|
76
|
+
if (color.get(nextId) === GRAY) return true; // cycle found
|
|
77
|
+
if (color.get(nextId) === WHITE && dfs(nextId)) return true;
|
|
78
|
+
}
|
|
79
|
+
color.set(nodeId, BLACK);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const stage of stages) {
|
|
84
|
+
if (color.get(stage.id) === WHITE) {
|
|
85
|
+
if (dfs(stage.id)) return false; // has cycle
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a new workflow with DAG validation.
|
|
94
|
+
* @param {object} db - Database instance
|
|
95
|
+
* @param {object} definition - { name, description, stages }
|
|
96
|
+
* @returns {{ id, status, stages }}
|
|
97
|
+
*/
|
|
98
|
+
export function createWorkflow(db, definition) {
|
|
99
|
+
ensureWorkflowTables(db);
|
|
100
|
+
|
|
101
|
+
const { name, description, stages } = definition;
|
|
102
|
+
if (!name) {
|
|
103
|
+
throw new Error("Workflow name is required");
|
|
104
|
+
}
|
|
105
|
+
if (!stages || !Array.isArray(stages) || stages.length === 0) {
|
|
106
|
+
throw new Error("Workflow must have at least one stage");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!validateDAG(stages)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"Invalid DAG: stages contain a cycle or invalid references",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const id = `wf-${crypto.randomBytes(8).toString("hex")}`;
|
|
116
|
+
|
|
117
|
+
db.prepare(
|
|
118
|
+
`INSERT INTO workflows (id, name, description, dag, status)
|
|
119
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
120
|
+
).run(id, name, description || null, JSON.stringify(stages), "active");
|
|
121
|
+
|
|
122
|
+
return { id, status: "active", stages };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get a single workflow by ID.
|
|
127
|
+
*/
|
|
128
|
+
export function getWorkflow(db, workflowId) {
|
|
129
|
+
ensureWorkflowTables(db);
|
|
130
|
+
const row = db
|
|
131
|
+
.prepare("SELECT * FROM workflows WHERE id = ?")
|
|
132
|
+
.get(workflowId);
|
|
133
|
+
if (!row) return null;
|
|
134
|
+
return { ...row, dag: JSON.parse(row.dag) };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* List all workflows with basic info.
|
|
139
|
+
*/
|
|
140
|
+
export function listWorkflows(db) {
|
|
141
|
+
ensureWorkflowTables(db);
|
|
142
|
+
const rows = db
|
|
143
|
+
.prepare("SELECT * FROM workflows ORDER BY created_at DESC")
|
|
144
|
+
.all();
|
|
145
|
+
return rows.map((row) => ({
|
|
146
|
+
...row,
|
|
147
|
+
dag: JSON.parse(row.dag),
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Delete a workflow by ID.
|
|
153
|
+
*/
|
|
154
|
+
export function deleteWorkflow(db, workflowId) {
|
|
155
|
+
ensureWorkflowTables(db);
|
|
156
|
+
const result = db
|
|
157
|
+
.prepare("DELETE FROM workflows WHERE id = ?")
|
|
158
|
+
.run(workflowId);
|
|
159
|
+
return { deleted: result.changes > 0 };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Execute a workflow, running through stages in topological order.
|
|
164
|
+
* @returns {{ id, workflowId, status, log }}
|
|
165
|
+
*/
|
|
166
|
+
export function executeWorkflow(db, workflowId, input = {}) {
|
|
167
|
+
ensureWorkflowTables(db);
|
|
168
|
+
|
|
169
|
+
const workflow = getWorkflow(db, workflowId);
|
|
170
|
+
if (!workflow) {
|
|
171
|
+
throw new Error(`Workflow not found: ${workflowId}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const execId = `exec-${crypto.randomBytes(8).toString("hex")}`;
|
|
175
|
+
const stages = workflow.dag;
|
|
176
|
+
const log = [];
|
|
177
|
+
|
|
178
|
+
// Topological sort for execution order
|
|
179
|
+
const order = topologicalSort(stages);
|
|
180
|
+
|
|
181
|
+
// Execute each stage
|
|
182
|
+
for (const stageId of order) {
|
|
183
|
+
const stage = stages.find((s) => s.id === stageId);
|
|
184
|
+
log.push({
|
|
185
|
+
stageId: stage.id,
|
|
186
|
+
stageName: stage.name,
|
|
187
|
+
type: stage.type || "action",
|
|
188
|
+
status: "completed",
|
|
189
|
+
timestamp: new Date().toISOString(),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const now = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
194
|
+
|
|
195
|
+
db.prepare(
|
|
196
|
+
`INSERT INTO workflow_executions (id, workflow_id, status, input, log, current_stage, started_at, completed_at)
|
|
197
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
198
|
+
).run(
|
|
199
|
+
execId,
|
|
200
|
+
workflowId,
|
|
201
|
+
"completed",
|
|
202
|
+
JSON.stringify(input),
|
|
203
|
+
JSON.stringify(log),
|
|
204
|
+
order[order.length - 1] || null,
|
|
205
|
+
now,
|
|
206
|
+
now,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return { id: execId, workflowId, status: "completed", log };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Topological sort of stages using Kahn's algorithm.
|
|
214
|
+
*/
|
|
215
|
+
function topologicalSort(stages) {
|
|
216
|
+
const inDegree = new Map();
|
|
217
|
+
const adjacency = new Map();
|
|
218
|
+
|
|
219
|
+
for (const stage of stages) {
|
|
220
|
+
inDegree.set(stage.id, 0);
|
|
221
|
+
adjacency.set(stage.id, stage.next || []);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const stage of stages) {
|
|
225
|
+
for (const nextId of stage.next || []) {
|
|
226
|
+
inDegree.set(nextId, (inDegree.get(nextId) || 0) + 1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const queue = [];
|
|
231
|
+
for (const [id, deg] of inDegree) {
|
|
232
|
+
if (deg === 0) queue.push(id);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const order = [];
|
|
236
|
+
while (queue.length > 0) {
|
|
237
|
+
const current = queue.shift();
|
|
238
|
+
order.push(current);
|
|
239
|
+
for (const nextId of adjacency.get(current) || []) {
|
|
240
|
+
inDegree.set(nextId, inDegree.get(nextId) - 1);
|
|
241
|
+
if (inDegree.get(nextId) === 0) queue.push(nextId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return order;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Pause a running or pending execution.
|
|
250
|
+
*/
|
|
251
|
+
export function pauseExecution(db, executionId) {
|
|
252
|
+
ensureWorkflowTables(db);
|
|
253
|
+
const exec = db
|
|
254
|
+
.prepare("SELECT * FROM workflow_executions WHERE id = ?")
|
|
255
|
+
.get(executionId);
|
|
256
|
+
if (!exec) {
|
|
257
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
exec.status !== "running" &&
|
|
261
|
+
exec.status !== "pending" &&
|
|
262
|
+
exec.status !== "completed"
|
|
263
|
+
) {
|
|
264
|
+
throw new Error(`Cannot pause execution in status: ${exec.status}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
db.prepare("UPDATE workflow_executions SET status = ? WHERE id = ?").run(
|
|
268
|
+
"paused",
|
|
269
|
+
executionId,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const log = JSON.parse(exec.log || "[]");
|
|
273
|
+
log.push({
|
|
274
|
+
action: "paused",
|
|
275
|
+
timestamp: new Date().toISOString(),
|
|
276
|
+
});
|
|
277
|
+
db.prepare("UPDATE workflow_executions SET log = ? WHERE id = ?").run(
|
|
278
|
+
JSON.stringify(log),
|
|
279
|
+
executionId,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return { id: executionId, status: "paused" };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Resume a paused execution.
|
|
287
|
+
*/
|
|
288
|
+
export function resumeExecution(db, executionId) {
|
|
289
|
+
ensureWorkflowTables(db);
|
|
290
|
+
const exec = db
|
|
291
|
+
.prepare("SELECT * FROM workflow_executions WHERE id = ?")
|
|
292
|
+
.get(executionId);
|
|
293
|
+
if (!exec) {
|
|
294
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
295
|
+
}
|
|
296
|
+
if (exec.status !== "paused") {
|
|
297
|
+
throw new Error(`Cannot resume execution in status: ${exec.status}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
db.prepare("UPDATE workflow_executions SET status = ? WHERE id = ?").run(
|
|
301
|
+
"running",
|
|
302
|
+
executionId,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const log = JSON.parse(exec.log || "[]");
|
|
306
|
+
log.push({
|
|
307
|
+
action: "resumed",
|
|
308
|
+
timestamp: new Date().toISOString(),
|
|
309
|
+
});
|
|
310
|
+
db.prepare("UPDATE workflow_executions SET log = ? WHERE id = ?").run(
|
|
311
|
+
JSON.stringify(log),
|
|
312
|
+
executionId,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return { id: executionId, status: "running" };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Rollback an execution.
|
|
320
|
+
*/
|
|
321
|
+
export function rollbackExecution(db, executionId) {
|
|
322
|
+
ensureWorkflowTables(db);
|
|
323
|
+
const exec = db
|
|
324
|
+
.prepare("SELECT * FROM workflow_executions WHERE id = ?")
|
|
325
|
+
.get(executionId);
|
|
326
|
+
if (!exec) {
|
|
327
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
328
|
+
}
|
|
329
|
+
if (exec.status === "rolled_back") {
|
|
330
|
+
throw new Error("Execution already rolled back");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
db.prepare("UPDATE workflow_executions SET status = ? WHERE id = ?").run(
|
|
334
|
+
"rolled_back",
|
|
335
|
+
executionId,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const log = JSON.parse(exec.log || "[]");
|
|
339
|
+
log.push({
|
|
340
|
+
action: "rolled_back",
|
|
341
|
+
timestamp: new Date().toISOString(),
|
|
342
|
+
});
|
|
343
|
+
db.prepare("UPDATE workflow_executions SET log = ? WHERE id = ?").run(
|
|
344
|
+
JSON.stringify(log),
|
|
345
|
+
executionId,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
return { id: executionId, status: "rolled_back" };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get the execution log for a given execution ID.
|
|
353
|
+
*/
|
|
354
|
+
export function getExecutionLog(db, executionId) {
|
|
355
|
+
ensureWorkflowTables(db);
|
|
356
|
+
const exec = db
|
|
357
|
+
.prepare("SELECT * FROM workflow_executions WHERE id = ?")
|
|
358
|
+
.get(executionId);
|
|
359
|
+
if (!exec) {
|
|
360
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
id: exec.id,
|
|
364
|
+
workflowId: exec.workflow_id,
|
|
365
|
+
status: exec.status,
|
|
366
|
+
log: JSON.parse(exec.log || "[]"),
|
|
367
|
+
startedAt: exec.started_at,
|
|
368
|
+
completedAt: exec.completed_at,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Return 5 built-in workflow templates.
|
|
374
|
+
*/
|
|
375
|
+
export function getTemplates() {
|
|
376
|
+
return [
|
|
377
|
+
{
|
|
378
|
+
id: "data-pipeline",
|
|
379
|
+
name: "Data Pipeline",
|
|
380
|
+
description: "Extract, transform, and load data with validation",
|
|
381
|
+
stages: [
|
|
382
|
+
{
|
|
383
|
+
id: "extract",
|
|
384
|
+
type: "action",
|
|
385
|
+
name: "Extract Data",
|
|
386
|
+
next: ["transform"],
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: "transform",
|
|
390
|
+
type: "action",
|
|
391
|
+
name: "Transform Data",
|
|
392
|
+
next: ["validate"],
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
id: "validate",
|
|
396
|
+
type: "action",
|
|
397
|
+
name: "Validate Output",
|
|
398
|
+
next: ["load"],
|
|
399
|
+
},
|
|
400
|
+
{ id: "load", type: "action", name: "Load to Target", next: [] },
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "code-review",
|
|
405
|
+
name: "Code Review",
|
|
406
|
+
description: "Automated code review with human approval gate",
|
|
407
|
+
stages: [
|
|
408
|
+
{ id: "lint", type: "action", name: "Lint Check", next: ["test"] },
|
|
409
|
+
{ id: "test", type: "action", name: "Run Tests", next: ["review"] },
|
|
410
|
+
{
|
|
411
|
+
id: "review",
|
|
412
|
+
type: "approval",
|
|
413
|
+
name: "Human Review",
|
|
414
|
+
next: ["merge"],
|
|
415
|
+
},
|
|
416
|
+
{ id: "merge", type: "action", name: "Merge to Main", next: [] },
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
id: "content-generation",
|
|
421
|
+
name: "Content Generation",
|
|
422
|
+
description: "AI-assisted content creation with editorial review",
|
|
423
|
+
stages: [
|
|
424
|
+
{ id: "draft", type: "action", name: "Generate Draft", next: ["edit"] },
|
|
425
|
+
{
|
|
426
|
+
id: "edit",
|
|
427
|
+
type: "action",
|
|
428
|
+
name: "AI Edit & Polish",
|
|
429
|
+
next: ["approve"],
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "approve",
|
|
433
|
+
type: "approval",
|
|
434
|
+
name: "Editorial Approval",
|
|
435
|
+
next: ["publish"],
|
|
436
|
+
},
|
|
437
|
+
{ id: "publish", type: "action", name: "Publish Content", next: [] },
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "employee-onboarding",
|
|
442
|
+
name: "Employee Onboarding",
|
|
443
|
+
description: "New employee onboarding workflow with approvals",
|
|
444
|
+
stages: [
|
|
445
|
+
{
|
|
446
|
+
id: "account",
|
|
447
|
+
type: "action",
|
|
448
|
+
name: "Create Accounts",
|
|
449
|
+
next: ["equipment"],
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: "equipment",
|
|
453
|
+
type: "action",
|
|
454
|
+
name: "Provision Equipment",
|
|
455
|
+
next: ["training"],
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
id: "training",
|
|
459
|
+
type: "action",
|
|
460
|
+
name: "Schedule Training",
|
|
461
|
+
next: ["verify"],
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: "verify",
|
|
465
|
+
type: "approval",
|
|
466
|
+
name: "Manager Verification",
|
|
467
|
+
next: [],
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: "incident-response",
|
|
473
|
+
name: "Incident Response",
|
|
474
|
+
description: "Automated incident detection, triage, and resolution",
|
|
475
|
+
stages: [
|
|
476
|
+
{
|
|
477
|
+
id: "detect",
|
|
478
|
+
type: "action",
|
|
479
|
+
name: "Detect Incident",
|
|
480
|
+
next: ["triage"],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
id: "triage",
|
|
484
|
+
type: "action",
|
|
485
|
+
name: "Triage & Classify",
|
|
486
|
+
next: ["respond"],
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
id: "respond",
|
|
490
|
+
type: "action",
|
|
491
|
+
name: "Execute Response",
|
|
492
|
+
next: ["postmortem"],
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
id: "postmortem",
|
|
496
|
+
type: "approval",
|
|
497
|
+
name: "Postmortem Review",
|
|
498
|
+
next: [],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
];
|
|
503
|
+
}
|