signature, Where the Declaration Writes ItStatus: Superseded by ADR-295 — its D1-D2 are D18-D19 there
Date: September 23, 2026
Target Version: Nikaia 0.0.181
Answers: open-decisions.md’s how does a bound
reach a caller across a package boundary?, decided by the owner in favour of the
page’s recommendation
Related: ADR-074 D2 (a generic parameter is recorded as a
variable), ADR-106 D1 and D3 (the bound’s spelling and the table of
ledger entries), ADR-024 D1 (what a ledger is for),
ADR-047 D1 (naming is per package), ADR-028 D5,
ADR-100 (the ledger’s hash and --locked)
A call into a package’s generic function was not checked against that function’s bound, and what a reader got was the backend’s words on their own line:
error: app/src/main.nika:6:5: the trait bound `Bare: Handler` is not satisfied
= the trait `Handler` is not implemented for `Bare`
= the trait `Handler` is implemented for `Static`
The position is right — ADR-005 D7’s translation puts the caret
where the program is — and the words are the backend’s, which is Part III
C.1. Handler is written without the path the
program must write, and Static is a name this program never mentions.
The check itself was four lines: Checker::declared_bounds already held which
bound each parameter carries, under the key a call resolves to. What was missing
was that key for a function in another package, and it could not be read from the
ledger, because the ledger had no column for a bound: a signature wrote
(h: $H) -> String and the : Handler was nowhere in it.
That is a format decision — every reader of a .contracts file parses it, and
--locked compares it byte for byte — so it went to
open-decisions.md with three options and a
recommendation. The owner took the recommendation.
signature, in front, where the declaration writes it.[fn."dispatch"]
pub = true
signature = "[H: Handler](h: $H) -> String"
[T] for a parameter with no bound is written nowhere: the signature’s $T
already says the parameter exists, so the list carries only what a caller has to
check. Several traits are +-joined and several parameters are ,-separated,
each the way the declaration spells them.
One string and not a second key, which is the reason the recommendation gave.
The signature already carries the type parameter as $H
(ADR-074 D2: a generic parameter is recorded as a variable, so a
caller binds it from what it passes and reads the result off the same
signature); a bound is the rest of that sentence, and a second key that has to
agree with the first is a second source of truth for one fact — the argument
ADR-106 D3 already makes about a trait’s methods, which are the
fn entries beside it rather than a list inside the trait table.
The change is additive. The list stands before the (, so a signature written
before this key existed parses unchanged, and no ledger in this tree changed a
byte: nothing in it publishes a generic function with a bound, which is what
#162 meant by the first package to use it
is the one that meets this.
A package writes its own names bare — [H: Handler] inside handler — and a
consumer writes handler::Handler, because naming is per package
(ADR-047 D1). So the trait name is qualified on the way in: every
prefix of the entry’s own key is tried, and the first spelling this program’s
trait map knows wins.
Only a spelling the program actually has. Where none matches, the name stands
as written and the fail-open line below it does what it always did — a trait this
compiler has not read is not one it refuses on (Part III
C.4). Without this the refusal never landed: the map
held handler::Handler and the bound said Handler, so the check found no trait
and passed the argument through.
It is the same under both spellings arrangement absorb_renaming already uses
for the implementations map, asked from the reading side.
NK1164 at a call into a package: handler::dispatch asks for a
handler::Handler here, and Bare is not one, with [H: handler::Handler]
quoted from the ledger and impl handler::Handler for Bare { … } as the help —
a line the program can copy. #162 closes, and §1 is empty.std entry could carry one and none does. The column is a package’s answer in
practice, which is what the question was about.where clause, or a bound on anything but a type parameter. The
declaration has no such form (ADR-106 D1), so the ledger needs no
spelling for one.[T: Struct] is ADR-088 D2’s and is
answered by a declaration rather than an impl; it records the same way, and
whether a consumer’s type is a struct is a question the consumer’s own checker
already answers.NK1171’s and
ADR-181’s ground and needs no column of its own: a trait’s methods
are the fn entries beside it.