Skip to main content

Rust Backend Patterns

Rust Backend Patterns

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Expert skill for Rust/Actix-web development in the T2 backend (GCP-deployed API).

When to Use

Use this skill when:

  • Implementing new REST API endpoints (GET, POST, PUT, DELETE)
  • Adding authentication middleware or authorization logic
  • Creating custom error types and error handling patterns
  • Writing repository methods for FDB data access
  • Setting up CORS, rate limiting, or other middleware
  • Structuring Actix-web handlers with proper dependency injection
  • Writing integration tests for backend endpoints
  • Need time savings: 50% faster endpoint implementation (40→20 min)

Don't use this skill when:

  • Working on frontend-only features (use React/TypeScript patterns)
  • Debugging FDB queries (use foundationdb-queries skill)
  • Deploying to GKE (use build-deploy-workflow skill)
  • Writing production error handling (use production-patterns skill for circuit breakers)

Handler Pattern

// backend/src/handlers/*.rs
use actix_web::{web, HttpResponse};
use crate::db::FDBService;
use crate::middleware::Claims;

#[post("/api/v5/resource")]
async fn create_resource(
claims: Claims, // JWT auth - always required!
data: web::Json<CreateResourceRequest>,
fdb: web::Data<FDBService>,
) -> Result<HttpResponse, ApiError> {
// 1. Extract tenant from claims (auto-validated by middleware)
let tenant_id = claims.tenant_id;

// 2. Use repository pattern
let resource = fdb.resources()
.create_with_tenant(tenant_id, data.into_inner())
.await?;

// 3. Return success
Ok(HttpResponse::Created().json(resource))
}

Error Handling Pattern

// Use map_err() for TransactionCommitError
trx.commit().await.map_err(|e| {
ApiError::DatabaseError(format!("Failed to commit: {:?}", e))
})?;

Auth Middleware

ALL endpoints except /health and /ready require JWT:

// Automatically validates JWT and provides Claims
async fn handler(claims: Claims) -> Result<HttpResponse, ApiError> {
let tenant_id = claims.tenant_id; // ✅ Validated
let user_id = claims.user_id; // ✅ Validated
// ...
}

Repository Pattern

NEVER use FDB directly. Always use repositories:

// ✅ CORRECT
let user = fdb.users().get_by_id(&tenant_id, &user_id).await?;

// ❌ WRONG
let key = format!("/{}/users/{}", tenant_id, user_id);
let value = trx.get(&key.as_bytes()).await?;

Complete CRUD Endpoint Patterns

GET - Retrieve Single Resource

// backend/src/handlers/users.rs
use actix_web::{get, web, HttpResponse};
use crate::middleware::Claims;
use crate::db::repositories::UserRepository;

#[get("/api/v5/users/{user_id}")]
async fn get_user(
claims: Claims,
path: web::Path<Uuid>,
db: web::Data<Database>,
) -> Result<HttpResponse, ApiError> {
let user_id = path.into_inner();
let tenant_id = claims.tenant_id;

// Repository pattern with tenant isolation
match UserRepository::get(&db, &tenant_id, &user_id).await? {
Some(user) => Ok(HttpResponse::Ok().json(user)),
None => Err(ApiError::NotFound(format!("User {} not found", user_id))),
}
}

GET - List Resources with Pagination

#[derive(Deserialize)]
struct ListUsersQuery {
#[serde(default = "default_limit")]
limit: usize,
#[serde(default)]
offset: usize,
}

fn default_limit() -> usize { 50 }

#[get("/api/v5/users")]
async fn list_users(
claims: Claims,
query: web::Query<ListUsersQuery>,
db: web::Data<Database>,
) -> Result<HttpResponse, ApiError> {
let tenant_id = claims.tenant_id;

let users = UserRepository::list_by_tenant(&db, &tenant_id).await?;

// Apply pagination
let total = users.len();
let paginated: Vec<_> = users
.into_iter()
.skip(query.offset)
.take(query.limit)
.collect();

Ok(HttpResponse::Ok().json(json!({
"users": paginated,
"total": total,
"limit": query.limit,
"offset": query.offset,
})))
}

POST - Create Resource

#[derive(Deserialize, Validate)]
struct CreateUserRequest {
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
#[validate(length(min = 1, max = 100))]
name: String,
}

#[post("/api/v5/users")]
async fn create_user(
claims: Claims,
data: web::Json<CreateUserRequest>,
db: web::Data<Database>,
) -> Result<HttpResponse, ApiError> {
// 1. Validate input
data.validate()
.map_err(|e| ApiError::ValidationError(format!("{}", e)))?;

// 2. Extract tenant from claims
let tenant_id = claims.tenant_id;

// 3. Create via repository
let user = UserRepository::create(&db, &tenant_id, &data.into_inner()).await?;

// 4. Return 201 Created
Ok(HttpResponse::Created().json(user))
}

PUT - Update Resource

#[derive(Deserialize, Validate)]
struct UpdateUserRequest {
#[validate(length(min = 1, max = 100))]
name: Option<String>,
#[validate(email)]
email: Option<String>,
}

#[put("/api/v5/users/{user_id}")]
async fn update_user(
claims: Claims,
path: web::Path<Uuid>,
data: web::Json<UpdateUserRequest>,
db: web::Data<Database>,
) -> Result<HttpResponse, ApiError> {
data.validate()
.map_err(|e| ApiError::ValidationError(format!("{}", e)))?;

let user_id = path.into_inner();
let tenant_id = claims.tenant_id;

// Check if user exists and belongs to tenant
let mut user = UserRepository::get(&db, &tenant_id, &user_id).await?
.ok_or_else(|| ApiError::NotFound(format!("User {} not found", user_id)))?;

// Apply updates
if let Some(name) = &data.name {
user.name = name.clone();
}
if let Some(email) = &data.email {
user.email = email.clone();
}

// Save changes
let updated_user = UserRepository::update(&db, &tenant_id, &user_id, user).await?;

Ok(HttpResponse::Ok().json(updated_user))
}

DELETE - Remove Resource

#[delete("/api/v5/users/{user_id}")]
async fn delete_user(
claims: Claims,
path: web::Path<Uuid>,
db: web::Data<Database>,
) -> Result<HttpResponse, ApiError> {
let user_id = path.into_inner();
let tenant_id = claims.tenant_id;

// Verify user exists before deleting
UserRepository::get(&db, &tenant_id, &user_id).await?
.ok_or_else(|| ApiError::NotFound(format!("User {} not found", user_id)))?;

// Delete
UserRepository::delete(&db, &tenant_id, &user_id).await?;

// Return 204 No Content
Ok(HttpResponse::NoContent().finish())
}

Custom Error Types

// backend/src/error.rs
use actix_web::{error::ResponseError, http::StatusCode, HttpResponse};
use derive_more::{Display, Error};

#[derive(Debug, Display, Error)]
pub enum ApiError {
#[display(fmt = "Not found: {}", _0)]
NotFound(String),

#[display(fmt = "Validation error: {}", _0)]
ValidationError(String),

#[display(fmt = "Unauthorized: {}", _0)]
Unauthorized(String),

#[display(fmt = "Forbidden: {}", _0)]
Forbidden(String),

#[display(fmt = "Database error: {}", _0)]
DatabaseError(String),

#[display(fmt = "Internal server error: {}", _0)]
InternalError(String),
}

impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
let (status, message) = match self {
ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
ApiError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg),
ApiError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
ApiError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg),
ApiError::DatabaseError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"),
ApiError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error"),
};

HttpResponse::build(status).json(json!({
"error": message,
"status": status.as_u16(),
}))
}

fn status_code(&self) -> StatusCode {
match self {
ApiError::NotFound(_) => StatusCode::NOT_FOUND,
ApiError::ValidationError(_) => StatusCode::BAD_REQUEST,
ApiError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
ApiError::Forbidden(_) => StatusCode::FORBIDDEN,
ApiError::DatabaseError(_) | ApiError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

// Convert FDB errors to ApiError
impl From<FdbError> for ApiError {
fn from(err: FdbError) -> Self {
match err {
FdbError::NotFound(msg) => ApiError::NotFound(msg),
FdbError::TransactionError(msg) => ApiError::DatabaseError(msg),
_ => ApiError::InternalError(format!("FDB error: {}", err)),
}
}
}

Middleware Patterns

CORS Configuration

// backend/src/main.rs
use actix_cors::Cors;

HttpServer::new(move || {
// CORS middleware - configure for production
let cors = Cors::default()
.allowed_origin("https://coditect.ai")
.allowed_origin("https://api.coditect.ai")
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
.allowed_headers(vec![
actix_web::http::header::AUTHORIZATION,
actix_web::http::header::CONTENT_TYPE,
])
.max_age(3600);

App::new()
.wrap(cors)
.wrap(middleware::Logger::default())
.configure(configure_routes)
})

Rate Limiting Middleware

// backend/src/middleware/rate_limit.rs
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use std::sync::Arc;
use tokio::sync::Mutex;
use std::collections::HashMap;
use std::time::{Duration, Instant};

pub struct RateLimiter {
requests: Arc<Mutex<HashMap<String, Vec<Instant>>>>,
max_requests: usize,
window: Duration,
}

impl RateLimiter {
pub fn new(max_requests: usize, window: Duration) -> Self {
Self {
requests: Arc::new(Mutex::new(HashMap::new())),
max_requests,
window,
}
}

pub async fn check(&self, key: &str) -> Result<(), ApiError> {
let mut requests = self.requests.lock().await;
let now = Instant::now();

// Get or create entry
let entry = requests.entry(key.to_string()).or_insert_with(Vec::new);

// Remove expired requests
entry.retain(|&req_time| now.duration_since(req_time) < self.window);

// Check limit
if entry.len() >= self.max_requests {
return Err(ApiError::TooManyRequests(format!(
"Rate limit exceeded: {} requests per {:?}",
self.max_requests, self.window
)));
}

// Add current request
entry.push(now);
Ok(())
}
}

Testing Patterns

Unit Tests

#[cfg(test)]
mod tests {
use super::*;
use actix_web::test;

#[actix_web::test]
async fn test_create_user_success() {
let app = test::init_service(
App::new().service(create_user)
).await;

let req = test::TestRequest::post()
.uri("/api/v5/users")
.insert_header(("Authorization", "Bearer test_token"))
.set_json(&json!({
"email": "test@example.com",
"password": "securepass123",
"name": "Test User"
}))
.to_request();

let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::CREATED);
}

#[actix_web::test]
async fn test_create_user_validation_error() {
let app = test::init_service(
App::new().service(create_user)
).await;

let req = test::TestRequest::post()
.uri("/api/v5/users")
.insert_header(("Authorization", "Bearer test_token"))
.set_json(&json!({
"email": "invalid-email", // ❌ Invalid email
"password": "short", // ❌ Too short
"name": "Test User"
}))
.to_request();

let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}

Integration Tests with Mock FDB

// tests/integration_test.rs
use actix_web::{test, App};

#[actix_web::test]
async fn test_user_crud_flow() {
// Setup mock FDB
let db = setup_test_db().await;

let app = test::init_service(
App::new()
.app_data(web::Data::new(db))
.service(create_user)
.service(get_user)
.service(update_user)
.service(delete_user)
).await;

// 1. Create user
let create_req = test::TestRequest::post()
.uri("/api/v5/users")
.insert_header(("Authorization", "Bearer test_token"))
.set_json(&json!({
"email": "test@example.com",
"password": "securepass123",
"name": "Test User"
}))
.to_request();

let create_resp = test::call_service(&app, create_req).await;
assert_eq!(create_resp.status(), StatusCode::CREATED);
let user: User = test::read_body_json(create_resp).await;

// 2. Get user
let get_req = test::TestRequest::get()
.uri(&format!("/api/v5/users/{}", user.user_id))
.insert_header(("Authorization", "Bearer test_token"))
.to_request();

let get_resp = test::call_service(&app, get_req).await;
assert_eq!(get_resp.status(), StatusCode::OK);

// 3. Update user
let update_req = test::TestRequest::put()
.uri(&format!("/api/v5/users/{}", user.user_id))
.insert_header(("Authorization", "Bearer test_token"))
.set_json(&json!({"name": "Updated Name"}))
.to_request();

let update_resp = test::call_service(&app, update_req).await;
assert_eq!(update_resp.status(), StatusCode::OK);

// 4. Delete user
let delete_req = test::TestRequest::delete()
.uri(&format!("/api/v5/users/{}", user.user_id))
.insert_header(("Authorization", "Bearer test_token"))
.to_request();

let delete_resp = test::call_service(&app, delete_req).await;
assert_eq!(delete_resp.status(), StatusCode::NO_CONTENT);
}

Integration with T2 Orchestrator

Orchestrator Phase 3: Backend Implementation

When the orchestrator coordinates backend feature development, it uses this skill for Actix-web endpoints:

Orchestrator Phase 3: Backend Implementation
├─ Use rust-backend-patterns for endpoint structure ← THIS SKILL
├─ Use foundationdb-queries for data persistence
├─ Use production-patterns for error handling
└─ Validate with TDD validator (endpoint tests)

Example Delegation:

"Use rust-backend-patterns skill to implement user profile CRUD endpoints with JWT auth."

Token Efficiency: Endpoint patterns save 50% implementation time (40→20 min) by providing:

  • Ready-to-adapt CRUD templates
  • Proven error handling patterns
  • Middleware configuration examples
  • Testing patterns with mocks

Troubleshooting

Issue 1: JWT Middleware Not Applied

Symptom: Endpoints accessible without JWT token

Cause: Middleware not registered in App configuration

Fix: Ensure JWT middleware is registered before routes

// WRONG: Middleware registered after routes
App::new()
.service(create_user) // ❌ No JWT validation
.wrap(JwtMiddleware::new());

// CORRECT: Middleware registered before routes
App::new()
.wrap(JwtMiddleware::new()) // ✅ Applied to all routes
.service(create_user);

Issue 2: CORS Errors in Browser

Symptom: Frontend requests blocked with CORS error

Cause: Missing or incorrect CORS configuration

Fix: Configure CORS with correct origins

let cors = Cors::default()
.allowed_origin("https://coditect.ai") // ✅ Production domain
.allowed_origin("http://localhost:5173") // ✅ Dev server
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
.allowed_headers(vec![
actix_web::http::header::AUTHORIZATION,
actix_web::http::header::CONTENT_TYPE,
]);

Issue 3: Request Body Deserialization Fails

Symptom:

Error: Json deserialize error: missing field `name`

Cause: Frontend sends different field names than backend expects

Fix: Use #[serde(rename)] or make fields optional

#[derive(Deserialize)]
struct CreateUserRequest {
#[serde(rename = "userName")] // ✅ Matches frontend
name: String,

// Or make optional
name: Option<String>,
}

Issue 4: Database Connection Pool Exhausted

Symptom:

Error: DatabaseError("Connection pool exhausted")

Cause: Too many concurrent requests or slow FDB transactions

Fix: Increase pool size or optimize transactions

// Increase pool size
let db = Database::new()
.max_connections(50) // ✅ Increased from default 10
.connect().await?;

// Or optimize transactions (keep them short)
let user = UserRepository::get(&db, &tenant_id, &user_id).await?;
// Don't do heavy processing inside transaction
process_user_data(&user); // ✅ Outside transaction

Issue 5: Validation Errors Not Returned

Symptom: Validation fails but client gets 500 Internal Error

Cause: Validation errors not mapped to ApiError

Fix: Use map_err to convert validation errors

data.validate()
.map_err(|e| ApiError::ValidationError(format!("{}", e)))?;

Multi-Context Window Support

This skill supports long-running Rust backend development across multiple context windows using Claude 4.5's enhanced state management capabilities.

State Tracking

Backend Implementation State (JSON):

{
"checkpoint_id": "ckpt_20251129_151000",
"endpoints_implemented": [
{"path": "/api/v5/users", "method": "GET", "status": "complete", "tests": "passing"},
{"path": "/api/v5/users/{id}", "method": "PUT", "status": "in_progress", "tests": "pending"},
{"path": "/api/v5/users/{id}", "method": "DELETE", "status": "pending", "tests": "not_started"}
],
"cargo_dependencies": ["actix-web", "validator", "serde_json"],
"test_status": {
"unit_tests": {"passed": 15, "failed": 1},
"integration_tests": {"passed": 8, "failed": 0}
},
"token_usage": 18000,
"created_at": "2025-11-29T15:10:00Z"
}

Progress Notes (Markdown):

# Rust Backend Progress - 2025-11-29

## Completed
- GET /api/v5/users endpoint with pagination
- POST /api/v5/users with validation
- Unit tests for user handlers (15/16 passing)

## In Progress
- PUT /api/v5/users/{id} - implementing update logic
- Fixing validation test failure (email format edge case)

## Next Actions
- Complete PUT endpoint implementation
- Add DELETE endpoint
- Fix email validation test
- Run cargo check and cargo test

Session Recovery

When starting a fresh context window after Rust backend work:

  1. Load Checkpoint State: Read .coditect/checkpoints/rust-backend-latest.json
  2. Review Progress Notes: Check rust-backend-progress.md for context
  3. Verify Cargo Status: Run cargo check to confirm compilation
  4. Check Test Results: Run cargo test to see current test status
  5. Resume Implementation: Continue from last incomplete endpoint

Recovery Commands:

# 1. Check latest checkpoint
cat .coditect/checkpoints/rust-backend-latest.json | jq '.endpoints_implemented'

# 2. Review progress
tail -30 rust-backend-progress.md

# 3. Verify compilation
cargo check

# 4. Run tests
cargo test --lib

# 5. Check for TODOs
rg "todo!" backend/src/

State Management Best Practices

Checkpoint Files (JSON Schema):

  • Store in .coditect/checkpoints/rust-backend-{timestamp}.json
  • Include Cargo.toml dependency list
  • Track test results per endpoint
  • Record compilation status

Progress Tracking (Markdown Narrative):

  • Maintain rust-backend-progress.md with endpoint completion status
  • Document Rust-specific patterns used (Result<T,E>, error handling)
  • Note test failures with stack traces
  • List next endpoints to implement

Git Integration:

  • Create checkpoint after each endpoint implementation
  • Commit with conventional format: feat(api): Add PUT /api/v5/users/{id}
  • Tag working states: git tag backend-users-crud-complete

Progress Checkpoints

Natural Breaking Points:

  1. After each CRUD endpoint implemented and tested
  2. After Cargo.toml dependencies added
  3. After integration tests passing
  4. Before middleware modifications
  5. After all endpoints validated with manual testing

Checkpoint Creation Pattern:

// Automatic checkpoint after endpoint completion
if endpoints_complete >= 3 || test_failures > 0 {
create_checkpoint(CheckpointData {
endpoints: implemented_endpoints,
tests: test_results,
cargo_status: compilation_ok,
tokens: current_tokens
});
}

Example: Multi-Context CRUD Implementation

Context Window 1: GET & POST Endpoints

{
"checkpoint_id": "ckpt_users_read_write",
"phase": "create_read_complete",
"endpoints": ["GET /users", "POST /users"],
"tests_passed": 12,
"next_action": "Implement PUT and DELETE",
"token_usage": 11000
}

Context Window 2: PUT & DELETE Endpoints

# Load checkpoint
cat .coditect/checkpoints/ckpt_users_read_write.json

# Continue with update/delete operations
# Token savings: ~9000 tokens (no re-reading GET/POST code)

Token Savings Analysis:

  • Without checkpoint: 20000 tokens (re-verify all endpoints)
  • With checkpoint: 12000 tokens (resume from working state)
  • Savings: 40% reduction (20000 → 12000 tokens)

Success Output

When successful, this skill MUST output:

✅ SKILL COMPLETE: rust-backend-patterns

Completed:
- [x] CRUD endpoints implemented (GET, POST, PUT, DELETE)
- [x] JWT authentication middleware applied
- [x] Repository pattern used for all DB access
- [x] Custom error types with proper ResponseError impl
- [x] Request validation with validator crate
- [x] CORS middleware configured
- [x] Unit tests passing (all endpoints)
- [x] Integration tests passing (full CRUD flow)

Endpoints Implemented:
- GET /api/v5/{resource}
- GET /api/v5/{resource}/{id}
- POST /api/v5/{resource}
- PUT /api/v5/{resource}/{id}
- DELETE /api/v5/{resource}/{id}

Test Results:
- Unit tests: {PASS_COUNT}/{TOTAL_COUNT} passing
- Integration tests: {INTEGRATION_PASS}/{INTEGRATION_TOTAL} passing
- Cargo check: 0 errors, {WARNING_COUNT} warnings

Outputs:
- backend/src/handlers/{resource}.rs created
- backend/src/db/repositories/{resource}.rs created
- tests/{resource}_test.rs created

Completion Checklist

Before marking this skill as complete, verify:

  • All endpoints use Claims extractor for JWT validation
  • Repository pattern used (no direct FDB access in handlers)
  • Error handling uses map_err() for FDB transaction errors
  • Request structs have #[derive(Deserialize, Validate)]
  • Validation called with .validate() before processing
  • Tenant ID extracted from JWT claims
  • CORS middleware configured with production domains
  • Custom error types implement ResponseError trait
  • HTTP status codes match REST conventions (200, 201, 204, 404, etc.)
  • Unit tests cover all endpoint handlers
  • Integration tests verify full CRUD flow
  • cargo check passes with no errors
  • cargo test passes all tests

Failure Indicators

This skill has FAILED if:

  • ❌ Endpoint accessible without JWT token (middleware not applied)
  • ❌ Handler uses direct FDB calls instead of repository
  • TransactionCommitError not handled with map_err()
  • ❌ Validation errors return 500 instead of 400
  • ❌ CORS errors block frontend requests
  • ❌ Request body deserialization fails silently
  • ❌ Tenant isolation broken (wrong tenant's data returned)
  • ❌ Database connection pool exhausted
  • ❌ Cargo build fails with compilation errors
  • ❌ Tests fail with panic or timeout
  • ❌ API returns raw database errors to client

When NOT to Use

Do NOT use this skill when:

  • Frontend-only features - Use React/TypeScript patterns skill instead
  • FDB query debugging - Use foundationdb-queries skill for data layer
  • GKE deployment - Use build-deploy-workflow skill for deployment
  • WebSocket/streaming - This skill focuses on REST, not real-time protocols
  • GraphQL API - Use graphql-rust-patterns skill (if available)
  • Background jobs - Use task-queue-patterns skill for async processing
  • Non-API backend code - Use rust-general-patterns for CLI/workers

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
No JWT middlewareEndpoints publicly accessibleAlways wrap routes with JwtMiddleware::new()
Direct FDB access in handlersTight coupling, hard to testUse repository pattern exclusively
Unwrapping FDB errorsApp panics on database failureUse map_err() to convert to ApiError
Missing validationBad data enters databaseAlways call .validate() on request structs
Hardcoded CORS originsProduction requests blockedConfigure CORS from environment variable
Exposing internal errorsSecurity risk, info leakReturn generic errors to client, log details
No tenant isolationData leakage across tenantsAlways filter by claims.tenant_id
Connection pool starvationSlow endpoints block othersKeep FDB transactions short
Ignoring cargo warningsTech debt accumulatesAddress warnings before deploying

Principles

This skill embodies:

  • #1 Recycle → Extend → Re-Use → Create - Proven CRUD patterns reused across endpoints
  • #3 Keep It Simple - Repository pattern separates concerns cleanly
  • #4 Separation of Concerns - Handlers, repositories, errors, validation all separate
  • #5 Eliminate Ambiguity - Explicit error types, clear HTTP status codes
  • #10 Security First - JWT required, tenant isolation enforced
  • #11 Quality is Non-Negotiable - Tests required, cargo check must pass
  • #13 Observability is Essential - Error logging, structured responses

Full Standard: CODITECT-STANDARD-AUTOMATION.md