Skip to content

Coding conventions

Code style

.editorconfig is the source of truth. The high-level rules:

  • 4 spaces, no tabs.
  • LF line endings.
  • File-scoped namespaces (no curly-braced namespaces).
  • using directives outside the namespace.
  • Prefer var only when the type is obvious from the right-hand side.
  • Public APIs require XML doc comments; internals do not.
  • record for value types; class for entities and services.
  • Async methods end in Async; cancellation tokens are the last parameter.

Naming

Kind Style Example
Class / record PascalCase RolloutService
Method PascalCase StartAsync
Property PascalCase Status
Field (private) _camelCase _repository
Constant PascalCase DefaultTimeout
Local camelCase rolloutId

Bounded-context rules

See Bounded contexts. The rules are non-negotiable:

  • Ampora.OpAmp.Core knows no Collector concepts.
  • Ampora.Collector.Config knows no OpAMP concepts.
  • Ampora.Fleet is the only place the two meet.
  • Ampora.Web consumes application services, never repositories.

A code-review check exists for the imports.

Commits

Conventional commits:

<type>(<scope>): <summary>

<body>

<footer>

Common types: feat, fix, docs, chore, refactor, test, perf. The footer is for BREAKING CHANGE: notes and Closes #... references.

Commit examples

feat(rollout): canary step-up schedule

Implements ADR-011. Adds dwell-time-between-steps for percentage rollouts.

Closes #321
fix(opamp): reject frames over MaxMessageBytes

We were silently accepting oversized frames and OOMing on large agent
populations. Reject with ServerErrorResponse and bump
ampora_opamp_frames_rejected_total{reason="oversize"}.

Pull request title

Same conventional format as the commit. CI lints the title on every push.

Code review

Two-reviewer minimum on master (one for refactors that move files without changing behaviour). Reviewers are responsible for:

  • correctness against the relevant ADR (link from PR description),
  • test coverage for the new behaviour,
  • audit-log row for any state change,
  • docs updated for any user-visible change.

A self-merged PR is a process bug.

Tests

  • Unit tests live next to the project they test.
  • Integration tests live in tests/Ampora.Integration.Tests/.
  • Test names describe the scenario in plain English: RolloutPause_WhenGateFires_RecordsAuditEventAndPausesBatch.
  • Arrange / Act / Assert as separate code blocks; do not collapse into one-liners.

See Testing for the test taxonomy.

Logging

  • Structured logs only. No string interpolation in log messages — use _logger.LogInformation("Rollout {RolloutId} entered state {State}", id, state).
  • Levels: Information for normal lifecycle events, Warning for recoverable, Error for unhandled.
  • No PII in messages. Tenant IDs and entity IDs are fine; user names and IP addresses are not (those go in audit, which is controlled).

Async

  • All I/O is async. No .Result, no .Wait().
  • Pass CancellationToken from the call site; do not capture ambient ones.
  • Background services derive from BackgroundService; they respect stoppingToken.

Database access

  • All DB access goes through AmporaDbContext.
  • Long queries belong on the repository / service classes, not in controllers / endpoints.
  • New columns require a migration (dotnet ef migrations add ...).
  • New JSONB columns require explicit indexes if they will be queried.