Determinism & calc_version
This API makes a specific, checkable promise:
The same request against the same
calc_versionreturns a byte-identical response. Every time, on every machine, on every CPU architecture we deploy to.
Not “the same to within floating-point noise.” Not “equivalent.” Byte-identical. If you replay a request from six months ago against the same calc_version, you can diff the two response bodies and get nothing.
This page explains what calc_version is, what changes it, what doesn’t, and how to build on it.
What calc_version is
Section titled “What calc_version is”calc_version is the version of the calculation semantics — the complete set of rules that turn an input into served numbers: the ephemeris data and how we query it, coordinate transformations, house and node conventions, and the exact quantization applied to every numeric field before it is serialized.
It appears in two places:
- In
meta.calc_versionon every computation response, alongsidesettings_resolvedandsubject_resolved. - On
GET /health, unauthenticated, so you can check what a deployment is serving without spending a credit:
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/healthThe determinism contract is scoped by this value. Two responses are guaranteed byte-identical when the request bodies are identical and the calc_version values match. Across different calc_version values, numbers may legitimately differ — and the version string is the explanation.
What bumps calc_version
Section titled “What bumps calc_version”Any change that alters any served numeric value for any input. There is no threshold below which a change is “too small to count.”
This is not hypothetical. Here is the real entry from our changelog:
The last decimal of a speed value affects almost nobody. It was version-bumped anyway, because the alternative — quietly changing served numbers under an unchanged version — breaks every downstream cache, golden test, and stored result that trusted us. If we will not silently change the fourth decimal place, you can reason about what we will do with anything larger.
What does not bump calc_version
Section titled “What does not bump calc_version”Changes that cannot alter an existing served value:
- Additive response fields. New keys in
dataormeta(for example, whenelement,modality, andout_of_boundswere added to positions). - New endpoints. A new computation route does not change what existing routes return.
- New optional settings or bodies. Requesting behavior you didn’t request before is your change, not ours.
Your JSON parsing should tolerate unknown fields; your numeric expectations can be exact.
How to use it
Section titled “How to use it”Pin it in your tests. If your integration tests assert exact values from our API, record the calc_version they were captured against. When a test breaks, the first check is whether meta.calc_version moved — if it did, the diff is explained and documented; if it didn’t, you have found a bug in your code or ours, and we want to hear about it.
Store it with cached results. If you cache or persist our responses, keep calc_version alongside them. It tells you exactly which cached entries are still identical to what a fresh call would return, and lets you invalidate precisely instead of guessing.
Use it to explain historical diffs. “This chart looked different in March” is answerable: compare the calc_version recorded then against now, and the changelog for the versions in between states what changed and why.
Where determinism comes from
Section titled “Where determinism comes from”The engine computes with the Swiss Ephemeris, the standard high-precision ephemeris used across professional astrology software — our raw astronomy is independently checkable against any other Swiss Ephemeris implementation.
Determinism is then enforced by quantizing at the source: every numeric value is rounded to its defined precision at the point of computation, before anything downstream touches it — not at serialization time, and never left to the whims of floating-point formatting. Cross-architecture floating-point differences live in the digits below the quantization boundary, so they never reach a served value. The 0.1.1 change above is exactly this principle being enforced: one field’s quantization boundary sat too low, the gate caught it, and the boundary moved.
How releases are gated
Section titled “How releases are gated”Every release candidate must reproduce a golden corpus of more than a thousand checks spanning the input space (historical and future dates, extreme latitudes, retrograde stations, sign boundaries, both zodiacs) — byte-for-byte, on every architecture we deploy to, before it is promoted to serve traffic.
A release that changes any golden output either carries a calc_version bump with a changelog entry, or it does not ship. That gate is what caught the x86/ARM speed discrepancy before it ever served a request — the contract on this page is enforced by machinery, not by policy.
API spec v0.1.0 · docs 0e4f5ec