-
Notifications
You must be signed in to change notification settings - Fork 1
Coding Standards
The rules a change must satisfy to be mergeable. Architecture explains why these patterns exist; this page is the checklist.
Clean Architecture direction: Domain ← Application ← Infrastructure ← Web.
| Allowed | Web → {Infrastructure, Application, Domain}; Infrastructure → {Application, Domain}; Application → Domain; any layer → Common.* of the same or lower tier |
| Forbidden | Domain → {Application, Infrastructure, Web}; Application → {Infrastructure, Web}; Infrastructure → Web |
-
DbContextusage in the Web layer — resolvers, endpoints, controllers. - Direct HTTP calls without
IGraphQLClientFactoryor a registered namedHttpClient. -
MediatR (the project uses the custom
Common.Mediator) or AutoMapper (manual mapping only). - Non-async database operations, or a missing
CancellationTokenpropagation. - Throwing a raw
Exception— useNotFoundException,ForbiddenAccessException,ValidationException,TooManyRequestsException. - Service locator; static state outside DI; hard-coded connection strings or URLs.
- Exposing domain entities directly in API responses — use records and DTOs.
- GraphQL resolvers containing business logic — resolvers only dispatch via
ISender. -
new Guid(user.Id)/Guid.Parse(user.Id)— service-client tokens carry a non-GUID subject. AlwaysGuid.TryParseand throwUnauthorizedAccessExceptionon failure. -
OrderBy/ThenByapplied AFTER projecting into a DTO or Vm record struct. Npgsql cannot translate an ordering whose key is a member of a constructor-built record struct; it throws at query time and surfaces as Unexpected Execution Error. Order on the entity columns inside the query expression, so the ordering survives a join. EF InMemory evaluates it client-side and the unit tests pass. -
Distinct()/GroupBy()/ set operations over an entity or projection containing a PostgreSQLjsoncolumn (transporter_position.attributes) —jsonhas no equality operator (error42883). De-duplicate withEXISTSor key-based predicates. - Re-typing a GraphQL query string in a test — always reference the production
internal const. -
System.DateTimeanywhere insrcor tests — useDateTimeOffset.UtcNow. -
[Caching]on a per-user or per-account query whose scope comes fromIUserrather than the request. - New
.js/.jsxfiles underTrackHub/src— an ESLint guard errors on them.
- Commands and queries as
readonly record struct : IRequest<TResult>with[Authorize(Resource, Action[, PrincipalTypes])]where authorization applies; handlers implementIRequestHandler<,>; validation via FluentValidation validators. - Reader/Writer separation —
I{Entity}Reader/I{Entity}WriterinDomain/Interfaces, implementations inInfrastructure/{DbContext}, registered Scoped. - New by-id readers and writers extend the service's
AccountScopedDataAccessbase and callRequireAccountAccess/RequireAccountWriteAccesson the loaded row's owning account. Never an inline ad-hocif. - EF configuration via
IEntityTypeConfiguration<T>;BaseAuditableEntityfor audited entities; domain events viaBaseEvent+AddDomainEvent(). - DI registration through a
DependencyInjection.csextension per layer. Every service's Application DI must includeservices.AddDistributedMemoryCache()—CachingBehaviorresolvesIDistributedCachefor every request type, and a missing registration fails every request with a masked DI error. - A service that uses
[RequireFeature]must register its ownIFeatureFlagService— Common's default is fail-open. - GraphQL server registration via
AddTrackHubGraphQLServer<Query, Mutation>(...); per-service extras chain on the returned builder. - REST endpoints via
EndpointGroupBasesubclasses. - Inter-service GraphQL query documents as
internal const stringfields on the client class, with[assembly: InternalsVisibleTo("TrackHub.ServiceContracts.Tests")]. - Service-identity calls require Security-side seeding: the client name in
security.clientsplus grants insecurity.service_client_permissions. - Paged queries return a
…PageVm { Items, TotalCount }envelope, takeint? Skip/int? Take, clamp throughPageRequest.Clamp(default 50, max 500), and order by a human column then the primary key. - Lookup endpoints fetch through
LookupLimits.FetchSizeand validate withLookupLimits.EnsureWithinCeiling— fail loudly rather than truncate silently. - Delete mutations return the deleted entity's identifier, not a boolean.
- Index names that would exceed PostgreSQL's 63-character limit get an explicit
.HasDatabaseName(...). - Provider mappers convert to the canonical
PositionVmunits (speed km/h, decimal degrees, UTCDateTimeOffset) and carry a value-correctness unit test.
- Assemblies and root namespaces:
TrackHub.{Service}.{Layer}(repository folder names keep their historical spellings). Consumer clients:TrackHub.{Service}.Infrastructure.{Target}Api. -
{Feature}{Action}Command|Query,{Name}Handler,{Name}Validator,{Entity}Reader|Writer,{Entity}Dto(input) /{Entity}Vm(output),ApplicationDbContext : IApplicationDbContext, endpoints{Feature} : EndpointGroupBase. - The cross-service telemetry attribute field is
hourmetereverywhere. Provider-specific spellings (CommandTrack'shobbsMeter) are remapped only at the provider model, withJsonPropertyName. - Resource and action names come from
Common.Domain.Constants— never string literals.
Application/{Feature}/Commands|Queries/{Name}.cs
Infrastructure/{DbContext}/{Entity}Reader|Writer.cs
Infrastructure/{DbContext}/Configurations/{Entity}Configuration.cs
Domain/Interfaces|Models/...
Web/GraphQL/Query|Mutation/{Feature}.cs
Web/Endpoints/{Feature}.cs
{Layer}/DependencyInjection.cs
One public class per file. Two IEntityTypeConfiguration<T> classes go in two files. Sanctioned exceptions: the CQRS feature file (a command plus its {Name}Handler and {Name}Validator), and grouped DTO/Vm contract files holding several related records.
- Domain entity in
Domain/Models(inheritBaseAuditableEntitywhere audited). -
I{Entity}Reader/I{Entity}WriterinDomain/Interfaces. - EF configuration +
DbSet+ migration (dotnet ef migrations add). - Reader and writer implementations, registered Scoped, extending
AccountScopedDataAccesswhere by-id. - Command and query record structs with
[Authorize], handlers, validators. - GraphQL resolver methods (partial
Query/Mutation) or anEndpointGroupBasefor REST. - If the feature adds or changes an inter-service call: extract the query to an
internal const, add the contract test, and seed service-client permissions if the flow runs under a service identity. - If the module ships a report: add the
IReport(withGetDatasetAsync) in Reporting and a seeded Manager catalog row with its governance metadata, plus portalreportList/reportDescriptionsi18n keys and a filter strategy. - Run the repository's unit tests and
dotnet test TrackHub.IntegrationTests/TrackHub.IntegrationTests.slnx.
- Portal work is TypeScript (
strict); new.js/.jsxunderTrackHub/srcis forbidden. - Data access is layered — components never call the network directly:
src/api/<backend>/<domain>Operations.ts(documents via the generatedgraphql()tag, values as variables only) →src/api/<backend>/<domain>.ts(typed functions that throwApiError) →src/queries/<domain>.ts(TanStack Query hooks owning cache keys and invalidation). REST file endpoints go throughsrc/api/core/restClient.ts; endpoint URLs live only insrc/api/core/endpoints.ts. - After any backend GraphQL surface change: run the contract tests (which re-export the SDLs), then
npm run codegen.npm run typecheck && npm test && npm run buildis the gate. - i18n keys are compile-checked — add them to both
locales/en.jsonandlocales/es.json. Dynamic keys cast at the key expression only. -
Escape everything interpolated into a map popup. Leaflet
bindPopup/bindTooltipand the Google InfoWindow assign viainnerHTML, so React's escaping does not apply — useescapeHtml(src/utils/htmlUtils.ts). -
datetime-local⇄ UTC goes throughtoDateTimeLocalInput/fromDateTimeLocalInput(src/utils/dateUtils.ts). Assert the round-trip property, never a literal. - Argon components and controls export real prop interfaces — import and use them directly. Never re-introduce local prop-slice interfaces or
as unknown asboundary casts at call sites; if a control lacks a prop you must pass, widen the control's exported prop type. Theme extensions live insrc/types/mui-theme.d.ts. - If the change adds or renames a screen, update the help topic's
screens:frontmatter —build-help.mjsvalidates the mapping in both directions and will fail the build.
-
TrackHubCommon —
TrackHubCommon/Directory.Build.propsis the source of truth. Every consumer tracks the current version: the eight service repositories throughDirectory.Packages.props, and the ServiceContracts harness through a directPackageReferencethat a props-only sweep misses. Bump every consumer together; never pin one back. -
HotChocolate 16.5, ASP.NET Core / EF Core 10.0 across all repositories, including
Tools.McpExtractor(which pins EF Core directly and must move with the stack). - Prefer latest. A newly introduced component is never pinned back; scope or disable a lagging tool instead.
Before reporting a finding — code review, security review, audit — check the workspace's system-context/findings.md and system-context/rules.md. In particular:
-
Committed generic development secrets are intentional. Seeded OAuth client secrets, sample
appsettings,.envexamples and DBInitializer seed data are kept in-repository so the multi-service dev environment stands up without manual secret plumbing; production overrides them through environment variables. Do not flag these. Only flag committed real production or third-party credentials. - Entity placement, duplicated entity shapes across DbContexts, and the shared Manager/Geofencing database are documented decisions, not defects.
- Open findings live only in
findings.md. When you fix one, remove it there — and note the decision if it was accepted rather than fixed.
The workspace's system-context/ holds machine-generated catalogs alongside hand-maintained documents:
-
dotnet runfromtools/Tools.McpExtractor— regeneratesroot.json,database.json,api.json,domain.json,auth.json,dtos.json,common.json,topology.json. -
node tools/extract-frontend.js— regeneratesfrontend.json. - Hand-update
architecture.md,rules.mdandfindings.md. - Verify the JSON parses and spot-check that new services and operations appear.
And update this wiki. It is the platform's technical documentation of record — repository README files carry only an overview, setup and gotchas.
Platform
- Architecture
- Technology
- Inter-Service Communication
- Database
- Security and Identity
- User Permissions Overview
Services
- Manager
- Telemetry
- Router
- Adding a Provider
- Geofencing
- Trip Management
- Reporting
- Common Library
- Frontend
Engineering
User docs
- User Guide (ships in the app)