Skip to content

feat(triggers): cron & one-shot schedules, multi-workflow triggers, and telemetry - #318

Merged
07prajwal2000 merged 3 commits into
Fluxify-rest:mainfrom
07prajwal2000:feat/cron-schedules
Sep 9, 2026
Merged

feat(triggers): cron & one-shot schedules, multi-workflow triggers, and telemetry#318
07prajwal2000 merged 3 commits into
Fluxify-rest:mainfrom
07prajwal2000:feat/cron-schedules

Conversation

@07prajwal2000

Copy link
Copy Markdown
Collaborator

Summary

Closes #304

This PR implements scheduled triggers (cron and one-shot schedules) backed by NATS JetStream message schedules, multi-target trigger dispatch to workflows, OpenTelemetry tracing/metrics for workflows, and UI management improvements across portal routes and workflows.

1. Cron & One-Shot Schedules (NATS JetStream)

  • Dedicated Stream: Configured FLUXIFY_TRIGGERS stream (allow_msg_schedules: true, allow_msg_ttl: true, retention: limits, discard: old) decoupled from work-queue streams.
  • Subjects: Structured as fluxify.trigger.sched.<projectId>.<triggerId> and fluxify.trigger.fire.<projectId>.<triggerId>.
  • Reconciler: Postgres-authoritative reconciler that synchronizes schedules on startup, purges orphaned subjects, and handles deterministic jitter and TTL suppression for catch-up storms.
  • Fire Consumer: Pull consumer on fire events enqueueing workflow jobs with deduplication keys (<triggerId>:<firedAt>).
  • Validation: Strict cron expression parsing and IANA timezone validations with UTC defaults.

2. Multi-Workflow Trigger Relations & Wizard

  • Schema & Migrations: Added trigger_workflows join table (0061_trigger_workflows.sql) supporting M:N relations between triggers and workflows.
  • API: Updated trigger DTOs, endpoints, service, and repository to manage linked workflows and dispatch events across all associated targets.
  • UI Wizard & Forms: Added new FormWizard, TriggerWizard, and modal editors for creating/editing triggers with workflow selection in apps/portal.

3. OpenTelemetry Tracing & Metrics for Workflows

  • OTLP Instrumentation: Extended @fluxify/common/otlp with fluxify.workflow.executions and fluxify.workflow.duration metrics along with consumer span kinds for background workflow runs.
  • Worker & Job Recording: Wired workflow execution tracing into workflowJob.ts and compiledWorker.ts with dedicated unit test coverage.

4. Portal UI Improvements

  • Enhanced route and workflow table views with interactive switch toggles, playground launchers, and empty states.
  • Cleaned up legacy modals and unified component patterns.

Verification

  • Pre-commit pipeline ran and passed all checks:
    • Linting (tsgo --noEmit)
    • Secrets detection
    • FTA complexity checks (fta-cli --score-cap 70)
    • Full automated test suite (1,113 passing tests)
    • Unit tests for triggers, schedule reconciliation, workflow jobs, and OTLP metrics/traces

07prajwal2000 and others added 3 commits September 9, 2026 20:31
Closes Fluxify-rest#304. A trigger can now be fired by the clock: a six-field cron
expression, a fixed interval, or a single instant.

The broker keeps the time. A schedule is a message carrying ADR-51's
`Nats-Schedule` headers, which forces three things:

- It needs its own stream. `FLUXIFY_TRIGGERS` and `FLUXIFY_JOBS` are both
  work-queue, and on those an ack removes the message — so the first consumer
  to read a schedule would delete it. `FLUXIFY_SCHEDULES` is `limits`.
- The fire subject must be captured by that same stream, so the hop from a
  fire to the job queue is structural rather than a design preference.
- One subject holds one schedule, so each trigger gets its own and an update
  is a republish.

Postgres stays authoritative. Every write republishes, and `loadSchedules`
reconciles both directions at boot: schedules NATS never got (a wiped volume,
a restored backup) and schedules whose trigger is gone. Both failures are
silent otherwise — a trigger that never runs, or one that runs forever.

Jitter and TTL live in the reconciler, not the UI. Every `@daily` schedule is
`0 0 0 * * *`, so without jitter they all fire on the same second; the seconds
field is rewritten to `hash(triggerId) % 60` unless the user named one. Fires
carry a TTL of half the interval (capped at 5m) so a server restarted after a
weekend does not replay every overdue fire at once.

The fire consumer runs on the workers rather than beside the reconciler: the
worker fleet is the always-on tier, so a control-plane node going down delays
schedule edits and not the schedules themselves. Its job id is
`<triggerId>:<firedAt>`, so a redelivered fire — or one seen by both a project
worker and a catch-all worker — enqueues exactly one job.

Cron is validated at the API boundary, because a bad cron that fails silently
at 3am is the worst failure mode this feature has. Five-field expressions are
rejected rather than run 60x too often, and timezones are IANA names only —
a fixed offset silently opts out of daylight saving. New schedules default to
UTC, with a warning in the form when they do not.

Found on a real 2.14 server, not in review: it rejects a timezone sent with
`@every` or `@at` outright ("message schedules pattern is invalid"), so the
header goes out for cron and predefined aliases only.

Also pins the NATS image to 2.14 in all three compose files. On an older
server a scheduled trigger is accepted and simply never fires.

Not automated: clustered R3 behaviour, which ADR-51 does not specify. The
catch-up storm is covered by an e2e against a real broker; standing up a
three-node cluster in the test harness is its own piece of work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@07prajwal2000
07prajwal2000 added this pull request to the merge queue Sep 9, 2026
Merged via the queue into Fluxify-rest:main with commit 71520e8 Sep 9, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cron and one-shot schedules

1 participant