Files
git.stella-ops.org/docs/18_CODING_STANDARDS.md

8.1 KiB
Raw Permalink Blame History

18 · Coding Standards & Contributor Guide — StellaOps

(v2.0 — 12Jul2025 · supersedes v1.0)

Audience — Anyone sending a pullrequest to the opensource Core.
Goal — Keep the codebase smallfiled, pluginfriendly, DIconsistent, and instantly readable.


0Why read this?

  • Cuts review time → quicker merges.
  • Guarantees code is hotloadsafe for runtime plugins.
  • Prevents style churn and merge conflicts.

1Highlevel principles

  1. SOLID first especially Interface & Dependency Inversion.
  2. 100line rule any file >100 physical lines must be split or refactored.
  3. Contractlevel ownership public abstractions live in lightweight Contracts libraries; impl classes live in runtime projects.
  4. Single Composition Root all DI wiring happens in StellaOps.Web/Program.cs and in each plugins IoCConfigurator; nothing else calls IServiceCollection.BuildServiceProvider.
  5. No Service Locator constructor injection only; static ServiceProvider is banned.
  6. Failfast startup configuration validated before the webhost listens.
  7. Hotload compatible no static singletons that survive plugin unload; avoid Assembly.LoadFrom outside the builtin plugin loader.

2Repository layout (flat, July2025)**

src/
├─ backend/
│   ├─ StellaOps.Web/                # ASP.NET host + composition root
│   ├─ StellaOps.Common/             # Serilog, Result<T>, helpers
│   ├─ StellaOps.Contracts/          # DTO + interface contracts (no impl)
│   ├─ StellaOps.Configuration/      # Options + validation
│   ├─ StellaOps.Localization/
│   ├─ StellaOps.PluginLoader/       # Cosign verify, hotload
│   ├─ StellaOps.Scanners.Trivy/     # Firstparty scanner
│   ├─ StellaOps.TlsProviders.OpenSsl/
│   └─ … (additional runtime projects)
├─ plugins-sdk/                      # Templated contracts & abstractions
└─ frontend/                         # Angular workspace
tests/                               # Mirrors src structure 1to1

There are no folders named “Module” and no nested solutions.

3Naming & style conventions

Element Rule Example
Namespaces Filescoped, StellaOps. namespace StellaOps.Scanners;
Interfaces I prefix, PascalCase IScannerRunner
Classes / records PascalCase ScanRequest, TrivyRunner
Private fields camelCase (no leading underscore) redisCache, httpClient
Constants SCREAMING_SNAKE_CASE const int MAX_RETRIES = 3;
Async methods End with Async Task ScanAsync()
File length 100 lines incl. using & braces enforced by dotnet format check
Using directives Outside namespace, sorted, no wildcards

Static analyzers (.editorconfig, StyleCop.Analyzers package) enforce the above.

4Dependencyinjection policy

Composition root exactly one per process:

builder.Services
       .AddStellaCore()          // extension methods from each runtime project
       .AddPluginLoader("/Plugins", cfg);   // hotload signed DLLs

Plugins register additional services via the IoCConfigurator convention described in the Plugin SDK Guide, §5. Never resolve services manually (provider.GetService()) outside the composition root; tests may use WebApplicationFactory or ServiceProvider.New() helpers. Scoped lifetime is default; singletons only for stateless, threadsafe helpers.

5Project organisation rules

Contracts vs. Runtime public DTO & interfaces live in .Contracts; implementation lives in sibling project. Feature folders inside each runtime project group classes by usecase, e.g.

├─ Scan/
│   ├─ ScanService.cs
│   └─ ScanController.cs
├─ Feed/
└─ Tls/

Tests mirror the structure under tests/ onetoone; no test code inside production projects.

6C# language features

Nullable reference types enabled. record for immutable DTOs. Pattern matching encouraged; avoid long switchcascades. Span & Memory OK when perfcritical, but measure first. Use await foreach over manual paginator loops.

7Errorhandling template

public async Task<IActionResult> PostScan([FromBody] ScanRequest req)
{
    if (!ModelState.IsValid) return BadRequest(ModelState);

    try
    {
        ScanResult result = await scanService.ScanAsync(req);
        if (result.Quota != null) 
        {
            Response.Headers.TryAdd("X-Stella-Quota-Remaining", result.Quota.Remaining.ToString());
            Response.Headers.TryAdd("X-Stella-Reset", result.Quota.ResetUtc.ToString("o"));
        }
        return Ok(result);
    }
}

RFC7807 ProblemDetails for all non200s. Capture structured logs with Serilogs messagetemplate syntax.

8Async & threading

  • All I/O is async; no .Result / .Wait().
  • Library code: ConfigureAwait(false).
  • Limit concurrency via Channel or Parallel.ForEachAsync, never raw Task.Run loops.

9Testing rules

Layer Framework Coverage gate
Unit xUnit + FluentAssertions 80% line, ≥60% branch
Integration Testcontainers Real Redis & Trivy
Mutation (critical libs) Stryker.NET 60% score

One test project per runtime/contract project; naming .Tests.

10Static analysis & formatting

  • dotnet format must exit clean (CI gate).
  • StyleCop.Analyzers + RoslynSecurityGuard run on every PR.
  • CodeQL workflow runs nightly on main.

11Commit & PR checklist

  • Conventional Commit prefix (feat:, fix:, etc.).
  • dotnet format & dotnet test both green.
  • Added or updated XMLdoc comments for public APIs.
  • File count & length comply with 100line rule.
  • If new public contract → update relevant markdown doc & JSONSchema.

12Common pitfalls

Symptom Root cause Fix
InvalidOperationException: Cannot consume scoped service... Mismatched DI lifetimes Use scoped everywhere unless truly stateless
Hotreload plugin crash Static singleton caching plugin types Store nothing static; rely on DI scopes

100line style violation |Large handlers or utils |Split into private helpers or new class

13Change log

Version Date Notes
v2.0 20250712 Updated DI policy, 100line rule, new repo layout, camelCase fields, removed “Module” terminology.
1.0 20250709 Original standards.