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_oftime-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-oftakes 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-offlag lives onox query;ox run-scenarioprints 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.