provisioning/adrs/adr-030-platform-crate-naming.ncl

91 lines
10 KiB
Text
Raw Normal View History

let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-030",
title = "Platform Workspace Crate Naming Convention",
status = 'Accepted,
date = "2026-04-19",
context = "The platform workspace accumulated three inconsistent naming patterns: (1) shared platform libraries had a `platform-` prefix (`platform-config`, `platform-nats`, `platform-db`); (2) the smart interface layer had a `provisioning-` prefix (`provisioning-core`, `provisioning-tool`, `provisioning-daemon`); (3) everything else had no prefix at all (`rag`, `mcp-server`, `service-clients`, `observability`, `machines`, `backup`, `encrypt`). The no-prefix group caused three concrete problems: (a) `cargo build -p rag` was ambiguous in the workspace resolver if a second `rag` dependency ever appeared; (b) `rag = { workspace = true }` in dependency declarations gave no indication which project the dep belonged to; (c) `observability` clashed with an identically-named crate on crates.io if the crate ever needed publishing. The binary names (set independently via `[[bin]]`) were already consistent — all used `provisioning-` prefix — so the inconsistency was purely at the Cargo package name level.",
decision = "Apply a four-rule naming convention across all workspace crates. Rule 1 — Shared platform libraries: `platform-<name>` package name; Rust crate name defaults to `platform_<name>`. Rule 2 — Smart interface layer: `provisioning-<name>` package name and binary name. Rule 3 — Service binaries: short package name (`orchestrator`, `vault-service`, etc.); binary name carries the `provisioning-` prefix via `[[bin]] name = 'provisioning-<name>'`. Rule 4 — Ecosystem crates with many existing `use` callers: `platform-<name>` package name + `[lib] name = 'old_name'` to preserve the Rust crate name across all existing `use` statements and doc tests without modifying call sites. Crates that are never declared as dependencies (service binaries) are exempt from the package name prefix because their Cargo identifier is only used in `cargo build -p <name>` invocations, never in `[dependencies]` sections.",
rationale = [
{
claim = "Service binaries do not need a prefix because they are never declared as dependencies",
detail = "The workspace resolver needs unique package names when packages are referenced as dependencies. Service binaries (orchestrator, control-center, vault-service, ai-service, extension-registry, ncl-sync) are leaf nodes — nothing in the workspace has `orchestrator = { ... }` in its `[dependencies]`. Their package name is only used in `cargo build -p orchestrator` and `cargo check -p orchestrator` invocations, where the directory context already disambiguates. Adding a `provisioning-` prefix would increase keystroke cost without adding disambiguation value. The binary name (the output artifact) already carries the prefix via `[[bin]]`.",
},
{
claim = "Ecosystem crates preserve Rust crate names via [lib] name to avoid touching 30+ doc test locations",
detail = "The ecosystem crates (machines, observability, backup, encrypt) use their crate name extensively in `///` and `//!` doc comment code examples that are compiled as doc tests. For `encrypt` alone, 25+ `use encrypt::` occurrences appear across `src/` and `examples/`. Changing the Rust crate name would require updating every one. The `[lib] name = 'old_name'` field in Cargo.toml decouples the package name (used by the workspace resolver) from the crate name (used by `use` statements). This preserves all existing Rust code, all doc tests, and all example files unchanged while making the package names consistent in `Cargo.toml` dependency declarations.",
},
{
claim = "platform-rag and provisioning-mcp required only Cargo.toml changes because they already had custom lib and binary names",
detail = "`rag` already had `[lib] name = 'provisioning_rag'` and `[[bin]] name = 'provisioning-rag'` — both Rust names were already correct. Only the `[package] name` field and workspace dep key needed updating. Similarly, `mcp-server` had `[lib] name = 'provisioning_mcp_server'` and two `[[bin]]` entries. Renaming these packages to `platform-rag` and `provisioning-mcp` was a pure Cargo identity change with zero impact on Rust compilation or binary output.",
},
{
claim = "service-clients required Rust use statement updates because it had no custom lib name",
detail = "Unlike the ecosystem crates, `service-clients` had no `[lib] name` override. Its Rust crate name was `service_clients` (derived from the package name by replacing hyphens with underscores). Renaming the package to `platform-clients` changes the default crate name to `platform_clients`. There were only three call sites in active crates: two in `provisioning-core/src/sources/ssh.rs` and one in `orchestrator/src/ssh/key_deployer.rs`. Updating three files was less friction than adding a `[lib] name = 'service_clients'` that would permanently diverge the package name from the Rust crate name.",
},
],
consequences = {
positive = [
"Every library crate declared as a `[dependencies]` entry now carries a `platform-` or `provisioning-` prefix — the project affiliation is unambiguous in any Cargo.toml context",
"The workspace resolver cannot silently select the wrong crate if an external dependency named `rag`, `observability`, or `machines` appears in the dependency tree",
"Binary output names are unchanged — no deployment scripts, systemd units, or Application Support paths require updates",
"NCL config keys (`rag = { ... }`, `mcp-server = { ... }`) are service identifiers unrelated to Cargo package names — they are unchanged",
"Ecosystem crate Rust code (doc tests, examples, use statements) compiles without modification",
"cargo check --workspace passes immediately after the rename",
],
negative = [
"The package name and Rust crate name are now different for the four ecosystem crates — `platform-machines` in Cargo.toml but `use machines::` in Rust. This is a supported Cargo feature but requires contributors to know about `[lib] name`",
"Cargo.lock contains the new package names — any tooling that parses Cargo.lock by package name (dashboards, audit tools) needs to be updated if it references the old names",
],
},
alternatives_considered = [
{
option = "Add provisioning- prefix to all service binary package names",
why_rejected = "Service binaries are never declared as dependencies — the prefix adds no disambiguation value. `cargo build -p provisioning-orchestrator` is longer than `cargo build -p orchestrator` with no benefit. The binary output already uses `provisioning-orchestrator` via `[[bin]]`.",
},
{
option = "Add [lib] name = 'service_clients' to platform-clients instead of updating use statements",
why_rejected = "There were only three call sites. Adding a divergent lib name permanently embeds a naming inconsistency in the codebase. Updating three files is the right call at this scale. If there had been 30+ call sites the decision would have been different.",
},
{
option = "Rename ecosystem crates and update all use statements",
why_rejected = "encrypt/src/ alone has 25+ doc test use statements across 6 files plus 3 examples. The work is mechanical but creates a large diff with no behavioral change. [lib] name achieves the same Cargo-level disambiguation with a one-line addition per crate.",
},
{
option = "Keep the status quo — no rename",
why_rejected = "The status quo had three inconsistent naming patterns in the same workspace. `cargo tree` output was confusing; dep declarations in Cargo.toml files were ambiguous about project affiliation; crates.io collision risk existed for generic names. The inconsistency was a maintenance friction that compounds with each new crate added.",
},
],
constraints = [
{
id = "new-library-crates-need-platform-prefix",
claim = "Any new library crate added to the platform workspace that will be declared as a dependency must use the platform- or provisioning- prefix in its [package] name",
scope = "platform/Cargo.toml members, platform/crates/, platform/prov-ecosystem/crates/",
severity = 'Soft,
check = { tag = 'NuCmd, cmd = "glob 'provisioning/platform/crates/*/Cargo.toml' | each {|f| open $f | get package.name } | where { not ($in =~ 'platform-|provisioning-|orchestrator|control-center|vault-service|ai-service|extension-registry|ncl-sync|contract-tests|prvng-cli') } | if ($in | is-empty) { exit 0 } else { print $in; exit 1 }", expect_exit = 0 },
rationale = "The naming convention is only useful if it is consistently applied to new crates. A new crate named 'cache' or 'metrics' has the same disambiguation problem the renamed crates had.",
},
{
id = "service-binary-package-names-stay-short",
claim = "Service binary package names (leaf nodes never declared as deps) must NOT get a provisioning- prefix — short name only, prefix lives in [[bin]] name",
scope = "platform/crates/orchestrator, platform/crates/control-center, platform/crates/vault-service, platform/crates/ai-service, platform/crates/extension-registry, platform/crates/ncl-sync",
severity = 'Soft,
check = { tag = 'Grep, pattern = "orchestrator\\s*=|control-center\\s*=|vault-service\\s*=|ai-service\\s*=|extension-registry\\s*=|ncl-sync\\s*=", paths = ["provisioning/platform/Cargo.toml"], must_be_empty = true },
rationale = "Adding a prefix to service binary package names would break existing cargo build -p <name> muscle memory and CI scripts without adding any correctness benefit. The rule is: prefix where disambiguation matters (dep declarations), not where it is only cosmetic (package name for leaf binaries).",
},
],
ontology_check = {
decision_string = "Rename platform workspace crates to apply a coherent naming convention",
invariants_at_risk = ["config-driven-always"],
verdict = 'Safe,
},
}