Files
git.stella-ops.org/docs/code-of-conduct/CODE_OF_CONDUCT.md

21 KiB

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.


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 — Project overview and navigation
  3. docs/07_HIGH_LEVEL_ARCHITECTURE.md — System architecture
  4. docs/modules/platform/architecture-overview.md — Platform design
  5. TESTING_PRACTICES.md — Testing requirements and evidence standards
  6. The relevant module's architecture dossier (docs/modules/<module>/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.

<!-- In .csproj or Directory.Build.props -->
<PropertyGroup>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

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.

// ❌ 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.

// ❌ 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.

// ❌ 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<T>, ImmutableArray<T>, or defensive copies. Never expose mutable backing stores.

// ❌ BAD - exposes mutable backing store
public class BadRegistry
{
    private readonly List<string> _scopes = new();
    public List<string> Scopes => _scopes;  // Callers can mutate!
}

// ✅ GOOD - immutable return
public class GoodRegistry
{
    private readonly List<string> _scopes = new();
    public IReadOnlyList<string> 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.

// ❌ BAD - silent stub masks missing implementation
public async Task<Result> ProcessAsync()
{
    // TODO: implement later
    return Result.Success();  // Ships broken feature!
}

// ✅ GOOD - explicit failure
public async Task<Result> 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.

// ❌ 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.

// ❌ 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.

// ❌ BAD - unbounded growth
private readonly ConcurrentDictionary<string, CacheEntry> _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<T> for complex validation.

// ❌ BAD - no validation until runtime failure
services.Configure<MyOptions>(config.GetSection("My"));

// ✅ GOOD - validated at startup
services.AddOptions<MyOptions>()
    .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.

// ❌ 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.

// ❌ 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<DateTimeOffset>(), not reader.GetDateTime().

// ❌ BAD - loses offset information
var createdAt = reader.GetDateTime(reader.GetOrdinal("created_at"));

// ✅ GOOD - preserves offset
var createdAt = reader.GetFieldValue<DateTimeOffset>(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.

// ❌ 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 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
// ❌ 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.

// ❌ 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.

*.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/<module>/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:

  • TODODOINGDONE / 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<T> 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.<ModuleName>
  • Libraries: StellaOps.<LibraryName>
  • Tests: StellaOps.<ModuleName>.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


Last updated: 2026-01-15
Next review: 2026-04-15