ADR-197: A path Is Relative to the File It Is Written In

Status: Superseded by ADR-286 — its D1-D3 are D16-D18 there Date: September 22, 2026 Target Version: Nikaia 0.0.161 Narrows: ADR-002 D1 — the value with type removed reaches Cargo verbatim. A path is resolved on the way through; everything else still travels untouched Answers: #150 Related: ADR-053 D1 (the directory that made the old rule wrong), ADR-047 D2 (a Nikaia package’s path, which already worked this way)

1. Context

nikaia.toml names a Rust crate by path like this:

[dependencies]
hyper-shim = { type = "rust", path = "../../../../shim" }

Four .., because ADR-002 D1 says the value reaches Cargo verbatim and the generated manifest used to sit at target/nikaia/build/Cargo.toml.

It does not sit there any more. ADR-053 D1 gave every Nikaia package a member directory of its own, so the manifest Cargo reads a path against is target/nikaia/build/<package>/Cargo.toml — one level deeper — and every one of those four .. is one short. All three projects under examples/foreign-runtime/ have been unbuildable since, and nothing said so: the tests that build them fetch hyper and tokio from crates.io and are #[ignore]d, and CI does not run ignored tests.

And the compiler’s own reader disagreed with Cargo in the same way. describe::crate_sources resolved the value against target/nikaia/build, so nikaia describe and cargo build looked in two different places for one crate’s sources — and only the first had a test, because crates/nikaia/tests/describing.rs never runs Cargo.

The two arms of [dependencies] had two different rules, which examples/hello-http/nikaia.toml explains in a comment because getting it wrong costs a confusing message: a Nikaia package’s path (ADR-047 D2) is relative to the file it is written in, and a Rust crate’s was relative to a directory the author has never seen.

2. Decisions

D1 — A path in nikaia.toml is relative to nikaia.toml, for both arms of [dependencies].

../shim means the directory beside this project’s, as it does in every other file anybody writes. One rule for both arms, and it is the one a reader would guess.

D2 — The generated manifest carries it absolute, and that is how D1’s passthrough is narrowed.

A relative path is resolved against the project root and written out as an absolute one. Everything else in the table still reaches Cargo untouched: version, git, tag, features, default-features, a package rename.

This is not a new mechanism. The generated member manifest already writes nikaia-std = { path = "/…/crates/nikaia-std" } absolute, for the same reason: a path in a generated file cannot be relative to a directory the generator chose and the author never sees.

And the alternative was counted out and refused. Keeping the value verbatim means writing ../../../../../shim — a number a person derives from a build layout, which changes when the layout does, and which changed once already without anybody noticing for as long as the tests that would say so stayed ignored.

D3 — What a path is relative to is a property of the file, not of the ecosystem.

The old split had one answer for a Nikaia dependency and another for a Rust one, and the difference was not about either ecosystem — it was about which generated file happened to hold the value. A rule that a person has to learn per arm is one rule too many for something this shape.

3. Consequences

4. What this does not decide

5. What is built

All of it. examples/foreign-runtime/crossing, serve and smuggled name ../shim; describe::crate_sources and describe::crate_root resolve against the project root; project::cargo_member writes the resolved path.

This site is open source. Improve this page.