Status: Superseded by ADR-280 — its D2 is D19, D3 is in D20 there; D1, generalized by ADR-241 D1, is contained in D13
Date: September 20, 2026
Target Version: Nikaia 0.0.89
Related: ADR-115 D1 and D2 (the decision this builds),
D3 and D4 (what is not built yet), ADR-050 D5 (the first failure
in written order wins), ADR-159 D2 and D3 (a library’s error
travels bare, and what the long form says when there is no site),
ADR-164 D1 and D2 (combine<n>, and one channel per block),
ADR-023 D6 (an error knows where it was raised),
ADR-157 D2 (the envelope is opened once at the binding),
Part I 8.1.2
ADR-115 D1 gives every error a secondary list — the failures
that joined it — and D2 fills it from an overlap: the first failing branch in
written order is the block’s error, and every later one joins its list. Its
context is three loads, two of them failing on one outage, and a log that shows
one.
Nothing about the list was hard until the channel got a type. When that
record was written every failure travelled in one box, and this compiler’s own
envelope had room for anything. Four records later the channel is one of four
things, and two of them had nowhere to put a list: a library’s error travels
bare (ADR-159 D2), and a generated sum’s members each keep
their own. Those two are the common ones — a function that only reads files has
io::IoError as its whole set, which is exactly this record’s own worked case.
docs/open-decisions.md carried the question. The owner answered A, and
added that it was always the plan and only badly documented — which is the
reading this record takes: D2’s reason was about the site, never about the
list.
ADR-159 D2 sends a library’s error bare because there is no
throw in this program to have a site. That holds, and it is about the site
alone: an overlap that combines failures is the language doing something,
so there is something to attach even where nothing was raised here.
So a function whose body holds a joining block gets Thrown<E> where it would
otherwise have had E, and a ? from a callee with a bare one converts through
From<E> for Thrown<E>. The envelope it puts on says no site recorded —
ADR-159 D3’s own sentence, which is what keeps D2 true in the part
that was ever about the site.
The body it is written in and no further. A caller that propagates such a
failure has a channel of its own, and whether the list survives that hop is a
question this does not answer; #199 carries it. What this
covers is the block, its catch, and the function around them — which is where
a handler for it is written and where an operator reads it.
A joining block hands every branch the same channel (ADR-164
D2), so the failures that meet there are the same kind of thing as the one they
meet: Thrown<E> carries Vec<Thrown<E>>. Each keeps its own envelope, so each
keeps the site ADR-023 D6 gives it — and a secondary with
secondaries of its own is D3’s tree, with nothing further to build for the
shape.
The boxed channel joins through a downcast, which is the price of the box rather than a shortcut. A failure that joins an error from below Nikaia — one with no envelope at all — is dropped, and that is the one case the list cannot cover. It is named here rather than discovered.
A main that hands back an Err is printed by the language below through
Debug, so that is the operator’s view of a program that stopped — and a short
form there would be the one place the joined failures are dropped on the floor.
It is the long form now, with each joined failure under the one it joined and
every line of it moved right, so the depth on the page is the depth in the list.
The no trace note is said once, on the primary: it is about how this process was started, and repeating it under every failure says one thing three times and buries them.
combine<n> stops returning at the first Err and looks at every outcome —
which is what ADR-164 D1 put it there for.error.secondary as a value a program reads — answered by
ADR-210: it is not one. The list is what a log
and an operator see; handing it to LoadFailed(error, error.secondary) needs
a Nikaia type for a list of errors, which nothing writes down yet.D1, D2 and D3. crates/nikaia-std’s own tests hold the carrier: what joined
printed under it in join order, the trace note said once, an enveloped error
from below still saying no site recorded, and the list surviving throw
error. crates/nikaia/tests/joining.rs holds the end of it — two failing
branches, compiled and run, with the first in written order winning and the
second printed under it. That test was written in
ADR-163’s package asserting the second failure was nowhere, and
turning it over is what building this meant.