@jeffrey2423/coding-standards 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -174
- package/bin/cli.js +373 -20
- package/package.json +13 -3
- package/standards/backend/architecture/event-driven.md +112 -0
- package/standards/backend/architecture/microservice-anatomy.md +106 -0
- package/standards/backend/architecture/multitenancy.md +112 -0
- package/standards/backend/architecture/public-api-facade.md +112 -0
- package/standards/backend/architecture/shared-vs-owned.md +62 -0
- package/standards/{backend-standards.md → backend/backend-standards.md} +8 -1
- package/standards/{database-conventions.md → backend/database-conventions.md} +7 -0
- package/standards/backend/technology-stack.md +73 -0
- package/standards/core/ai-collaboration.md +64 -0
- package/standards/core/clean-architecture-ddd.md +69 -0
- package/standards/core/coding-conventions.md +66 -0
- package/standards/core/testing-strategy.md +46 -0
- package/standards/{mobile-flutter-standards.md → mobile/flutter/flutter-standards.md} +9 -1
- package/standards/{mobile-react-native-standards.md → mobile/react-native/react-native-standards.md} +9 -1
- package/standards/{technical-preferences-ux.md → web/_base/design-system-ux.md} +8 -1
- package/standards/web/_base/frontend-architecture.md +75 -0
- package/standards/{frontend-standards.md → web/_base/frontend-standards.md} +7 -0
- package/standards/web/_base/technology-stack.md +40 -0
- package/standards/web/microfrontends/module-federation-standard.md +216 -0
- package/standards/web/single-spa/single-spa-standard.md +196 -0
- package/standards/web/spa/spa-standard.md +53 -0
- package/standards/architecture-patterns.md +0 -444
- package/standards/technology-stack.md +0 -294
- package/standards/vite-config-standard.md +0 -531
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Multi-Tenancy Strategy
|
|
3
|
+
platform: backend
|
|
4
|
+
track: distributed-architecture
|
|
5
|
+
load_when: "Building a multi-tenant SaaS — tenant isolation, RLS, partitioning, and tenant routing."
|
|
6
|
+
updated: 2026-06
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Multi-Tenancy Strategy
|
|
10
|
+
|
|
11
|
+
A modern multi-tenant SaaS uses a **hybrid "bridge" model**: pooled by default, siloed selectively. This is the AWS SaaS Lens recommendation and the pattern the industry has converged on.
|
|
12
|
+
|
|
13
|
+
## The model
|
|
14
|
+
|
|
15
|
+
- **Pool by default** — standard tenants share one database **per microservice**, isolated by a `tenant_id` column + **Row-Level Security (RLS)**. Cheapest, simplest to operate.
|
|
16
|
+
- **Silo selectively** — enterprise/regulated/high-volume tenants are promoted to a dedicated database. A **per-microservice** decision (a tenant can be siloed in Sales, pooled in Catalog).
|
|
17
|
+
- **Tenant catalog (control plane)** — a small central DB mapping `tenant_id → connection string` per microservice, enabling dynamic routing.
|
|
18
|
+
|
|
19
|
+
This keeps DB-per-service intact: Sales never shares a DB with Inventory; within Sales, tenants share until promoted.
|
|
20
|
+
|
|
21
|
+
## Row-Level Security — the isolation guarantee
|
|
22
|
+
|
|
23
|
+
Isolation is **structural** (enforced by PostgreSQL), not application-level (a forgotten `WHERE`).
|
|
24
|
+
|
|
25
|
+
```sql
|
|
26
|
+
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
|
27
|
+
ALTER TABLE orders FORCE ROW LEVEL SECURITY; -- owner is constrained too
|
|
28
|
+
CREATE POLICY tenant_isolation ON orders
|
|
29
|
+
USING (tenant_id = current_setting('app.tenant_id')::uuid)
|
|
30
|
+
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### RLS rules (verified against PostgreSQL 18 docs)
|
|
34
|
+
|
|
35
|
+
- **MUST** run the app under a **non-owner, non-superuser role without `BYPASSRLS`**. Table owners and superusers bypass RLS unless `FORCE ROW LEVEL SECURITY` is set — relying on the app role is cleaner.
|
|
36
|
+
- **MUST** include `WITH CHECK`, or a tenant can write rows belonging to another tenant.
|
|
37
|
+
- **MUST** set the tenant per unit of work with **`SET LOCAL app.tenant_id = '…'` inside an explicit transaction** so it can't leak across pooled connections.
|
|
38
|
+
- **MUST NOT** grant `BYPASSRLS` to the application role, ever.
|
|
39
|
+
- **SHOULD** read the GUC safely: `current_setting('app.tenant_id', true)` (missing-ok) to avoid errors when unset.
|
|
40
|
+
|
|
41
|
+
### EF Core 10 / Npgsql wiring (defense in depth)
|
|
42
|
+
|
|
43
|
+
- Add an EF Core **global query filter** (`HasQueryFilter(e => e.TenantId == _tenantId)`) for ergonomics **and** keep DB-side RLS as the real guarantee.
|
|
44
|
+
- Set the session variable via a `DbConnectionInterceptor` / `SaveChanges` hook using **`SET LOCAL` within a transaction**.
|
|
45
|
+
- **PgBouncer transaction pooling**: a plain `SET` leaks across tenants on connection reuse — `SET LOCAL` in a transaction resets at commit and is mandatory in that setup. With DbContext pooling, reset the tenant per scope.
|
|
46
|
+
- Silo (DB-per-tenant) in EF Core: swap the **connection string** via an interceptor / `IDbContextFactory`; use `IModelCacheKeyFactory` if per-tenant schemas diverge.
|
|
47
|
+
|
|
48
|
+
## PostgreSQL 18 features to use (released Sept 2025)
|
|
49
|
+
|
|
50
|
+
- **`uuidv7()`** — timestamp-ordered UUIDs; far better index locality than v4. **Default PK type for new entities.**
|
|
51
|
+
- **Asynchronous I/O** (`io_method`) — up to ~3× faster scans/vacuum.
|
|
52
|
+
- **B-tree skip scan** — eases composite-index design (but still lead with `tenant_id`).
|
|
53
|
+
- **Virtual generated columns** (now the default) and **`RETURNING OLD/NEW`** for audit trails.
|
|
54
|
+
- **OAuth/OAUTHBEARER** auth method in `pg_hba.conf`.
|
|
55
|
+
|
|
56
|
+
## Partitioning & indexing
|
|
57
|
+
|
|
58
|
+
- Hash-partition high-volume tables by `tenant_id` to spread load and enable partition pruning on `WHERE tenant_id = …`.
|
|
59
|
+
- **MUST** lead every composite index with `tenant_id` (e.g. `(tenant_id, created_at DESC)`); unique constraints must include the partition key.
|
|
60
|
+
- Keep partition counts modest (4/8/16) — thousands degrade planning.
|
|
61
|
+
|
|
62
|
+
```sql
|
|
63
|
+
CREATE TABLE orders (
|
|
64
|
+
id UUID NOT NULL DEFAULT uuidv7(),
|
|
65
|
+
tenant_id UUID NOT NULL,
|
|
66
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
67
|
+
PRIMARY KEY (tenant_id, id)
|
|
68
|
+
) PARTITION BY HASH (tenant_id);
|
|
69
|
+
|
|
70
|
+
CREATE INDEX ix_orders_tenant_created ON orders (tenant_id, created_at DESC);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Tenant catalog (control plane)
|
|
74
|
+
|
|
75
|
+
A small, separate DB — the "phone book" that answers *"where does each tenant live?"* It is **not** any microservice's DB; it's shared platform infrastructure (see [`shared-vs-owned.md`](shared-vs-owned.md)).
|
|
76
|
+
|
|
77
|
+
```sql
|
|
78
|
+
CREATE TABLE tenant_connections (
|
|
79
|
+
tenant_id UUID NOT NULL,
|
|
80
|
+
microservice VARCHAR(100) NOT NULL,
|
|
81
|
+
connection_string VARCHAR(500) NOT NULL,
|
|
82
|
+
is_silo BOOLEAN NOT NULL DEFAULT false,
|
|
83
|
+
PRIMARY KEY (tenant_id, microservice)
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Request flow: extract `tenant_id` from JWT → look up routing (**cache in Redis**, >99% hit) → open connection to the right DB → `SET LOCAL app.tenant_id` → RLS applies automatically.
|
|
88
|
+
|
|
89
|
+
- **MUST** carry `tenant_id` in the JWT (identity), **not** the connection string (location). Location is resolved server-side; putting it in the JWT leaks infrastructure and breaks on silo promotion.
|
|
90
|
+
- **SHOULD** invalidate the Redis routing cache via Pub/Sub when a tenant is moved.
|
|
91
|
+
|
|
92
|
+
## Promotion pool → silo (operational, not architectural)
|
|
93
|
+
|
|
94
|
+
Create the silo DB → copy the tenant's rows (logical replication for near-zero downtime) → verify → update the tenant catalog (`is_silo = true`) + invalidate cache → clean the pool. **No code change** — it's a control-plane operation.
|
|
95
|
+
|
|
96
|
+
## Scaling beyond one node
|
|
97
|
+
|
|
98
|
+
Reach for **Citus** (first-party in Azure Cosmos DB for PostgreSQL) only after vertical scaling + partitioning are exhausted; shard by `tenant_id` as the distribution column with colocated tables.
|
|
99
|
+
|
|
100
|
+
## Anti-patterns
|
|
101
|
+
|
|
102
|
+
- Application-only isolation (no RLS) → one missing `WHERE` leaks data across tenants.
|
|
103
|
+
- `BYPASSRLS` on the app role → silently defeats every policy.
|
|
104
|
+
- Connection string in the JWT → infra leak + stale routing after promotion.
|
|
105
|
+
- Per-microservice routing tables instead of a central catalog → reintroduces distributed coordination.
|
|
106
|
+
|
|
107
|
+
## Sources
|
|
108
|
+
|
|
109
|
+
- PostgreSQL 18 release notes — https://www.postgresql.org/docs/18/release-18.html
|
|
110
|
+
- PostgreSQL RLS — https://www.postgresql.org/docs/18/ddl-rowsecurity.html
|
|
111
|
+
- AWS SaaS Lens (silo/pool/bridge) — https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/silo-pool-and-bridge-models.html
|
|
112
|
+
- EF Core multitenancy — https://learn.microsoft.com/en-us/ef/core/miscellaneous/multitenancy
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Public API Facade
|
|
3
|
+
platform: backend
|
|
4
|
+
track: distributed-architecture
|
|
5
|
+
load_when: "Exposing the platform to external consumers — gateway, contracts, webhooks, auth, versioning."
|
|
6
|
+
updated: 2026-06
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Public API Facade
|
|
10
|
+
|
|
11
|
+
The public layer of a platform. **Contracts are the product**: OpenAPI for sync, AsyncAPI for events. Their versioning discipline is what makes a platform (vs an app) — third parties depend on them.
|
|
12
|
+
|
|
13
|
+
## Pieces
|
|
14
|
+
|
|
15
|
+
- **API Gateway** — single entry point for inbound HTTP (`api.org.com`).
|
|
16
|
+
- **Microservices** — each owns its OpenAPI + AsyncAPI, exposed via the gateway.
|
|
17
|
+
- **Webhook Dispatcher** — delivers events to external subscribers.
|
|
18
|
+
- **Identity Provider** — issues JWTs.
|
|
19
|
+
- **Developer Portal** — aggregated docs, sandbox, credential & subscription management.
|
|
20
|
+
|
|
21
|
+
## Two channels
|
|
22
|
+
|
|
23
|
+
| | REST API | Webhooks |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Direction | consumer → platform | platform → consumer |
|
|
26
|
+
| Model | synchronous | asynchronous |
|
|
27
|
+
| Contract | **OpenAPI 3.1** | **AsyncAPI 3.0** |
|
|
28
|
+
| Versioning | `/v1/`, `/v2/` | `EventV1`, `EventV2` |
|
|
29
|
+
| Auth | JWT (`Authorization`) | HMAC signature |
|
|
30
|
+
| Idempotency | `Idempotency-Key` | `eventId` |
|
|
31
|
+
|
|
32
|
+
## API Gateway
|
|
33
|
+
|
|
34
|
+
Creates the illusion of one platform over many services: routing by path, central JWT auth, rate limiting, observability, version routing, single public URL.
|
|
35
|
+
|
|
36
|
+
- **2026 default for .NET: YARP 2.x** (first-class reverse proxy). Use Kong/Envoy for polyglot estates; Azure APIM / AWS API Gateway when you need a managed portal/quotas.
|
|
37
|
+
- **MUST** authenticate at the gateway; downstream services trust that a request that arrived is authenticated.
|
|
38
|
+
|
|
39
|
+
## OpenAPI (.NET 10)
|
|
40
|
+
|
|
41
|
+
- **MUST** use the built-in `Microsoft.AspNetCore.OpenApi` (`AddOpenApi()`), which emits **OpenAPI 3.1** by default at `/openapi/{document}.json`. Swashbuckle is no longer in the default templates.
|
|
42
|
+
- **MUST** serve a docs UI with **Scalar** (`app.MapScalarApiReference()`) — **Development only**.
|
|
43
|
+
- **SHOULD** lint the spec with **Spectral** in CI and generate client SDKs from it at build time (`IOpenApiDocumentProvider`).
|
|
44
|
+
- Each microservice owns its OpenAPI; the portal aggregates them (like Stripe/Twilio/Shopify: monolithic docs outside, N independent APIs inside).
|
|
45
|
+
|
|
46
|
+
## Errors — Problem Details (RFC 9457)
|
|
47
|
+
|
|
48
|
+
All endpoints return errors as `application/problem+json` per **RFC 9457** (which obsoletes RFC 7807). .NET's `Results.Problem`/`ProblemDetails` aligns.
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"type": "https://api.org.com/errors/sku-duplicate",
|
|
53
|
+
"title": "Duplicate SKU",
|
|
54
|
+
"status": 409,
|
|
55
|
+
"detail": "A product with SKU 'ABC123' already exists in this tenant",
|
|
56
|
+
"instance": "/api/v1/catalog/products",
|
|
57
|
+
"traceId": "…"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Versioning & deprecation
|
|
62
|
+
|
|
63
|
+
- **MUST NOT** make breaking changes within a published version. Breaking change ⇒ new version; both coexist during deprecation (typically 6–12 months).
|
|
64
|
+
- **Non-breaking (same version):** add optional fields, new endpoints, new enum values (carefully), better error messages.
|
|
65
|
+
- **Breaking (new version):** remove/rename fields, change types, make an optional field required, change semantics, remove endpoints.
|
|
66
|
+
- **MUST** design consumers as **tolerant readers** (ignore unknown fields, tolerate missing optionals).
|
|
67
|
+
- **SHOULD** signal end-of-life with the `Deprecation` (RFC 9745) and `Sunset` (RFC 8594) headers + a public changelog.
|
|
68
|
+
|
|
69
|
+
## Authentication (OAuth 2.1)
|
|
70
|
+
|
|
71
|
+
- Machine-to-machine integrators use the **`client_credentials`** grant; prefer **`private_key_jwt`** over shared secrets.
|
|
72
|
+
- JWT carries **identity** (`sub`, `tenant_id`, `scope`), short TTL, asymmetric keys (RS/ES) with JWKS rotation, validated `aud`/`iss`/`exp`.
|
|
73
|
+
- The `tenant_id` claim scopes the integrator to its tenant — it cannot push data to another.
|
|
74
|
+
- **SHOULD** use sender-constrained tokens (**DPoP**, RFC 9449) or **mTLS** (RFC 8705) on high-value APIs.
|
|
75
|
+
|
|
76
|
+
Scopes are per API + action: `catalog:read`, `catalog:write`, `sales:write`, …
|
|
77
|
+
|
|
78
|
+
## Webhooks (Standard Webhooks)
|
|
79
|
+
|
|
80
|
+
Follow the **Standard Webhooks** baseline:
|
|
81
|
+
|
|
82
|
+
- **MUST** sign every payload with **HMAC-SHA256**; send `webhook-id`, `webhook-timestamp`, `webhook-signature` headers; support versioned signatures for key rotation.
|
|
83
|
+
- **MUST** deliver at-least-once with exponential-backoff retries (e.g. 1m, 5m, 15m, 1h, 6h, 24h) then dead-letter + notify.
|
|
84
|
+
- **MUST** treat consumers as idempotent (dedupe on `eventId`); ack fast with 2xx.
|
|
85
|
+
- **MUST** reject on signature mismatch or stale timestamp (replay protection).
|
|
86
|
+
|
|
87
|
+
AsyncAPI 3.0 documents the event contracts; the broker stays internal (see [`event-driven.md`](event-driven.md)).
|
|
88
|
+
|
|
89
|
+
## Your own UI uses the public API
|
|
90
|
+
|
|
91
|
+
The platform's own UI/PWA **MUST** consume the **same** public contracts as external integrators — **no private APIs**. This forces contract quality, guarantees parity, removes duplication, and surfaces problems via dogfooding (Stripe/Twilio/Shopify do this).
|
|
92
|
+
|
|
93
|
+
## Standard headers
|
|
94
|
+
|
|
95
|
+
| Header | Purpose |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `Authorization: Bearer <JWT>` | auth |
|
|
98
|
+
| `Idempotency-Key: <uuid>` | idempotent POSTs |
|
|
99
|
+
| `X-Correlation-Id: <uuid>` | distributed tracing |
|
|
100
|
+
| `webhook-signature` | webhook HMAC (Standard Webhooks) |
|
|
101
|
+
|
|
102
|
+
## Adding a microservice doesn't break consumers
|
|
103
|
+
|
|
104
|
+
New context ⇒ create the service + its OpenAPI, add a gateway route, publish docs (+ AsyncAPI if it emits events). Existing services and integrators are unaffected. That non-disruption is the mark of a well-designed facade.
|
|
105
|
+
|
|
106
|
+
## Sources
|
|
107
|
+
|
|
108
|
+
- .NET 10 OpenAPI — https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-10.0
|
|
109
|
+
- Problem Details RFC 9457 — https://www.rfc-editor.org/rfc/rfc9457
|
|
110
|
+
- Standard Webhooks — https://www.standardwebhooks.com/
|
|
111
|
+
- Sunset header RFC 8594 — https://www.rfc-editor.org/rfc/rfc8594
|
|
112
|
+
- YARP — https://microsoft.github.io/reverse-proxy/
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shared vs Owned Components
|
|
3
|
+
platform: backend
|
|
4
|
+
track: distributed-architecture
|
|
5
|
+
load_when: "Deciding whether a new component belongs to a microservice or is shared platform infrastructure."
|
|
6
|
+
updated: 2026-06
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Shared vs Owned Components
|
|
10
|
+
|
|
11
|
+
A platform has two kinds of components. Confusing them blurs ownership and makes operations harder.
|
|
12
|
+
|
|
13
|
+
> **The rule:** each microservice owns its **data** and **domain logic**. **Transport, coordination, and identity** are shared platform infrastructure.
|
|
14
|
+
|
|
15
|
+
When you see a component, ask:
|
|
16
|
+
- Stores **business domain data**? → owned by a microservice.
|
|
17
|
+
- Runs **business logic**? → owned by a microservice.
|
|
18
|
+
- Only **transports** things between services? → shared.
|
|
19
|
+
- Only **coordinates** access/identity across services? → shared.
|
|
20
|
+
|
|
21
|
+
## Owned by each microservice
|
|
22
|
+
|
|
23
|
+
Its transactional database (including enterprise silo DBs); its tables (aggregates, `outbox_messages`, `processed_messages`, projections); its code (the 5 projects); its HTTP endpoints; its event consumers; its **queues and exchanges within** the shared broker; its CI/CD pipeline; its emitted telemetry.
|
|
24
|
+
|
|
25
|
+
> The broker **cluster** is shared, but each service owns its queues/exchanges inside it — like a shared office building where each company has its own offices.
|
|
26
|
+
|
|
27
|
+
## Shared platform infrastructure
|
|
28
|
+
|
|
29
|
+
| Component | Why shared |
|
|
30
|
+
|---|---|
|
|
31
|
+
| **Broker** (RabbitMQ/Kafka) | exists so producers/consumers meet without knowing each other |
|
|
32
|
+
| **API Gateway** | unifies the entry point; one place for auth/rate-limit |
|
|
33
|
+
| **Webhook Dispatcher** | connects internal events to external subscribers |
|
|
34
|
+
| **Identity Provider** | identity must be single platform-wide (SSO) |
|
|
35
|
+
| **Tenant Catalog** | tenant routing must be consistent platform-wide |
|
|
36
|
+
| **Redis (cross-cutting cache)** | caches transversal data (tenant routing, IdP keys) |
|
|
37
|
+
| **Observability stack** | value is correlating all services in one place |
|
|
38
|
+
| **DB engine (infra)** | the *engine* can be shared; the *logical DB* is owned |
|
|
39
|
+
| **Orchestration cluster** (K8s) | shared by nature |
|
|
40
|
+
|
|
41
|
+
These exist once, operated by the platform team; every service uses them, none "owns" them.
|
|
42
|
+
|
|
43
|
+
## Deciding for a new component
|
|
44
|
+
|
|
45
|
+
1. Stores/processes **domain data** of one context? → **owned**.
|
|
46
|
+
2. Cross-cutting transport/coordination/identity? → **shared**.
|
|
47
|
+
3. Needs **consistent state across services**? → **shared**.
|
|
48
|
+
4. Each service would evolve it differently? → **owned**.
|
|
49
|
+
5. Would N copies be redundant/harmful? → **shared**.
|
|
50
|
+
|
|
51
|
+
Examples: push notifications → shared (transport); PDF generation → likely shared; payment processing → probably an owned `Payments` microservice (it has domain logic); full-text search → owned if one context needs it, shared (with separate indices) if many.
|
|
52
|
+
|
|
53
|
+
## Why it matters
|
|
54
|
+
|
|
55
|
+
Clear ownership (who fixes what), boundary discipline (domain teams don't touch shared infra; the platform team doesn't write business logic), clean scaling decisions (per-service vs cluster), a clean cost model (shared = fixed/amortized; owned = scales with services), and fast team onboarding.
|
|
56
|
+
|
|
57
|
+
## Anti-patterns
|
|
58
|
+
|
|
59
|
+
- Shared DB between microservices → distributed monolith.
|
|
60
|
+
- A service with its own broker/IdP → breaks the purpose of shared coordination/identity.
|
|
61
|
+
- Private cache for transversal data → invalidation becomes impossible.
|
|
62
|
+
- Domain data living in "shared infrastructure" → it's a mis-named microservice.
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Backend Development Standards
|
|
3
|
+
platform: backend
|
|
4
|
+
load_when: "Any backend/.NET implementation — endpoints, handlers, EF Core, validation, testing."
|
|
5
|
+
updated: 2026-06
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# Backend Development Standards
|
|
2
9
|
|
|
3
|
-
> **Note**: For architecture
|
|
10
|
+
> **Note**: For architecture principles (Clean Architecture, DDD), see [`../core/clean-architecture-ddd.md`](../core/clean-architecture-ddd.md). For microservice structure and the distributed-architecture docs, see [`architecture/microservice-anatomy.md`](architecture/microservice-anatomy.md).
|
|
4
11
|
|
|
5
12
|
## Technology Stack Standards
|
|
6
13
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Backend Technology Stack
|
|
3
|
+
platform: backend
|
|
4
|
+
load_when: "Any backend/.NET work — the approved stack, ORM strategy, and 2026 library notes."
|
|
5
|
+
updated: 2026-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Backend Technology Stack
|
|
9
|
+
|
|
10
|
+
The approved .NET toolchain. Use these by default; deviations need a stated reason.
|
|
11
|
+
|
|
12
|
+
| Category | Technology | Version | Notes |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| Runtime | .NET | 10 (LTS, support to Nov 2028) | C# 14 |
|
|
15
|
+
| API style | Minimal API | — | endpoint grouping, `TypedResults` |
|
|
16
|
+
| Primary ORM | Entity Framework Core | 10 | CRUD, aggregates, tracking, migrations |
|
|
17
|
+
| Performance ORM | linq2db | latest | hot-path queries without tracking |
|
|
18
|
+
| Runtime filters | System.Linq.Dynamic.Core | latest | string-based dynamic filters (sanitize!) |
|
|
19
|
+
| Composable predicates | LinqKit | latest | type-safe predicate composition over EF Core |
|
|
20
|
+
| Database | PostgreSQL | 18+ (mandatory) | `uuidv7()` PKs, RLS, partitioning |
|
|
21
|
+
| Validation | FluentValidation | latest | format/structural validation |
|
|
22
|
+
| API docs | OpenAPI (built-in `Microsoft.AspNetCore.OpenApi`) + **Scalar** | — | 3.1 by default; Scalar UI dev-only; **not** Swagger |
|
|
23
|
+
| Orchestration (local + deploy) | .NET Aspire | 13 | service discovery, telemetry, integrations |
|
|
24
|
+
| Gateway | YARP | 2.x | first-class .NET reverse proxy |
|
|
25
|
+
| Testing | xUnit + Testcontainers | latest | EF InMemory for pure unit only |
|
|
26
|
+
| Resilience | Microsoft.Extensions.Resilience (Polly v8) | latest | retries, timeouts, circuit breakers |
|
|
27
|
+
| Observability | OpenTelemetry | latest | traces/metrics/logs (Aspire wires it) |
|
|
28
|
+
| Mediation & messaging | Wolverine | latest | MIT; CQRS mediation + message bus + Outbox |
|
|
29
|
+
| PDF | PdfSharp / MigraDoc | 6+ | MIT; documents (invoices, reports) |
|
|
30
|
+
|
|
31
|
+
## Open-source-only policy
|
|
32
|
+
|
|
33
|
+
This stack is **100% open source under permissive licenses**. Every dependency MUST be **MIT, Apache-2.0, BSD, ISC or the PostgreSQL License**.
|
|
34
|
+
|
|
35
|
+
- **MUST NOT** introduce source-available / revenue-gated libraries — notably **MediatR, AutoMapper, MassTransit** (commercial since 2025–2026) and **QuestPDF** (free only under a revenue cap; never for public-sector or publicly-traded companies).
|
|
36
|
+
- **MUST NOT** introduce network-copyleft (AGPL — e.g. **iText**) or commercial PDF/reporting SDKs (**Syncfusion, Aspose, IronPDF**).
|
|
37
|
+
- **MUST** verify a dependency's license before adding it. For **open-core** projects (e.g. Wolverine/Marten), confirm you use the **MIT core**, not a commercial add-on.
|
|
38
|
+
- Old MIT versions of the now-commercial libraries are archived/unmaintained — don't pin to them for new work.
|
|
39
|
+
|
|
40
|
+
### Approved OSS replacements
|
|
41
|
+
|
|
42
|
+
| Instead of (non-OSS) | Use (OSS) | License |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| MediatR | **Wolverine** (mediation + bus + Outbox) or the `Mediator` source-generator | MIT |
|
|
45
|
+
| MassTransit | **Wolverine** | MIT |
|
|
46
|
+
| AutoMapper | **Mapperly** (source-generated) or hand-written mapping | Apache-2.0 |
|
|
47
|
+
| QuestPDF | **PdfSharp / MigraDoc**, or **PuppeteerSharp / Playwright** for HTML→PDF | MIT |
|
|
48
|
+
|
|
49
|
+
## Multi-ORM strategy — when to use each
|
|
50
|
+
|
|
51
|
+
### EF Core 10 — primary
|
|
52
|
+
Use for: standard CRUD, DDD aggregates with tracking and domain events, simple-to-moderate queries, navigation properties, anything in the aggregate lifecycle.
|
|
53
|
+
|
|
54
|
+
### linq2db — performance
|
|
55
|
+
Use for: highly optimized read queries, complex projections/joins EF can't translate well, analytics/dashboards/reports, high-volume endpoints, tracking-free reads.
|
|
56
|
+
Do **not** use for: full DDD aggregates with tracking/events, or trivial filters.
|
|
57
|
+
|
|
58
|
+
### System.Linq.Dynamic.Core — runtime filters
|
|
59
|
+
Use for: API-driven dynamic filters/sorts (`?filter=Age > 30 AND Country == "CO"`), configurable grids, data explorers.
|
|
60
|
+
Do **not** use when: filters are fixed in code, you need strict security without sanitization, or you need maximum performance (use LinqKit/linq2db).
|
|
61
|
+
|
|
62
|
+
### LinqKit — composable predicates
|
|
63
|
+
Use for: composable business-rule predicates with type safety, combining multiple conditions in one `Where()`, reusable repository queries that still translate through EF Core.
|
|
64
|
+
Do **not** use when: filters are trivial, text-based dynamic (use Dynamic.Core), or need max performance (use linq2db).
|
|
65
|
+
|
|
66
|
+
## Rules
|
|
67
|
+
|
|
68
|
+
- **MUST** use `DateTimeOffset` for timestamps, never `DateTime`.
|
|
69
|
+
- **MUST** use `Guid` (UUID, prefer `uuidv7()` server-side) primary keys for all entities.
|
|
70
|
+
- **MUST** document the API with built-in OpenAPI; serve Scalar UI in Development only.
|
|
71
|
+
- **MUST** run integration tests against real PostgreSQL via Testcontainers.
|
|
72
|
+
- **SHOULD** orchestrate the local dev environment with .NET Aspire.
|
|
73
|
+
- **SHOULD** put resilience policies on every outbound HTTP call.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AI Collaboration Standard
|
|
3
|
+
platform: all
|
|
4
|
+
load_when: "Always. How AI coding agents should consume and apply this standards library."
|
|
5
|
+
updated: 2026-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AI Collaboration Standard
|
|
9
|
+
|
|
10
|
+
This library is designed to be read by **AI coding agents** (Claude Code, Cursor, Copilot, etc.) as much as by humans. This document defines how an agent loads, prioritizes, and applies these standards.
|
|
11
|
+
|
|
12
|
+
## How to read this library
|
|
13
|
+
|
|
14
|
+
1. **Start at the index.** The installed `coding-standards/INDEX.md` lists only the standards active in *this* project, each with a one-line "load when" trigger. Read it first.
|
|
15
|
+
2. **Load on demand.** Don't load every file. Pull the standard that matches the task (e.g. UI work → `web/_base/frontend-standards.md`; an event handler → `backend/architecture/event-driven.md`). Each file's front-matter has a `load_when` hint.
|
|
16
|
+
3. **Respect precedence.** When rules appear to conflict: a more specific platform/track doc overrides a general one, and `MUST` overrides `SHOULD`. Surface the conflict to the user rather than guessing.
|
|
17
|
+
|
|
18
|
+
## How rules are written (and how to obey them)
|
|
19
|
+
|
|
20
|
+
This library uses RFC 2119 keywords. Treat them literally:
|
|
21
|
+
|
|
22
|
+
- **MUST / MUST NOT** — hard constraint. Never violate without explicit user approval.
|
|
23
|
+
- **SHOULD / SHOULD NOT** — strong default. Deviate only with a stated reason.
|
|
24
|
+
- **MAY** — discretionary.
|
|
25
|
+
|
|
26
|
+
Rules are imperative bullets, often paired with **DO / DON'T** examples. Pattern-match to the examples — they encode the intent more precisely than prose.
|
|
27
|
+
|
|
28
|
+
## Agent guardrails
|
|
29
|
+
|
|
30
|
+
- **MUST NOT** introduce a dependency, framework, or pattern that contradicts the project's active standards. If the task seems to require one, ask first.
|
|
31
|
+
- **MUST** follow the [language rule](coding-conventions.md): code/comments/logs in English, user-facing text in Spanish.
|
|
32
|
+
- **MUST** write tests per [`testing-strategy.md`](testing-strategy.md) and never claim untested code is tested.
|
|
33
|
+
- **MUST** place code where the architecture dictates (see [`clean-architecture-ddd.md`](clean-architecture-ddd.md)) instead of inventing new top-level structure.
|
|
34
|
+
- **MUST** verify that any file, symbol, version, or flag named in a standard still exists in the codebase before acting on it — standards can drift from code.
|
|
35
|
+
- **SHOULD** prefer the smallest change that satisfies the request; do not add speculative abstractions or modules.
|
|
36
|
+
- **SHOULD** cite the standard and section that drove a decision when explaining your work (e.g. "per `event-driven.md` → Idempotency").
|
|
37
|
+
|
|
38
|
+
## Keeping standards agent-friendly (for authors)
|
|
39
|
+
|
|
40
|
+
When editing or adding a standard:
|
|
41
|
+
|
|
42
|
+
- **MUST** give every file YAML front-matter: `title`, `platform`/`track`, `load_when`, `updated`.
|
|
43
|
+
- **MUST** state rules as imperative MUST/SHOULD bullets, not narrative paragraphs.
|
|
44
|
+
- **SHOULD** include at least one DO/DON'T or good/bad code pair per non-trivial rule.
|
|
45
|
+
- **SHOULD** keep each file focused on one topic and reasonably short — agents pay tokens for everything they load.
|
|
46
|
+
- **SHOULD NOT** hardcode volatile facts (exact patch versions, file lists) the agent can read from source; date-stamp instead and review.
|
|
47
|
+
- **MUST NOT** duplicate a rule across files — link to the single source of truth.
|
|
48
|
+
|
|
49
|
+
## Relationship to AGENTS.md / CLAUDE.md
|
|
50
|
+
|
|
51
|
+
The cross-vendor convention in 2026 is a root **`AGENTS.md`** as the single instruction file, with tool-specific files (`CLAUDE.md`, `.github/copilot-instructions.md`, `.cursor/rules/`) acting as thin pointers to it. A project using this library SHOULD add a root `AGENTS.md` whose body points the agent to `coding-standards/INDEX.md` as the standards source of truth.
|
|
52
|
+
|
|
53
|
+
```md
|
|
54
|
+
<!-- AGENTS.md -->
|
|
55
|
+
# Project agent instructions
|
|
56
|
+
Coding standards live in `coding-standards/`. Read `coding-standards/INDEX.md`
|
|
57
|
+
first and load standards on demand per their `load_when` triggers.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## References
|
|
61
|
+
|
|
62
|
+
- AGENTS.md convention — https://agents.md
|
|
63
|
+
- Anthropic — Claude Code best practices — https://www.anthropic.com/engineering/claude-code-best-practices
|
|
64
|
+
- RFC 2119 (requirement keywords) — https://www.rfc-editor.org/rfc/rfc2119
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Clean Architecture & Domain-Driven Design
|
|
3
|
+
platform: all
|
|
4
|
+
load_when: "Always. The architectural foundation every other standard assumes."
|
|
5
|
+
updated: 2026-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Clean Architecture & Domain-Driven Design
|
|
9
|
+
|
|
10
|
+
This is the architectural foundation shared by **every** platform in this library (backend, web, mobile). Platform docs build on these rules; they never contradict them.
|
|
11
|
+
|
|
12
|
+
## The dependency rule
|
|
13
|
+
|
|
14
|
+
Dependencies point **inward**, toward the domain. Inner layers know nothing about outer layers.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Presentation → Application → Domain ← Infrastructure
|
|
18
|
+
(UI/API) (use cases) (rules) (DB, HTTP, SDKs)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **Domain** depends on **nothing**. No framework, no ORM, no HTTP, no UI library. Pure language constructs, testable in isolation.
|
|
22
|
+
- **Application** depends only on Domain. Orchestrates use cases; declares interfaces it needs.
|
|
23
|
+
- **Infrastructure** depends on Application + Domain — it *implements* the interfaces Application declares (dependency inversion).
|
|
24
|
+
- **Presentation** depends on Application. Translates external input (HTTP request, UI event) into use-case calls.
|
|
25
|
+
|
|
26
|
+
> **The litmus test:** you must be able to swap the database, message broker, HTTP framework or UI library by touching **only Infrastructure/Presentation**. If a domain change is forced by a tech change, the dependency rule is broken.
|
|
27
|
+
|
|
28
|
+
## Layer responsibilities
|
|
29
|
+
|
|
30
|
+
| Layer | Owns | MUST NOT |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **Domain** | Entities, value objects, aggregates, domain events, invariants, domain services | Reference any framework or I/O |
|
|
33
|
+
| **Application** | Use cases (commands/queries), DTOs, port interfaces, orchestration | Contain business rules or touch I/O directly |
|
|
34
|
+
| **Infrastructure** | Repository impls, ORM mappings, API clients, external SDKs | Contain business rules |
|
|
35
|
+
| **Presentation** | Endpoints/components, routing, (de)serialization, auth checks | Contain business rules or access data directly |
|
|
36
|
+
|
|
37
|
+
## DDD building blocks
|
|
38
|
+
|
|
39
|
+
- **Aggregate** — a cluster of objects treated as one unit, with a **root** that guards invariants. Loaded and persisted atomically. References other aggregates **by ID**, never by direct object reference.
|
|
40
|
+
- **Entity** — has identity that persists across changes.
|
|
41
|
+
- **Value Object** — immutable, defined by its attributes (`Money`, `Email`, `Sku`). Validates on construction.
|
|
42
|
+
- **Domain Event** — an immutable fact, named in the past tense (`OrderConfirmed`), emitted by an aggregate when something business-relevant happens.
|
|
43
|
+
- **Domain Service** — a domain operation that doesn't belong to a single entity.
|
|
44
|
+
- **Repository** — one per aggregate root; loads/saves the whole aggregate; exposes use-case methods, not raw query builders.
|
|
45
|
+
|
|
46
|
+
## Core rules
|
|
47
|
+
|
|
48
|
+
- **MUST** keep business rules in the Domain. Validators check **format**; the domain enforces **rules**.
|
|
49
|
+
|
|
50
|
+
| Format (validator) | Rule (domain) |
|
|
51
|
+
|---|---|
|
|
52
|
+
| "SKU is required, max 50 chars" | "an order with no lines cannot be confirmed" |
|
|
53
|
+
| "email has valid shape" | "a cancelled order cannot be confirmed again" |
|
|
54
|
+
|
|
55
|
+
- **MUST** expose behavior, not setters. Aggregates have methods (`confirm()`, `cancel()`), not public mutable state.
|
|
56
|
+
- **MUST** organize code by **business module / domain / feature**, not by technical layer at the top level.
|
|
57
|
+
- **MUST** keep aggregates small. If an aggregate has dozens of sub-entities, it is probably several aggregates linked by ID.
|
|
58
|
+
- **SHOULD** protect the domain from external models with an **Anticorruption Layer** when consuming another context's data.
|
|
59
|
+
- **SHOULD NOT** build anemic models (only getters/setters + a CRUD service). If the domain has no behavior, it is a table with an API on top, not a domain.
|
|
60
|
+
|
|
61
|
+
## Why this matters for AI-assisted development
|
|
62
|
+
|
|
63
|
+
A consistent, layered structure lets an AI agent (and a human) predict **where** code goes without re-reading the whole repo: a rule → Domain; an orchestration → Application; a DB detail → Infrastructure; an endpoint/screen → Presentation. The structure is the map.
|
|
64
|
+
|
|
65
|
+
## Platform specifics
|
|
66
|
+
|
|
67
|
+
- Backend layering & microservice anatomy → [`backend/architecture/microservice-anatomy.md`](../backend/architecture/microservice-anatomy.md)
|
|
68
|
+
- Web layering & folders → [`web/_base/frontend-architecture.md`](../web/_base/frontend-architecture.md)
|
|
69
|
+
- Mobile layering → `mobile/<framework>/` standards
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Coding Conventions
|
|
3
|
+
platform: all
|
|
4
|
+
load_when: "Always. Cross-cutting naming, language, and git conventions."
|
|
5
|
+
updated: 2026-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Coding Conventions
|
|
9
|
+
|
|
10
|
+
Cross-cutting conventions that apply to every platform. Platform docs may add specifics but never contradict these.
|
|
11
|
+
|
|
12
|
+
## Language rule
|
|
13
|
+
|
|
14
|
+
- **MUST** write all **code, identifiers, comments, logs, commit messages and technical docs in English**.
|
|
15
|
+
- **MUST** write all **user-visible text in Spanish** (UI labels, validation messages shown to users, emails). Use i18n/l10n resources — never hardcode user-facing strings in components.
|
|
16
|
+
- **Rationale:** English code keeps the codebase universally maintainable; Spanish UI serves the product's users.
|
|
17
|
+
|
|
18
|
+
## Naming
|
|
19
|
+
|
|
20
|
+
| Element | Convention | Example |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| C# types / methods | PascalCase | `OrderService`, `ConfirmOrder` |
|
|
23
|
+
| C# locals / params | camelCase | `orderId` |
|
|
24
|
+
| TS/JS components | PascalCase file & symbol | `OrderCard.tsx` |
|
|
25
|
+
| TS/JS variables / functions | camelCase | `useOrderCart` |
|
|
26
|
+
| TS/JS constants | UPPER_SNAKE_CASE | `MAX_RETRIES` |
|
|
27
|
+
| Dart classes | PascalCase | `OrderRepository` |
|
|
28
|
+
| `data-testid` | kebab-case | `order-card-submit` |
|
|
29
|
+
| Files (non-component) | kebab-case | `api-client.ts` |
|
|
30
|
+
| Folders | kebab-case | `use-cases/` |
|
|
31
|
+
|
|
32
|
+
Database naming (snake_case, FKs, PKs) is defined in [`backend/database-conventions.md`](../backend/database-conventions.md).
|
|
33
|
+
|
|
34
|
+
## Type safety
|
|
35
|
+
|
|
36
|
+
- **MUST** enable TypeScript `strict` mode (web/React Native) — no implicit `any`.
|
|
37
|
+
- **MUST** use nullable reference types and treat warnings as errors where the team config allows (C#).
|
|
38
|
+
- **MUST** use sound null safety (Dart).
|
|
39
|
+
- **SHOULD** model illegal states as unrepresentable (discriminated unions, value objects) rather than runtime checks.
|
|
40
|
+
|
|
41
|
+
## Git & commits
|
|
42
|
+
|
|
43
|
+
- **MUST** use Conventional Commits: `type(scope): summary` (`feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`).
|
|
44
|
+
- **MUST NOT** commit secrets, `.env` files, or generated artifacts. Honor `.gitignore`.
|
|
45
|
+
- **SHOULD** keep commits small and scoped to one logical change.
|
|
46
|
+
- **SHOULD** branch off the default branch for any non-trivial change; never force-push shared branches.
|
|
47
|
+
|
|
48
|
+
## Comments
|
|
49
|
+
|
|
50
|
+
- **MUST** explain *why*, not *what*. The code already says what it does.
|
|
51
|
+
- **MUST NOT** leave commented-out code in commits.
|
|
52
|
+
- **SHOULD** match the comment density of surrounding code.
|
|
53
|
+
|
|
54
|
+
## Dependency licensing (open-source only)
|
|
55
|
+
|
|
56
|
+
- **MUST** use only dependencies under **permissive OSS licenses**: MIT, Apache-2.0, BSD, ISC, PostgreSQL License.
|
|
57
|
+
- **MUST NOT** introduce **source-available / revenue-gated** libraries (e.g. MediatR, AutoMapper, MassTransit, QuestPDF), **network-copyleft** licenses (AGPL, e.g. iText), or **commercial SDKs** (Syncfusion, Aspose, IronPDF).
|
|
58
|
+
- **MUST** verify a dependency's license **before** adding it. For **open-core** projects, confirm the piece you use is the OSS core, not a commercial add-on.
|
|
59
|
+
- **SHOULD** prefer a well-maintained OSS option over a feature-richer non-OSS one. Per-platform approved replacements live in each track's technology-stack doc.
|
|
60
|
+
|
|
61
|
+
## Security baseline
|
|
62
|
+
|
|
63
|
+
- **MUST** validate and sanitize all external input at the boundary.
|
|
64
|
+
- **MUST** keep secrets in configuration/secret managers, never in source.
|
|
65
|
+
- **MUST** use parameterized queries / ORM — never string-concatenated SQL.
|
|
66
|
+
- **SHOULD** apply least privilege everywhere (DB roles, API scopes, tokens).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Testing Strategy
|
|
3
|
+
platform: all
|
|
4
|
+
load_when: "Always. Defines test types, the pyramid, and coverage expectations."
|
|
5
|
+
updated: 2026-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Testing Strategy
|
|
9
|
+
|
|
10
|
+
Test-Driven Development is the default: write tests **before or alongside** implementation. A task is not done until its tests exist and pass 100%.
|
|
11
|
+
|
|
12
|
+
## The pyramid
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
╱ E2E ╲ few — critical user journeys only
|
|
16
|
+
╱ Integration ╲ some — features, repos, API contracts
|
|
17
|
+
╱ Unit tests ╲ many — use cases, domain rules, components
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **MUST** cover every domain invariant and business rule with a unit test, including the violation case.
|
|
21
|
+
- **MUST** cover every use case (command/query handler) with a unit test.
|
|
22
|
+
- **MUST** keep ≥ 80% line coverage on Domain + Application layers.
|
|
23
|
+
- **SHOULD** test features end-to-end at the integration level (real DB via containers where applicable).
|
|
24
|
+
- **SHOULD** reserve E2E for critical paths (login, checkout, the few flows that must never break).
|
|
25
|
+
|
|
26
|
+
## By platform
|
|
27
|
+
|
|
28
|
+
| Platform | Unit | Component / Integration | Mocking |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| Backend (.NET) | xUnit | Integration tests with **Testcontainers** (real PostgreSQL); EF Core InMemory only for fast pure-unit cases | substitute ports/interfaces |
|
|
31
|
+
| Web (React) | Vitest | React Testing Library | **MSW** for API mocking |
|
|
32
|
+
| Flutter | `flutter_test` | widget + integration tests | `mocktail` |
|
|
33
|
+
| React Native | Jest / Vitest | RN Testing Library | MSW |
|
|
34
|
+
|
|
35
|
+
## Rules
|
|
36
|
+
|
|
37
|
+
- **MUST NOT** claim a test exists or passes unless it actually does. Never fabricate test results.
|
|
38
|
+
- **MUST** make tests deterministic — no reliance on wall-clock time, network, or ordering. Inject clocks and randomness.
|
|
39
|
+
- **MUST** run the full suite after each task; never proceed on a red suite.
|
|
40
|
+
- **SHOULD** name tests by behavior: `Confirm_WithNoLines_Throws`.
|
|
41
|
+
- **SHOULD** prefer real implementations over mocks for value objects and pure domain logic; mock only at architectural boundaries (ports).
|
|
42
|
+
- **SHOULD** add a regression test for every bug fixed.
|
|
43
|
+
|
|
44
|
+
## What good coverage looks like
|
|
45
|
+
|
|
46
|
+
Coverage percentage is a floor, not a goal. A meaningful suite proves: every business rule holds, every invariant rejects illegal input, and every critical user journey works against real infrastructure.
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Flutter Development Standards
|
|
3
|
+
platform: mobile
|
|
4
|
+
track: flutter
|
|
5
|
+
load_when: "Building a Flutter app — Riverpod, GoRouter, Clean Architecture, Material 3."
|
|
6
|
+
updated: 2026-06
|
|
7
|
+
---
|
|
8
|
+
|
|
1
9
|
# Mobile Flutter Development Standards
|
|
2
10
|
|
|
3
|
-
> **Note**: For shared architectural principles (Clean Architecture, DDD
|
|
11
|
+
> **Note**: For shared architectural principles (Clean Architecture, DDD), refer to [`../../core/clean-architecture-ddd.md`](../../core/clean-architecture-ddd.md). For design system (colors, typography, UX), refer to [`../../web/_base/design-system-ux.md`](../../web/_base/design-system-ux.md).
|
|
4
12
|
|
|
5
13
|
---
|
|
6
14
|
|