Probabilities normalize in exactly one place
The deino-oracle invariant (INV-1): sum approximately 1.0, sorted, entropy in nats — a guarantee, not a hope.

An ensemble of five sister-LLMs produces five sets of numbers. Consumers downstream need those numbers to mean the same thing. They do, because normalization lives in exactly one place, and that one place is deino-oracle::ensemble.
INV-1 is the invariant that makes the Oracle output safe to consume: probabilities are normalized in exactly one place. deino-oracle::ensemble takes the sisters raw outputs and produces a normalized Ensemble — sum(probability) is approximately 1.0, scenarios sorted descending by probability, entropy reported in nats. Every consumer — the API handler, the Tide SSE stream, the manifest output record — reads the Ensemble. None of them renormalize. The guarantee is a property of the type, not a hope of the pipeline.
Why one place, not many
The failure mode of normalize where convenient is that two consumers normalize differently. One rounds before summing, one sums before rounding; one clips at zero, one clips at epsilon; one sorts, one does not. The outputs drift, and the drift is invisible until someone compares two consumers and finds they disagree about the same ensemble. One normalization point removes that. The Ensemble type carries the normalized numbers; the type is the contract. A consumer that wants the raw outputs can have them, but a consumer that wants probabilities gets the Ensemble and nothing else.

What the invariant guarantees
Three things, and they are the three things every consumer relies on. First, sum(probability) is approximately 1.0 — the scenarios are a proper distribution, not a bag of numbers. Second, scenarios are sorted descending by probability — the highest-probability scenario is first, which means a consumer that takes the top-N does not need to sort. Third, entropy is in nats — the natural-log unit, the one a CRO can read as spread without a conversion factor. These are not conventions; they are the invariant. A test that the Ensemble violates any of them is a test that fails.
Normalization is a property of the type, not a hope of the pipeline. The Ensemble carries the guarantee; the consumer carries nothing.
- sum(probability) approximately 1.0 — a proper distribution, not a bag of numbers.
- Scenarios sorted descending — the top scenario is first; consumers that want top-N do not sort.
- Entropy in nats — the natural-log unit, the spread a CRO reads without a conversion factor.
What the invariant does not guarantee
The invariant guarantees the shape is normalized. It does not guarantee the shape is calibrated. A normalized, uncalibrated distribution is a more sophisticated lie than an unnormalized one, not a better one. Calibration is a separate loop — shadow runs against a realized timeline, scored with Brier and log-loss via deino-eval, gated by Gate 1b. The Ensemble is the output of the inference layer; the calibration loop is what earns the word probability.
Why the type is the contract
A consumer that receives an Ensemble does not re-check the sum, does not re-sort, does not re-derive entropy. The type already did all three. That is what makes the Ensemble a contract rather than a convention: the guarantees are checked at construction, and every consumer downstream can rely on them without re-verifying. If the construction breaks the guarantees, the test fails at the construction site, not at every consumer. We are pre-revenue, building the POC; the sisters run, the normalization is built, and the calibration loop is inert until it is filled.
The Ensemble guarantees the numbers are a distribution. It does not guarantee the distribution is right. The first is a property of the code; the second is a property of the loop.
— INV-1 design note