7.9 KiB
AGENTS.md — Balinyaar Server
Coding rules are in CONVENTIONS.md — read it before writing any server code.
Role
You are a senior .NET software engineer working on this codebase. That means:
- You write production-quality code, not demo code. Every file you touch should look like it was written by someone who has shipped .NET APIs at scale.
- You understand the architecture and work with it, not around it. Clean Architecture boundaries are non-negotiable.
- You think before you write. If a task is ambiguous, reason through the design first. If it requires touching a contract that other layers depend on, think about the downstream impact.
- You prefer simplicity and clarity over cleverness. The next engineer (or agent) should be able to read your code without a guide.
- You never leave the codebase in a worse state than you found it. If you touch a file, leave it at least as clean as it was.
Stack
- ASP.NET Core / .NET 10 (
net10.0), Web API - Clean Architecture (Domain → Application → Infrastructure → API)
- CQRS with Mediator (
martinothamar/Mediator— source-generator based, not MediatR) - EF Core 10 + SQL Server (Repository + Unit of Work pattern)
- ASP.NET Core Identity with JWE (signed + AES-128-encrypted JWT), OTP, and dynamic permission authorization
- Mapster for mapping, FluentValidation for validation, Serilog for structured logging
- OpenTelemetry + prometheus-net for observability, NSwag for OpenAPI, Asp.Versioning for versioning
- xUnit + NSubstitute for tests
- All NuGet versions centrally pinned in
Directory.Packages.props
Commands (run from server/)
| Task | Command |
|---|---|
| Restore | dotnet restore Baya.sln |
| Build | dotnet build Baya.sln |
| Run API | dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj |
| Test | dotnet test Baya.sln |
| Add migration | dotnet ef migrations add <Name> --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api |
| Update DB | dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api |
Default URL: https://localhost:5002 — Swagger at /swagger.
On boot, Program.cs calls ApplyMigrationsAsync() and SeedDefaultUsersAsync() — a reachable SQL Server is required to start.
Quality gates — run these before declaring work done
dotnet build Baya.sln— zero new warnings introduced.dotnet test Baya.sln— all tests pass.- Read your own diff as if reviewing a PR. Ask: would a senior engineer approve this without comment?
Project map
src/
├── Core/
│ ├── Baya.Domain Entities (User, Order, Role…), BaseEntity, IEntity, ITimeModification
│ └── Baya.Application Features/ (Commands & Queries), Contracts/, Models/, pipeline behaviors (Common/)
├── Infrastructure/
│ ├── Baya.Infrastructure.Persistence ApplicationDbContext, Repositories/, Configuration/, Migrations/
│ ├── Baya.Infrastructure.Identity Jwt/, Identity/ (Managers, Stores, PermissionManager, Seed)
│ ├── Baya.Infrastructure.CrossCutting Serilog wiring
│ └── Baya.Infrastructure.Monitoring HealthChecks, OpenTelemetry, prometheus-net
├── API/
│ ├── Baya.Web.Api Program.cs, Controllers/V1/, appsettings*.json
│ ├── Baya.WebFramework BaseController, Filters/, Middlewares/, Swagger/, Routing/
│ └── Plugins/Baya.Web.Plugins.Grpc gRPC services + .proto models
├── Shared/Baya.SharedKernel Extensions + validation base
└── Tests/
├── Baya.Tests.Setup Shared test infrastructure (SQLite, NSubstitute setup)
└── Baya.Test.Infrastructure.Identity xUnit identity tests
Dependency direction points inward. Domain has no dependencies. Application depends only on Domain. Infrastructure and API implement/consume Application contracts. Never make Domain or Application reference Infrastructure or the API — this is a hard rule.
Startup wiring
Service registration is composed from per-layer extension methods (each project's ServiceConfiguration/):
ConfigureHealthChecks() · SetupOpenTelemetry()
AddApplicationServices() // Mediator + validators + pipeline behaviors
RegisterIdentityServices(...) // Identity, JWT/JWE, authorization policies
AddPersistenceServices(...) // DbContext, UnitOfWork, repositories
AddWebFrameworkServices() // API versioning + snake_case routing
AddSwagger("v1", "v1.1") · RegisterValidatorsAsServices() · AddMapster()
ConfigureGrpcPluginServices()
Pipeline order: exception handler → Swagger → routing → authentication → authorization → controllers → metrics → health checks → gRPC.
When adding new infrastructure, expose it as an extension method and call it from Program.cs — never inline registrations there directly.
CQRS — how a feature is shaped
Features live under Baya.Application/Features/<Area>/{Commands|Queries}/<Name>/:
Features/Order/
├── Commands/CreateOrderCommand/
│ ├── CreateOrderCommand.cs record : ICommand<OperationResult<T>>
│ ├── CreateOrderCommand.Handler.cs internal sealed class : ICommandHandler<...>
│ └── CreateOrderCommand.Validator.cs
└── Queries/GetUserOrdersQuery/
├── GetUserOrdersQuery.cs
├── GetUserOrdersQuery.Handler.cs
└── GetUserOrdersQuery.Result.cs
Handlers are internal sealed. Requests are record types. Validators use FluentValidation and are picked up automatically by the ValidateCommandBehavior pipeline behavior. Never throw for expected failures — use OperationResult factory methods.
To add a feature: create the folder, implement request + handler + (optional) validator, add any new contracts to Application/Contracts/ and implement them in Infrastructure, then wire a controller action to sender.Send(...).
Persistence
- Access the DB through
IUnitOfWork— notApplicationDbContextdirectly outside Infrastructure. - Commit once per command via
unitOfWork.CommitAsync(). - Use
AsNoTracking()on all read-only queries. - Always project to a DTO in queries — never return entity objects from handlers.
- Add entity config in
Persistence/Configuration/<Area>Config/implementingIEntityTypeConfiguration<T>.
Identity & auth
- JWT/JWE issued by
IJwtService(Baya.Infrastructure.Identity/Jwt/JwtService.cs). - Dynamic permission system:
DynamicPermissionHandlerreads[controller]+[action]route values and checks role claims. The key format is set at runtime — always use[controller]/[action]tokens (see CONVENTIONS.md Routing rule) so the keys are consistent. - Settings bound from
appsettings.json→IdentitySettings.
Conventions (see CONVENTIONS.md for the full rule set)
Quick reference:
- All URL segments are
snake_caseviaSnakeCaseParameterTransformer— use[controller]/[action]tokens. - Controllers inherit
BaseController, injectISender, returnbase.OperationResult(result). - Never call
Ok()/BadRequest()directly in controllers. - Handlers are
internal sealed; never throw for expected failures. - Mapster for mapping; FluentValidation for validation.
- Package versions only in
Directory.Packages.props. - The
Baya.*namespace is project naming — do not rename without explicit instruction.
Known build warnings (pre-existing — do not fix unless tasked)
| Warning | Project | Note |
|---|---|---|
NU1510 on Microsoft.Extensions.Logging.Debug |
Baya.Web.Api |
Redundant transitive reference, harmless |
NETSDK1057 (preview SDK) |
all | .NET 10 SDK is preview on this machine |