Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Time-travel: a refinement’s verdict changes over time

Area: Bitemporal time Teaches: how an iff-refinement composes with bitemporal time-travel — as a field rises across transaction-time, the individual’s membership in the refinement changes per snapshot. The classifier is re-run against the field value as of each instant. Prerequisites: as_of time-travel, and refinement (iff). Run: ox build examples/temporal_promotion && ox run-scenario examples/temporal_promotion

In temporal_as_of_surface the query moved through time. Here the classification moves: an employee crosses a threshold, and the refinement that depends on that field flips from non-member to member — but only for snapshots taken after the crossing.

What to read in hr.ar

FullTime is the iff-refinement over an employee’s weekly hours:

pub type Employee { mut weekly_hours: Int }
pub type FullTime <: Employee iff { self.weekly_hours >= 35 };

pub query all_employees() -> Employee;
pub query full_timers()   -> FullTime;

There is no as_of on these declared queries; the time-travel here is driven by the --as-of flag on ox query, which reads the FullTime extent visible at a chosen transaction-time.

Running it

The demo.toml scenario hires alice at 20 hours/week (below the threshold), then promotes her to 40. The latest snapshot has her full-time:

$ ox run-scenario examples/temporal_promotion
query hr::all_employees: 1 row(s)  alice
query hr::full_timers:   1 row(s)  alice      — 40 >= 35 in the latest state

Travel back, and the verdict flips. The same FullTime extent, read at different transaction-times:

$ ox query examples/temporal_promotion --extent hr::FullTime --as-of 1
extent(hr::FullTime as_of 1): 0 individual(s)             — alice still at 20h, not FullTime

$ ox query examples/temporal_promotion --extent hr::FullTime --as-of 100
extent(hr::FullTime as_of 100): 1 individual(s)  alice    — past the promotion, 40 >= 35

The decisive contrast is --as-of 1 vs --as-of 100: one individual, one refinement, two snapshots — empty before the promotion, {alice} after. The field value is read as of the same instant the membership is evaluated (the latest property assertion with tx_from <= tx wins per field), so the iff classifier sees 20 in the past and 40 in the present.

Honest caveats (what runs today)

  • --as-of takes a transaction-time stamp as an integer. A stamp before any event (--as-of 1) reads the pre-promotion state; a large stamp (--as-of 100) reads the latest.
  • The --as-of flag lives on ox query; ox run-scenario prints the latest snapshot for the declared queries.

This example is compiled and run in CI; the corpus test (oxc-runtime/tests/examples_corpus.rs::corpus_temporal_promotion_full_time_eligibility_changes_over_time) pins exactly this — the FullTime extent differing across transaction-time as the hours rise — so the refinement-over-time composition can’t drift.