Files
cargoxx/SPEC.md
2026-05-07 23:32:46 +00:00

642 lines
24 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cargoxx — project specification
A Cargo-style frontend for modern C++ that uses Nix as the source of truth for dependencies and generates CMake for the build. Users author projects with C++23 modules and never touch CMake or `flake.nix` by hand.
This document defines the user-facing contract: project layout, CLI commands, manifest schema, generated files, and the algorithms that drive code generation. It is the source of truth for what cargoxx must do. Implementation details are in `TECH_SPEC.md`.
---
## 1. Goals and non-goals
### Goals (v0.1)
- Single binary CLI `cargoxx` with `new`, `build`, `add`, `run`, `clean` commands.
- Cargo-compatible project layout. A user who knows Cargo recognizes everything.
- Module-first authoring: every C++ source unit is a module interface (`.cppm`) or a module implementation (`.cpp`). Headers are consumed via the global module fragment, not authored.
- Reproducible builds via `flake.nix` + `flake.lock`. The Nix store is the dependency cache.
- Generated CMake is hidden in `build/` and treated as a build artifact. Users never edit it.
- Curated link database covering ~25 popular libraries, shipped with the tool.
### Non-goals (v0.1)
- Not a workspace tool. No multi-package repos in v0.1.
- Not a publisher. No `cargoxx publish`, no central registry.
- No support for non-Nix package managers as primary (Conan / vcpkg recipes are read-only data sources for the link database).
- No Windows support in v0.1. Linux + macOS via Nix.
- No automatic resolution of arbitrary packages from Conan / vcpkg recipes. v0.1 ships a hand-curated database; v0.2 adds automatic resolution.
### Explicit scope cuts that may be requested but are out
- Cross-compilation
- Custom build scripts (`build.rs` equivalent)
- Features / conditional compilation flags
- Dev-dependencies separate from regular dependencies
- Profiles other than `debug` and `release`
---
## 2. Glossary
- **Manifest** — the `Cargoxx.toml` file at the project root.
- **Link recipe** — the CMake snippet (`find_package(...)` + target names) needed to consume a package. Stored per `(package, version)` in the link database.
- **Link database** — SQLite database at `~/.cache/cargoxx/linkdb.sqlite` plus a curated JSON file shipped with cargoxx.
- **Module unit** — a `.cppm` file containing `export module foo;`.
- **Implementation unit** — a `.cpp` file containing `module foo;` (no `export`).
- **Crate root** — Cargo terminology for the entry source file. We use the same convention: `src/main.cpp` is the binary crate root, `src/lib.cppm` is the library crate root.
---
## 3. User-facing project layout
The layout is fixed. cargoxx infers targets from file paths; it does not read globs from the manifest.
```
my-project/
├── Cargoxx.toml # manifest (committed)
├── Cargoxx.lock # resolved dep versions (committed)
├── flake.nix # generated from manifest (committed)
├── flake.lock # nix lockfile (committed)
├── .gitignore # cargoxx-managed entries
├── src/
│ ├── main.cpp # → binary target named after [package].name
│ ├── lib.cppm # → library target named after [package].name
│ └── bin/
│ └── tool.cpp # → additional binary target "tool"
├── tests/
│ └── basic.cpp # → test target "basic"
├── examples/
│ └── demo.cpp # → example target "demo"
└── build/ # gitignored
├── CMakeLists.txt # generated
├── debug/ # cmake binary dir for debug profile
└── release/ # cmake binary dir for release profile
```
### Target inference rules
Applied in order, each rule produces zero or more CMake targets.
| Path | Target kind | Target name |
| --- | --- | --- |
| `src/lib.cppm` | static library | `<package-name>` |
| `src/main.cpp` | executable | `<package-name>` |
| `src/bin/<name>.cpp` | executable | `<name>` |
| `src/<dir>/*.cppm` | module units belonging to `src/lib.cppm` | (none — added to library) |
| `src/<dir>/*.cpp` | implementation units belonging to `src/lib.cppm` | (none — added to library) |
| `tests/<name>.cpp` | executable + `add_test` | `test_<name>` |
| `examples/<name>.cpp` | executable | `example_<name>` |
Rules are deliberately rigid. If a user wants exotic layouts, they can drop down to raw CMake — but cargoxx will refuse to generate from a layout it doesn't recognize.
### Conflict and validation
- If `src/lib.cppm` exists, `src/main.cpp` (when present) must `import <package-name>;` — cargoxx does not enforce this with a parser, but documents it.
- If neither `src/main.cpp` nor `src/lib.cppm` exists, `cargoxx build` errors with: `no target found: expected src/main.cpp or src/lib.cppm`.
- File names other than the listed ones in `src/`, `src/bin/`, `tests/`, `examples/` are ignored (not an error). This allows users to keep auxiliary scripts in those directories.
- Subdirectories under `src/bin/`, `tests/`, `examples/` are not walked. Each top-level file is one target.
---
## 4. Manifest format — `Cargoxx.toml`
```toml
[package]
name = "my-project"
version = "0.1.0"
edition = "cpp23" # one of: cpp20, cpp23, cpp26
authors = ["Name <email>"] # optional
license = "MIT" # optional, SPDX expression
[dependencies]
fmt = "10.2"
spdlog = "1.13"
boost = { version = "1.84", components = ["filesystem", "system"] }
nlohmann_json = "3.11"
[build]
warnings_as_errors = true # optional, default false
sanitizers = ["address"] # optional, list of: address, undefined, thread, leak
```
### Dependency syntax
Two forms allowed:
```toml
fmt = "10.2" # version string only
boost = { version = "1.84", components = ["filesystem"] } # table form
```
Version strings follow Cargo semver semantics (`"10.2"` means `>=10.2.0, <11.0.0`). cargoxx resolves to the highest matching version available in the configured Nixpkgs revision.
`components` is meaningful only for packages whose link recipe declares component support (Boost, Qt, abseil). Specified components map directly to CMake targets.
### Reserved fields
The following keys are parsed and stored but not yet acted on. They MUST be accepted without error so manifests written for v0.1 stay valid in later versions:
- `[dev-dependencies]`
- `[features]`
- `[workspace]`
- `[package].repository`
- `[package].description`
---
## 5. Lockfile — `Cargoxx.lock`
Generated and updated by cargoxx. Format is TOML, committed to version control.
```toml
version = 1
[[package]]
name = "my-project"
version = "0.1.0"
dependencies = [
"fmt 10.2.1",
"spdlog 1.13.0",
]
[[package]]
name = "fmt"
version = "10.2.1"
nixpkgs_attr = "fmt_10"
nixpkgs_rev = "8a3f...c2d1"
linkdb_source = "curated"
[[package]]
name = "spdlog"
version = "1.13.0"
nixpkgs_attr = "spdlog"
nixpkgs_rev = "8a3f...c2d1"
linkdb_source = "curated"
```
`nixpkgs_rev` is the same for all entries in a single resolution — cargoxx pins one Nixpkgs revision per project, picked to satisfy all `[dependencies]` simultaneously. This is the simplest model and matches what Nix flakes do natively.
---
## 6. CLI commands
### `cargoxx new <name>` and `cargoxx new --lib <name>`
Creates a new project directory.
Default (`cargoxx new foo`) creates a binary project:
```
foo/
├── Cargoxx.toml
├── flake.nix
├── flake.lock
├── .gitignore
└── src/
└── main.cpp
```
With `--lib`:
```
foo/
├── Cargoxx.toml
├── flake.nix
├── flake.lock
├── .gitignore
└── src/
└── lib.cppm
```
`src/main.cpp` template:
```cpp
import std;
int main() {
std::println("Hello from {}!", "foo");
return 0;
}
```
`src/lib.cppm` template:
```cpp
export module foo;
import std;
export namespace foo {
auto greeting() -> std::string_view {
return "Hello from foo!";
}
}
```
After scaffolding, runs `cargoxx build --no-build` (generate flake.nix and CMakeLists.txt, do not invoke nix/cmake) so the project opens correctly in IDEs immediately.
### `cargoxx add <pkg>[@<version>]` and `cargoxx add <pkg> --components <a,b>`
Adds a dependency.
Algorithm:
1. Parse the package spec.
2. If `@<version>` is omitted, query the resolver for the latest version available in Nixpkgs and use that.
3. Resolve the link recipe (see §9).
4. Update `Cargoxx.toml` `[dependencies]` table, preserving formatting where possible.
5. Update `Cargoxx.lock`.
6. Regenerate `flake.nix`.
7. Print the chosen version and link recipe source for transparency: `Added fmt 10.2.1 (linkdb: curated)`.
If the link recipe cannot be resolved, the command fails with instructions for the user to file an issue or supply a manual recipe via `Cargoxx.toml`.
### `cargoxx remove <pkg>`
Inverse of `add`. Removes from manifest, lockfile, and regenerates `flake.nix`.
### `cargoxx build [--release] [--target <name>]`
1. Validate manifest and project layout.
2. Resolve dependencies if `Cargoxx.lock` is missing or stale.
3. Generate `build/CMakeLists.txt` from manifest and source tree.
4. Generate / update `flake.nix` if stale.
5. Run `nix develop --command cmake -B build/<profile> -S build -G Ninja -DCMAKE_BUILD_TYPE=<Profile>` if the binary dir doesn't exist or is stale.
6. Run `nix develop --command cmake --build build/<profile> [--target <name>]`.
`<profile>` is `debug` (default) or `release` (with `--release`). Capitalized `<Profile>` is `Debug` or `Release`.
### `cargoxx run [--release] [--bin <name>] [-- <args>...]`
`build` then exec the chosen binary. If the project has multiple binary targets and `--bin` is omitted, fail with a list. If exactly one binary exists, run it.
### `cargoxx test [--release]`
Build all `tests/*.cpp` targets, then run them via `ctest --output-on-failure` inside `build/<profile>`.
### `cargoxx clean`
Removes `build/`. Does not touch `Cargoxx.lock` or `flake.lock`.
### `cargoxx fmt` and `cargoxx check`
Stubs in v0.1 — accepted but only print a deprecation-style message: `cargoxx fmt: not implemented in v0.1, run clang-format directly`. This reserves the command names.
---
## 7. Generated `flake.nix`
cargoxx generates exactly this template. Fields in `<<...>>` are substituted from the manifest.
```nix
{
description = "<<package.name>>";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/<<resolved-rev>>";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
llvmPkgs = pkgs.llvmPackages;
in {
devShell = llvmPkgs.libcxxStdenv.mkDerivation {
name = "shell";
version = "1.0";
nativeBuildInputs = [
pkgs.ninja
pkgs.cmake
pkgs.clang-tools
];
env.NIX_CFLAGS_COMPILE = toString [
"-stdlib=libc++"
"-Wno-unused-command-line-argument"
"-B${pkgs.lib.getLib pkgs.libcxx}/lib"
"-isystem ${pkgs.lib.getDev pkgs.libcxx}/include/c++/v1"
];
hardeningDisable = [
"all"
];
};
});
}
```
`<<resolved-rev>>` is the Nixpkgs commit hash from `Cargoxx.lock`. The toolchain (`clang_21`, `cmake`, `ninja`) is fixed in v0.1 — Clang because module support is most mature there.
Regeneration is idempotent: cargoxx writes the file only if its content would change. This avoids spurious `flake.lock` updates.
---
## 8. Generated `build/CMakeLists.txt`
```cmake
cmake_minimum_required(VERSION 3.30)
project(<<package.name>> LANGUAGES CXX)
# ----- toolchain configuration -----
set(CMAKE_CXX_STANDARD <<edition-numeric>>)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Generated by cargoxx — do not edit.
# Source of truth: ../Cargoxx.toml
# ----- dependencies -----
<<one find_package line per dependency, from link recipe>>
# ----- library target (if present) -----
<<if src/lib.cppm exists>>
add_library(<<package.name>> STATIC)
target_sources(<<package.name>>
PUBLIC
FILE_SET CXX_MODULES FILES
../src/lib.cppm
<<each additional .cppm under src/, recursive>>
PRIVATE
<<each .cpp under src/ except src/main.cpp and src/bin/*, recursive>>
)
target_link_libraries(<<package.name>> PUBLIC
<<each dep's CMake target name from link recipe>>
)
<<endif>>
# ----- binary target (if src/main.cpp exists) -----
<<if src/main.cpp exists>>
add_executable(<<package.name>>_bin ../src/main.cpp)
set_target_properties(<<package.name>>_bin PROPERTIES OUTPUT_NAME <<package.name>>)
target_link_libraries(<<package.name>>_bin PRIVATE
<<package.name>> # only if library target also exists
<<each dep's CMake target name>>
)
<<endif>>
# ----- additional binaries (src/bin/*.cpp) -----
<<for each src/bin/<name>.cpp>>
add_executable(<<name>> ../src/bin/<<name>>.cpp)
target_link_libraries(<<name>> PRIVATE
<<package.name>> # only if library target also exists
<<each dep's CMake target name>>
)
<<endfor>>
# ----- tests -----
<<if any tests/*.cpp>>
enable_testing()
<<for each tests/<name>.cpp>>
add_executable(test_<<name>> ../tests/<<name>>.cpp)
target_link_libraries(test_<<name>> PRIVATE
<<package.name>> # only if library target also exists
<<each dep's CMake target name>>
)
add_test(NAME <<name>> COMMAND test_<<name>>)
<<endfor>>
<<endif>>
# ----- examples -----
<<for each examples/<name>.cpp>>
add_executable(example_<<name>> ../examples/<<name>>.cpp)
target_link_libraries(example_<<name>> PRIVATE
<<package.name>> # only if library target also exists
<<each dep's CMake target name>>
)
<<endfor>>
# ----- warnings and sanitizers from [build] section -----
<<if build.warnings_as_errors>>
foreach(target_name IN ITEMS <<all_targets>>)
target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wpedantic -Werror)
endforeach()
<<endif>>
<<if build.sanitizers non-empty>>
<<emit -fsanitize=... flags via target_compile_options and target_link_options>>
<<endif>>
```
Edition mapping:
- `cpp20``set(CMAKE_CXX_STANDARD 20)`
- `cpp23``set(CMAKE_CXX_STANDARD 23)`
- `cpp26``set(CMAKE_CXX_STANDARD 26)` (CMake 3.30 supports the value as experimental)
Source paths are written relative to `build/` (i.e. prefixed with `../`) because the file lives in `build/CMakeLists.txt` but sources live in `../src/`.
---
## 9. Link database — schema and resolution
### On-disk format
Curated database shipped with cargoxx, embedded as a resource (compiled in or read from `${prefix}/share/cargoxx/linkdb.json`):
```json
{
"version": 1,
"packages": {
"fmt": [
{
"version": ">=10.0.0",
"nixpkgs_attr": "fmt_10",
"find_package": "fmt CONFIG REQUIRED",
"targets": ["fmt::fmt"]
},
{
"version": ">=8.0.0,<10.0.0",
"nixpkgs_attr": "fmt_8",
"find_package": "fmt CONFIG REQUIRED",
"targets": ["fmt::fmt"]
}
],
"boost": [
{
"version": ">=1.70.0",
"nixpkgs_attr": "boost",
"find_package": "Boost REQUIRED COMPONENTS {{components}}",
"targets": ["Boost::{{component}}"],
"components": "supported"
}
],
"openssl": [
{
"version": "*",
"nixpkgs_attr": "openssl",
"find_package": "OpenSSL REQUIRED",
"targets": ["OpenSSL::SSL", "OpenSSL::Crypto"]
}
]
}
}
```
The `version` field is a Cargo-style range. cargoxx picks the first entry whose range matches the resolved version.
`{{components}}` and `{{component}}` are Mustache-style placeholders substituted at codegen time.
### User-overlay database
`~/.cache/cargoxx/linkdb.sqlite`:
```sql
CREATE TABLE recipes (
package TEXT NOT NULL,
version_range TEXT NOT NULL,
nixpkgs_attr TEXT NOT NULL,
find_package TEXT NOT NULL,
targets TEXT NOT NULL, -- JSON array
components TEXT, -- 'supported' | NULL
source TEXT NOT NULL, -- 'curated' | 'conan' | 'vcpkg' | 'nix-probe' | 'manual'
verified_at INTEGER NOT NULL, -- unix epoch
PRIMARY KEY (package, version_range, source)
);
CREATE TABLE resolution_failures (
package TEXT NOT NULL,
version TEXT NOT NULL,
last_attempt INTEGER NOT NULL,
error TEXT NOT NULL,
PRIMARY KEY (package, version)
);
```
Lookup precedence: user overlay (any source) → curated (shipped JSON) → automatic resolution (v0.2+) → fail.
### Resolution algorithm (v0.1)
`resolve_link_recipe(package, version) -> Recipe | Error`:
1. Query user overlay SQLite. If a row matches and `verified_at` is recent (<30 days for `nix-probe`, never expires for `curated` / `manual`), return it.
2. Query the embedded curated JSON. If a range matches, return it.
3. Return `Error::Unknown { package }`.
In v0.2, after step 2 the algorithm continues:
4. Try `conan-center-index` GitHub raw lookup for `recipes/<package>/all/conanfile.py`. Parse `cmake_target_name`, `cmake_file_name`. Convert to a recipe.
5. Try `microsoft/vcpkg/ports/<package>/usage`. Parse the literal CMake snippet.
6. Run `nix-build` for the package, then scan the output for `lib/cmake/*/*Config.cmake` and grep `add_library(... IMPORTED)` lines.
Each successful step writes the result to the user overlay before returning.
---
## 10. Version resolution algorithm
`resolve_version(package, version_spec) -> (version, nixpkgs_rev)`:
1. If `Cargoxx.lock` already pins this package and the spec is satisfied, return the lockfile entry.
2. Query nixhub.io: `https://www.nixhub.io/packages/<package>?_data=routes%2F_nixhub.packages.%24pkg._index`. Parse JSON for available versions and their commits.
3. If nixhub.io is unreachable, fall back to lazamar: `https://lazamar.co.uk/nix-versions/?package=<package>&channel=nixpkgs-unstable`. Parse HTML (well-formed table).
4. If both fail, fall back to a local Nixpkgs git clone at `~/.cache/cargoxx/nixpkgs/`. Run `git log --all -S 'version = "<version>"' -- pkgs/`.
5. Filter the candidate list by the version spec, choose the highest match, return `(version, rev)`.
For the *whole-project* resolution (multiple deps), cargoxx picks one revision: the latest revision that contains acceptable versions of every dependency. This is brute-force in v0.1: for each candidate revision (newest first, capped at 50 attempts), check whether all deps resolve. Take the first hit.
If no revision satisfies all constraints simultaneously, fail with a list of conflicting deps. The user resolves manually by relaxing version specs.
---
## 11. Curated package list (v0.1)
cargoxx ships with link recipes for these 25 packages. They cover the common cases. The list is fixed for v0.1; any package outside it requires a v0.2 automatic resolver or a manual user-overlay entry.
| Package | Nixpkgs attr | CMake `find_package` | CMake targets |
| --- | --- | --- | --- |
| fmt | `fmt` | `fmt CONFIG REQUIRED` | `fmt::fmt` |
| spdlog | `spdlog` | `spdlog CONFIG REQUIRED` | `spdlog::spdlog` |
| nlohmann_json | `nlohmann_json` | `nlohmann_json CONFIG REQUIRED` | `nlohmann_json::nlohmann_json` |
| boost | `boost` | `Boost REQUIRED COMPONENTS {{c}}` | `Boost::{{c}}` |
| openssl | `openssl` | `OpenSSL REQUIRED` | `OpenSSL::SSL`, `OpenSSL::Crypto` |
| zlib | `zlib` | `ZLIB REQUIRED` | `ZLIB::ZLIB` |
| sqlite3 | `sqlite` | `SQLite3 REQUIRED` | `SQLite::SQLite3` |
| curl | `curl` | `CURL REQUIRED` | `CURL::libcurl` |
| protobuf | `protobuf` | `Protobuf REQUIRED` | `protobuf::libprotobuf` |
| grpc | `grpc` | `gRPC CONFIG REQUIRED` | `gRPC::grpc++` |
| abseil-cpp | `abseil-cpp` | `absl CONFIG REQUIRED` | `absl::{{c}}` |
| gtest | `gtest` | `GTest CONFIG REQUIRED` | `GTest::gtest`, `GTest::gtest_main` |
| catch2 | `catch2_3` | `Catch2 CONFIG REQUIRED` | `Catch2::Catch2WithMain` |
| eigen | `eigen` | `Eigen3 CONFIG REQUIRED` | `Eigen3::Eigen` |
| tbb | `tbb` | `TBB CONFIG REQUIRED` | `TBB::tbb` |
| libpng | `libpng` | `PNG REQUIRED` | `PNG::PNG` |
| libjpeg | `libjpeg` | `JPEG REQUIRED` | `JPEG::JPEG` |
| freetype | `freetype` | `Freetype REQUIRED` | `Freetype::Freetype` |
| glfw | `glfw` | `glfw3 CONFIG REQUIRED` | `glfw` |
| glm | `glm` | `glm CONFIG REQUIRED` | `glm::glm` |
| sdl2 | `SDL2` | `SDL2 CONFIG REQUIRED` | `SDL2::SDL2` |
| cli11 | `cli11` | `CLI11 CONFIG REQUIRED` | `CLI11::CLI11` |
| cxxopts | `cxxopts` | `cxxopts CONFIG REQUIRED` | `cxxopts::cxxopts` |
| range-v3 | `range-v3` | `range-v3 CONFIG REQUIRED` | `range-v3::range-v3` |
| magic_enum | `magic-enum` | `magic_enum CONFIG REQUIRED` | `magic_enum::magic_enum` |
Verification of these entries is part of acceptance for v0.1 — see §13 of `TECH_SPEC.md`.
---
## 12. Error model
User-facing errors must be actionable. Each error has a code, a one-line message, and a "what to do" hint.
Examples:
```
error[E0001]: no target found
--> ./
expected one of: src/main.cpp, src/lib.cppm
hint: run `cargoxx new --lib <name>` to create a library project
```
```
error[E0042]: package not in link database
--> Cargoxx.toml:7:1
package "obscurelib" has no known CMake link recipe
hint: file an issue at <repo>/issues/new, or add a manual recipe via:
cargoxx linkdb add obscurelib --find-package "obscurelib CONFIG REQUIRED" --targets "obscurelib::obscurelib"
```
```
error[E0010]: unsatisfiable version constraint
fmt = "11.0" is not available in any Nixpkgs revision that also has spdlog = "1.13"
available revisions for fmt 11.0: 8a3f...c2d1, 7e21...b09a
available revisions for spdlog 1.13: 4d22...e1f8
hint: relax one constraint and re-run `cargoxx add`
```
The full list of error codes is in `TECH_SPEC.md` §6.
---
## 13. Tools and dependencies (host project)
cargoxx itself is a C++23 application. It is a single static binary at install time.
| Concern | Library | Why |
| --- | --- | --- |
| TOML parsing | `toml++` (header-only) | Best C++ TOML library, supports comment-preserving writes via toml::table |
| JSON parsing | `nlohmann/json` | Familiar, header-only, fine for our scale |
| HTTP client | `cpp-httplib` (header-only) | No OpenSSL hard dep at build time, bundled TLS via system OpenSSL |
| SQLite | `sqlite3` C API directly | One translation unit, no abstraction tax |
| Git operations | shell out to `git` | libgit2 is a heavy dep for what we need |
| Subprocess | `reproc` (or `boost::process`) | Cross-platform, capture stdout/stderr cleanly |
| CLI parsing | `CLI11` | Subcommand support, good UX |
| Logging | `spdlog` | Everyone has it |
| Filesystem | `std::filesystem` | Built in |
| Testing | `Catch2 v3` | Module support, modern |
Bootstrap toolchain: `clang_21`, `cmake_3_30+`, `ninja`. Provided by a bootstrap `flake.nix` shipped in cargoxx's own repo.
---
## 14. Security considerations
- cargoxx never runs commands from network responses. Conan recipes are Python files; v0.2's automatic resolver MUST parse them as text (regex / minimal AST), never execute them.
- vcpkg `usage` files are plain text and safe to read.
- nixhub.io and lazamar are trusted only as version directories. The actual fetch happens through Nix, which verifies hashes via `flake.lock`.
- The user overlay SQLite is per-user with default permissions (0600).
- cargoxx never writes outside the project directory and `~/.cache/cargoxx/`.
---
## 15. Versioning and stability
v0.1 is explicitly experimental. Manifest format may break at v0.2. After v1.0:
- Bumping `[package].edition` is the only way to opt into breaking C++ language changes.
- The manifest format is stable across minor versions.
- The link database format is independent of the manifest version.
- Generated `flake.nix` and `CMakeLists.txt` are not part of the stability contract — they're build artifacts.
`Cargoxx.lock` format version is bumped on incompatible changes; cargoxx refuses to read a newer lockfile than it understands.