@checkstack/automation-backend 0.2.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/CHANGELOG.md +453 -0
- package/drizzle/0000_acoustic_diamondback.sql +80 -0
- package/drizzle/0001_mute_vindicator.sql +12 -0
- package/drizzle/0002_silky_omega_red.sql +12 -0
- package/drizzle/meta/0000_snapshot.json +688 -0
- package/drizzle/meta/0001_snapshot.json +785 -0
- package/drizzle/meta/0002_snapshot.json +861 -0
- package/drizzle/meta/_journal.json +27 -0
- package/drizzle.config.ts +12 -0
- package/package.json +41 -0
- package/src/action-registry.ts +83 -0
- package/src/action-types.ts +324 -0
- package/src/artifact-store.ts +140 -0
- package/src/artifact-type-registry.ts +64 -0
- package/src/automation-store.ts +227 -0
- package/src/builtin-actions.test.ts +185 -0
- package/src/builtin-actions.ts +132 -0
- package/src/builtin-triggers.test.ts +264 -0
- package/src/builtin-triggers.ts +365 -0
- package/src/dispatch/action-kind.ts +44 -0
- package/src/dispatch/condition.ts +61 -0
- package/src/dispatch/delay-queue.ts +91 -0
- package/src/dispatch/engine.test.ts +1198 -0
- package/src/dispatch/engine.ts +1672 -0
- package/src/dispatch/path-nav.ts +65 -0
- package/src/dispatch/render.test.ts +75 -0
- package/src/dispatch/render.ts +136 -0
- package/src/dispatch/run-state-store.ts +143 -0
- package/src/dispatch/run-state.ts +298 -0
- package/src/dispatch/scope.test.ts +40 -0
- package/src/dispatch/scope.ts +125 -0
- package/src/dispatch/stalled-sweeper.ts +164 -0
- package/src/dispatch/test-fixtures.ts +558 -0
- package/src/dispatch/trigger-subscriber.ts +397 -0
- package/src/dispatch/types.ts +259 -0
- package/src/extension-points.ts +88 -0
- package/src/index.ts +379 -0
- package/src/migration/from-webhook-subscriptions.test.ts +237 -0
- package/src/migration/from-webhook-subscriptions.ts +398 -0
- package/src/registries.test.ts +357 -0
- package/src/router.test.ts +724 -0
- package/src/router.ts +556 -0
- package/src/schema.ts +310 -0
- package/src/trigger-registry.ts +99 -0
- package/src/validate-definition.test.ts +306 -0
- package/src/validate-definition.ts +304 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Condition evaluation for the dispatch engine.
|
|
3
|
+
*
|
|
4
|
+
* Conditions come in two shapes from the schema:
|
|
5
|
+
*
|
|
6
|
+
* - A template string returning truthy/falsy.
|
|
7
|
+
* - A combinator object — `{ and: [...] }`, `{ or: [...] }`, or
|
|
8
|
+
* `{ not: condition }` — recursing into nested conditions.
|
|
9
|
+
*
|
|
10
|
+
* Both forms eval against the current dispatch scope through the shared
|
|
11
|
+
* template engine.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
evaluateBoolean,
|
|
15
|
+
parseCondition,
|
|
16
|
+
type FilterRegistry,
|
|
17
|
+
type TemplateContext,
|
|
18
|
+
} from "@checkstack/template-engine";
|
|
19
|
+
import type { Condition } from "@checkstack/automation-common";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Evaluate a condition to boolean.
|
|
23
|
+
*
|
|
24
|
+
* Pure functions of the scope — no side effects, no async work. Throws
|
|
25
|
+
* if a template fails to parse; callers convert that to a step failure.
|
|
26
|
+
*/
|
|
27
|
+
export function evaluateCondition(
|
|
28
|
+
condition: Condition,
|
|
29
|
+
context: TemplateContext,
|
|
30
|
+
filters: FilterRegistry,
|
|
31
|
+
): boolean {
|
|
32
|
+
if (typeof condition === "string") {
|
|
33
|
+
return evaluateBoolean(parseCondition(condition), context, { filters });
|
|
34
|
+
}
|
|
35
|
+
if ("and" in condition) {
|
|
36
|
+
return condition.and.every((c) =>
|
|
37
|
+
evaluateCondition(c, context, filters),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if ("or" in condition) {
|
|
41
|
+
return condition.or.some((c) => evaluateCondition(c, context, filters));
|
|
42
|
+
}
|
|
43
|
+
// not
|
|
44
|
+
return !evaluateCondition(condition.not, context, filters);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Evaluate every condition in a list. Returns the first failing
|
|
49
|
+
* condition's index (or `-1` when all pass) so the caller can log which
|
|
50
|
+
* gate rejected the run.
|
|
51
|
+
*/
|
|
52
|
+
export function evaluateAllConditions(
|
|
53
|
+
conditions: ReadonlyArray<Condition>,
|
|
54
|
+
context: TemplateContext,
|
|
55
|
+
filters: FilterRegistry,
|
|
56
|
+
): number {
|
|
57
|
+
for (const [i, condition] of conditions.entries()) {
|
|
58
|
+
if (!evaluateCondition(condition, context, filters)) return i;
|
|
59
|
+
}
|
|
60
|
+
return -1;
|
|
61
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue consumer that resumes runs suspended by `delay` actions.
|
|
3
|
+
*
|
|
4
|
+
* The dispatch engine's delay primitive persists a wait lock of
|
|
5
|
+
* `kind: "delay"` and enqueues a `automation-delay` job with the
|
|
6
|
+
* configured `startDelay`. When the scheduler fires the job, this
|
|
7
|
+
* consumer:
|
|
8
|
+
*
|
|
9
|
+
* 1. Loads the wait lock; bails if it's been deleted (e.g. by the
|
|
10
|
+
* stalled sweeper or a cancelled run).
|
|
11
|
+
* 2. Loads the run + its automation definition.
|
|
12
|
+
* 3. Deletes the wait lock.
|
|
13
|
+
* 4. Calls `resumeRun` — which takes the advisory lock so a
|
|
14
|
+
* concurrent sweeper / consumer can't double-fire.
|
|
15
|
+
*/
|
|
16
|
+
import type { Logger } from "@checkstack/backend-api";
|
|
17
|
+
|
|
18
|
+
import type { AutomationStore } from "../automation-store";
|
|
19
|
+
import {
|
|
20
|
+
DELAY_QUEUE_NAME,
|
|
21
|
+
resumeRun,
|
|
22
|
+
type DelayResumeJob,
|
|
23
|
+
} from "./engine";
|
|
24
|
+
import type { DispatchDeps } from "./types";
|
|
25
|
+
|
|
26
|
+
export interface DelayQueueConsumerArgs {
|
|
27
|
+
deps: DispatchDeps;
|
|
28
|
+
automationStore: AutomationStore;
|
|
29
|
+
logger: Logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DelayQueueConsumer {
|
|
33
|
+
/** Stop consuming. */
|
|
34
|
+
stop: () => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function startDelayQueueConsumer(
|
|
38
|
+
args: DelayQueueConsumerArgs,
|
|
39
|
+
): Promise<DelayQueueConsumer> {
|
|
40
|
+
const queue = args.deps.queueManager.getQueue<DelayResumeJob>(
|
|
41
|
+
DELAY_QUEUE_NAME,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await queue.consume(
|
|
45
|
+
async (job) => {
|
|
46
|
+
const { runId, waitLockId } = job.data;
|
|
47
|
+
const lock = await args.deps.runStore.loadWaitLock(waitLockId);
|
|
48
|
+
if (!lock) {
|
|
49
|
+
args.logger.debug(
|
|
50
|
+
`delay-queue: wait lock ${waitLockId} no longer exists (run cancelled or already resumed)`,
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const run = await args.deps.runStore.loadRun(runId);
|
|
55
|
+
if (!run) {
|
|
56
|
+
await args.deps.runStore.deleteWaitLock(waitLockId);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const automation = await args.automationStore.getById(run.automationId);
|
|
60
|
+
if (!automation) {
|
|
61
|
+
await args.deps.runStore.deleteWaitLock(waitLockId);
|
|
62
|
+
await args.deps.runStore.updateRunStatus(
|
|
63
|
+
runId,
|
|
64
|
+
"failed",
|
|
65
|
+
"automation deleted while run was suspended on delay",
|
|
66
|
+
);
|
|
67
|
+
await args.deps.runStateStore.clear(runId);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await args.deps.runStore.deleteWaitLock(waitLockId);
|
|
72
|
+
await resumeRun(args.deps, {
|
|
73
|
+
runId,
|
|
74
|
+
automation: {
|
|
75
|
+
id: automation.id,
|
|
76
|
+
name: automation.name,
|
|
77
|
+
status: automation.status,
|
|
78
|
+
definition: automation.definition,
|
|
79
|
+
},
|
|
80
|
+
waitedAtPath: lock.actionPath,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
{ consumerGroup: "automation-delay-resume", maxRetries: 3 },
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
stop: async () => {
|
|
88
|
+
await queue.stop();
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|