Drift Validator — High-Level Design
Reading guide: The current package architecture defines the binder boundary. Sections below describe reference designs unless an implementation note says otherwise. Standalone facade, cache and provenance services are dropped; their integration responsibilities belong to the consumer.
Status: Draft for review · Part of: Astro Binder · Updated: 2026-10-24
The drift validator recomputes sampled cases with Swiss Ephemeris and JPL Horizons, then alerts if the primary engine drifts from it. It never runs on a request path.
The comparison and batch-run boundary is implemented in the separate
@astrology/drift-validator package.
It accepts injected adapters and persistence/alert/breaker hooks. Aggregates and streaks are
per run; daily scheduling, concrete stores/alerters, worker isolation and a live Horizons
adapter remain host integrations. TT is checked from provider declarations rather than forced
through the current UTC-instant adapter port. The design below describes the broader target.
1. Purpose
Swiss Ephemeris is the source of record, so its mistakes stay invisible without a second opinion. Drift enters through engine upgrades, different file sets, wrong time-scale or ΔT assumptions, or changed Horizons responses. The validator reports it daily and alerts.
It also proves the Horizons fallback works before anyone needs it.
2. Where it sits
scheduler · live samples · backfill
│
▼
drift validator ──▶ drift store
│
▼
alerter ──▶ on-call
- Reads: its scheduler and a sample of live calculation metadata.
- Calls: the Swiss worker and Horizons adapter via the service ports.
- Reuses: the canonical normalizer to align both sides.
- Never: changes a response, switches a provider, retries in a hot loop.
3. Responsibilities
| Component | Owns | Notes |
|---|---|---|
| Triggers | Periodic sweep, live-sample capture, manual backfill | Backfill covers post-upgrade checks |
| Case selector | Deterministic body × instant × epoch bucket sampling |
Covers file boundaries and historical/future dates |
| Comparator | Recomputing the same case with both providers | Same TT, apparent, airless, same ayanamsa |
| Judge | Angular delta, thresholds and breach streak | Distance recorded but not alerted |
| Drift store | Raw samples and daily aggregates | Keyed by engine version |
| Alerter | Paging on-call with body, epoch, delta and engine tag | Links to the failing sample |
4. Architecture
Diagram: reference design, not deployed behaviour.
The asynchronous worker shares no thread pool, breaker or quota with user traffic. Its only shared code is the normalizer and the two provider adapters.
5. Rules
- Compare like with like. Both sides use the same time scale (TT), frame (apparent, ecliptic of date, airless), observer mode and ayanamsa; otherwise the delta measures configuration.
- Compare tropical, derive sidereal. Horizons has no native sidereal mode, so both sides use tropical longitudes with the same ayanamsa.
- Recompute, do not read the cache. A cached primary result could hide a regression.
- A single outlier is not an alert. A breach must persist across a streak or exceed the daily p95.
- No birth coordinates in stored samples. Geocentric or offset sites only, per the parent privacy rules.
- Failure pauses, it does not storm. If Horizons is down or throttling, the breaker opens and the run is marked incomplete.
- Drift never auto-corrects. It reports; a human or a release gate decides.
6. Thresholds
| Body class | Warning | Critical |
|---|---|---|
| Sun, planets, Pluto | 0.7 arcsec | 1.0 arcsec |
| Moon | 3 arcsec | 5 arcsec |
The Moon band is wider: apparent motion is about 0.55 arcsec per second, so time-scale and ΔT differences dominate. Samples outside the leap-second era record the ΔT difference and are reported as time-scale sensitive, not an ephemeris regression.
7. Edge cases
| Case | Behaviour |
|---|---|
| A body outside a provider’s date range | Skipped and counted as unsupported; no alert |
| Unrecognised Horizons table layout | Run fails loudly with the API version; no partial aggregates |
| Engine or file upgrade | Old and new engines compared against the same Horizons reference |
| Moon at perigee or station | Same threshold; raw delta recorded |
| Topocentric live sample | Coordinates stripped; re-run geocentric |
| Breach during a known outage | Suppressed while the breaker is open; noted in the run record |
| No samples for a bucket | Coverage gap, not agreement |
8. Out of scope
- Houses, ayanamsa correctness, sidereal definitions: no independent reference exists.
- User-facing validation or blocking a request (the facade owns degradation).
- Automatic provider switching or automatic engine rollback.
- Astrology semantics and chart-level checks.
9. Planned code
| Piece | Responsibility |
|---|---|
drift/CaseSelector |
Stratified body and epoch sampling |
drift/Comparator |
Dual recomputation through the provider ports |
drift/DeltaJudge |
Wrapped angular delta, thresholds and streak logic |
drift/DriftStore |
Samples, aggregates and coverage gaps |
drift/DriftAlerter |
Paging payload and capability agreement status |