Repository navigation
Add cron-scheduled collection anchor (#491) - #518
Merged
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/v3ParseStandard), 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.signals.schedule/schedule_timezone/schedule_jitter(+SIGNALS_SCHEDULE*envs), validated fail-fast inValidateStrict(bad cron / unknown tz / negative jitter = hard error). A set schedule relaxes thepoll_interval>0requirement (ignored in scheduled mode).WithScheduleoption + arunScheduledloop — 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; traceabilityR-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/wunits. New direct depgithub.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 allgreen +scripts/preflight.sh lintexplicitly green (caches pre-cleared). No--no-verify, no weakened gates, no AI attribution.