HomeDocsAspects & Orbs

Aspects & Orbs

Moira's aspect backend standard: relation geometry, orb policy, classification, graph invariants, and deterministic assembly.

Moira Aspect Backend Standard

Governing Principle

The Moira aspect backend is a sovereign computational subsystem. Its definitions, layer boundaries, invariants, failure doctrine, and determinism rules are stated here and are frozen until explicitly superseded by a revision to this document.

This document reflects current implementation truth. Focused verification counts belong in completion receipts rather than frozen doctrine.


Part I — Architecture Standard

1. Authoritative Computational Definitions

1.1 Ecliptic aspect

An ecliptic aspect in Moira is:

A detected angular relationship between two distinct celestial bodies whose angular separation along the ecliptic falls within a declared orb of a canonical aspect angle.

ElementDefinition
two distinct bodiesbody1 != body2; no self-aspects
angular separationangular_distance(lon1, lon2)[0, 180] degrees, folded at 180°
canonical aspect angleAn angle from moira.constants.Aspect.ALL
orbabs(separation - angle)
within declared orborb <= allowed_orb
allowed_orbdefault_orb * orb_factor, or the caller-supplied override for that angle

The admission test is fully reconstructable from the stored vessel:

orb        == abs(separation - angle)
orb        <= allowed_orb
separation  = angular_distance(lon1, lon2)

1.2 Declination aspect

A declination aspect in Moira is:

A parallel or contra-parallel between two distinct celestial bodies whose signed declinations satisfy one of the two defined relationships within a declared orb.

TypeAdmission testOrb formula
ParallelSame nonzero hemisphere and abs(dec1 - dec2) <= allowed_orborb = abs(dec1 - dec2)
Contra-ParallelOpposite nonzero hemispheres and abs(dec1 + dec2) <= allowed_orborb = abs(dec1 + dec2)

Two points exactly on the equator form one exact Parallel. A single equatorial point has no hemisphere and forms neither relation with a non-equatorial point. Hemisphere qualification precedes the orb formula so both relations cannot be emitted for one pair near 0°.

Detected DeclinationAspect vessels carry no motion data; consequently the legacy generic aspect_motion_state(DeclinationAspect) remains NONE. The separate DeclinationAspectMotionWitness is the first-class temporal product when declination rates are supplied.

1.2.1 Module ownership

moira.declination_aspects governs parallels, contra-parallels, their hemisphere/equator doctrine, policy, result vessels, and signed motion witness. moira.aspects governs ecliptic-longitude and whole-sign relationships and retains compatibility re-exports for the historical declination imports. Shared domain/tier/family classification types live in the private moira._aspect_types taxonomy layer so both engines use identical enum and classification objects without circular imports.

Declination is a second angular coordinate on the celestial sphere. It is not full Cartesian 3D geometry because radial distance is not part of these relationships.

1.2.2 Whole-sign direction and overcoming

moira.aspects also owns Hellenistic aspect direction and whole-sign overcoming. hellenistic_superiority_truth() is the governing atomic receipt for one ordered body pair: HellenisticDirectionTruth preserves directed-arc geometry and sinister/dexter applicability, while HellenisticOvercomingTruth preserves both inclusive sign places and the typed winner-or-neither relation.

Direction is not_evaluable when no aspect angle is supplied, for conjunction or opposition, or at an exact directed conjunction/opposition boundary. The reason is preserved and an evaluable direction cannot be suppressed. Overcoming remains evaluated for finite pair geometry. Sign counting is inclusive: the first body overcomes the second only when the first occupies the tenth sign from the second, which is nine zero-based sign steps forward. Reversing the operands reverses the question. A third-sign/fourth-sign confusion must not be accepted merely because the bodies form an undirected square.

AspectData.direction and overcoming(lon1, lon2) are compatibility projections from that shared raw truth. Detector-produced AspectData also carries the aggregate hellenistic_superiority_truth receipt. No additive dignity or superiority score is admitted by this composition layer.

1.3 Admitted aspect

An aspect is admitted when the admission test passes. Admission is binary: the aspect either qualifies or it does not. There is no partial admission and no confidence score at the detection layer.


2. Layer Structure

The backend is organized into twelve phases. Each phase operates only on outputs produced by phases below it. No phase reaches upward.

Phase  1 — Core aspect detection
Phase  2 — Relational truth preservation
Phase  3 — Classification
Phase  4 — Inspectability
Phase  5 — Doctrine inputs
Phase  6 — Policy surface
Phase  7 — Geometric strength
Phase  8 — Temporal state
Phase  9 — Canonical configuration
Phase 10 — Multi-body pattern layer
Phase 11 — Relational graph / network layer
Phase 12 — Harmonic / family intelligence layer

Layer boundary rule

A function in phase N may consume results from phases 1 through N−1. It may not:

  • re-run position arithmetic
  • re-compute aspect admission
  • alter a vessel produced by an earlier phase in place
  • introduce new doctrine inputs not present in that phase's entry point

3. Delegated Assumptions

The aspect engine delegates to external modules without redefining them:

ConcernDelegated toConvention
Angular distance arithmeticmoira.coordinates.angular_distanceReturns [0, 180], fold at 180°
Canonical aspect definitionsmoira.constants.Aspect.ALL22 zodiacal aspects
Default orb tablemoira.constants.DEFAULT_ORBS{angle: max_orb}
Aspect tier listsmoira.constants.ASPECT_TIERSMajor / Common Minor / Extended Minor

The aspect backend does not redefine any of these. Changes to these constants propagate automatically.


4. Canonical Aspect Set

The complete set of recognised and detectable aspect types is declared in CANONICAL_ASPECTS — a module-level tuple of 24 names, frozen at import time.

TierCountNames
Major5Conjunction, Sextile, Square, Trine, Opposition
Common Minor6Semisextile, Semisquare, Sesquiquadrate, Quincunx, Quintile, Biquintile
Extended Minor11Septile, Biseptile, Triseptile, Novile, Binovile, Quadnovile, Decile, Tredecile, Undecile, Quindecile, Vigintile
Declination2Parallel, Contra-Parallel

Rules:

  • The 22 zodiacal names correspond 1-to-1 with entries in Aspect.ALL.
  • The 2 declination names are produced exclusively by find_declination_aspects.
  • CANONICAL_ASPECTS carries no detection logic; it is a declaration only.
  • No aspect not in CANONICAL_ASPECTS can be produced by any detection function.

5. Classification

AspectClassification classifies every admitted aspect on three independent axes:

AxisTypeRule
domainAspectDomainZODIACAL for ecliptic; DECLINATION for parallels
tierAspectTierDerived from AspectDefinition.is_major and membership in Aspect.EXTENDED_MINOR
familyAspectFamilyDerived from _FAMILY_BY_NAME; maps each aspect name to its harmonic family

Classification is descriptive only. It describes what was detected, not how it should be interpreted. Strength, dignity weighting, and reception scoring are excluded from the classification layer.

Family grouping

Aspects in the same harmonic series share a family:

FamilyMembers
CONJUNCTIONConjunction
OPPOSITIONOpposition
SQUARESquare
TRINETrine
SEXTILESextile
SEMISEXTILESemisextile
SEMISQUARESemisquare
SESQUIQUADRATESesquiquadrate
QUINCUNXQuincunx
QUINTILEQuintile, Biquintile
SEPTILESeptile, Biseptile, Triseptile
NOVILENovile, Binovile, Quadnovile
DECILEDecile, Tredecile
UNDECILEUndecile
QUINDECILEQuindecile
VIGINTILEVigintile
DECLINATIONParallel, Contra-Parallel

6. Policy Layer

AspectPolicy encapsulates longitude-aspect doctrine and retains declination_orb as a compatibility adapter for historical callers.

FieldTypeDefaultEffect
tierint | NoneNone0=Major only, 1=Major+Common Minor, 2=All; None defers to include_minor
include_minorboolTrueInclude Common Minor when tier is None
orbsdict[float, float] | NoneNoneCustom orb table {angle: max_orb}; overrides orb_factor when set
orb_factorfloat1.0Multiplier on all default orbs; ignored when orbs is set
declination_orbfloat1.0Ceiling for Parallel and Contra-Parallel detection

When a policy argument is passed to a detection function it takes full precedence over any corresponding individual keyword arguments. Individual parameters remain available for backward compatibility.

DEFAULT_POLICY reproduces the historical default behaviour of all four detection functions.

DeclinationAspectPolicy is the governing declination policy:

FieldDefaultEffect
orb1.0Admission ceiling for both relationships
exact_tolerance_deg1e-9Exactness precedence threshold
rate_tolerance_deg_per_day1e-12Relative signed-error rate treated as stalled
hemisphere_policySTRICT_SIGNEDParallel uses the same nonzero hemisphere; Contra-Parallel uses opposite nonzero hemispheres
equator_policyPAIRED_EQUATORIAL_PARALLELTwo equatorial points form one Parallel; one equatorial point is unclassified

Policy validation

AspectPolicy validates its fields at construction:

ConditionRaises
orb_factor <= 0ValueError
declination_orb < 0ValueError

7. Geometric Strength

AspectStrength is a pure arithmetic exactness summary derived from orb and allowed_orb only.

surplus   = allowed_orb - orb
exactness = 1.0 - orb / allowed_orb
FieldDefinition
orbAngular deviation from target angle; always non-negative
allowed_orbOrb ceiling applied at admission
surplusallowed_orb - orb; remaining headroom
exactness1.0 - orb / allowed_orb; 1.0 = exact, 0.0 = at boundary

aspect_strength does not interpret strength. It does not weight by aspect family, body dignity, or orbital speed.

Strength validation

aspect_strength validates its input before computing:

ConditionRaises
orb or allowed_orb is non-finite, or orb < 0ValueError
allowed_orb <= 0ValueError
orb > allowed_orbValueError

Whole-sign aspects are categorical rather than orb-admitted. Their strength identity is orb = allowed_orb = surplus = 0 and exactness = 1.


8. Temporal-State Doctrine

MotionState formalises the motion-aware truth already stored in applying and stationary. It maps the complete decision space without ambiguity:

Vessel/domainorbstationaryapplying→ MotionState
DeclinationAspectanyNONE
Whole-sign AspectDataanyNONE
Zodiacal AspectData<= 1e-9anyanyEXACT
Zodiacal AspectData> 1e-9TrueanySTATIONARY
Zodiacal AspectData> 1e-9FalseTrueAPPLYING
Zodiacal AspectData> 1e-9FalseFalseSEPARATING
Zodiacal AspectData> 1e-9FalseNoneINDETERMINATE

Categorical/no-motion domains yield NONE. For zodiacal aspects, EXACT takes precedence over stationary and applying/separating classification.

Temporal consistency rules

  • APPLYINGis_applying is True and is_separating is False
  • SEPARATINGis_separating is True and is_applying is False
  • is_applying and is_separating are never simultaneously True
  • Both are False when applying is None

First-class declination motion

DeclinationAspectMotionWitness classifies a selected relationship from caller-supplied declinations and optional declination rates, all expressed in the same declared frame, timescale, origin, and correction regime.

RelationshipSigned error ERelative rate dE/dt
Paralleldec1 - dec2speed1 - speed2
Contra-Paralleldec1 + dec2speed1 + speed2

Away from exactness, orb = abs(E) and orb_rate = sign(E) * dE/dt. A negative orb rate is APPLYING; a positive orb rate is SEPARATING. Exactness takes precedence. A relative rate inside the policy tolerance is STATIONARY, while missing or partial rates are INDETERMINATE. An individual zero declination rate does not force STATIONARY when the relationship's relative error is still changing.

The witness is instantaneous (instantaneous_no_event_search). It does not prove that a relationship will perfect before either body reverses in declination; that stronger claim requires an epoch-based exact-root search.


9. Multi-Body Pattern Doctrine

find_patterns operates over an already-admitted list[AspectData]. It does not re-run position arithmetic.

Implemented patterns and their structural requirements:

KindBodiesRequired edges
STELLIUM≥3, maximal cliqueMutual Conjunction between every pair
T_SQUAREexactly 3One Opposition (A–B) + Square(A–C) + Square(B–C)
GRAND_TRINEexactly 3Trine(A–B) + Trine(B–C) + Trine(A–C)
GRAND_CROSSexactly 4Two Oppositions + four Squares (closed cross)
YODexactly 3Sextile(B–C) + Quincunx(A–B) + Quincunx(A–C)

Pattern ordering rules

  • Output order: Stellia, T-Squares, Grand Trines, Grand Crosses, Yods.
  • Each pattern kind is emitted at most once per unique body set (frozenset).
  • Stellium: smaller subsets contained within a larger Stellium are suppressed.
  • All other kinds are independent. A Grand Cross may also contain T-Squares; both are reported.
  • Within each kind, patterns are ordered by sorted body-name iteration (outer loops always iterate over sorted(all_bodies)).

Pattern contributing-aspects ordering

The aspects tuple inside each AspectPattern is sorted by (body1, body2, aspect). This ordering is stable and independent of the input list ordering.

Structural aspect counts

Kindlen(aspects)
STELLIUM (3-body)3
STELLIUM (4-body)6
T_SQUARE3
GRAND_TRINE3
GRAND_CROSS6
YOD3

10. Relational Graph Doctrine

build_aspect_graph expresses the chart as a deterministic aspect network. Bodies become nodes; each admitted AspectData becomes an edge.

Graph construction rules

  • Every body that appears in at least one aspect gets a node.
  • Bodies supplied via bodies= that have no aspects get degree-0 nodes.
  • nodes is sorted by body name.
  • edges is sorted by (body1, body2, aspect).
  • components is sorted by (min(component), len(component)) ascending.

Node invariants

InvariantExpression
Degree consistencydegree == len(edges)
Family count consistencysum(family_counts.values()) == degree
IncidenceEvery edge in node.edges has body1 == name or body2 == name
Edge orderingedges sorted by (body1, body2, aspect)

family_counts keys are AspectData.aspect strings (e.g. "Trine"), not AspectFamily enum values. This is intentional: it preserves per-name granularity at the graph layer (Trine vs Biquintile both count as QUINTILE family at the harmonic layer, but remain distinct at the graph layer).

Derived properties

PropertyDefinition
hubsNodes with maximum degree; empty tuple when all nodes are isolated
isolatedNodes with degree 0, sorted by name

11. Harmonic Intelligence Doctrine

aspect_harmonic_profile derives the family distribution of admitted aspects at both the chart level and per body.

Profile construction rules

  • chart covers all aspects in the input list.
  • by_body has one entry per body that appears in at least one aspect.
  • by_body keys are in sorted body-name order.
  • A body with no aspects has no entry in by_body.

Family resolution order (when classification is absent)

  1. a.classification.family when classification is not None (normal case).
  2. _FAMILY_BY_NAME[a.aspect] when classification is None and the name is a known zodiacal name.
  3. AspectFamily.DECLINATION as the fallback for any unrecognised name (covers "Parallel", "Contra-Parallel", or custom names).

AspectFamilyProfile invariants

InvariantExpression
Totalsum(counts.values()) == total
Proportions countlen(proportions) == len(counts)
Proportions sumabs(sum(proportions.values()) - 1.0) < 1e-9 when total > 0
Dominant membershipEvery member of dominant is a key in counts
Proportion rangeAll proportions in [0.0, 1.0]
Key orderingcounts and proportions keys follow AspectFamily declaration order
Dominant orderingdominant sorted by AspectFamily.value (alphabetical)

12. Non-Goals

The following concerns are explicitly outside the scope of the current aspect backend:

Excluded concernReason
Interpretation (e.g. "this aspect is challenging")Doctrine-specific; belongs above the engine
Dignity weighting or reception scoringRequires a separate dignity model
Body-specific orb weightsNot in current AspectPolicy
Antiscion contactsA separate geometric computation
Cross-chart (synastry) relational policiesMulti-chart context not yet in scope
Kite, Mystic Rectangle, Grand QuintileRequire oriented topology or 5-body matching
UI rendering or serializationBelongs above the engine
Harmonic chart generationSeparate from aspect detection

Part II — Validation Codex

1. Validation Environment

PropertyValue
Authoritative runtime.venv in the project root
Python version3.14.x (as resolved by .venv)
Test runnerpytest via .venv\Scripts\python.exe -m pytest
Test filetests/unit/test_aspects.py
BaselineFocused aspect test file passes under strict known-issues mode
Acceptable result0 failures, 0 errors

No test in test_aspects.py may be modified to make the implementation pass. A failing test is always treated as an implementation defect, not a test defect, unless the test itself is proven incorrect.


2. Invariant Register

This register is the normative source of truth for all subsystem invariants. Each invariant is identified by a short code for traceable reference.

INV-TRUTH — Truth preservation

CodeInvariant
T-1Zodiacal orb == abs(separation - angle) to floating-point precision
T-2Zodiacal orb <= allowed_orb; whole-sign orb == allowed_orb == 0
T-3orb_surplus == allowed_orb - orb >= 0
T-4separation is in [0, 180] degrees
T-5For a Parallel: orb == abs(dec1 - dec2)
T-6For a Contra-Parallel: orb == abs(dec1 + dec2)
T-7dec1 and dec2 are in [-90, +90]
T-8Parallel and Contra-Parallel hemisphere predicates are mutually exclusive
T-9overcoming(lon1, lon2) is true exactly when (sign1 - sign2) mod 12 == 9
T-10HellenisticDirectionTruth is evaluated only for a supplied non-conjunction/non-opposition angle away from directed 0°/180° boundaries
T-11HellenisticOvercomingTruth.relation exactly matches the two inclusive sign places and admits an evaluated neither result
T-12Detector-produced AspectData.direction is the compatibility projection of its hellenistic_superiority_truth.direction_truth

INV-CLASS — Classification

CodeInvariant
C-1classification.domain is ZODIACAL or WHOLE_SIGN for detector-produced AspectData
C-2classification.domain == DECLINATION for every DeclinationAspect
C-3classification.family == _FAMILY_BY_NAME[aspect] for every zodiacal aspect
C-4classification.family == AspectFamily.DECLINATION for every DeclinationAspect
C-5classification.tier == MAJOR for every aspect in {"Conjunction","Sextile","Square","Trine","Opposition"}
C-6Classification is identical for the same aspect name across all calls

INV-STR — Geometric strength

CodeInvariant
S-10.0 <= orb <= allowed_orb for any orb-admitted vessel passed to aspect_strength
S-2surplus == allowed_orb - orb
S-30.0 <= exactness <= 1.0
S-4exactness == 1.0 - orb / allowed_orb
S-5aspect_strength raises ValueError when allowed_orb <= 0
S-6aspect_strength raises ValueError when orb > allowed_orb
S-7Whole-sign strength is (orb=0, allowed_orb=0, surplus=0, exactness=1)

INV-MOT — Temporal state

CodeInvariant
M-1APPLYINGis_applying is True and is_separating is False
M-2SEPARATINGis_separating is True and is_applying is False
M-3Zodiacal exactness yields EXACT before stationary/applying classification
M-4Non-exact zodiacal data yields STATIONARY when stationary is True
M-5A detection-only DeclinationAspect and whole-sign data yield generic MotionState.NONE
M-6is_applying and is_separating are never simultaneously True
M-7Non-exact zodiacal data yields INDETERMINATE when motion is unavailable
M-8Parallel signed error/rate are dec1 - dec2 and speed1 - speed2
M-9Contra-Parallel signed error/rate are dec1 + dec2 and speed1 + speed2
M-10Non-exact declination orb_rate < 0 yields APPLYING; orb_rate > 0 yields SEPARATING
M-11Declination exactness precedes rate classification
M-12Missing declination rates yield INDETERMINATE unless exact
M-13Declination STATIONARY means the relative error rate is inside tolerance, not that either individual body has zero rate

INV-PAT — Pattern layer

CodeInvariant
P-1Every body in pattern.bodies appears in at least one aspect in pattern.aspects
P-2pattern.aspects is sorted by (body1, body2, aspect)
P-3No pattern body-set is emitted more than once per kind
P-4Stellium sub-cliques contained in a larger Stellium are suppressed
P-5T_SQUARE has exactly 3 contributing aspects
P-6GRAND_TRINE has exactly 3 contributing aspects
P-7GRAND_CROSS has exactly 6 contributing aspects
P-8YOD has exactly 3 contributing aspects
P-9STELLIUM (3-body) has exactly 3; (4-body) has exactly 6 contributing aspects
P-10Output order: Stellia, T-Squares, Grand Trines, Grand Crosses, Yods
P-11find_patterns does not mutate the input list

INV-GRAPH — Relational graph

CodeInvariant
G-1degree == len(edges) for every node
G-2sum(family_counts.values()) == degree for every node
G-3Every edge in node.edges involves that node as body1 or body2
G-4node.edges sorted by (body1, body2, aspect)
G-5graph.nodes sorted by body name
G-6graph.edges sorted by (body1, body2, aspect)
G-7graph.components sorted by (min(c), len(c)) ascending
G-8build_aspect_graph does not mutate the input list

INV-HARM — Harmonic layer

CodeInvariant
H-1sum(counts.values()) == total
H-2len(proportions) == len(counts)
H-3abs(sum(proportions.values()) - 1.0) < 1e-9 when total > 0
H-4Every member of dominant is a key in counts
H-5All proportions are in [0.0, 1.0]
H-6chart.total == len(aspects)
H-7by_body[name].total equals the number of aspects in which name participates
H-8by_body keys are in sorted body-name order
H-9aspect_harmonic_profile does not mutate the input list

INV-POL — Policy

CodeInvariant
PO-1AspectPolicy raises ValueError when orb_factor <= 0
PO-2AspectPolicy raises ValueError when declination_orb < 0
PO-3DEFAULT_POLICY is a valid, constructable AspectPolicy
PO-4Invalid tiers, non-boolean flags, and non-finite policy values raise ValueError
PO-5Caller-supplied orb mappings are defensively copied and exposed read-only

3. Determinism Register

The following ordering guarantees are normative. Any permutation of an input list must produce identical output on all of these:

ContextDeterminism guarantee
find_aspects result orderSorted by orb ascending
find_declination_aspects result orderSorted by orb ascending
find_patterns — pattern orderStellia, T-Squares, Grand Trines, Grand Crosses, Yods
find_patterns — body-set per patternfrozenset (order-independent identity)
find_patternsaspects tupleSorted by (body1, body2, aspect)
build_aspect_graphnodesSorted by body name
build_aspect_graphedgesSorted by (body1, body2, aspect)
build_aspect_graphcomponentsSorted by (min(c), len(c))
build_aspect_graphnode.edgesSorted by (body1, body2, aspect)
aspect_harmonic_profileby_body keysSorted body-name order
aspect_harmonic_profilecounts keysAspectFamily declaration order
aspect_harmonic_profiledominantSorted by AspectFamily.value (alphabetical)

4. Failure Doctrine

The following table lists every condition that raises an exception, the exception type, and the diagnostic guarantee:

Function / constructorConditionExceptionDiagnostic guarantee
aspect_strengthallowed_orb <= 0ValueErrorMessage includes "allowed_orb" and the offending value
aspect_strengthorb > allowed_orbValueErrorMessage includes "orb" and both values
aspect_strengthNon-finite or negative geometryValueErrorMessage identifies orb or allowed_orb
AspectPolicyorb_factor <= 0ValueErrorMessage includes "orb_factor"
AspectPolicydeclination_orb < 0ValueErrorMessage includes "declination_orb"
AspectPolicyInvalid tier, flag, mapping, or non-finite valueValueErrorMessage identifies the policy field
Detection entry pointsNon-finite longitude/declination/speed or out-of-range declinationValueErrorMessage identifies the geometry field

All functions remain pure computations over valid inputs. Collection-oriented detectors return empty results for empty valid mappings; first-class analysis surfaces require at least two included points.

Behaviour on empty input

FunctionInputReturns
find_aspects{}[]
find_declination_aspects{}[]
find_patterns[][]
build_aspect_graph[]AspectGraph(nodes=(), edges=(), components=())
aspect_harmonic_profile[]AspectHarmonicProfile(chart=empty, by_body={})

5. No-Mutation Guarantee

Every public function beyond the detection layer accepts its inputs by value and does not mutate them:

FunctionGuarantee
find_patterns(aspects)Does not alter aspects or any element of it
build_aspect_graph(aspects, ...)Does not alter aspects or any element of it
aspect_harmonic_profile(aspects)Does not alter aspects or any element of it
aspect_strength(aspect)Does not alter aspect
aspect_motion_state(aspect)Does not alter aspect

6. Cross-Layer Consistency Rules

These rules govern the logical relationship between layers. Each rule must hold on any output produced by the detection layer.

RuleExpression
Classification–familya.classification.family == _FAMILY_BY_NAME[a.aspect] for all zodiacal AspectData
Classification–domainDetector-produced AspectData is explicitly ZODIACAL or WHOLE_SIGN
Strength–surplusa.orb_surplus == aspect_strength(a).surplus
Graph–harmonicsum(node.family_counts.values()) == hp.by_body[node.name].total for every node
Harmonic–totalhp.chart.total == len(aspects)
Motion–is_applyingaspect_motion_state(a) == APPLYINGa.is_applying is True
Motion–is_separatingaspect_motion_state(a) == SEPARATINGa.is_separating is True
is_major–is_minora.is_major != a.is_minor for any classified AspectData

7. Public Surface Register

Complete public surface of moira.aspects as of Phase 12:

Enumerations

NameValues
AspectDomainZODIACAL, DECLINATION, WHOLE_SIGN
AspectTierMAJOR, COMMON_MINOR, EXTENDED_MINOR
AspectFamily18 members, including explicit DECLINATION and UNKNOWN families
MotionStateAPPLYING, EXACT, SEPARATING, STATIONARY, INDETERMINATE, NONE
DeclinationMotionStateAPPLYING, EXACT, SEPARATING, STATIONARY, INDETERMINATE
AspectPatternKindSTELLIUM, T_SQUARE, GRAND_TRINE, GRAND_CROSS, YOD
HellenisticAspectEvaluationStatusEVALUATED, NOT_EVALUABLE
HellenisticOvercomingRelationBODY1_OVERCOMES_BODY2, BODY2_OVERCOMES_BODY1, NEITHER

Frozen dataclasses

NameFields
AspectClassificationdomain, tier, family
HellenisticDirectionTruthevaluation status, aspect angle, directed arc, direction, reason
HellenisticOvercomingTruthevaluation status, sign indices, inclusive places, relation, reason
HellenisticSuperiorityTruthordered body names and longitudes plus direction and overcoming components
AspectPolicytier, include_minor, orbs, orb_factor, declination_orb
AspectStrengthorb, allowed_orb, surplus, exactness
AspectPatternkind, bodies, aspects
AspectGraphNodename, degree, edges, family_counts
AspectGraphnodes, edges, components; properties hubs, isolated
AspectFamilyProfilecounts, total, proportions, dominant
AspectHarmonicProfilechart, by_body
DeclinationAspectAnalysispositions, aspects, orb, reference_frame, timescale, provenance; derived counts and normalized mapping
DeclinationAspectPolicyorb, exact/rate tolerances, hemisphere policy, equator policy
DeclinationAspectMotionWitnessselected relationship, declinations/rates, signed error, relative rate, orb/orb rate, state, policy and coordinate provenance

Immutable result vessels

NameRationale
AspectDataFrozen after complete construction by a detector
DeclinationAspectFrozen after complete construction by a detector
OutOfBoundsBodyFrozen after complete construction by its detector

The vessels are terminal (not designed for subclassing) and document their structural invariants explicitly in their class docstrings.

Module-level constants

NameTypeContent
CANONICAL_ASPECTStuple[str, ...]24 canonical aspect names
DEFAULT_POLICYAspectPolicyPolicy matching historical detection defaults

Detection functions

NameSignatureReturns
find_aspects(positions, *, include_minor, tier, orbs, orb_factor, policy)list[AspectData]
aspects_between(body1, lon1, speed1, body2, lon2, speed2, ...)list[AspectData]
aspects_to_point(positions, point_name, point_lon, ...)list[AspectData]
hellenistic_superiority_truth(lon1, lon2, aspect_angle=None, *, body1="body1", body2="body2")HellenisticSuperiorityTruth
find_declination_aspects(declinations, *, orb, policy)list[DeclinationAspect]
declination_aspects_from_declinations(declinations, *, reference_frame, timescale, orb, policy)DeclinationAspectAnalysis
declination_aspect_motion_witness(body1, dec1, body2, dec2, aspect, *, speeds, orb, tolerances, reference_frame, timescale, policy)DeclinationAspectMotionWitness

Derived-layer functions

NameInputReturns
aspect_strengthAspectData | DeclinationAspectAspectStrength
aspect_motion_stateAspectData | DeclinationAspectMotionState
find_patternslist[AspectData]list[AspectPattern]
build_aspect_graphlist[AspectData], bodies=NoneAspectGraph
aspect_harmonic_profilelist[AspectData]AspectHarmonicProfile

8. Validation Baseline

Validation uses the authoritative project .venv and the focused aspect, moiety, signed-motion, synastry, pattern, public-surface, external-reference, and relationship REST slices. Test counts are not frozen doctrine; the completion receipt for a change records the exact commands, outcomes, skips, fixtures, and scope exercised. Tests may change only when an explicitly admitted contract changes, with regression coverage for the replacement law.