Determinism¶
This page explains the property the whole architecture rests on: compile(x) equals
compile(x) byte-for-byte, across processes, machines, and Python hash seeds. It
covers why byte-stability is load-bearing rather than cosmetic, what the project
fingerprint promises and pointedly does not, the rules that keep the package
deterministic, and how those rules are enforced mechanically rather than by vigilance.
Why bytes, not just semantics¶
"Semantically equivalent output" would be cheaper to guarantee. Four consumers need more than that:
plan()is a structural diff of two IRs. If compiling the same specs twice could produce different IRs, every diff would drown in phantom changes and change classification would be noise.- Golden tests assert byte equality. Every fixture × target × dialect pair renders to a checked-in file; an unexplained diff means the compiler changed behavior. That signal only exists if unchanged behavior produces unchanged bytes.
- The SQLMesh replan must be a no-op. The strongest end-to-end test in the suite
writes emitted artifacts into a SQLMesh context, plans, and asserts
plan.has_changesis false — the compiler and SQLMesh agree on what the models mean, proven end to end. Nondeterministic emission breaks this first. - The hydration cache is keyed by fingerprint. At request time the planner hydrates a MetricFlow manifest from a cache whose key includes the spec fingerprint. If equal specs could fingerprint differently, the cache would silently miss forever; if different specs could collide, it would silently serve the wrong tenant's semantics.
The fingerprint¶
project_fingerprint(ir) # "blm1:3f9a…" — sha256 over a canonical encoding of the IR
The fingerprint is a content hash of the entire ProjectIR, computed over a
purpose-built canonical byte encoding (not JSON — JSON invites float-formatting and
key-ordering bugs). The blm1: prefix makes it greppable in logs and artifact headers.
It answers "has anything about this project's compiled meaning changed?" and it keys
two caches: the caller's compilation cache, and — as HydrationKey.spec_fingerprint —
the planner's manifest hydration cache.
What it is not:
- Not stable across bloomery versions. The IR shape version is part of the hash, so an IR change alters every fingerprint loudly instead of colliding silently. Specs are the durable thing; fingerprints and artifacts are cache.
- Not a migration key. The full hydration key is
HydrationKey(spec_fingerprint, bloomery_version, metricflow_version)— a bump of bloomery or of the pinned MetricFlow changes the key, so the old cache entry is simply never looked up again. Version mismatch is a cache miss by construction, never an error and never a migration: on a miss, rebuild from specs.
The rules¶
Stated once, applied package-wide:
- No clocks, no randomness, no environment.
datetime.now(),uuid4(),random,os.environ, filesystem, and network are all banned under the package. If an artifact ever needs a timestamp, it is an input parameter. - Never iterate a set where order can reach output. Every IR collection is a tuple; dict-shaped data is stored as sorted key-value tuples.
- Floats are banned in the IR and on emission paths —
Decimalorintonly. Float repr is the classic cross-platform byte-drift source. - Ties break lexicographically. Topological sort ties, mart-selection ties, violation ordering in error aggregates — every ordering decision has an explicit, stable sort key. Sorting is never left to insertion accidents.
- SQLGlot is pinned exactly. Its canonical rendering is part of the output, so a version bump is a deliberate PR that regenerates goldens, never an ambient drift.
Enforcement¶
Intent does not survive contact with a hash-randomized dict, so the contract is proven
by test, not review. The cornerstone is the cross-seed subprocess test: compile the
same fixture in two fresh interpreter processes with different PYTHONHASHSEED values
and assert identical bytes.
def test_determinism_across_processes(project):
out1 = run_compile_subprocess(project, env={"PYTHONHASHSEED": "0"})
out2 = run_compile_subprocess(project, env={"PYTHONHASHSEED": "1"})
assert out1 == out2 # artifact bytes and fingerprint, identical
Any set iteration or hash-ordered traversal anywhere on the output path fails this test immediately, because hash randomization changes iteration order between the two processes. Around it sit property tests (compiling twice in-process yields identical results; the fingerprint survives a spec round trip) and lint rules that reject set iteration and float literals in the IR before a human ever reviews them.
Determinism is also why the compiler never chooses — recipes are recorded upstream and validated here, as Specs and the catalog explains, and every refusal the guardrails make is exactly reproducible for the same spec.