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).
Use the Result<T> failure model
The procedure for our hand-rolled, railway-oriented failure model. Source of truth (the facts — read it):
.claude/rules/backend/result-and-errors.md and docs/projectStandards/backend-architecture.md §4.2–§4.3.
C# (.cs) edits via Serena. No FP library (e.g. CSharpFunctionalExtensions) without Dan's approval.
The types (in {{ProjectName}}.Shared — must NOT reference ASP.NET Core)
Result/Result<T>—readonly structs holding either a value or anError. Reading.Valueon a failure throws (loud bug, not a silent null).Error— arecord:Code(stable string),Message(human),Type(ErrorType:Failure, Validation, NotFound, Conflict, Unauthorized, Forbidden).Typedrives the HTTP status; theValidationcase carries aFailuresdictionary (field → messages[]). OneError, not aList<Error>.- v1 surface only:
Map(transform value),Then(chain a fallible step),Match(collapse) — sync and async (Task<Result<T>>) overloads withConfigureAwait(false); implicitT → Result<T>andError → Result<T>; factory statics. Deferred until a call site needs them:Tap/Ensure/MapError/Combine.
Using it
- Handlers return
Result<T>(orResult) for expected failures —return dto;(implicit) on success,return Error.NotFound("project.not_found", "…");(implicit) on failure. Do not throw for expected failures. - Catch domain exceptions — domain invariants throw; in the handler catch a narrow
DomainException(never bareException) and fold intoResult.Failure. Unexpected exceptions bubble to the global handler. - Compose with
Then/Mapto chain fallible steps without nesting;Matchto collapse to a value/response.
Boundaries (who lives where)
- The exception→Result catch lives in the Application handler.
- The Result→ProblemDetails mapper (
ToHttpResult/ToProblem) lives in the API layer, using .NET 10TypedResults.Problem/ValidationProblem;Error.Typeselects the status code. Every API failure → ProblemDetails, always (+AddProblemDetails()so unhandled exceptions render the same way). - Validation failures (the Kommand
IValidatorscope — see thevalidation-scopesskill) surface asError.Validation(failures)→ValidationProblemDetails.