Status: Superseded by ADR-280 — its D1-D3 are D21-D23 there
Date: September 25, 2026
Target Version: Nikaia 0.0.186
Answers: open-decisions.md’s does the language
have a type for a list of errors?, decided by the owner — option 3, and
against that page’s recommendation of 1
Revises: ADR-115 D4’s example; ADR-170 §4’s
second bullet is answered
Corrected in part by: ADR-211 — D3’s the success path never
sees an envelope: a Result is as large as its failure half whether it fails
or not, and the fields cost 88 bytes on every call until they became one word
Related: ADR-050 (overlap), ADR-078 §4 (no
dyn), ADR-159 D2 (the bare channel)
When the branches of an overlap fail together, or a cleanup fails while an
error is already on its way, two failures exist at once and a throw can carry
one. ADR-115 keeps the others: they are attached to the one that
leaves as its secondary list, and ADR-170 built that — the
list is on the envelope, survives throw error, and an uncaught failure prints
it indented under the first.
ADR-115 D4 went one step further and wrote a handler that reads the list as a
value: throw LoadFailed(error, error.secondary). That needs a type a program
can write for a list of errors, and the question was which: an opaque
Failures, dyn as a type, or neither.
Asked why the list exists at all, the answer is one sentence: the language makes two failures happen at once, so it may not silently drop one. That purpose is the operator’s — an outage that takes the database and the cache down must say both in the log — and it is built.
Asked who needs to read the list in code, nothing in this repository does, and no program has been written that would. The one place it appeared is the record’s own example. A type introduced for an example is the shape this project refuses elsewhere: a construct whose only reader is the sentence that introduced it.
It travels with the failure, is kept by throw error, and is shown by the long
form, an uncaught failure and nikaia explain. There is no error.secondary
in the language, and no type for a list of errors.
Combining failures is still a catch on the block, and the handler has the
winner. A handler that re-throws it with throw error hands the whole list on;
one that wraps it in an error of its own starts a new failure whose own site is
the throw, and the joined failures stay with the log line of the one it
caught.
Only an overlap and a failing cleanup fill the list. It still sits on every
envelope (Thrown<E>, Raised) because a failure travels: one raised in a
function without an overlap can reach, through ?, a function with one and
become the failure the others join. Two envelope types — with and without a
list — would need a conversion at every boundary between them. An empty list
allocates nothing and is paid only on the failure path; the success path never
sees an envelope. A bare failure from a library (ADR-159 D2)
has no envelope and no list, and gains one only where a body joins
(ADR-170 D1).
If a program turns up that must read the list, an opaque Failures in std is
still the answer — count, walk, print — and adding it then breaks nothing,
because no program can have been written against a name that does not exist.
The direction taken is the cheap one to reverse.
Nothing new. Part I 7.1 and 8.1.2 are corrected: they promised a readable
error.secondary, which was never built. open-decisions.md loses its last
open question, and #199 the bullet that pointed at it.