# StellaOps Engineering Code of Conduct *Technical excellence + safe for change = best-in-class product* --- ## 0 · Mission and Values **StellaOps** is a sovereign, self-hostable release control plane delivering reproducible, auditable, and secure software releases for non-Kubernetes container estates. We are committed to building a **best-in-class product that is safe for change** — where every contribution improves quality, maintainability, and security without regression. ### Our Engineering Pledge We pledge to uphold: 1. **Technical Excellence** — Code that is deterministic, testable, and production-ready from day one. 2. **Safety for Change** — Comprehensive testing, minimal surprise, and zero tolerance for silent failures. 3. **Security by Design** — Input validation, least privilege, cryptographic correctness, and defense in depth. 4. **Maintainability First** — Clear contracts, minimal coupling, immutable outputs, and self-documenting code. 5. **Transparency and Auditability** — Every decision, every release, every change is traceable and reproducible. This document codifies the **technical standards** all contributors must follow. Behavioral expectations are covered in [COMMUNITY_CONDUCT.md](./COMMUNITY_CONDUCT.md). --- ## 1 · Core Principles ### 1.1 Quality Quality is not negotiable. Every line of code must be: - **Correct** — Does what it claims, handles errors gracefully, fails fast when assumptions break - **Tested** — Unit tests for logic, integration tests for contracts, E2E tests for workflows - **Deterministic** — Same inputs always produce same outputs; no hidden state, no timing dependencies - **Observable** — Logs structured events, emits metrics, traces execution paths - **Documented** — Self-explanatory code; architecture decisions recorded; APIs have examples **Why it matters**: Quality debt compounds. A shortcut today becomes a week-long incident tomorrow. We build for the long term. --- ### 1.2 Maintainability Code is read 10x more than it's written. Optimize for the next engineer: - **Clear intent** — Names reveal purpose; functions do one thing; classes have single responsibilities - **Low coupling** — Modules depend on interfaces, not implementations; changes propagate predictably - **High cohesion** — Related logic lives together; unrelated logic stays separate - **Minimal surprise** — Standard patterns over clever tricks; explicit over implicit - **Refactorable** — Tests enable confident changes; abstractions hide complexity without obscuring behavior **Why it matters**: Unmaintainable code slows velocity to zero. We build systems that evolve, not calcify. --- ### 1.3 Security Security is a design constraint, not a feature: - **Defense in depth** — Multiple layers: input validation, authorization, cryptographic verification, audit trails - **Least privilege** — Services run with minimal permissions; users see only what they need - **Fail secure** — Errors deny access; missing config stops startup; invalid crypto rejects requests - **Cryptographic correctness** — Use vetted libraries; never roll your own crypto; verify all signatures - **Supply chain integrity** — Pin dependencies; scan for vulnerabilities; generate SBOMs; issue VEX statements - **Auditability** — Every action logged; every release signed; every decision traceable **Why it matters**: Security failures destroy trust. We protect our users' infrastructure and their reputation. --- ## 2 · Scope and Authority This Code of Conduct applies to: - All code contributions (C#, TypeScript, Angular, SQL, Dockerfiles, Helm charts, CI/CD pipelines) - All documentation (architecture, API references, runbooks, sprint files) - All testing artifacts (unit, integration, E2E, performance, security tests) - All infrastructure-as-code (Terraform, Ansible, Compose, Kubernetes manifests) **Authority**: This document supersedes informal guidance. When in conflict with external standards, StellaOps rules win. Module-specific `AGENTS.md` files may impose stricter requirements but cannot relax the rules defined here. --- ## 3 · Mandatory Reading Before contributing to any module, you **must** read and understand: 1. **This document** — The engineering code of conduct (you're reading it now) 2. [docs/README.md](../README.md) — Project overview and navigation 3. [docs/07_HIGH_LEVEL_ARCHITECTURE.md](../07_HIGH_LEVEL_ARCHITECTURE.md) — System architecture 4. [docs/modules/platform/architecture-overview.md](../modules/platform/architecture-overview.md) — Platform design 5. [TESTING_PRACTICES.md](./TESTING_PRACTICES.md) — Testing requirements and evidence standards 6. The relevant module's architecture dossier (`docs/modules//architecture.md`) 7. The module's `AGENTS.md` if present (e.g., `src/Scanner/AGENTS.md`) **Enforcement**: Pull requests that violate documented architecture or module-specific constraints will be rejected with a reference to the violated document. --- ## 4 · Code Quality Standards ### 3.1 Compiler Discipline **Rule**: All projects must enable `TreatWarningsAsErrors`. ```xml true ``` **Rationale**: Warnings mask regressions and code quality drift. Zero-warning builds are mandatory. --- ### 3.2 Determinism: Time, IDs, and Randomness **Rule**: Never use `DateTime.UtcNow`, `DateTimeOffset.UtcNow`, `Guid.NewGuid()`, or `Random.Shared` directly in production code. **Required**: Inject `TimeProvider` and `IGuidGenerator` abstractions. ```csharp // ❌ BAD - nondeterministic, untestable public class BadService { public Record CreateRecord() => new Record { Id = Guid.NewGuid(), CreatedAt = DateTimeOffset.UtcNow }; } // ✅ GOOD - injectable, testable, deterministic public class GoodService(TimeProvider timeProvider, IGuidGenerator guidGenerator) { public Record CreateRecord() => new Record { Id = guidGenerator.NewGuid(), CreatedAt = timeProvider.GetUtcNow() }; } ``` **Rationale**: Deterministic outputs enable reproducible builds, reliable tests, and cryptographic verification. Nondeterministic code breaks evidence chains. --- ### 3.3 Culture-Invariant Parsing and Formatting **Rule**: Always use `CultureInfo.InvariantCulture` for parsing and formatting dates, numbers, percentages, and any string that will be persisted, hashed, or compared. ```csharp // ❌ BAD - culture-sensitive, locale-dependent var value = double.Parse(input); var formatted = percentage.ToString("P2"); // ✅ GOOD - culture-invariant, deterministic var value = double.Parse(input, CultureInfo.InvariantCulture); var formatted = percentage.ToString("P2", CultureInfo.InvariantCulture); ``` **Rationale**: Current culture causes nondeterministic behavior across environments. All outputs must be reproducible regardless of locale. --- ### 3.4 ASCII-Only Output **Rule**: Use ASCII-only characters in comments, output strings, and log messages. No mojibake (`ƒ?`), Unicode glyphs (`✓`, `→`, `バ`), or box-drawing characters. ```csharp // ❌ BAD - non-ASCII glyphs Console.WriteLine("✓ Success → proceeding"); // ✅ GOOD - ASCII only Console.WriteLine("[OK] Success - proceeding"); ``` **Exceptions**: When Unicode is **required** (e.g., internationalized user messages), use explicit escapes (`\uXXXX`) and document the rationale. **Rationale**: Non-ASCII characters break in constrained environments (containers, SSH, logs). ASCII ensures universal readability. --- ### 3.5 Immutable Collection Returns **Rule**: Public APIs must return `IReadOnlyList`, `ImmutableArray`, or defensive copies. Never expose mutable backing stores. ```csharp // ❌ BAD - exposes mutable backing store public class BadRegistry { private readonly List _scopes = new(); public List Scopes => _scopes; // Callers can mutate! } // ✅ GOOD - immutable return public class GoodRegistry { private readonly List _scopes = new(); public IReadOnlyList Scopes => _scopes.AsReadOnly(); } ``` **Rationale**: Mutable returns create hidden coupling and race conditions. Immutability is a safety contract. --- ### 3.6 No Silent Stubs **Rule**: Placeholder code must throw `NotImplementedException` or return an explicit error status. Never return success from unimplemented paths. ```csharp // ❌ BAD - silent stub masks missing implementation public async Task ProcessAsync() { // TODO: implement later return Result.Success(); // Ships broken feature! } // ✅ GOOD - explicit failure public async Task ProcessAsync() { throw new NotImplementedException("ProcessAsync not yet implemented. See SPRINT_20251218_001_BE_ReleasePromotion.md"); } ``` **Rationale**: Silent stubs ship broken features. Explicit failures prevent production incidents. --- ### 3.7 CancellationToken Propagation **Rule**: Always propagate `CancellationToken` through async call chains. Never use `CancellationToken.None` in production code. ```csharp // ❌ BAD - ignores cancellation public async Task ProcessAsync(CancellationToken ct) { await _repository.SaveAsync(data, CancellationToken.None); // Wrong! await Task.Delay(1000); // Missing ct } // ✅ GOOD - propagates cancellation public async Task ProcessAsync(CancellationToken ct) { await _repository.SaveAsync(data, ct); await Task.Delay(1000, ct); } ``` **Rationale**: Proper cancellation prevents resource leaks and enables graceful shutdown. --- ### 3.8 HttpClient via IHttpClientFactory **Rule**: Never instantiate `HttpClient` directly. Use `IHttpClientFactory` with configured timeouts and retry policies. ```csharp // ❌ BAD - direct instantiation public class BadService { public async Task FetchAsync() { using var client = new HttpClient(); // Socket exhaustion risk await client.GetAsync(url); } } // ✅ GOOD - factory with resilience public class GoodService(IHttpClientFactory httpClientFactory) { public async Task FetchAsync() { var client = httpClientFactory.CreateClient("MyApi"); await client.GetAsync(url); } } // Registration with timeout/retry services.AddHttpClient("MyApi") .ConfigureHttpClient(c => c.Timeout = TimeSpan.FromSeconds(30)) .AddStandardResilienceHandler(); ``` **Rationale**: Direct `HttpClient` creation causes socket exhaustion. Factories enable connection pooling and resilience patterns. --- ### 3.9 Bounded Caches with Eviction **Rule**: Do not use `ConcurrentDictionary` or `Dictionary` for caching without eviction policies. ```csharp // ❌ BAD - unbounded growth private readonly ConcurrentDictionary _cache = new(); public void Add(string key, CacheEntry entry) { _cache[key] = entry; // Never evicts, memory grows forever } // ✅ GOOD - bounded with eviction private readonly MemoryCache _cache = new(new MemoryCacheOptions { SizeLimit = 10_000 }); public void Add(string key, CacheEntry entry) { _cache.Set(key, entry, new MemoryCacheEntryOptions { Size = 1, SlidingExpiration = TimeSpan.FromMinutes(30) }); } ``` **Rationale**: Unbounded caches cause memory exhaustion in long-running services. Bounded caches with TTL/LRU eviction are mandatory. --- ### 3.10 Options Validation at Startup **Rule**: Use `ValidateDataAnnotations()` and `ValidateOnStart()` for all options classes. Implement `IValidateOptions` for complex validation. ```csharp // ❌ BAD - no validation until runtime failure services.Configure(config.GetSection("My")); // ✅ GOOD - validated at startup services.AddOptions() .Bind(config.GetSection("My")) .ValidateDataAnnotations() .ValidateOnStart(); ``` **Rationale**: All required config must be validated at startup, not at first use. Fail fast prevents runtime surprises. --- ## 5 · Cryptographic and Security Standards ### 4.1 DSSE PAE Consistency **Rule**: Use one spec-compliant DSSE PAE helper (`StellaOps.Attestation.DsseHelper`) across the codebase. Never reimplement PAE encoding. ```csharp // ❌ BAD - custom PAE implementation var pae = $"DSSEv1 {payloadType.Length} {payloadType} {payload.Length} "; // ✅ GOOD - use shared helper var pae = DsseHelper.ComputePreAuthenticationEncoding(payloadType, payload); ``` **Rationale**: DSSE v1 requires ASCII decimal lengths and space separators. Reimplementations introduce cryptographic vulnerabilities. --- ### 4.2 RFC 8785 JSON Canonicalization **Rule**: Use a shared RFC 8785-compliant JSON canonicalizer for digest/signature inputs. Do not use `UnsafeRelaxedJsonEscaping` or `CamelCase` naming for canonical outputs. ```csharp // ❌ BAD - non-canonical JSON var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); // ✅ GOOD - use shared canonicalizer var canonicalJson = CanonicalJsonSerializer.Serialize(obj); var digest = ComputeDigest(canonicalJson); ``` **Rationale**: RFC 8785 ensures deterministic JSON serialization. Non-canonical JSON breaks signature verification. --- ### 4.3 DateTimeOffset for PostgreSQL timestamptz **Rule**: PostgreSQL `timestamptz` columns must be read via `reader.GetFieldValue()`, not `reader.GetDateTime()`. ```csharp // ❌ BAD - loses offset information var createdAt = reader.GetDateTime(reader.GetOrdinal("created_at")); // ✅ GOOD - preserves offset var createdAt = reader.GetFieldValue(reader.GetOrdinal("created_at")); ``` **Rationale**: `GetDateTime()` loses offset information and causes UTC/local confusion. All timestamps must be stored and retrieved as UTC `DateTimeOffset`. --- ### 4.4 Explicit CLI Options for Paths **Rule**: Do not derive repository root from `AppContext.BaseDirectory` with parent directory walks. Use explicit CLI options (`--repo-root`) or environment variables. ```csharp // ❌ BAD - fragile parent walks var repoRoot = Path.GetFullPath(Path.Combine( AppContext.BaseDirectory, "..", "..", "..", "..")); // ✅ GOOD - explicit option with fallback [Option("--repo-root", Description = "Repository root path")] public string? RepoRoot { get; set; } public string GetRepoRoot() => RepoRoot ?? Environment.GetEnvironmentVariable("STELLAOPS_REPO_ROOT") ?? throw new InvalidOperationException("Repository root not specified. Use --repo-root or set STELLAOPS_REPO_ROOT."); ``` **Rationale**: Parent walks break in containerized and CI environments. Explicit paths are mandatory. --- ## 6 · Testing Requirements **All code contributions must include tests.** See [TESTING_PRACTICES.md](./TESTING_PRACTICES.md) for comprehensive guidance. ### 5.1 Test Project Requirements **Rule**: All production libraries/services must have a corresponding `*.Tests` project covering: - (a) Happy paths - (b) Error/edge cases - (c) Determinism - (d) Serialization round-trips ``` src/ Scanner/ __Libraries/ StellaOps.Scanner.Core/ __Tests/ StellaOps.Scanner.Core.Tests/ <-- Required ``` --- ### 5.2 Test Categorization **Rule**: Tag tests correctly: - `[Trait("Category", "Unit")]` for pure unit tests - `[Trait("Category", "Integration")]` for tests requiring databases, containers, or network ```csharp // ❌ BAD - integration test marked as unit public class UserRepositoryTests // Uses Testcontainers/Postgres { [Fact] // Missing category public async Task Save_PersistsUser() { ... } } // ✅ GOOD - correctly categorized [Trait("Category", "Integration")] public class UserRepositoryTests { [Fact] public async Task Save_PersistsUser() { ... } } [Trait("Category", "Unit")] public class UserValidatorTests { [Fact] public void Validate_EmptyEmail_ReturnsFalse() { ... } } ``` **Rationale**: Unit tests must run fast and offline. Integration tests require infrastructure. Mixing categories breaks CI pipelines. --- ### 5.3 Test Production Code, Not Reimplementations **Rule**: Test helpers must call production code, not reimplement algorithms. ```csharp // ❌ BAD - test reimplements production logic [Fact] public void Merkle_ComputesCorrectRoot() { var root = TestMerkleHelper.ComputeRoot(leaves); // Drift risk! Assert.Equal(expected, root); } // ✅ GOOD - test exercises production code [Fact] public void Merkle_ComputesCorrectRoot() { var root = MerkleTreeBuilder.ComputeRoot(leaves); Assert.Equal(expected, root); } ``` **Rationale**: Reimplementations in tests cause test/production drift. Only mock I/O and network boundaries. --- ### 5.4 Offline and Deterministic Tests **Rule**: All tests must run without network access. Use: - UTC timestamps - Fixed seeds - `CultureInfo.InvariantCulture` - Injected `TimeProvider` and `IGuidGenerator` **Rationale**: Network-dependent tests are flaky and break in air-gapped environments. Deterministic tests are reproducible. --- ## 7 · Architecture and Design Principles ### 6.1 SOLID Principles All service and library code must follow: 1. **Single Responsibility Principle (SRP)** — One class, one reason to change 2. **Open/Closed Principle (OCP)** — Open for extension, closed for modification 3. **Liskov Substitution Principle (LSP)** — Subtypes must be substitutable for base types 4. **Interface Segregation Principle (ISP)** — Clients should not depend on interfaces they don't use 5. **Dependency Inversion Principle (DIP)** — Depend on abstractions, not concretions --- ### 6.2 Directory Ownership **Rule**: Work only inside the module's directory defined by the sprint's "Working directory". Cross-module edits require explicit approval and documentation. **Example**: - Sprint scope: `src/Scanner/` - Allowed: Edits to `StellaOps.Scanner.*` projects - Forbidden: Edits to `src/Concelier/` without explicit approval **Rationale**: Directory boundaries enforce module isolation and prevent unintended coupling. --- ### 6.3 No Backup Files in Source **Rule**: Add backup patterns to `.gitignore` and remove stray artifacts during code review. ```gitignore *.Backup.tmp *.bak *.orig *~ ``` **Rationale**: Backup files pollute the repository and create confusion. --- ## 8 · Documentation Standards ### 7.1 Required Documentation Every change must update: 1. **Module architecture docs** (`docs/modules//architecture.md`) 2. **API references** (`docs/api/`) 3. **Sprint files** (`docs/implplan/SPRINT_*.md`) 4. **Risk/airgap docs** if applicable (`docs/risk/`, `docs/airgap/`) --- ### 7.2 Sprint File Discipline **Rule**: Always update task status in `docs/implplan/SPRINT_*.md`: - `TODO` → `DOING` → `DONE` / `BLOCKED` Sprint files are the single source of truth for project state. --- ## 9 · Security and Hardening ### 8.1 Input Validation **Rule**: All external inputs (HTTP requests, CLI arguments, file uploads, database queries) must be validated and sanitized. **Required**: - Use `[Required]`, `[Range]`, `[RegularExpression]` attributes on DTOs - Implement `IValidateOptions` for complex validation - Reject unexpected inputs with explicit error messages --- ### 8.2 Least Privilege **Rule**: Services must run with minimal permissions: - Database users: read-only where possible - File system: restrict to required directories - Network: allowlist remote hosts --- ### 8.3 Dependency Security **Enforcement**: PRs introducing new dependencies must include: - SBOM entry - VEX statement if vulnerabilities exist - Justification for the dependency --- ## 10 · Technology Stack Compliance ### 9.1 Mandatory Technologies - **Runtime**: .NET 10 (`net10.0`) with latest C# preview features - **Frontend**: Angular v17 - **Database**: PostgreSQL ≥16 - **Testing**: xUnit, Testcontainers, Moq - **NuGet**: Standard feeds configured in `nuget.config`. Always strive to use latest stable verison of dependencies. Never specify versions on nugets on the csproj files. Use src/Directory.Packages.props to specify versions. --- ### 9.2 Naming Conventions - Module projects: `StellaOps.` - Libraries: `StellaOps.` - Tests: `StellaOps..Tests` --- ## 11 · Enforcement and Compliance ### 10.1 Pull Request Requirements All PRs must: 1. Pass all unit and integration tests 2. Pass determinism checks 3. Include test coverage for new code 4. Update relevant documentation 5. Follow sprint file discipline 6. Pass security scans (no high/critical CVEs) --- ### 10.2 Rejection Criteria PRs will be **rejected** if they: - Violate any rule in this document - Introduce compiler warnings - Fail tests - Lack required documentation - Contain silent stubs or nondeterministic code --- ### 10.3 Continuous Improvement This document is a **living standard**. Contributors are encouraged to: - Propose improvements via PRs - Document new patterns in module-specific `AGENTS.md` files - Share lessons learned in sprint retrospectives --- ## 12 · Attribution and License This Code of Conduct incorporates engineering standards from: - **AGENTS.md** — Autonomous engineering workflows - **CLAUDE.md** — Claude Code integration guidance - **TESTING_PRACTICES.md** — Testing and evidence standards Copyright © 2025 StellaOps Contributors Licensed under [AGPL-3.0-or-later](../../LICENSE) --- **Last updated**: 2026-01-15 **Next review**: 2026-04-15