Contributing Guide
Thank you for your interest in contributing to Zyvor Fabric. This document covers the development workflow, code style conventions, testing requirements, and pull request process.
Table of Contents
- Development Environment Setup
- Building the Project
- Running Tests
- Code Style
- Architecture Guidelines
- Adding a New API Endpoint
- Adding a New Crate
- Pull Request Process
- Commit Message Format
Development Environment Setup
Prerequisites
- Rust 1.75 or later (install via rustup)
- Linux (x86_64) with systemd 254+
- Development headers:
openssl-devel,pam-devel,systemd-devel - Node.js 20+ and npm (for web UI development)
- Git
Initial Setup
# Clone the repository
git clone https://github.com/zyvorai/fabric.git
cd zyvor-fabric
# Install Rust toolchain components
rustup component add clippy rustfmt
# Build everything to verify the environment
cd backend && cargo build
# Run the test suite
cargo test
# Set up the web UI
cd ../web && npm install
Editor Setup
For VS Code with rust-analyzer:
{
"rust-analyzer.linkedProjects": ["backend/Cargo.toml"],
"rust-analyzer.check.command": "clippy"
}
Building the Project
cd backend
# Full debug build (all 53 crates)
cargo build
# Release build (optimized)
cargo build --release
# Fast compile check (no codegen, faster than build)
cargo check
# Build a single crate
cargo build -p vm-model
cargo build -p state-store
# Check for warnings with clippy
cargo clippy -- -W clippy::all
See building.md for detailed build instructions including the web UI.
Running Tests
cd backend
# Run all tests
cargo test
# Run tests for a specific crate
cargo test -p vm-model
cargo test -p state-store
cargo test -p security
# Run a specific test by name
cargo test test_create_vm
# Run tests with output
cargo test -- --nocapture
# Run integration tests only
cargo test -p Zyvor Fabric --test '*'
Catch-up coverage (FluxVM boundary)
CI also runs an explicit Catch-up coverage job (see root CONTRIBUTING.md and
.github/workflows/ci.yml): FluxVM stub smoke, Rust serde/HTTP contracts for
migration receivers / dataplane pod fields / QGA / agent-runtime, and Vitest API
path filters. Fabric E2E sources scripts/ci-feat-catchup-stub.sh against the
same stub. Lab-only verify: scripts/feat-catchup-verify.sh.
Agent sessions have their own workflow, .github/workflows/agent-runtime.yml.
The sessions job runs agent-runtime/tests/session-ci.sh with Node, Rust,
and Go. It does not need FluxVM. A push to main also runs
.github/workflows/lab-deploy.yml, which SSHes to the lab host with
LAB_DEPLOY_KEY and runs scripts/deploy-remote.sh --quick --e2e --verify-apis.
Test Requirements
- All tests must pass before submitting a PR
- Zero compiler warnings are required
- New features should include unit tests
- API endpoint changes should include integration tests
Code Style
Rust Conventions
Zyvor Fabric follows standard Rust conventions with these specific preferences:
Formatting: Use rustfmt with default settings. Run cargo fmt before
committing.
Naming:
- Crate names:
kebab-case(e.g.,vm-model,state-store) - Module names:
snake_case(e.g.,network_policy.rs) - Types:
PascalCase(e.g.,VMState,CreateVMRequest) - Functions:
snake_case(e.g.,list_vms,validate_vm_name) - Constants:
SCREAMING_SNAKE_CASE(e.g.,MAX_WS_CONNECTIONS)
Error Handling:
- Use
anyhow::Resultfor functions that can fail in multiple ways - Use
thiserrorfor crate-specific error types - Never
unwrap()in production code; useunwrap_or_elsewith recovery - Return structured JSON errors from API handlers
Logging:
- Use
tracingmacros:tracing::info!,tracing::warn!,tracing::error! - Include structured fields:
tracing::info!(vm_name = %name, "Starting VM") - Use
debug!for detailed operational information - Use
warn!for recoverable anomalies - Use
error!for conditions that require operator attention - Never log secrets, passwords, or JWT tokens
Documentation:
- All public functions must have a doc comment (
///) - Include
# Errorssection for functions returningResult - Include
# Panicssection if a function can panic
Security:
- Validate all user input before use
- Use
validate_vm_name()for VM names,validate_entity_name()for others - Sanitize error messages for non-admin users
- Never include file paths in errors returned to non-admin users
API Conventions
- All API endpoints are under
/api/v1/ - Use plural nouns for resource collections:
/app/vms,/app/templates,/app/backups - Use nested resources for relationships:
/app/vms/{name}/snapshots - Use POST for actions:
/app/vms/{name}/start,/app/vms/{name}/stop - Return paginated responses:
{ items: [...], total, offset, limit } - Return
201 Createdfor successful creation - Return
404 Not Foundfor missing resources - Return
409 Conflictfor state conflicts (e.g., starting an already-running VM) - Return
422 Unprocessable Entityfor validation errors
Architecture Guidelines
Adding New Functionality
When adding new functionality, follow the established pattern:
- Define data models in the appropriate crate (usually
vm-modelor a domain-specific crate) - Implement business logic in a domain crate (e.g.,
encryption,network-policy) - Add state persistence via
StateStore::save_entity/get_entity - Create API handlers in a new module under
Zyvor Fabric/src/api/ - Register routes in
Zyvor Fabric/src/server.rsbuild_router() - Add auth extractors (
RequireRead,RequireWrite,RequireAdmin) - Write audit log entries for state-changing operations
- Emit SSE events for real-time notifications
- Add tests for both the domain crate and API handlers
Concurrency
- Use
Arc<AppState>for shared state access across handlers - Acquire per-VM locks via
state.vm_lock(name)for state-changing operations - Use
tokio::sync::RwLockfor read-heavy shared state - Use
std::sync::Mutexfor simple, non-async critical sections - Prefer
tokio::task::spawn_blockingfor CPU-bound or blocking I/O work
State Management
- All persistent state goes through the
StateStore - Use atomic writes (write to
.tmp, thenrename) for crash safety - Validate entity IDs to prevent path traversal
- Keep the in-memory VM cache in sync with the filesystem state
Adding a New API Endpoint
Step-by-step example: adding a GET /api/v1/vms/{name}/health endpoint.
1. Create or update the API module
// Zyvor Fabric/src/api/vm_health.rs
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
use std::sync::Arc;
use security::RequireRead;
use crate::server::AppState;
use crate::validation::validate_vm_name;
pub async fn get_vm_health(
RequireRead(_claims): RequireRead,
State(state): State<Arc<AppState>>,
Path(name): Path<String>,
) -> impl IntoResponse {
if let Err((status, msg)) = validate_vm_name(&name) {
return (status, Json(json!({ "error": msg }))).into_response();
}
// Business logic here
(StatusCode::OK, Json(json!({ "status": "healthy" }))).into_response()
}
2. Register the module
// Zyvor Fabric/src/api/mod.rs
pub mod vm_health;
3. Add the route
// Zyvor Fabric/src/server.rs, inside build_router()
.route("/vms/{name}/health", get(api::vm_health::get_vm_health))
4. Add tests
// Zyvor Fabric/tests/vm_health_test.rs
#[tokio::test]
async fn test_get_vm_health() {
// Set up test AppState, make request, assert response
}
Adding a New Crate
-
Create the crate directory under
backend/:cd backend && cargo init --lib my-new-crate -
Add it to
backend/Cargo.tomlworkspace members:members = [# ... existing members"my-new-crate",] -
Use workspace dependencies:
# my-new-crate/Cargo.toml[dependencies]serde.workspace = trueanyhow.workspace = true -
Add it as a dependency to
Zyvor Fabric/Cargo.toml:my-new-crate = { path = "../my-new-crate" }
Pull Request Process
-
Create a feature branch from
main:git checkout -b feature/my-feature -
Make your changes following the code style and architecture guidelines
-
Verify locally:
cd backendcargo fmt -- --check # Check formattingcargo clippy # Check for lint warningscargo test # Run all testscargo check # Verify compilation -
Commit with a descriptive message (see format below)
-
Push and open a pull request against
main -
PR requirements:
- All CI checks pass (build, test, clippy, fmt)
- Zero compiler warnings
- Zero test failures
- Code review approval
- New code has appropriate test coverage
Commit Message Format
Use descriptive, multi-line commit messages:
<type>: <short summary in imperative mood>
<body: explain what changed and why, not how>
<optional: list notable changes>
Types:
feat: New featurefix: Bug fixrefactor: Code restructuring without behavior changedocs: Documentation changestest: Test additions or fixesperf: Performance improvementsecurity: Security fix or hardeningchore: Build, tooling, or dependency updates
Examples:
feat: Add per-VM health check endpoint
Adds GET /api/v1/vms/{name}/health that returns the health status
of a VM based on its current state and recent error history.
- New api module: vm_health.rs
- Requires RequireRead authentication
- Returns JSON with status and last_check timestamp
fix: Prevent path traversal in entity ID validation
The entity ID validator was not checking for null bytes, which could
allow path traversal on certain filesystems. Added \0 to the
rejected character set.