ADR-103: A Package Is Found by Version Through Cargo, Under the Crate Name nikaia_<name>

Status: Superseded by ADR-286 — its D1-D5 are D23-D27 there Date: September 15, 2026 Target Version: Nikaia 0.0.8 Related: ADR-002 D1 (one CLI wraps Cargo, and a Rust dependency already reaches it by version), ADR-053 D1 and D3 (one crate per package; a transitive package stays invisible), ADR-047 D2 (a package by path, which stays), ADR-100 D1 and D3 (a fetched package is read through its ledger, hashed against its sources), ADR-021 D2 (resolved versions are recorded in the lockfile), ADR-046 D1 (the manifest key is the use name)

1. Context

A version names a package only relative to somewhere packages of that name live. nikaia.toml already says which version a package is; what a consumer writing http = "1.2" lacks is where to look, what "1.2" ranges over, and what it will find there. All three exist in the tool every Nikaia build already runs: Cargo has an index, a version grammar, a lockfile and git dependencies with a tag, and a Rust dependency already reaches it as { type = "rust", version = "1.5" }. What Nikaia has to decide is only the name it publishes under, because a Nikaia package called http and the Rust crate called http would otherwise be one name on one index.

2. Decisions

D1 — A published Nikaia package is a crate named nikaia_<name>; the package’s own name is <name>.

http is nikaia_http on crates.io. The prefix is the index’s business and never a program’s: no .nika file, no use, no diagnostic and no ledger entry ever carries it.

D2 — A version in nikaia.toml resolves through Cargo, and the generated manifest writes the rename.

[dependencies]
http = "1.2"                                       # crates.io: nikaia_http, Cargo's semver
http = { git = "https://…/http", tag = "v1.2" }    # a tag instead of an index
http = { path = "../http" }                        # as before (ADR-047 D2)
regex = { type = "rust", version = "1.5" }         # a Rust crate, as before

The first becomes http = { package = "nikaia_http", version = "1.2" } in the generated Cargo.toml — Cargo’s own renaming, so the local name is the use name and the prefix appears once, where Cargo reads it. "1.2" means what it means to Cargo. The resolved version is recorded in nikaia.lock (ADR-021 D2), and --locked means for it what it means there.

A key is an identifier, because it is a use name: http_server, not http-server.

D3 — Cargo finds and fetches; the compiler reads what it finds and builds as it does today.

A published crate carries nikaia.toml, src/*.nika and nikaia.contracts, and a Cargo.toml whose only job is to exist for the resolver — its name, its version, and its own nikaia_ dependencies. Nothing lowered is published: the lowering is the consuming compiler’s and keyed by its fingerprint (ADR-021). The consumer asks Cargo where the crate landed, reads its sources and its ledger from there, and emits it into its own workspace as a member, exactly as a path dependency is emitted now (ADR-053 D1). The stub is never linked.

D4 — A crate under the prefix that is not a Nikaia package is refused by the compiler, not trusted by the name.

nikaia_ is a convention the index does not enforce. A fetched crate with no .nika sources or no nikaia.contracts is refused as not a Nikaia package, naming the crate. One that has both is read the way any package with sources is: its ledger is believed while the source hashes match and derived again where they do not (ADR-100 D3).

D5 — A fetched package’s own dependencies are resolved in the same resolution, and stay invisible to the consumer.

The stub’s Cargo.toml lists them, so Cargo fetches the whole graph at once, and a path dependency’s nikaia.toml is followed the same way. What the consumer may name is unchanged: a transitive package is not in its ledger and not in its namespace (ADR-053 D3); what it reads is its direct dependency’s ledger, computed with that dependency’s own dependencies in view (ADR-100 D5).

3. Consequences

A second author can publish a library and a consumer can depend on it with one line, and nothing in the language changes: the prefix lives in one generated line, the version grammar is borrowed, the lockfile already had the column.

What the project gives up is a name space of its own. A Nikaia package lives on crates.io under a prefix, and an index of Nikaia’s own would be a second place to publish. That is a real thing to give up, and it is reversible: the compiler reads a directory, and where the directory came from is D3’s business alone.

What a squatter can do is take nikaia_foo first. D4 makes the crate useless as a Nikaia package and names it; it does not free the name. That is crates.io’s rule for every prefix and is accepted.

4. What this does not decide

5. What is built

Nothing. http = "1.2" is refused today with the sentence Part III 13.3 used to carry, and a package’s own package dependencies are refused rather than followed. The order of work:

  1. the manifest arm — a bare version and a git table are read, and the generated Cargo.toml writes the rename (D2);
  2. resolution — the compiler asks Cargo where each nikaia_ crate landed and reads it as a package (D3), refusing one that is not (D4);
  3. following a dependency’s own nikaia.toml and stub, in both arms (D5);
  4. the stub written by the build, so that cargo publish on it is the whole of publishing.

#99 carries it.

This site is open source. Improve this page.