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:
- Technical Excellence — Code that is deterministic, testable, and production-ready from day one.
- Safety for Change — Comprehensive testing, minimal surprise, and zero tolerance for silent failures.
- Security by Design — Input validation, least privilege, cryptographic correctness, and defense in depth.
- Maintainability First — Clear contracts, minimal coupling, immutable outputs, and self-documenting code.
- 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:
- This document — The engineering code of conduct (you're reading it now)
- docs/README.md — Project overview and navigation
- docs/07_HIGH_LEVEL_ARCHITECTURE.md — System architecture
- docs/modules/platform/architecture-overview.md — Platform design
- TESTING_PRACTICES.md — Testing requirements and evidence standards
- The relevant module's architecture dossier (
docs/modules/<module>/architecture.md) - The module's
AGENTS.mdif 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
TimeProviderandIGuidGenerator
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:
- Single Responsibility Principle (SRP) — One class, one reason to change
- Open/Closed Principle (OCP) — Open for extension, closed for modification
- Liskov Substitution Principle (LSP) — Subtypes must be substitutable for base types
- Interface Segregation Principle (ISP) — Clients should not depend on interfaces they don't use
- 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:
- Module architecture docs (
docs/modules/<module>/architecture.md) - API references (
docs/api/) - Sprint files (
docs/implplan/SPRINT_*.md) - 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<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:
- Pass all unit and integration tests
- Pass determinism checks
- Include test coverage for new code
- Update relevant documentation
- Follow sprint file discipline
- 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.mdfiles - 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