Workflows: a schedule trigger in the tenant's time zone, with catch-up and one run per due time
- Dominant language
- C#
- Stars
- 6
- Forks
- 7
- Avg merge
- 4h 42m
- Merged PRs (30d)
- 307
Description
Split from #255 with Arnel's agreement on 14 September 2026: the workflow side of scheduling moves to 4.3.0, beside the workflow chain work it pairs with (#575, #831, #835). #255 keeps the scheduling API other modules use, on 5.0.0.
Acceptance case: every day at midnight, sweep attendance (#835).
## Change
- **A schedule trigger on a workflow.** `{ "schedule": "0 0 * * *", "timeZone": "tenant" }`. Cron with five fields, validated when the workflow is saved, with the next five due times returned so barakoBrew can show them. The tenant's time zone comes from site settings (#793); an explicit IANA zone is also accepted.
- **The due time as input.** A scheduled run carries `{{schedule.at}}` and `{{schedule.previousAt}}`, so "yesterday" is computed from when the run was due, not when it ran.
- **Missed runs.** If the API was down at a due time, a catch-up policy per workflow decides: `once` (default, fire one run for the latest missed time), `each` (fire every missed time, capped), or `skip`.
- **Exactly one run per due time.** The due time is the idempotency key, claimed in the database, so several API instances or a restart never fire it twice.
- **Daylight saving.** A due time that does not exist in the zone fires at the next valid minute; one that occurs twice fires once.
- **Operations.** Scheduled runs appear in workflow runs with their due time. A workflow can be paused, which stops new due times without deleting the schedule.
- **Built on `ScheduledContentService`'s sweep,** so there is one timer in the process, and #255 can later move both onto its module API.
## Done when
- With a fake clock, a midnight schedule in Asia/Manila fires once at 16:00 UTC, with `schedule.previousAt` the previous midnight.
- Stopping the host across two due times and starting it fires one run under `once` and two under `each`.
- Two hosts on one database fire one run for a due time.
## Where it lives
Core: workflow triggers and the one sweep timer, `ScheduledContentService`. #255 keeps the scheduling API for modules.
Contributor guide
Research direction
Start with the workflow trigger implementation and ScheduledContentService, then trace how workflow runs and tenant site settings are represented. Use the fake-clock acceptance cases as the initial test map, including Asia/Manila, catch-up policies, and two hosts sharing a database. Done means the listed due-time, pause, daylight-saving, and exactly-once behaviors are covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend, databases, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100