ADR-205: A Bound Reaches a Caller Inside the signature, Where the Declaration Writes It

Status: 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)

1. Context

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.

2. Decisions

D1 — The bound goes inside the 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.

D2 — The bound arrives in the declaring package’s namespace, and is read in the consumer’s.

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.

3. Consequences

4. What this does not decide

This site is open source. Improve this page.