Ascendant
Developer guides

Provenance and verification

Read the school, method, and version attached to results, look up the method specification behind them, and store what you need to repeat a calculation.

An astrology result is only useful if you can say how it was produced. Ascendant answers that in two ways. Every result carries a provenance, a short label naming the method that made it. And every method has a method specification in a registry, which lists its steps and the properties it guarantees. This guide shows how to read both, how to connect them, and what to store alongside a result.

What provenance is

A provenance has three fields:

FieldMeaningExample
schoolThe tradition the method belongs to: Parashari, Jaimini, or Universal.Jaimini
methodThe name of the specific method.plain-projection
versionThe date the method last changed, as YYYY-MM-DD.2026-11-01

Together they identify exactly one specification. Results that came from different versions came from different specifications. Today every method has the version 2026-11-01.

A D1 chart is Universal, because its houses follow whichever house system and ayanamsa you chose. Divisional charts, Placements, Vimshottari, Ashtakavarga, and transit events are Parashari. The Jaimini results and the Chara and Sthira dashas are Jaimini.

Where to find it

Provenance sits on the result itself. This table shows where to find it and which method each result reports:

ResultWhereSchool / method
Placementsplacements.provenanceParashari / sidereal-ephemeris-placements
D1 chartcharts[0].provenanceUniversal / house-system-cusps
Divisional chartcharts[n].provenanceParashari / whole-sign-from-mapped-lagna
Vimshottari dashatimeline[n].provenance, on each MahadashaParashari / moon-nakshatra-balance
Ashtakavargaresult.provenanceParashari / bphs-bav-trikona-ekadhipatya
Chara Karakasresult.provenanceJaimini / exact-degree-shared-roles
Rashi Drishtiresult.provenanceJaimini / movable-fixed-dual
Karakamsharesult.provenanceJaimini / atmakaraka-d9-sign
Arudha Padaresult.provenanceJaimini / plain-projection
Upapadaresult.provenanceJaimini / twelfth-house-plain-projection
Argalaresult.provenanceJaimini / structural-positions
Chara dashatimeline.provenanceJaimini / kn-rao-co-lord-strength
Sthira dashatimeline.provenanceJaimini / bv-raman-koch-brahma-strength
Transit eventevent.provenanceParashari / transit-sample-bisect

Two details: Vimshottari returns a plain array, so its provenance is on each Mahadasha and not on the array. And placements.provenance is optional in the type, because you can build Placements yourself. Chart.generate always fills it.

Reading one is ordinary property access:

import { CharaKarakas, Chart, Runtime } from "astro-ascendant";
import { DateTime, ManagedRuntime } from "effect";

const runtime = ManagedRuntime.make(Runtime.MainLayer);

const located = Chart.LocatedMoment.make({
  moment: Chart.Moment.make({ date: DateTime.makeUnsafe("2000-01-01T12:00:00Z") }),
  latitude: 12.9716,
  longitude: 77.5946,
});

const { placements, charts } = await runtime.runPromise(Chart.generate(located, [9]));
const karakas = await runtime.runPromise(CharaKarakas.calculate(placements));

charts[0].provenance; // { school: "Universal", method: "house-system-cusps", version: "2026-11-01" }
karakas.provenance; // { school: "Jaimini", method: "exact-degree-shared-roles", version: "2026-11-01" }

The method registry

Provenance.methods is the registry. It holds one specification for each method in the table above:

KeyMethod
placementsPlacements from the ephemeris
d1ChartProjectionD1 chart from house-system cusps
divisionalChartProjectionDivisional chart from the mapped Lagna
vimshottariDashaVimshottari dasha from the Moon's nakshatra balance
ashtakavargaAshtakavarga with Trikona and Ekadhipatya reduction
jaiminiCharaKarakasChara Karakas
jaiminiRashiDrishtiRashi Drishti
jaiminiKarakamshaKarakamsha
jaiminiArudhaPadaArudha Pada
jaiminiUpapadaUpapada
jaiminiArgalaArgala
charaDashaChara dasha
sthiraDashaSthira dasha
transitSearchTransit search

Each specification has three parts: its provenance, an ordered list of steps, and a verification list of the properties it guarantees. Here is the Arudha Pada:

import { Provenance } from "astro-ascendant";

Provenance.methods.jaiminiArudhaPada;
// {
//   provenance: { school: "Jaimini", method: "plain-projection", version: "2026-11-01" },
//   steps: [
//     { id: "derive-source", description: "Find the source sign and its lord for the requested house." },
//     { id: "project", description: "Project the lord's distance from the source sign." },
//   ],
//   verification: ["Exactly one placement is required for the house lord."],
// }

The Chara dasha runs three steps, derive-direction, resolve-co-lords, and build-periods. The transit search guarantees that every zero-crossing counts in strict time order, that an exhausted search fails with a typed error instead of returning a short list, and that it finds the events an hourly scan of the ephemeris finds. The Ashtakavarga checks that each planetary table totals its classical bindu count and that the Sarvashtakavarga totals 337.

Provenance.MethodId is the union of the registry keys, so you can write a lookup that TypeScript checks:

const id: Provenance.MethodId = "charaDasha";
Provenance.methods[id].steps.map((step) => step.id);
// ["derive-direction", "resolve-co-lords", "build-periods"]

From a result to its specification

To explain a result, find the specification whose provenance matches it:

const specificationFor = (provenance: Provenance.CalculationProvenance) =>
  Object.values(Provenance.methods).find(
    (spec) =>
      spec.provenance.school === provenance.school &&
      spec.provenance.method === provenance.method &&
      spec.provenance.version === provenance.version,
  );

const spec = specificationFor(karakas.provenance);
spec?.steps; // rank, assign
spec?.verification; // ["All seven classical planet placements are required.", "Exact ties share roles."]

Every provenance in the table above resolves to a specification this way. Use it to build a "how was this calculated?" view in your own product, showing the steps in plain words next to the result.

Store what you need to repeat a result

A result is repeatable when you keep its inputs, its settings, and the methods that produced it. This record holds all three:

const { placements, charts, astroParams } = await runtime.runPromise(
  Chart.generate(located, [9]),
);

const record = {
  moment: DateTime.formatIso(located.moment.date),
  latitude: located.latitude,
  longitude: located.longitude,
  astroParams, // { ayanamsa: "KrishnamurtiVP291", houseSystem: "Placidus" }
  methods: [charts[0].provenance, charts[1].provenance, karakas.provenance],
};

const stored = JSON.stringify(record);

When you read it back, validate the provenance with the library's schema before you trust it:

import { Schema } from "effect";

const restored = JSON.parse(stored) as typeof record;
const methods = restored.methods.map((method) =>
  Schema.decodeUnknownSync(Provenance.CalculationProvenance)(method),
);

A value with a missing field fails with a schema error, so a damaged record does not slip through. Also store the version of astro-ascendant you ran. It is the simplest way to reproduce a result exactly.

What to show readers

For any reading you present, state:

  • The settings: ayanamsa and house system, from calculation.astroParams.
  • The school of each judgment, and that the two schools were not mixed. See Choose a school and settings.
  • The method and version of each result.

On this page