@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +453 -0
  2. package/drizzle/0000_acoustic_diamondback.sql +80 -0
  3. package/drizzle/0001_mute_vindicator.sql +12 -0
  4. package/drizzle/0002_silky_omega_red.sql +12 -0
  5. package/drizzle/meta/0000_snapshot.json +688 -0
  6. package/drizzle/meta/0001_snapshot.json +785 -0
  7. package/drizzle/meta/0002_snapshot.json +861 -0
  8. package/drizzle/meta/_journal.json +27 -0
  9. package/drizzle.config.ts +12 -0
  10. package/package.json +41 -0
  11. package/src/action-registry.ts +83 -0
  12. package/src/action-types.ts +324 -0
  13. package/src/artifact-store.ts +140 -0
  14. package/src/artifact-type-registry.ts +64 -0
  15. package/src/automation-store.ts +227 -0
  16. package/src/builtin-actions.test.ts +185 -0
  17. package/src/builtin-actions.ts +132 -0
  18. package/src/builtin-triggers.test.ts +264 -0
  19. package/src/builtin-triggers.ts +365 -0
  20. package/src/dispatch/action-kind.ts +44 -0
  21. package/src/dispatch/condition.ts +61 -0
  22. package/src/dispatch/delay-queue.ts +91 -0
  23. package/src/dispatch/engine.test.ts +1198 -0
  24. package/src/dispatch/engine.ts +1672 -0
  25. package/src/dispatch/path-nav.ts +65 -0
  26. package/src/dispatch/render.test.ts +75 -0
  27. package/src/dispatch/render.ts +136 -0
  28. package/src/dispatch/run-state-store.ts +143 -0
  29. package/src/dispatch/run-state.ts +298 -0
  30. package/src/dispatch/scope.test.ts +40 -0
  31. package/src/dispatch/scope.ts +125 -0
  32. package/src/dispatch/stalled-sweeper.ts +164 -0
  33. package/src/dispatch/test-fixtures.ts +558 -0
  34. package/src/dispatch/trigger-subscriber.ts +397 -0
  35. package/src/dispatch/types.ts +259 -0
  36. package/src/extension-points.ts +88 -0
  37. package/src/index.ts +379 -0
  38. package/src/migration/from-webhook-subscriptions.test.ts +237 -0
  39. package/src/migration/from-webhook-subscriptions.ts +398 -0
  40. package/src/registries.test.ts +357 -0
  41. package/src/router.test.ts +724 -0
  42. package/src/router.ts +556 -0
  43. package/src/schema.ts +310 -0
  44. package/src/trigger-registry.ts +99 -0
  45. package/src/validate-definition.test.ts +306 -0
  46. package/src/validate-definition.ts +304 -0
  47. package/tsconfig.json +41 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,453 @@
1
+ # @checkstack/automation-backend
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e2d6f25: feat(automation): connection picker for integration actions + restore Integrations menu
8
+
9
+ Connection-backed automation actions (Jira, Teams, Webex) now render a
10
+ working connection picker plus cascading provider dropdowns in the
11
+ visual editor, and the Integrations entry is back in the user menu.
12
+
13
+ **Contract.** `ActionDefinition` gained an optional
14
+ `connectionProviderId` (and it is surfaced on `ActionInfoSchema` and
15
+ mapped in the `listActions` router). It carries the integration
16
+ provider's fully-qualified id, derived from the provider plugin's own
17
+ `pluginMetadata.pluginId` (never a hardcoded string), so the editor
18
+ knows which provider backs an action's dropdowns and it matches the
19
+ `qualifiedId` the integration provider registry assigns.
20
+
21
+ **Providers.** Jira, Teams and Webex each export
22
+ `*_PROVIDER_LOCAL_ID` / `*_PROVIDER_QUALIFIED_ID`, register their
23
+ provider with the local id, and add a `CONNECTION_OPTIONS`
24
+ (`"connectionOptions"`) resolver name. Their `post_message` /
25
+ issue actions set `connectionProviderId` and expose `connectionId`
26
+ as an `x-options-resolver` dropdown instead of a hidden field.
27
+
28
+ **Frontend bridge.** A new `useConnectionOptionResolvers` hook
29
+ (`@checkstack/automation-frontend`, which now depends on
30
+ `@checkstack/integration-common`) turns an action's
31
+ `x-options-resolver` schema fields into live data: the
32
+ `connectionOptions` resolver lists the provider's connections via
33
+ `listConnections`, and every other resolver name is forwarded to
34
+ `getConnectionOptions` for the selected `connectionId`, passing the
35
+ live form values as `context` for dependent fields. `ProviderActionBody`
36
+ now passes this map to `DynamicForm` (it was previously missing
37
+ entirely, so connection-backed actions had no working dropdowns).
38
+
39
+ **frontend-api.** `usePluginClient` procedures now also expose a typed
40
+ imperative `.call(input)` alongside `.useQuery` / `.useMutation`, for
41
+ async callbacks that cannot host a hook (such as a `DynamicForm`
42
+ options resolver). Additive, non-breaking.
43
+
44
+ **Integrations menu.** Re-added `IntegrationMenuItem` and a new
45
+ `IntegrationsLandingPage`, wired into `integration-frontend` as a list
46
+ route and a `UserMenuItemsSlot` entry under the "Configuration" group.
47
+
48
+ **Action card polish.** The action editor's secondary metadata (id,
49
+ description, failure behaviour) is now grouped into one quiet settings
50
+ panel with consistent small uppercase "eyebrow" labels, so the action's
51
+ own configuration stays the focal point. The raw failure checkbox was
52
+ replaced with the standard `Checkbox` control, and the provider action
53
+ picker / configuration sections gained consistent section headers and a
54
+ divider. The per-step "type" dropdown was removed: an action's kind is
55
+ fixed at creation, so changing it now means adding a new step and
56
+ deleting the old one (avoids the surprising full-config reset that
57
+ switching kinds used to trigger).
58
+
59
+ **Add-step picker.** Adding a step now opens a Home-Assistant-style
60
+ dialog where the operator decides the step type up front: an "Actions"
61
+ tab lists the registered provider actions grouped by category
62
+ (searchable; picking one presets the step's `action`), and a "Blocks"
63
+ tab lists the structural building blocks (choose / parallel / repeat /
64
+ etc.). Because the concrete action is chosen here, the in-card action
65
+ switcher was removed - a step's action is fixed once created. Composite
66
+ blocks now start with an empty child list (filled via the nested
67
+ add-step picker) instead of seeding an unconfigurable empty action.
68
+
69
+ - 41c77f4: feat(automation): deep + live definition validation surfaces invalid values, keys and ids — marked inline
70
+
71
+ Previously `validateDefinition` only checked the structural shape via
72
+ `AutomationDefinitionSchema`, where an action's `config` is typed as
73
+ `z.record(z.unknown())`. So a bad config value (e.g. `level:
74
+ debugthisiswrong` on `automation.log`) passed validation, and switching
75
+ to the visual editor just showed an empty dropdown with no explanation.
76
+
77
+ **Backend — deep validation.** New `collectDefinitionIssues` walker
78
+ validates the whole definition semantically, not just structurally:
79
+
80
+ - unknown trigger `event` / action `action` ids,
81
+ - each provider action's `config` against the registered action's own
82
+ schema (wrong enum value, missing required field, wrong type),
83
+ - each trigger's `config` against the trigger's `configSchema`,
84
+ - **unknown / typo'd config keys** — object configs are validated in
85
+ strict mode, so `levle: "info"` is reported rather than silently
86
+ stripped,
87
+ - recurses through `choose` / `parallel` / `repeat` / `sequence` so
88
+ nested action configs are covered too.
89
+
90
+ Issues come back with a dot-joinable `path` (e.g.
91
+ `actions.0.config.level`, `triggers.1.event`). The `validateDefinition`
92
+ RPC now returns these.
93
+
94
+ **Frontend — live + inline.** The automation editor re-validates on
95
+ every edit (debounced ~400ms) in BOTH tabs, and marks the offending
96
+ content in place rather than in a separate alert panel:
97
+
98
+ - **YAML tab** — issues (and YAML syntax errors) are squiggled at the
99
+ exact node. `@checkstack/ui`'s `CodeEditor` gained a `markers` prop;
100
+ the editor maps each issue's `path` onto the YAML document's node
101
+ range via a new `computeYamlMarkers` helper (walking up to the
102
+ nearest existing ancestor when a key is absent, e.g. a missing
103
+ required field).
104
+ - **Visual tab** — the specific card carrying an issue is marked: a
105
+ destructive border + warning icon + the field-level messages. A
106
+ `ValidationProvider` context partitions issues by owner (action card
107
+ / trigger card / condition / top-level) using the action-node path
108
+ grammar, so a nested action's config error attaches to the nested
109
+ card, and a `choose`'s own `when` error attaches to the choose card.
110
+ `ActionCard` gained an `errors` prop. So importing YAML with a bad
111
+ value (the empty-dropdown case) now visibly flags the card instead of
112
+ being silent.
113
+
114
+ The big error alert is gone; the only residual panel is a slim fallback
115
+ for the rare top-level issue that can't attach to any card.
116
+
117
+ Note: strict config validation means an action whose config schema
118
+ intentionally allowed extra keys would now flag them; action configs
119
+ across the platform declare all their fields, so this only catches
120
+ genuine typos.
121
+
122
+ - e1a2077: feat(automation): reference artifacts by explicit action id (`artifacts.<id>.<name>`)
123
+
124
+ Multiple actions of the same type (e.g. two "create Jira issue" steps) used
125
+ to collide: both produced the artifact type `integration-jira.issue`, so a
126
+ template could only ever reach "the most recent one of that type". Artifacts
127
+ are now addressed by the producing action's instance `id` instead.
128
+
129
+ - Templates reference a produced artifact solely as
130
+ `{{ artifacts.<actionId>.<localArtifactName>.<field> }}`, e.g.
131
+ `{{ artifacts.open_jira.issue.issueKey }}`. The local artifact name is the
132
+ producing action's `produces` id with the owning plugin prefix stripped
133
+ (`integration-jira.issue` -> `issue`).
134
+ - `@checkstack/automation-backend`: the dispatch engine nests each produced
135
+ artifact under `artifacts[actionId][localName]` in the template scope and
136
+ records the `actionId` on the artifact row. `validate-definition` now
137
+ enforces that action ids are unique within an automation and that every
138
+ artifact-producing action carries an id.
139
+ - `@checkstack/automation-common`: action `id` is constrained to an
140
+ identifier (`/^[a-zA-Z_][a-zA-Z0-9_]*$/`) so it is always usable as a
141
+ plain template segment. The variable-scope resolver surfaces
142
+ `artifacts.<id>.<name>` (with full field completion) in the editor.
143
+ - `@checkstack/automation-frontend`: the action editor now has editable `Id`
144
+ and `Description` inputs (previously settable only via the YAML view), and
145
+ new steps get an auto-assigned, unique, log-friendly default id that the
146
+ operator can rename. Action ids are recorded on every run step, so run
147
+ logs are parseable by id regardless of kind.
148
+
149
+ **BREAKING (beta):** the previous flat, type-keyed scope form
150
+ `{{ artifacts["integration-jira.issue"] }}` is removed. Reference artifacts
151
+ by the producing action's id instead. Action ids may no longer contain
152
+ hyphens or dots (identifier characters only). Artifacts are per-run and
153
+ ephemeral, so no stored-data migration is needed.
154
+
155
+ - 41c77f4: feat(automation): native per-editor context for script actions (typed `context` for TS, `$ENV` for shell)
156
+
157
+ Script action editors had a confusing dual system: the TypeScript editor
158
+ type-checked `{{ }}` template text as code (so `{{ artifact.x }}` errored
159
+ with "Cannot find name"), and the runtime never actually populated the
160
+ `context` object. This standardises on a single, native context-access
161
+ mechanism per editor kind.
162
+
163
+ **Run scope reaches actions.** `ActionExecutionContext` gains a `scope`
164
+ (`{ trigger, artifacts, vars, repeat? }`), populated by the dispatch
165
+ engine from the same scope it already uses for `{{ }}` rendering. Actions
166
+ that need broad context (the script actions) read from it instead of
167
+ having to declare every artifact type in `consumes`. Additive and
168
+ optional, so existing actions are unaffected.
169
+
170
+ **TypeScript / JavaScript → typed `context`.** `run_script` now builds
171
+ `context` from the run scope, so `context.trigger.payload`,
172
+ `context.artifacts`, `context.var`, `context.repeat`, and
173
+ `context.automation` are populated at run time (previously
174
+ `context.trigger` was always empty). The editor types match via
175
+ `generateAutomationContextTypes`.
176
+
177
+ **Shell → `$CHECKSTACK_*` env vars.** `run_shell` flattens the run scope
178
+ into environment variables (e.g. `$CHECKSTACK_TRIGGER_PAYLOAD_TITLE`,
179
+ `$CHECKSTACK_ARTIFACT_INTEGRATION_JIRA_ISSUE_ISSUEKEY`). Arrays become a
180
+ single newline-separated var (iterate with `while IFS= read -r x; do …;
181
+ done <<< "$VAR"`). Every value is a plain string — no JSON blob, since
182
+ the container has no `jq` to parse one. A shared `toShellEnvKey`
183
+ helper (in `@checkstack/automation-common`) derives the names so the
184
+ shell editor's `$` autocomplete lists exactly what the runtime injects.
185
+
186
+ **One syntax per field kind (editor + runtime).** `MultiTypeEditorField`
187
+ no longer offers `{{ }}` autocomplete in `typescript` / `javascript` /
188
+ `shell` editors, and the dispatch engine no longer template-renders
189
+ native-code config fields (those whose `x-editor-types` is a code type) —
190
+ so `{{ }}` can't be used in a script by accident. Text / markup editors
191
+ (`raw`, `json`, `yaml`, `xml`, `markdown`, `formdata`) and plain string
192
+ fields keep `{{ }}` as before. Because both the automation and
193
+ health-check editors share `MultiTypeEditorField`, they behave
194
+ identically.
195
+
196
+ **Script-editor IntelliSense polish.** The code editors got a few
197
+ ergonomic fixes so the typed context is actually usable: the suggestion
198
+ **details panel auto-opens** (so long completion names are legible
199
+ on-focus, not hidden behind the chevron); word-based keyword noise is
200
+ disabled in favour of language-service + provider completions; and a
201
+ TS/JS completion provider makes `context.artifacts.` list the in-scope
202
+ artifact ids and **auto-convert the dot to bracket notation** —
203
+ `context.artifacts["integration-jira.issue"]` — since those ids aren't
204
+ valid identifiers. (Driven by a new opt-in `dottedKeyCompletions` prop on
205
+ the editor / `DynamicForm`.)
206
+
207
+ **BREAKING (beta):** `{{ }}` interpolation inside a script action's
208
+ `script` field (shell or TypeScript) is no longer expanded at run time —
209
+ read run data via the typed `context` object (TS) or `$CHECKSTACK_*` env
210
+ vars (shell) instead. Non-script config fields are unchanged.
211
+
212
+ Also fixes: switching a provider action in the visual editor now resets
213
+ its config, so the validator no longer reports the previous action's keys
214
+ as unrecognised.
215
+
216
+ - 41c77f4: feat(automation): Phase 10 — built-in triggers + actions
217
+
218
+ Ships the core automation catalog every install has out of the box:
219
+
220
+ **Triggers** (setup-backed via the shared
221
+ `automation-builtin-triggers` queue):
222
+
223
+ - `automation.cron` — recurring queue job on a cron pattern. Config:
224
+ `{ cronPattern }`. Payload: `{ firedAt }`.
225
+ - `automation.interval` — recurring queue job on a fixed interval.
226
+ Config: `{ intervalSeconds }`. `startDelay = intervalSeconds` so an
227
+ operator doesn't see a tick the instant they save the automation.
228
+ - `automation.template` — polls a boolean template at `intervalSeconds`
229
+ cadence and fires on the false → true edge. Uses
230
+ `template-engine.evaluateBoolean` with `{ now }` in scope; invalid
231
+ templates throw at setup so the operator sees the error in the
232
+ editor rather than as silently-never-firing.
233
+
234
+ All three share a single consumer + module-scoped `tickHandlers` map
235
+ keyed by jobId. Restart semantics work the same way regardless of the
236
+ queue backend: `setupTriggerSubscriptions` re-runs every enabled
237
+ automation's `setup()` in `afterPluginsReady` on every boot, and
238
+ `setup()` calls `scheduleRecurring(...)` with a deterministic jobId.
239
+ On a persistent queue (BullMQ/Redis), the second call is an in-place
240
+ update of the surviving recurring job. On the in-memory queue — whose
241
+ recurring-schedule map is wiped at shutdown — it re-creates the
242
+ schedule from scratch. Either way the schedule is back in place
243
+ before the consumer would dispatch.
244
+
245
+ **Actions**:
246
+
247
+ - `automation.log` — write a single line to the run logger at the
248
+ requested level (debug/info/warn/error). No artifact, no external
249
+ delivery — the cheapest "I want to see something happened here"
250
+ primitive, useful inside `choose` / `parallel` branches as a no-op
251
+ placeholder until the operator wires the real action.
252
+ - `automation.notify_user` — thin wrapper over
253
+ `NotificationApi.sendTransactional` so the core install has a
254
+ "notify a user" action without depending on the integration-
255
+ notification plugin. Produces `automation.notify_user_result`
256
+ (per-strategy outcome).
257
+
258
+ The built-in catalog is registered directly via the trigger/action
259
+ registries in `init()` — no extension-point round-trip needed, since
260
+ automation-backend owns the registry. Pulls in
261
+ `@checkstack/notification-common` as a runtime dep for the
262
+ service-mode RPC call.
263
+
264
+ - 41c77f4: feat(automation): backend RPC router with the full 15-endpoint contract
265
+
266
+ Wires up `core/automation-backend/src/router.ts` covering automation CRUD,
267
+ definition validation, manual runs, run history, registry introspection,
268
+ and a template playground. The contract is refactored to use the
269
+ project's `proc()` pattern so `autoAuthMiddleware` enforces `read` /
270
+ `manage` access automatically, and `AutomationApi` is exported via
271
+ `createClientDefinition` for the frontend client.
272
+
273
+ - 41c77f4: feat(automation): one-time migration of webhook subscriptions + remove legacy integration backend
274
+
275
+ **BREAKING CHANGES** (platform is in BETA — no major bump):
276
+
277
+ - `IntegrationProvider` no longer carries `config` (subscription
278
+ config) or `deliver`. The interface now models a connection provider
279
+ only: connection schema + `getConnectionOptions` + `testConnection`.
280
+ - The legacy subscription / delivery-log / event endpoints
281
+ (`listSubscriptions`, `createSubscription`, `getDeliveryLogs`,
282
+ `listEventTypes`, …) are removed from `integrationContract`.
283
+ - `delivery-coordinator`, `hook-subscriber`, `event-registry`, and the
284
+ `integrationEventExtensionPoint` are deleted. Plugins that
285
+ previously called `integrationEvents.registerEvent(...)` now
286
+ register their hooks as automation triggers via
287
+ `automationTriggerExtensionPoint.registerTrigger(...)`.
288
+ - Frontend pages `IntegrationsPage` and `DeliveryLogsPage` are gone;
289
+ the integration plugin's only remaining UI is connection
290
+ management. Subscription management lives under `/automation/...`.
291
+ - `webhook_subscriptions` and `delivery_logs` tables stay in the
292
+ database for one release as a safety net (no code reads or writes
293
+ them), and will be dropped in a follow-up migration.
294
+
295
+ **New**:
296
+
297
+ - `jira.create_issue`, `teams.post_message`, `webex.post_message`,
298
+ `webhook.send`, `integration-script.run_shell`, and
299
+ `integration-script.run_script` actions registered against the
300
+ Automation Platform with matching `*.message`, `*.delivery`,
301
+ `shell.result`, and `script.result` artifact types. The script
302
+ plugin exposes **two** actions — `run_shell` runs bash via the
303
+ shared `ShellScriptRunner` (Monaco `shell` editor), `run_script`
304
+ runs an ESM module in a Bun subprocess via `EsmScriptRunner`
305
+ (Monaco `typescript` editor + `defineIntegration` helper) — to
306
+ preserve the legacy provider split. `jira.create_issue` keeps the
307
+ dynamic field-mapping dropdown (driven by
308
+ `JIRA_RESOLVERS.FIELD_OPTIONS`).
309
+ - One-time data migration runs on boot in
310
+ `automation-backend.afterPluginsReady`. It reads
311
+ `webhook_subscriptions` via a new service RPC
312
+ `IntegrationApi.listLegacySubscriptions`, translates each row into
313
+ a single-trigger / single-action automation (marked with
314
+ `managed_by = "migrated-subscription:<id>"`), and is idempotent
315
+ across restarts.
316
+ - Failed translations are recorded in a new
317
+ `automation_migration_failures` table and surfaced via
318
+ `AutomationApi.listMigrationFailures` /
319
+ `acknowledgeMigrationFailure` so admins can review and re-create
320
+ failed entries by hand.
321
+
322
+ - 41c77f4: fix(automation): qualify action `produces` / `consumes` with the owning plugin id
323
+
324
+ `context.artifacts` showed up untyped (no fields) in the script editor
325
+ because action `produces` / `consumes` were hand-written full strings
326
+ (`"jira.issue"`) that did not match the artifact-type registry's
327
+ qualified id. The registry derives `${pluginId}.${id}`, and the plugin's
328
+ id is the package name `integration-jira`, so the artifact type actually
329
+ registers as `integration-jira.issue` — the editor's schema lookup
330
+ (`produces` vs registered `qualifiedId`) missed, leaving the artifact's
331
+ fields unknown. (Runtime store/consume happened to agree with each other
332
+ on the short string, so it "worked" but typed nothing.)
333
+
334
+ The action registry now qualifies `produces` with the owning plugin id,
335
+ exactly as it already qualifies the action's own `id` and as the
336
+ artifact-type registry qualifies the artifact type id — so the three can
337
+ never drift. Actions declare the **local** artifact id:
338
+
339
+ - `produces: "issue"` → registered as `integration-jira.issue`,
340
+ - `consumes: ["issue"]` → resolved against the owning plugin's namespace
341
+ at run time; `consumedArtifacts` is keyed by the local id, so an
342
+ action's `execute` reads `consumedArtifacts["issue"]`.
343
+
344
+ All five artifact-producing integration plugins (jira / teams / webex /
345
+ webhook / script) now declare local ids. With `produces` matching the
346
+ registered artifact type, the editor types `context.artifacts[...]` with
347
+ the real schema (e.g. `issueKey`, `projectKey`, `issueUrl`).
348
+
349
+ **BREAKING (beta):** the fully-qualified artifact type ids change from
350
+ the short form to the plugin-prefixed form, e.g. `jira.issue` →
351
+ `integration-jira.issue`. This affects how artifacts are referenced in
352
+ templates (`{{ artifact.integration-jira.issue.issueKey }}`), the TS
353
+ script `context.artifacts["integration-jira.issue"]`, and shell env names
354
+ (`$CHECKSTACK_ARTIFACT_INTEGRATION_JIRA_ISSUE_ISSUEKEY`). Artifacts are
355
+ per-run and ephemeral, so no stored-data migration is needed.
356
+
357
+ Note: this keeps the same-plugin produce→consume handoff (the current
358
+ pattern). Cross-plugin artifact consumption would need a follow-up to
359
+ allow a fully-qualified `consumes` ref.
360
+
361
+ - 6d52276: feat(automation): expose `trigger.actor` so automations can filter on who/what caused an event
362
+
363
+ Every platform event now carries an **actor** - the user, application (API
364
+ client), service (backend-to-backend), or `system` (background /
365
+ unauthenticated) that caused it - and the automation engine surfaces it to
366
+ automations as `trigger.actor`. This lets a trigger filter gate on the
367
+ origin of the event it reacts to:
368
+
369
+ ```text
370
+ {{ trigger.actor.type == "system" }} # auto-created by the platform
371
+ {{ trigger.actor.type == "user" }} # a human
372
+ {{ trigger.actor.id == "app-deploybot" }} # a specific application
373
+ ```
374
+
375
+ `trigger.actor` is available on **every** trigger - it is injected by the
376
+ platform, not declared per trigger - and editor autocomplete + Run Script
377
+ context types include `trigger.actor.{type,id,name}`.
378
+
379
+ How it works:
380
+
381
+ - **`@checkstack/common`** adds the canonical `Actor` type / `ActorSchema`
382
+ and `SYSTEM_ACTOR`.
383
+ - **`@checkstack/backend-api`** adds `resolveActor(user)` and a
384
+ `HookEventMeta` envelope. The hook listener / `onHook` signature gains an
385
+ optional second `meta` argument (additive, backward compatible).
386
+ - **`@checkstack/backend`** wraps emitted hooks in an envelope so the actor
387
+ travels with the payload through the distributed queue, unwrapping it
388
+ before delivery. The RPC emit path captures the authenticated caller;
389
+ background emits default to the system actor. Raw/legacy queue data is
390
+ treated as a system-actor payload, so delivery stays backward compatible.
391
+ - **`@checkstack/automation-backend`** threads the actor into the dispatch
392
+ scope (`trigger.actor`), available to trigger filters, top-level
393
+ conditions, and all run templates, and persisted in the run's scope
394
+ snapshot. Manual runs are attributed to the invoking user.
395
+ - **`@checkstack/automation-common`** / **`@checkstack/automation-frontend`**
396
+ expose `trigger.actor` in the editor variable scope and the generated
397
+ Run Script `context.trigger.actor` types.
398
+
399
+ No database migration and no per-trigger schema changes: the actor rides as
400
+ event-envelope metadata and in the run scope snapshot.
401
+
402
+ - 6d52276: feat(automation): expose `trigger.id` and reconcile the trigger scope so multiple triggers are distinguishable
403
+
404
+ Automations with more than one trigger could not tell which trigger fired:
405
+ the trigger id wasn't queryable, and scripts only received `trigger.event`
406
+ (so two triggers on the same event were indistinguishable). This exposes a
407
+ consistent trigger contract everywhere - `trigger.id`, `trigger.event`,
408
+ `trigger.actor`, `trigger.payload` - in templates, shell, and TypeScript
409
+ scripts.
410
+
411
+ - **`trigger.id` is now available** in templates (`{{ trigger.id }}`) and in
412
+ the script context (`context.trigger.id`). It is typed as the **literal
413
+ union** of the automation's trigger ids, so it discriminates triggers -
414
+ including two subscribed to the same `event`.
415
+ - **Auto-generated trigger ids.** The editor now assigns a unique, log-
416
+ friendly id to every trigger (derived from its event, e.g.
417
+ `incident_created`, deduped as `incident_created_2`), mirroring action ids:
418
+ seeded on the starter automation, assigned on add, and re-filled on blur.
419
+ - **Scripts now receive `trigger.id` and `trigger.actor`.** The
420
+ `ActionRunScope` projection previously dropped both (it only forwarded
421
+ `event` + `payload`), so `context.trigger.actor` was typed but never
422
+ populated - that gap is fixed.
423
+ - **Scope key reconciled.** The internal dispatch scope now exposes
424
+ `trigger.event` as the canonical key (matching the editor and script
425
+ contract) instead of leaking `trigger.eventId`; `trigger.eventId` is kept
426
+ as a back-compat alias, so `{{ trigger.event }}` now resolves in template
427
+ fields where it previously returned `undefined`.
428
+
429
+ No database migration: the actor and id ride in the run scope snapshot. A
430
+ shared `deriveTriggerId` is exported from `@checkstack/automation-common` so
431
+ the editor, generated script types, and the runtime all agree on derived ids.
432
+
433
+ ### Patch Changes
434
+
435
+ - Updated dependencies [e2d6f25]
436
+ - Updated dependencies [e1a2077]
437
+ - Updated dependencies [41c77f4]
438
+ - Updated dependencies [41c77f4]
439
+ - Updated dependencies [41c77f4]
440
+ - Updated dependencies [41c77f4]
441
+ - Updated dependencies [4832e33]
442
+ - Updated dependencies [6d52276]
443
+ - Updated dependencies [6d52276]
444
+ - Updated dependencies [35bc682]
445
+ - @checkstack/automation-common@0.2.0
446
+ - @checkstack/template-engine@0.2.0
447
+ - @checkstack/integration-common@0.6.0
448
+ - @checkstack/common@0.12.0
449
+ - @checkstack/backend-api@0.18.0
450
+ - @checkstack/command-backend@0.1.31
451
+ - @checkstack/notification-common@1.2.1
452
+ - @checkstack/signal-common@0.2.5
453
+ - @checkstack/queue-api@0.3.6
@@ -0,0 +1,80 @@
1
+ CREATE TABLE "automation_artifacts" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "automation_id" text NOT NULL,
4
+ "run_id" text NOT NULL,
5
+ "step_id" text NOT NULL,
6
+ "action_id" text,
7
+ "artifact_type" text NOT NULL,
8
+ "data" jsonb NOT NULL,
9
+ "context_key" text,
10
+ "closed_at" timestamp,
11
+ "created_at" timestamp DEFAULT now() NOT NULL
12
+ );
13
+ --> statement-breakpoint
14
+ CREATE TABLE "automation_run_steps" (
15
+ "id" text PRIMARY KEY NOT NULL,
16
+ "run_id" text NOT NULL,
17
+ "action_path" text NOT NULL,
18
+ "action_id" text,
19
+ "action_kind" text NOT NULL,
20
+ "provider_action_id" text,
21
+ "status" text DEFAULT 'pending' NOT NULL,
22
+ "attempts" integer DEFAULT 0 NOT NULL,
23
+ "error_message" text,
24
+ "result_payload" jsonb,
25
+ "started_at" timestamp DEFAULT now() NOT NULL,
26
+ "finished_at" timestamp
27
+ );
28
+ --> statement-breakpoint
29
+ CREATE TABLE "automation_runs" (
30
+ "id" text PRIMARY KEY NOT NULL,
31
+ "automation_id" text NOT NULL,
32
+ "trigger_id" text NOT NULL,
33
+ "trigger_event_id" text NOT NULL,
34
+ "trigger_payload" jsonb NOT NULL,
35
+ "context_key" text,
36
+ "status" text DEFAULT 'pending' NOT NULL,
37
+ "error_message" text,
38
+ "started_at" timestamp DEFAULT now() NOT NULL,
39
+ "finished_at" timestamp
40
+ );
41
+ --> statement-breakpoint
42
+ CREATE TABLE "automation_wait_locks" (
43
+ "id" text PRIMARY KEY NOT NULL,
44
+ "run_id" text NOT NULL,
45
+ "action_path" text NOT NULL,
46
+ "event_id" text NOT NULL,
47
+ "context_key" text,
48
+ "filter_template" text,
49
+ "timeout_at" timestamp,
50
+ "created_at" timestamp DEFAULT now() NOT NULL
51
+ );
52
+ --> statement-breakpoint
53
+ CREATE TABLE "automations" (
54
+ "id" text PRIMARY KEY NOT NULL,
55
+ "name" text NOT NULL,
56
+ "description" text,
57
+ "status" text DEFAULT 'enabled' NOT NULL,
58
+ "definition" jsonb NOT NULL,
59
+ "managed_by" text,
60
+ "created_at" timestamp DEFAULT now() NOT NULL,
61
+ "updated_at" timestamp DEFAULT now() NOT NULL
62
+ );
63
+ --> statement-breakpoint
64
+ ALTER TABLE "automation_artifacts" ADD CONSTRAINT "automation_artifacts_automation_id_automations_id_fk" FOREIGN KEY ("automation_id") REFERENCES "automations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
65
+ ALTER TABLE "automation_artifacts" ADD CONSTRAINT "automation_artifacts_run_id_automation_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "automation_runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
66
+ ALTER TABLE "automation_artifacts" ADD CONSTRAINT "automation_artifacts_step_id_automation_run_steps_id_fk" FOREIGN KEY ("step_id") REFERENCES "automation_run_steps"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
67
+ ALTER TABLE "automation_run_steps" ADD CONSTRAINT "automation_run_steps_run_id_automation_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "automation_runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
68
+ ALTER TABLE "automation_runs" ADD CONSTRAINT "automation_runs_automation_id_automations_id_fk" FOREIGN KEY ("automation_id") REFERENCES "automations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
69
+ ALTER TABLE "automation_wait_locks" ADD CONSTRAINT "automation_wait_locks_run_id_automation_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "automation_runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
70
+ CREATE INDEX "automation_artifacts_context_lookup_idx" ON "automation_artifacts" USING btree ("automation_id","context_key","artifact_type","created_at");--> statement-breakpoint
71
+ CREATE INDEX "automation_artifacts_action_lookup_idx" ON "automation_artifacts" USING btree ("automation_id","action_id","created_at");--> statement-breakpoint
72
+ CREATE INDEX "automation_artifacts_open_idx" ON "automation_artifacts" USING btree ("automation_id","closed_at");--> statement-breakpoint
73
+ CREATE INDEX "automation_run_steps_run_idx" ON "automation_run_steps" USING btree ("run_id");--> statement-breakpoint
74
+ CREATE INDEX "automation_runs_automation_idx" ON "automation_runs" USING btree ("automation_id","started_at");--> statement-breakpoint
75
+ CREATE INDEX "automation_runs_status_idx" ON "automation_runs" USING btree ("status");--> statement-breakpoint
76
+ CREATE INDEX "automation_runs_context_key_idx" ON "automation_runs" USING btree ("automation_id","context_key");--> statement-breakpoint
77
+ CREATE INDEX "automation_wait_locks_event_lookup_idx" ON "automation_wait_locks" USING btree ("event_id","context_key");--> statement-breakpoint
78
+ CREATE INDEX "automation_wait_locks_timeout_idx" ON "automation_wait_locks" USING btree ("timeout_at");--> statement-breakpoint
79
+ CREATE INDEX "automations_status_idx" ON "automations" USING btree ("status");--> statement-breakpoint
80
+ CREATE INDEX "automations_managed_by_idx" ON "automations" USING btree ("managed_by");
@@ -0,0 +1,12 @@
1
+ CREATE TABLE "automation_run_state" (
2
+ "run_id" text PRIMARY KEY NOT NULL,
3
+ "scope_snapshot" jsonb NOT NULL,
4
+ "last_action_path" text,
5
+ "last_heartbeat_at" timestamp DEFAULT now() NOT NULL,
6
+ "updated_at" timestamp DEFAULT now() NOT NULL
7
+ );
8
+ --> statement-breakpoint
9
+ ALTER TABLE "automation_wait_locks" ADD COLUMN "kind" text DEFAULT 'trigger' NOT NULL;--> statement-breakpoint
10
+ ALTER TABLE "automation_run_state" ADD CONSTRAINT "automation_run_state_run_id_automation_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "automation_runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
11
+ CREATE INDEX "automation_run_state_heartbeat_idx" ON "automation_run_state" USING btree ("last_heartbeat_at");--> statement-breakpoint
12
+ CREATE INDEX "automation_wait_locks_run_idx" ON "automation_wait_locks" USING btree ("run_id");
@@ -0,0 +1,12 @@
1
+ CREATE TABLE "automation_migration_failures" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "subscription_id" text NOT NULL,
4
+ "subscription_name" text NOT NULL,
5
+ "provider_id" text NOT NULL,
6
+ "event_id" text NOT NULL,
7
+ "reason" text NOT NULL,
8
+ "detail" text,
9
+ "provider_config" jsonb NOT NULL,
10
+ "created_at" timestamp DEFAULT now() NOT NULL,
11
+ CONSTRAINT "automation_migration_failures_subscription_id_unique" UNIQUE("subscription_id")
12
+ );