Internal module guide. This module is now part of
@astrology/ephemeris-validation; use the public package README for application setup.
drift-validator module
@astrology/ephemeris-validation is a separate background-worker package that measures drift between providers. Unlike semantics,
it calls providers and may persist reports or emit alerts through host integrations. It depends
on the shared adapter contract, canonical normalizer and shared logger, with no dependency on Swiss native
code, NASA transport, the router, a cache, a database or an alerting service.
import { DriftValidator, selectDriftCases } from '@astrology/ephemeris-validation';
// Supply dedicated uncached EphemerisAdapter instances from the background worker host.
// A future NASA/Horizons adapter can implement the existing contract.
const validator = new DriftValidator(primaryAdapter, referenceAdapter, {
breachStreak: 3,
minimumP95Samples: 20,
ports: {
breakerOpen: () => backgroundBreaker.isOpen(),
recordFailure: code => backgroundBreaker.recordFailure(code),
store: report => driftStore.append(report),
alert: (alert, report) => driftAlerter.send(alert, report),
},
});
const cases = selectDriftCases(liveOrBackfillRequests, 100);
const report = await validator.run(cases);
The host invokes runs from a scheduler, backfill job or sample queue, using independent worker resources and provider deadlines/quotas. Constructing a validator creates no processes or timers. It cannot enforce isolation if a host injects the same adapters/resources as user traffic.
selectDriftCases validates position requests, deduplicates equivalent instants and selects
deterministically across body × century buckets. It retains only body and epoch milliseconds.
Observer coordinates and sidereal options are stripped: both providers recompute tropical,
geocentric positions. A host supplies historical/future/file-boundary cases; the selector does
not discover ephemeris coverage. Lunar-node cases are recorded as unsupported, without provider
calls. Houses and independent ayanamsa validation are outside this module’s scope.
The runner validates and detaches the batch before I/O and deduplicates cases so repeats cannot
manufacture streaks. Each case calls the primary and reference once, sequentially, through the
real NormalizingAdapter. Normalization uses fifteen decimal places for comparison. Bodies,
origins, frames and zodiac must match; the reference must have a different provider id. The
host remains responsible for choosing scientifically independent reference implementations.
Reports contain wrapped longitude delta and latitude delta in arcseconds, distance delta in
AU, Terrestrial Time (TT) and Delta-T (TT minus Earth-rotation time) differences in seconds, body, instant, century and both provider declarations.
comparePositions and judgeDelta are also available as pure functions. Since the adapter port
accepts UTC/offset instants rather than an explicit TT input, the module checks the returned TT
declarations. It cannot force two providers to use the same time model.
The default TT difference limit is 0.001 seconds; the Delta-T difference limit is 0.1 seconds.
Both can be configured with finite nonnegative values. Differences above either limit, and all
instants before 1972, are recorded as time-scale-sensitive. Degraded answers are recorded as
degraded. Their deltas remain visible but cannot generate regression alerts or enter p95.
Their observations reset the comparable streak for that group.
Warning/critical thresholds are strict exceedances of 0.7/1.0 arcseconds for the Sun/planets and 3/5 for the Moon. The greater longitude/latitude delta is judged; distance is recorded only. A warning streak includes critical samples. Alerts need a consecutive streak (default three) or a nearest-rank p95 breach with enough samples (default twenty). Both settings must be at least two; a single outlier never suffices by itself. A run has no cross-run mutable streak. Groups include both engine, binding, ephemeris/file identities, body and century, so upgrades do not share aggregate evidence. Unsupported gaps break streaks for that body.
Aggregates are per run. For daily p95, the host submits the complete daily cases as a batch;
this package does not load historical records or maintain daily state. Empty buckets produce
no aggregate and are coverage gaps, not evidence of agreement. Missing body/date support is
counted in gaps. Remaining cases after a paused run are listed in pending.
An open breaker or a provider/normalizer/format failure pauses the batch with incomplete.
There is no retry loop. Provider failures call recordFailure; hook implementations own
breaker transitions. Incomplete runs retain diagnostic samples and safe error codes but return
no aggregates or alerts. Breaker state is checked before cases and again before publication.
The package does not store arbitrary exception messages, stacks or observer coordinates.
The report is persisted before alert delivery. Hooks receive detached reports; their failures reject the run promise. The host owns delivery retry/idempotency and incident suppression. Nothing changes a user response, switches providers or rolls back an engine.
From the root: pnpm build:ephemeris-validation, pnpm check:ephemeris-validation,
pnpm test:ephemeris-validation. Tests use fixture adapters and the real normalizer. NASA networking,
concrete persistence/alert delivery, daily scheduling and process isolation require future host
integrations; no live NASA comparison is claimed.
See the reference design.
Source ownership follows the background comparison stages:
src/case-selector.tsselects privacy-safe cases and validates/deduplicates execution batches.src/comparison-policy.tsowns numeric representation and time-model tolerance settings.src/comparison.tsowns context matching, raw deltas and comparison eligibility.src/thresholds.tsjudges body-specific warning/critical bands.src/aggregation.tsgroups reproducible evidence and computes streaks, p95 and alert decisions.src/report.tsdefines reports/host ports and delivers detached evidence in store-before-alert order.src/validator.tsowns batch execution, unsupported cases, pauses and publication suppression.
src/index.ts only re-exports the public API. Consumers continue importing from
@astrology/ephemeris-validation; internal helpers are not added to the package’s public exports.