Skip to content

Add cron-scheduled collection anchor (#491) - #518

Merged
fheikens merged 1 commit into
mainfrom
491-cron-schedule
Oct 13, 2026
Merged

fheikens merged 1 commit into
mainfrom
491-cron-schedule

Conversation

@fheikens

Copy link
Copy Markdown
Contributor

Closes #491. Relates to #489, #490.

Base origin/main: ad4c99ac6f45c945dfb01e2f93de3b947515533e.

Design decisions confirmed with the product owner (2026-10-13): cron expression (precedence over poll_interval), UTC default + opt-in timezone, wait-for-next-slot + jitter (no catch-up).

What

An optional cron schedule anchors collection to wall-clock times instead of ticking from process start (there was no way to say "collect daily at 02:00").

  • internal/schedule (new): parse a standard 5-field cron (robfig/cron/v3 ParseStandard), resolve an IANA timezone (default UTC), compute the next fire strictly after a given instant with optional bounded jitter. now + jitter randomness injected → fully deterministic unit tests, no live clock.
  • Config: signals.schedule / schedule_timezone / schedule_jitter (+ SIGNALS_SCHEDULE* envs), validated fail-fast in ValidateStrict (bad cron / unknown tz / negative jitter = hard error). A set schedule relaxes the poll_interval>0 requirement (ignored in scheduled mode).
  • Collector: WithSchedule option + a runScheduled loop — no start-time baseline, no ticker, never back-fills missed slots, on-demand collection still immediate. No schedule ⇒ interval path byte-for-byte unchanged.

STDD

Spec specifications/scheduled-collection-anchor.md (ACTIVE) + .acceptance.md; traceability R-SCHED-01..08 / INV-SCHED-01..04. Tests: internal/schedule/schedule_test.go (cron Next, timezone/DST, jitter bounds, strictly-after-now, invalid-input), internal/config/schedule_config_test.go (fail-fast validation + precedence), internal/collector/scheduled_run_test.go (no-baseline + on-demand-still-runs, with an interval-mode control). Docs: config schema appendix + examples/signals.yaml.

Notes

Supersedes #490's months/years punt: calendar cadence (daily/weekly/monthly at a time) lives here; flat intervals keep their fixed d/w units. New direct dep github.com/robfig/cron/v3 v3.0.1 (vetted, pure-Go; used for parsing only — the loop stays in our control).

Gates

Full scripts/preflight.sh all green + scripts/preflight.sh lint explicitly green (caches pre-cleared). No --no-verify, no weakened gates, no AI attribution.

Collection ticked every poll_interval anchored to process start, so there
was no way to say "collect daily at 02:00" — a restart re-phased the
cadence to the new boot time.

Add an optional cron schedule that anchors collection to wall-clock times.
Design confirmed with the product owner (2026-10-13): cron expression, UTC
default with an opt-in timezone, wait-for-next-slot with optional jitter
(no catch-up).

- New internal/schedule package: parse a standard 5-field cron expression
  (robfig/cron/v3 ParseStandard), resolve an IANA timezone (default UTC),
  and compute the next fire strictly after a given instant with optional
  bounded jitter. now and the jitter randomness are injected, so cron-Next,
  timezone, and jitter behaviour are unit-tested without a live clock.
- Config: signals.schedule / schedule_timezone / schedule_jitter (+ env),
  validated fail-fast in ValidateStrict (invalid cron/tz/negative jitter is a
  hard error) so the daemon never starts half-configured. A set schedule
  relaxes the poll_interval>0 requirement (it is ignored in scheduled mode).
- Collector: WithSchedule option + a runScheduled loop. When a schedule is
  installed Run does NO start-time baseline and arms no ticker — it waits for
  each cron match, never back-fills slots missed while down, and still serves
  on-demand collection immediately. No schedule ⇒ the interval path is byte-
  for-byte unchanged.

Spec specifications/scheduled-collection-anchor.md (+ .acceptance.md),
traceability R-SCHED-01..08 / INV-SCHED-01..04. Tests: internal/schedule,
internal/config/schedule_config_test.go, internal/collector/scheduled_run_test.go.
Docs: config schema appendix + examples/signals.yaml.

This supersedes the #490 note on months/years: a cron schedule is the
calendar path (daily/weekly/monthly at a chosen time), so flat intervals
keep their fixed-duration d/w units and calendar cadence lives here.

closes #491
@fheikens
fheikens merged commit 466c773 into main Oct 13, 2026
12 checks passed
@fheikens
fheikens deleted the 491-cron-schedule branch October 13, 2026 14:25
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.

Configurable first-run time-of-day / schedule anchor for collection (today it anchors to process start)

2 participants