Skip to content

CreatorTrack Automations engine (generic trigger/condition/action)

URL: https://mkdocs.justinsforge.com/memory/general/reference_creatortrack_automations/

A GENERIC per-workspace automation engine: a rule is {trigger:{event,filters}, actions:[...]} evaluated after every task create/update. NOT a "dev mode" feature, the dev-pipeline rules (status to In Review, status to Ready to Merge => open PR) are just PRELOADED EXAMPLES of the same primitive a content creator uses ("status to Published => notify"). Built 2026-07-11 (Phase 2 of the automated-dev-pipeline plan) on branch feat/automations-github-pipeline.

Status: DEPLOYED to prod 2026-07-12 (merged to main 1b4a3b4, automations migration applied, creatortrack-prod restarted). Verified live: app.automations table + RLS present, task writes unaffected by the mutation-path hook. Security-reviewed before deploy (6 cautions fixed: XSS href allowlist, fan-out cap MAX_ACTIONS=50, zero-rule query cache, admin-gated authoring, outbound idempotency, uniform webhook 401).

Data model

  • Table app.automations (migration 20260711T233000_feat_automations_automations_rule_engine.sql): id, workspace_id, name, enabled, trigger jsonb, actions jsonb, position, created_by, created_at, updated_at. Workspace RLS via core.apply_workspace_rls. Index automations_ws_enabled_idx (workspace_id, enabled).
  • Storage is a real workspace-scoped table (not user prefs) because automations belong to the WORKSPACE: any member's task change fires them.

Engine (lib/automations.ts)

  • Events: task.created, task.updated. Conditions: {field, op, value} where op is equals|not_equals|changed|changed_to|contains|is_empty|is_not_empty; field is a core task key (status/priority/title/done/due/assignee/tags) OR a custom field NAME on the tasks db (e.g. Result). Filters are AND.
  • Actions: internal set_status | set_priority | set_field | assign | add_subtask | notify (run through updateTask/createTask) + external webhook (delivers via lib/webhooks.ts deliverOutboundWebhook, SSRF-guarded, signed) + github (opens branch/PR/issue via lib/github.ts, see github integration).
  • Hooked into lib/tasks.ts createTask/updateTask via fireAutomations (awaited, best-effort, never fails the task write).
  • LOOP GUARD (critical): AsyncLocalStorage execution-depth counter, MAX_DEPTH = 5. An action that updates a task re-enters the engine at depth+1; past MAX_DEPTH the engine stops and logs one automation_blocked activity row instead of cascading forever. Verified: a no-filter self-cascading rule runs exactly 5 times then blocks, the request returns (no hang).
  • Each rule that fires logs an automation_run activity row (actorKind service, label Automation) with per-action results, for the unified feed.

Builder UI (components/apps/automations/AutomationsSettings.tsx)

  • CRUD API app/api/automations/route.ts (+ [id]), session-gated, RLS-enforced. GET returns rules + catalog (events/ops/fields/actions) + the workspace status options + custom field names + outbound webhooks.
  • Pane in Settings under Workspace ("Automations" section, added to components/profile/SettingsModal.tsx). Per-workspace selector, enable toggles, a WHEN/IF/THEN rule editor rendered from the catalog, and "Add starter rules" (idempotent preload of the pipeline examples, disabled by default).

Verification

scripts/dev-verify-github-* and inline dev checks: rule fires (status changed_to In Review => set_priority High + notify), loop guard holds, builder CRUD + preload + headless render all pass. See Phase 2 results.

[Claude Code]