Serena MCP is mandatory for C# code. First call
mcp__serena__initial_instructionsto load the Serena tool manual, then use the Serena tools for ALL.csreading / searching / navigation / creation / editing — prefer symbol navigation (get_symbols_overview/find_symbol/find_referencing_symbols) over whole-file reads. NativeEdit/Writeon.csis hook-blocked (the TS/React frontend uses the native tools).
Validation — the three scopes
Validation is three distinct concerns; put each check in the right one. Source of truth (the facts —
read them): .claude/rules/backend/api-design.md (§4.1), cqrs-kommand.md, domain-model.md, and
docs/projectStandards/backend-architecture.md §4.1. We do NOT use FluentValidation or any validation
library. C# (.cs) edits via Serena.
| Scope | Where | Validates | On failure |
|---|---|---|---|
| Contract | API endpoint, before dispatch | request shape, required fields, types, and access control inferable from the JWT/cookie | reject → ProblemDetails; never reaches a handler. No DB calls. |
| Business | Application — a Kommand IValidator<T> on the command/query |
rules needing data/understanding ("is this buyer ≥18 for alcohol?") | a failed Result (Error.Validation(failures)) → ValidationProblemDetails |
| Invariant | Domain entity factories/methods | an object can never be created or mutated into an invalid state | throw; the handler catches and folds into Result.Failure |
Procedure — placing a check
- Is it pure shape / required-field / JWT-derivable authz, with no DB? → Contract (API). Hand-roll the check in the endpoint (or a small reusable guard); short-circuit to ProblemDetails before dispatching.
- Does it need data or business understanding? → Business. Add a
IValidator<T>beside the command/query (co-located inCommands//Queries/, named…Validator). Surface failures as a failedResult— prefer a validation interceptor that returns the failedResult<T>over Kommand's throwing default (seecqrs-kommandreference/patterns.md). - Is it an invariant that must hold for the object to exist or change? → Invariant. Enforce it inside
the domain factory/method and throw (use the
add-domain-entityskill). Never rely on outer layers to keep the domain valid — it's the last line of defence.
Don'ts
- Don't repeat the same rule across scopes (e.g. re-checking a business rule in the handler that the
IValidatoralready guarantees) — each rule lives in exactly one scope. - Don't do DB work in contract validation; don't put business logic in the endpoint.
- Failures become
Result/ProblemDetails (see theresult-patternskill), not ad-hoc exceptions at the boundary.