<overview>
SmartStack-specific conventions and patterns. This reference is used when reviewing SmartStack projects to ensure compliance with the architecture and security standards.

**IMPORTANT**: All convention validation MUST be delegated to the MCP SmartStack tools. This file documents what to look for; the MCP validates automatically.
</overview>

<mcp_tools>
## MCP SmartStack Tools for Code Review

### Primary Tool - Unified Code Review

| Tool | Purpose | When to use |
|------|---------|-------------|
| `mcp__smartstack__review_code` | **Unified code review** covering 9 categories | Always use first |

**Parameters:**
```
scope: "all" | "changed" | "staged"  # Which files to review
checks: ["all"] | ["security", ...]   # Categories to check
severity: "all" | "blocking"          # Filter by severity
```

**Categories covered:**
| Category | ID Prefix | Severity | What it detects |
|----------|-----------|----------|-----------------|
| Security | SEC-xxx | blocking | Secrets, SQL injection, XSS, missing [Authorize] |
| Architecture | ARCH-xxx | critical | Layer violations, DI bypass |
| Hardcoded Values | HARD-xxx | warning | Magic numbers, URLs, feature flags |
| Tests | TEST-xxx | critical | Missing tests, useless assertions |
| AI Hallucinations | AI-xxx | blocking | Phantom imports, non-existent methods |
| Performance | PERF-xxx | warning | N+1 queries, over-fetching |
| Dead Code | DEAD-xxx | warning | Unused imports, commented code, TODOs |
| i18n | I18N-xxx | info | Hardcoded UI text, missing translations |
| Accessibility | A11Y-xxx | info | Missing alt, ARIA, focus issues |

### Additional Tools (Optional)

| Tool | Purpose | When to use |
|------|---------|-------------|
| `mcp__smartstack__validate_conventions` | Detailed conventions validation | Deep convention checks |
| `mcp__smartstack__validate_test_conventions` | Test patterns validation | Test structure review |
| `mcp__smartstack__check_migrations` | EF Core migration conflicts | Migration changes |
</mcp_tools>

<architecture>
## SmartStack Architecture (Clean Architecture)

```
SmartStack/
├── SmartStack.Domain/           # Entities, Value Objects, Domain Events
├── SmartStack.Application/      # Services, DTOs, Commands/Queries
├── SmartStack.Infrastructure/   # EF Core, External services
├── SmartStack.Api/              # Controllers, Middleware
└── SmartStack.Web/              # React frontend
```

**Dependency rule**: Dependencies point inward only (Api → Application → Domain).
</architecture>

<entities>
## Entity Conventions

<tenant_aware>
**Tenant-aware entities** (most business entities):
```csharp
public class Order : BaseEntity, ITenantEntity
{
    public Guid TenantId { get; private set; }

    private Order() { } // EF Core constructor

    public static Order Create(Guid tenantId, ...)
    {
        return new Order { TenantId = tenantId, ... };
    }
}
```

**Requirements:**
- [ ] Implements `ITenantEntity`
- [ ] Has `TenantId` property
- [ ] Private parameterless constructor
- [ ] Static `Create()` factory method with `tenantId` as first parameter
</tenant_aware>

<system_entity>
**System entities** (platform-level, no tenant):
```csharp
public class Permission : SystemEntity
{
    private Permission() { }

    public static Permission Create(...)
    {
        return new Permission { ... };
    }
}
```

**Requirements:**
- [ ] Inherits from `SystemEntity`
- [ ] NO `TenantId` property
- [ ] Private parameterless constructor
- [ ] Static `Create()` factory method
</system_entity>
</entities>

<value_objects>
## Value Objects (DDD Pattern)

Value Objects are immutable types defined by their attributes rather than identity. Use them to replace primitive types that carry domain meaning and validation.

**When to use a Value Object:**
| Instead of | Use | Why |
|------------|-----|-----|
| `string Email` | `Email` record | Self-validating, prevents invalid state |
| `decimal Amount` + `string Currency` | `Money` record | Always consistent pair |
| `string Street` + `string City` + ... | `Address` record | Cohesive group |
| `DateTime Start` + `DateTime End` | `DateRange` record | Enforces Start < End invariant |

**Structure (C# record):**
```csharp
// Domain/ValueObjects/Email.cs
public sealed record Email
{
    public string Value { get; }

    public Email(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new DomainException("Email is required");
        if (!value.Contains('@'))
            throw new DomainException("Invalid email format");

        Value = value.Trim().ToLowerInvariant();
    }

    public static implicit operator string(Email email) => email.Value;
    public override string ToString() => Value;
}
```

**EF Core mapping:**
```csharp
// Option 1: Owned type (separate columns)
builder.OwnsOne(e => e.Email, email =>
{
    email.Property(v => v.Value).HasColumnName("Email").HasMaxLength(256);
});

// Option 2: Value conversion (single column)
builder.Property(e => e.Email)
    .HasConversion(
        v => v.Value,
        v => new Email(v))
    .HasMaxLength(256);
```

**Entity vs Value Object:**
| Criteria | Entity | Value Object |
|----------|--------|-------------|
| Identity | Has `Id` (Guid) | No identity |
| Equality | By Id | By all properties |
| Mutability | Mutable (behavior methods) | Immutable (records) |
| Lifecycle | Independent | Owned by entity |
| Example | `User`, `Order` | `Email`, `Money`, `Address` |

**Code review detection:** If an entity has `string Email`, `string Phone`, `decimal Amount` + `string Currency` as separate primitives, suggest replacing with Value Objects.
</value_objects>

<domain_events>
## Domain Events

Domain Events capture something that happened in the domain. They enable decoupled communication between aggregates and complement the existing Workflow trigger system.

**Relationship with Workflow Triggers:**
| Mechanism | Scope | Use case |
|-----------|-------|----------|
| Domain Events (MediatR) | In-process, synchronous | Cache invalidation, audit logging, read-model updates |
| Workflow Triggers (`TriggerAsync`) | Cross-process, async | Emails, webhooks, multi-step business processes |
| **Combined** | Domain Event handler calls `TriggerAsync()` | Best of both worlds |

**Structure:**
```csharp
// Domain/Events/OrderCreatedEvent.cs
public sealed record OrderCreatedEvent(
    Guid OrderId,
    Guid TenantId,
    Guid CreatedById,
    DateTime OccurredAt) : IDomainEvent;

// Domain/Common/IDomainEvent.cs
public interface IDomainEvent : INotification { }  // MediatR marker
```

**Publishing from entities:**
```csharp
public class Order : BaseEntity, ITenantEntity
{
    private readonly List<IDomainEvent> _domainEvents = new();
    public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    public static Order Create(Guid tenantId, string description, Guid createdById)
    {
        var order = new Order
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            Description = description
        };

        order._domainEvents.Add(new OrderCreatedEvent(
            order.Id, tenantId, createdById, DateTime.UtcNow));

        return order;
    }

    public void ClearDomainEvents() => _domainEvents.Clear();
}
```

**Handling events:**
```csharp
// Application/EventHandlers/OrderCreatedEventHandler.cs
public class OrderCreatedEventHandler : INotificationHandler<OrderCreatedEvent>
{
    private readonly IWorkflowService _workflowService;
    private readonly ILogger<OrderCreatedEventHandler> _logger;

    public async Task Handle(OrderCreatedEvent notification, CancellationToken ct)
    {
        _logger.LogInformation("Order {OrderId} created", notification.OrderId);

        // Bridge to Workflow triggers for cross-process actions
        await _workflowService.TriggerAsync("order.created",
            new Dictionary<string, object>
            {
                ["orderId"] = notification.OrderId,
                ["tenantId"] = notification.TenantId
            }, ct: ct);
    }
}
```

**Dispatching (in DbContext):**
```csharp
// Infrastructure/Persistence/ApplicationDbContext.cs
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
    var entities = ChangeTracker.Entries<BaseEntity>()
        .Where(e => e.Entity.DomainEvents.Any())
        .ToList();

    var events = entities.SelectMany(e => e.Entity.DomainEvents).ToList();

    var result = await base.SaveChangesAsync(ct);

    foreach (var @event in events)
        await _mediator.Publish(@event, ct);

    foreach (var entity in entities)
        entity.Entity.ClearDomainEvents();

    return result;
}
```

**When to add Domain Events (code review check):**
- Entity has side effects in `Create()` or behavior methods → Domain Event candidate
- Service calls `TriggerAsync()` right after `SaveChangesAsync()` → Should be Domain Event
- Multiple services react to the same entity change → Domain Event decouples them
</domain_events>

<tables>
## Table Naming Conventions

**Schema**: All tables MUST specify schema explicitly.
```csharp
builder.ToTable("auth_users", SchemaConstants.Core);
```

**Prefixes by domain:**
| Prefix | Domain | Examples |
|--------|--------|----------|
| `auth_` | Authentication | auth_users, auth_roles |
| `nav_` | Navigation | nav_menus, nav_routes |
| `cfg_` | Configuration | cfg_settings, cfg_features |
| `ai_` | AI/Prompts | ai_prompts, ai_models |
| `ntf_` | Notifications | ntf_templates, ntf_logs |
| `wkf_` | Workflows | wkf_definitions, wkf_instances |
| `doc_` | Documents | doc_files, doc_versions |
| `tkt_` | Tickets/Support | tkt_tickets, tkt_comments |

**Custom Application Prefixes:**

Business applications define their own table prefix during `/business-analyse` cadrage phase.
Format: `{2-5 lowercase letters}_` — stored in `feature.json` → `metadata.tablePrefix`.

| Application | Prefix | Examples |
|-------------|--------|----------|
| Human Resources | `rh_` | rh_Employees, rh_Contracts |
| Finance | `fi_` | fi_Invoices, fi_Payments |
| CRM | `crm_` | crm_Contacts, crm_Opportunities |

Rules:
- Must not collide with platform prefixes
- Registered in MCP config via `conventions.customTablePrefixes`
- All entities in the same application share the same prefix

**Schemas:**
- `core` (SchemaConstants.Core): SmartStack platform tables
- `extensions` (SchemaConstants.Extensions): Client-specific tables
</tables>

<migrations>
## Migration Naming

**Format**: `{context}_v{version}_{sequence}_{Description}.cs`

**Examples:**
- `core_v1.0.0_001_CreateAuthUsers.cs`
- `core_v1.2.0_001_AddUserProfiles.cs`
- `extensions_v1.0.0_001_CreateClientOrders.cs`

**Rules:**
- [ ] Context: `core` or `extensions`
- [ ] Version: semver format (1.0.0)
- [ ] Sequence: 3-digit padded (001, 002)
- [ ] Description: PascalCase, descriptive
</migrations>

<controllers>
## Controller Conventions

**NavRoute attribute** (preferred):
```csharp
[ApiController]
[NavRoute("administration.users")]
public class UsersController : ControllerBase  // MUST be ControllerBase, NOT Controller
{
    // Routes generated from NavRoute: /api/administration/users
}
```

**CRITICAL: Controller Inheritance (S6934)**

```csharp
// ❌ BAD - Inheriting from Controller
public class UsersController : Controller
{
    // Brings in MVC View support (Razor, ViewData, ViewBag)
    // Unnecessary for API controllers
}

// ✅ GOOD - Inheriting from ControllerBase
public class UsersController : ControllerBase
{
    // Lightweight, API-only base class
    // No MVC View support (cleaner, more appropriate)
}
```

**Why ControllerBase?**
- API controllers don't need MVC View support
- `Controller` adds unnecessary dependencies (Razor, ViewData, etc.)
- `ControllerBase` is lighter and more focused on APIs
- Better performance and smaller footprint

**NavRoute format:**
- Lowercase only
- Dot-separated hierarchy
- Minimum 3 levels: `application.module.section`
- Full path: `application.module.section[.resource]`

**Examples:**
- `administration.users.management`
- `administration.roles.list`
- `crm.contacts.details`
- `crm.contacts.details.notes` (with resource)
</controllers>

<services>
## Service Conventions

**Interface pattern:**
```csharp
// Interface
public interface IUserService
{
    Task<UserDto> GetByIdAsync(Guid id, CancellationToken ct);
    Task<UserDto> CreateAsync(CreateUserDto dto, CancellationToken ct);
}

// Implementation
public class UserService : IUserService
{
    // ...
}
```

**Requirements:**
- [ ] Interface named `I{Name}Service`
- [ ] Implementation named `{Name}Service`
- [ ] All methods async with CancellationToken
- [ ] Return DTOs, not entities
</services>

<security>
## SmartStack Security Patterns

<multi_tenant>
**Multi-tenant isolation:**
- ALL queries MUST filter by TenantId (automatic via EF Core global filters)
- NEVER expose TenantId in URLs
- NEVER allow cross-tenant data access
- Create methods MUST require tenantId parameter

**Check for violations:**
```csharp
// BAD: No tenant filter
var users = await _context.Users.ToListAsync();

// GOOD: Tenant filter applied (via global filter or explicit)
var users = await _context.Users
    .Where(u => u.TenantId == _tenantId)
    .ToListAsync();
```
</multi_tenant>

<authorization>
**RBAC with NavRoute:**
- Controllers use `[NavRoute]` for automatic permission mapping
- Permissions follow NavRoute pattern: `{navroute}.{action}`
- Actions: `read`, `create`, `update`, `delete`, `*` (wildcard)

**Example permissions:**
- `administration.users.read`
- `administration.users.create`
- `administration.users.*` (all actions)
</authorization>

<input_validation>
**FluentValidation:**
```csharp
public class CreateUserDtoValidator : AbstractValidator<CreateUserDto>
{
    public CreateUserDtoValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress()
            .MaximumLength(256);
    }
}
```
</input_validation>
</security>

<tests>
## Test Conventions

**Naming pattern:** `{Method}_When{Condition}_Should{Result}`
```csharp
public class UserServiceTests
{
    [Fact]
    public async Task GetById_WhenUserExists_ShouldReturnUser() { }

    [Fact]
    public async Task GetById_WhenUserNotFound_ShouldThrowNotFoundException() { }

    [Fact]
    public async Task Create_WhenValidDto_ShouldCreateAndReturnUser() { }
}
```

**Structure:**
```
Tests/
├── Unit/
│   ├── Services/
│   └── Validators/
└── Integration/
    └── Controllers/
```

**Patterns:**
- [ ] AAA pattern (Arrange, Act, Assert)
- [ ] FluentAssertions for assertions
- [ ] Moq for mocking
- [ ] Tenant isolation tests for all tenant-aware services
</tests>

<review_checklist>
## SmartStack Code Review Checklist

**Run unified code review first:**
```
mcp__smartstack__review_code
  scope: "changed"    # or "all" or "staged"
  checks: ["all"]     # 9 categories
  severity: "all"
```

**The tool automatically checks:**

<blocking_checks>
### Blocking Issues (Must fix before merge)
**Security (SEC-xxx):**
- Hardcoded credentials or secrets
- SQL injection patterns
- XSS vulnerabilities
- Missing [Authorize] attributes

**AI Hallucinations (AI-xxx):**
- Non-existent imports/namespaces
- Phantom method calls
- Undefined types
</blocking_checks>

<critical_checks>
### Critical Issues (Should fix ASAP)
**Architecture (ARCH-xxx):**
- Layer violations (Web → Infrastructure)
- Direct DbContext usage in controllers
- Service instantiation instead of DI

**Tests (TEST-xxx):**
- Missing tests for new entities/services
- Tests without real assertions
</critical_checks>

<warning_checks>
### Warnings (Recommended fixes)
**Hardcoded Values (HARD-xxx):**
- Magic numbers
- Hardcoded URLs
- Feature flags in code

**Performance (PERF-xxx):**
- N+1 queries
- ToList() before Where()
- Multiple API calls per page

**Dead Code (DEAD-xxx):**
- Unused imports
- Commented code
- Old TODOs
</warning_checks>

<info_checks>
### Info (Nice to have)
**i18n (I18N-xxx):**
- Hardcoded UI text
- Missing translations

**Accessibility (A11Y-xxx):**
- Missing alt attributes
- Missing ARIA labels
- Non-interactive click handlers
</info_checks>
</review_checklist>

<sources>
- SmartStack Architecture Documentation
- SmartStack.mcp validation tools
- Clean Architecture by Robert C. Martin
</sources>
