Skip to content

feat(engine): Clock trait — virtual time for scheduler and time-based checks#65

Merged
ovasylenko merged 1 commit into
mainfrom
feat/clock-trait
Jun 10, 2026
Merged

feat(engine): Clock trait — virtual time for scheduler and time-based checks#65
ovasylenko merged 1 commit into
mainfrom
feat/clock-trait

Conversation

@ovasylenko

Copy link
Copy Markdown
Contributor

Summary

Item #11 from docs/FEATURE_OPPORTUNITIES.md: now() abstracted behind a Clock trait (SystemClock default, ManualClock for tests/tooling), injected via SchedulerConfig.clock so no public signature changed — engine, mobile, server all compile unchanged.

  • Clock governs: due-instance claiming, all deferral next_fire_at computation (delay, send-window, rate-limit, concurrency, retry backoff), SLA/human-input timeout checks, cron due-claiming + next-fire. Record stamps (created_at/updated_at, audit columns) stay on real time — boundary documented in orch8_types::clock.
  • No storage SQL changed: claim queries already bind app-side timestamps, so the virtual clock fully controls claiming.
  • A 3-day-delay workflow now tests in 0.02s via ManualClock::advance(Duration::days(3)). README's "no time-skipping test tooling" limitation rewritten.

⚠️ One deliberate behavior change

Fast-path step delays previously re-applied on every claim — a duration-delayed step could never execute (pinned by the old tick_delay_reapplies_on_retick test). check_step_delay now records a _delay_until:<block_id> marker and lets the step run once now >= fire_at. Known edge: resume-from-block re-runs of the same block don't re-apply a served delay.

Tests

2650 passed across engine/storage/mobile (39 suites); new virtual-time e2e suite (3-day delay, send-window boundary crossing via set(), SystemClock sanity). fmt/clippy(pedantic)/doc gates clean.

🤖 Generated with Claude Code

… checks

Abstract "now" behind a Clock so tests (and a future orch8 dev
--skip-timers mode) can advance time manually.

- orch8-types/src/clock.rs: Clock trait, SystemClock (default),
  ManualClock (advance/set, lock-protected), SharedClock handle.
- SchedulerConfig gains a serde-skipped `clock` field defaulting to
  SystemClock — no public signature changes anywhere; orch8-mobile
  compiles unchanged.
- Scheduler reads the clock for all scheduling decisions: claiming due
  instances, semaphore/concurrency deferrals, delay / send-window /
  rate-limit pre-flight checks, retry backoff, SLA deadline and
  human-input timeout baselines, signalled-instance wakes, and cron
  evaluation (run_cron_loop_with_clock, calculate_next_fire_after).
- Delay checks now record a "_delay_until:<block>" metadata marker so
  a served delay executes instead of re-deferring forever — this makes
  duration delays actually complete (previously they re-applied on
  every claim) and enables the 3-day-delay virtual-time test.
- Record timestamps (created_at/updated_at, audit rows, DB-side NOW())
  stay on real time; boundary documented in orch8_types::clock.
- Tests: ManualClock/SystemClock unit tests; e2e tests where a 3-day
  delay and a send-window boundary complete in milliseconds via
  ManualClock; full engine/storage/mobile suites pass (2650 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ovasylenko ovasylenko merged commit 5ef964c into main Jun 10, 2026
14 checks passed
@ovasylenko ovasylenko deleted the feat/clock-trait branch June 10, 2026 10:30
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.

1 participant