Effective dating: which rate is in force on a date
Area: Bitemporal time Teaches: the valid-time axis —
insert iof(…) at #date#writes a fact whose effectivity begins on a civil date, andas_of <#date#>reads which facts were in force on a given day. This is distinct from (and composable with) the transaction-timeas_of <int>of temporal_as_of_surface. Prerequisites:as_oftime-travel, and first-class relations. Run:ox build examples/effective_dated_tax_v0 && ox run-scenario examples/effective_dated_tax_v0
“What was the standard tax rate on 2024-04-15?” is a valid-time question: it asks what was true in the world on that day, not what the system happened to know at some processing instant. Hand-rolling an effectiveFrom: Date field forces every query to re-implement interval logic. Argon puts effectivity on the substrate: an enactment is written at its effective date, and a date-stamped as_of read projects the rates in force.
What to read in tax.ar
StandardRate is “the standard rate in force”; each enactment is one individual, and its value rides on a rate relation tuple. The enactment writes both at the effective date, so before that date neither holds:
pub type StandardRate;
pub rel rate(r: StandardRate, pct: Decimal);
pub mutate enact(r: StandardRate, pct: Decimal, effective: Date) {
insert iof(r, StandardRate) at effective;
insert rate(r, pct) at effective;
}
The queries read the valid-time axis with a date-literal as_of:
pub query rate_on_2023_06_01() -> StandardRate as_of #2023-06-01#;
pub query rate_on_2024_04_15() -> StandardRate as_of #2024-04-15#;
pub query rate_on_2025_06_01() -> StandardRate as_of #2025-06-01#;
pub query rate_now() -> StandardRate;
Running it
The demo.toml enacts three rates — 20% effective 2020-01-01, 22% effective 2024-01-01, 19% effective 2025-01-01 — passing the Date and Decimal arguments in their exact table forms ({ date = "…" }, { decimal = "…" }). Each date-stamped query then projects the rates whose valid-time has begun by that day:
query tax::rate_on_2023_06_01: 1 row(s) — only the 2020 rate has begun
query tax::rate_on_2024_04_15: 2 row(s) — 2020 + 2024 are both in force by a 2024 filing
query tax::rate_on_2025_06_01: 3 row(s) — 2020 + 2024 + 2025
query tax::rate_now: 3 row(s) — every begun rate (valid = now)
The decisive contrast is rate_on_2023_06_01 vs rate_on_2024_04_15: the same query shape, two civil dates, and the row count rises from 1 to 2 because the 2024 enactment’s valid-time interval opens between them. A rate enacted at #2024-01-01# is invisible to a 2023 read and visible to a 2024 read — the effectivity is enforced by the substrate, not by a where clause in the query.
Honest caveats (what runs today)
- The rate table is append-only in this v0: each enactment opens a valid-time interval but does not close the prior one, so a later date sees all begun rates accumulated (hence 3 rows in 2025), not only the single rate that superseded. Closing the prior interval (so a date sees exactly one rate) is the
during/ bitemporal-retraction follow-on. Datearguments use the{ date = "…" }table form (a bare string parses as an individual);Decimalarguments use{ decimal = "…" }, parsed exactly rather than through a float.- This is the valid-time axis (true-in-the-world). It composes with — and is distinct from — the transaction-time axis of temporal_as_of_surface (when the system learned a fact).
This example is compiled and run in CI; its valid-time as_of <#date#> round-trip is pinned by a corpus test (oxc-runtime/tests/examples_corpus.rs), so the effective-dating surface can’t drift from the language.