Packages4 MIN READ115 / 116

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.ts selects privacy-safe cases and validates/deduplicates execution batches.
  • src/comparison-policy.ts owns numeric representation and time-model tolerance settings.
  • src/comparison.ts owns context matching, raw deltas and comparison eligibility.
  • src/thresholds.ts judges body-specific warning/critical bands.
  • src/aggregation.ts groups reproducible evidence and computes streaks, p95 and alert decisions.
  • src/report.ts defines reports/host ports and delivers detached evidence in store-before-alert order.
  • src/validator.ts owns 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.