All documentation

Event Engine

How and where workflow evaluation actually gets triggered — written with the same honesty standard as docs/ARCHITECTURE.md's connector Event Bus section, because the exact same deployment constraint applies here.

The constraint

> This app has no persistent worker process. Every dashboard action > (a Server Component render, a Server Action, a Route Handler request) > stands up what it needs and tears it down within that one request.

A truly "always-on, push-based" event (a trade fires at 3am with nobody looking) needs a long-running process holding state between events — this deployment doesn't have one, the same gap documented for live broker streaming. The Event Engine is real and typed, not faked, but it only fires at points that are honestly reachable within that constraint.

Where it actually fires today

1. External cron — GET /api/automation/workflows/run, gated by AUTOMATION_WORKFLOW_RUN_SECRET (same shared-secret pattern as /api/admin/alerts/check and /api/ai/automations/run — a separate route and a separate secret from the latter, which still drives the distinct, simpler ai_automations engine unchanged). Point an external scheduler (Vercel Cron, GitHub Actions, any cron host) at this URL. Each run: loads every user with at least one active workflow, builds that user's SharedEvaluationContext for real (context-builder.ts), and evaluates every trigger type that doesn't need a same-request event — daily/weekly/monthly_drawdown, profit_target_reached, consecutive_wins/losses, time_schedule, market_open/close, prop_firm_rule_warning, ai_recommendation_generated, and news_event's best-effort signal. 2. Manual trigger — the dashboard's "Run Now" button (runManualWorkflowAction) evaluates one specific workflow inline, for real, within that request. 3. The guided wizard's natural-language box — calls the Workflow Parser inline; not an "event" in the trigger sense, but a real, working request-scoped AI call.

Where it's real but not wired to anything yet

evaluateSyncEvent() (src/lib/automation/sync-bridge.ts) is a complete, tested bridge from a same-request event to WorkflowEngine — for trade_opened, trade_closed, position_modified, broker_connected, broker_disconnected. It is not called from anywhere in this codebase today, because:

  • This app has no inbound trade-signal receiver — CopyEngine.submit()

is only ever invoked from tests today; there's no webhook or live broker push session that would produce a trade_opened/trade_closed moment to bridge from (confirmed by an exhaustive search of every CopyEngine.submit()/new CopyEngine() call site in src/app).

  • Nothing in this codebase's current request flows connects a broker

and then does something worth firing a workflow from at that exact moment — withLiveConnector()'s existing call sites (Test Connection, Broker Status, Live Positions) are read-only checks, not a "this broker just came online" event worth propagating yet.

This is the same shape as RithmicTransport/MarketDataProvider: a real, documented, fully-typed interface with no fabricated caller. Wiring it in later — the moment either gap above is closed — is exactly one function call (evaluateSyncEvent(supabase, manager, userId, { type: 'trade_opened', ... })) at that new code's own call site. Nothing in WorkflowEngine, trigger-engine.ts, or anywhere else needs to change.

What every event type actually needs

Trigger typeFires via
daily_drawdown, weekly_drawdown, monthly_drawdownCron (real, computed from trades)
profit_target_reachedCron
consecutive_wins, consecutive_lossesCron
time_scheduleCron, gated by scheduler.ts
market_open, market_closeCron, real FX session hours (market-sessions.ts)
prop_firm_rule_warningCron (real, from getPropFirmAccountStatus)
ai_recommendation_generatedCron (real, from ai_insights)
news_eventManual, or cron's best-effort "a fresh market brief exists" signal — no live news feed exists
manual_triggerManual only, by design
trade_opened, trade_closed, position_modified, broker_connected, broker_disconnectedReady (evaluateSyncEvent), not wired — see above

Deploying the cron job

Same steps as docs/ARCHITECTURE.md documents for the existing /api/admin/alerts/check and /api/ai/automations/run routes:

1. Set AUTOMATION_WORKFLOW_RUN_SECRET in your deployment environment (generate with node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"). 2. Point an external scheduler at GET /api/automation/workflows/run with header x-automation-run-secret: <the secret>. A 1-5 minute interval is reasonable for time-based triggers; the route is cheap when there are no active workflows to evaluate.