Skip to content

ADR 0017 pure deterministic core

Karthik P edited this page Oct 31, 2026 · 1 revision

ADR-0017: Pure, deterministic core with no global state

The core and the bindings read no clock, randomness, environment variable or global state, and every operation takes a calendar value and its inputs explicitly.

Last reviewed: 31 Oct 2026

Status

Accepted on 31 Oct 2026 by maintainer decision. The rule comes from design principles 4 and 8 of the research proposal; the maintainer accepted it as a separate ADR, which the original ADR list did not have. Built in 0.1.0.

Context

The proposal lists fiscalyear's global module state as a limit: its open issue #6 asks for several calendar settings in one app (fiscalyear #6, open as of 31 Oct 2026). A server that answers for many organisations needs many calendars at once. Free-threaded Python (ADR-0011) needs code that is safe without the GIL. Agents need repeated calls to give the same answer, so "current period" queries cannot read the clock.

Decision drivers

  • Many calendars in one process.
  • Thread safety, including free-threaded Python.
  • Reproducible results for tests and agents.

Considered options

Option 1: A module-level default calendar (as in fiscalyear)

Pros:

  • Short calls once set up.

Cons:

  • One calendar per process; tests and multi-tenant servers interfere with each other.

Option 2: Explicit, immutable calendar values; no clock or environment (chosen)

Pros:

  • Any number of calendars, each safe to share between threads.
  • Same inputs, same output, on any machine.

Cons:

  • Callers pass the calendar and the "today" date on every call.

Option 3: Explicit values, but with today() conveniences

Pros:

  • Shorter calls for dashboards.

Cons:

  • Results depend on when and where the code runs, which breaks reproducibility and the cross-language comparison.

Decision

Option 2. AGENTS.md design rules 3 and 6:

  • No static mut, thread_local!, lazy global cache, module-level mutable variable or environment-variable read exists in crates/core or in any binding. Every operation takes a calendar value.
  • No code in crates/core or the bindings calls a clock or reads randomness. "Current" queries take the date as an argument.

The core is #![no_std] with alloc and #![forbid(unsafe_code)] (crate README). spec/api-v1.md states that no operation reads the clock, the environment or any global state.

Consequences

Good:

  • Calendars are thread-safe without locks, which is what makes the free-threaded wheels safe.
  • Cross-language transcripts can be compared byte for byte, because nothing in them depends on the run.

Bad:

  • Callers who want "this quarter" must supply today's date themselves.
  • There is no global cache, so a binding that wants caching must hold it in the calendar value.

Links

Clone this wiki locally