diff --git a/AGENTS.md b/AGENTS.md index 4442e5f091..889259630d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,10 +5,10 @@ This file provides guidance for AI coding agents working in the ## Project Overview -**NCX Infra Controller (NICo)** is an API-based microservice written in Rust -that provides site-local, zero-trust, bare-metal lifecycle management with -DPU-enforced isolation. It automates the complexity of the bare-metal lifecycle -to fast-track building next-generation AI Cloud offerings. +**NVIDIA Infra Controller (NICo)** is an API-based microservice written in Rust +and Golang that provides site-local, zero-trust, bare-metal lifecycle +management with DPU-enforced isolation. It automates the complexity of the +bare-metal lifecycle to fast-track building next-generation AI Cloud offerings. > **Status:** Experimental/Preview. APIs, configurations, and features may > change without notice between releases. @@ -45,6 +45,7 @@ infra-controller/ ├── lints/ # Custom Clippy lints (carbide-lints crate) ├── include/ # Shared Makefile fragments ├── .github/ # GitHub Actions workflows and templates +├── rest-api/ # Golang-based REST API ├── Cargo.toml # Workspace dependency management ├── Makefile.toml # Primary build/task automation ├── Makefile-build.toml # Build-specific tasks @@ -53,6 +54,7 @@ infra-controller/ ## Technology Stack +### gRPC API and components: - **Language:** Rust (edition 2024, toolchain pinned in `rust-toolchain.toml`) - **Async runtime:** Tokio - **gRPC framework:** Tonic (with TLS via Rustls/aws_lc_rs) @@ -62,6 +64,9 @@ infra-controller/ - **Build tool:** `cargo-make` (TOML task runner) - **API definitions:** Protocol Buffers (protobuf) +### REST API and components +- **Language (REST API):** Golang 1.26.x + ## Build, Test, and Lint Commands All task automation uses `cargo-make`. Install it with: @@ -159,17 +164,6 @@ verification expectations. See [`STYLE_GUIDE.md`](STYLE_GUIDE.md) for detailed Rust coding conventions. Make sure to review it to ensure changes meet the expected style of the codebase. -### Avoid stringly-typed values - -When a value has a known, finite set of possibilities, model it with an enum (or -a struct of enums) and derive its string form via `Display`/`FromStr` — do not -pass it around as a bare `String` or `&str` literal. Stringly-typed values are -easy to misspell (`NICO-` vs `NICOO-`), silently break log filters and alerts, -and can't be exhaustively checked by the compiler. See -[`ErrorCode`](crates/api-model/src/errors.rs) for the pattern: typed -`ErrorSystem`/`ErrorSubsystem` parts plus a `code`, rendered to the wire string -in one place. Reserve raw strings for genuinely open-ended values. - ### Instrumentation: logs and metrics The decision rule: diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index 18bff70019..d216af2c52 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -695,3 +695,74 @@ fn prefer() { fails().ok(); } ``` + +### Avoid stringly-typed values + +When a value has a known, finite set of possibilities, model it with an enum +(or a struct of enums) and implement traits `Display` and `FromStr` — do not +pass it around as a bare `String` or `&str` literal. Stringly-typed values are +easy to misspell (`NICO-` vs `NICOO-`), silently break log filters and alerts, +and can't be exhaustively checked by the compiler. See +[`ErrorCode`](crates/api-model/src/errors.rs) for the pattern: typed +`ErrorSystem`/`ErrorSubsystem` parts plus a `code`, rendered to the wire string +in one place. Reserve raw strings for genuinely open-ended values. + +### Prefer methods over free functions + +When a function operates primarily on a specific type, define it as a method on that type rather than a free-standing function. This keeps related behavior co-located with the type, makes it easier to discover via autocomplete, and reads more naturally at the call site. + +```rust +// Avoid — free function that operates on a specific type +fn machine_display_name(machine: &Machine) -> String { + format!("{} ({})", machine.hostname, machine.id) +} + +fn is_machine_ready(machine: &Machine) -> bool { + machine.state == MachineState::Ready && machine.health.is_ok() +} + +// Prefer — methods on the type itself +impl Machine { + fn display_name(&self) -> String { + format!("{} ({})", self.hostname, self.id) + } + + fn is_ready(&self) -> bool { + self.state == MachineState::Ready && self.health.is_ok() + } +} +``` + +This applies to enums as well: + +```rust +// Avoid +fn is_terminal_state(state: &MachineState) -> bool { + matches!(state, MachineState::Failed | MachineState::Decommissioned) +} + +fn state_label(state: &MachineState) -> &'static str { + match state { + MachineState::Ready => "ready", + MachineState::Failed => "failed", + MachineState::Decommissioned => "decommissioned", + } +} + +// Prefer +impl MachineState { + fn is_terminal(&self) -> bool { + matches!(self, Self::Failed | Self::Decommissioned) + } + + fn label(&self) -> &'static str { + match self { + Self::Ready => "ready", + Self::Failed => "failed", + Self::Decommissioned => "decommissioned", + } + } +} +``` + +Free functions are still appropriate when the logic genuinely spans multiple unrelated types, belongs in a module rather than a single type, or is a utility with no natural owner.