# SmartStack Layers — Execution Rules & Skill/MCP Mapping

> **Loaded by:** step-02 (planning) and step-03 (execution)
> **Purpose:** Defines per-layer folder conventions, skill/MCP tools, and verification rules.

---

## Layer 0 — Domain (sequential)

**Entities:** `Domain/Entities/{ContextPascal}/{App}/{Module}/`
**Enums:** `Domain/Enums/{ContextPascal}/{App}/{Module}/`
**Exceptions:** `Domain/Exceptions/{ContextPascal}/{App}/{Module}/`

| Action | Tool |
|--------|------|
| Create entity | MCP `scaffold_extension` (type: entity) |
| Validate | MCP `validate_conventions` |

**Rules:**
- Inherit `BaseEntity`, choose tenant interface based on data isolation requirements:
  - **`ITenantEntity`** (strict mode, default): User can only access their tenant's data. Implement `Guid TenantId { get; set; }`. Service MUST filter by `_currentTenant.TenantId` with guard clause.
  - **`IOptionalTenantEntity`** (optional mode): Cross-tenant data (shared lookup tables, public content). Implement `Guid? TenantId { get; set; }`. Service MAY accept null TenantId. EF global filter handles both shared (null) and tenant-scoped (TenantId == current) rows automatically.
  - **`IScopedTenantEntity`** (scoped mode): Data scoped by Scope + TenantId combination. Validate both match current context in service layer.
  - **No tenant interface** (none mode): Cross-tenant system data (no isolation). No TenantId property, no filtering needed.
- Also implement `IAuditableEntity` for CreatedBy/UpdatedBy tracking
- See `references/smartstack-api.md` for exact BaseEntity API (Id, CreatedAt, UpdatedAt only)
- No Code/IsDeleted/RowVersion in BaseEntity — add business properties yourself
- Domain events for state changes
- Value objects for composite values

---

## Layer 0 — Infrastructure: EF Core (sequential)

**Configs:** `Infrastructure/Persistence/Configurations/{ContextPascal}/{App}/{Module}/`
**Migrations:** `Infrastructure/Persistence/Migrations/` (NEVER subdirectories)

| Action | Tool |
|--------|------|
| Create config | MCP `scaffold_extension` (type: configuration) |
| Create migration | MCP `suggest_migration` → `dotnet ef migrations add` |
| Validate | MCP `check_migrations` |

**Rules:**
- One config per entity, table name = plural
- All relationships explicitly configured
- Indexes on FKs and frequent queries
- Register DbSet in ExtensionsDbContext
- Migration name ALWAYS from MCP `suggest_migration`
- Migration flag: `-o Persistence/Migrations`

**BLOCKING:** `dotnet build --no-restore` MUST pass after migration.

---

## Layer 1 — Application (parallel with API)

**Services:** `Application/Services/{ContextPascal}/{App}/{Module}/` (interface)
**Service impls:** `Infrastructure/Services/{ContextPascal}/{App}/{Module}/` (implementation)
**DTOs:** `Application/DTOs/{ContextPascal}/{App}/{Module}/`
**Validators:** `Application/Validators/{ContextPascal}/{App}/{Module}/`

| Action | Tool |
|--------|------|
| Create service/DTO | MCP `scaffold_extension` |
| Complex service logic | /application skill |
| Validate | MCP `validate_conventions` |

**Rules:**
- **Tenant injection depends on entity tenant mode** (see entity rules below):
  - **strict mode (default):** Inject `ICurrentTenantService` + guard clause: `var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();` (mandatory, returns 400)
  - **optional mode:** Inject `ICurrentTenantService`, no guard: `var tenantId = _currentTenant.TenantId;` (nullable). EF global filter handles shared+tenant data.
  - **scoped mode:** Inject `ICurrentTenantService`, validate Scope + TenantId consistency. Tenant scope requires TenantId.
  - **none mode:** No `ICurrentTenantService` injection needed (cross-tenant data, no filtering)
- **ALL services MUST inject `ICurrentUserService`** for audit trails
- **ALL services MUST inject `ILogger<T>`** for production diagnostics
- CQRS with MediatR
- FluentValidation for all commands
- DTOs separate from domain entities
- Service interfaces in Application, implementations in Infrastructure
- **FORBIDDEN:** `tenantId: Guid.Empty`, `TenantId!.Value`, queries without TenantId filter, `ICurrentUser` (does not exist — use `ICurrentUserService` + `ICurrentTenantService`)

---

## Layer 1 — API (parallel with Application)

**Controllers:** `Api/Controllers/{ContextShort}/{App}/{Entity}Controller.cs`

| Context | Controller Folder |
|---------|-------------------|
| `platform.administration` | `Admin` |
| `platform.support` | `Support` |
| `business.*` | `Business` |
| `personal.*` | `User` |

| Action | Tool |
|--------|------|
| Simple CRUD controller | MCP `scaffold_extension` |
| Complex controller | /controller skill |
| Validate | MCP `validate_security` |

**Rules:**
- `[RequirePermission(Permissions.{Module}.{Action})]` on EVERY endpoint
- Permission paths MUST use kebab-case matching NavRoute codes (e.g., `business.human-resources.employees.read`)
- FORBIDDEN: concatenated segments like `humanresources` — must be `human-resources`
- NEVER use `[Authorize]` without specific permission
- Swagger XML documentation
- Return DTOs, never domain entities

---

## Layer 1 — Seed Data (parallel)

**Folder:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/`

> **Detailed templates:** See ralph-loop `references/core-seed-data.md` for complete C# code templates.
> Navigation hierarchy: Context → Application → Module → Section → Resource (ALL levels need seed data).

| Action | Tool |
|--------|------|
| Generate permissions | MCP `generate_permissions` (PRIMARY) |
| Navigation seed | Templates below |
| Roles seed | Templates below |
| Provider | Templates below |

### Seed Data Chain (9 files minimum)

**Application-level (created ONCE, shared across modules):**
1. **NavigationApplicationSeedData.cs** — Application-level navigation entry (MUST be first)
2. **ApplicationRolesSeedData.cs** — Application-scoped roles (admin, manager, contributor, viewer) with deterministic GUIDs. Provides role entries for SeedRolesAsync().

**Per-module:**
3. **NavigationModuleSeedData.cs** — Deterministic GUIDs (SHA256), 4 languages (fr, en, it, de)
4. **NavigationSectionSeedData.cs** — Section-level navigation (if sections defined)
5. **NavigationResourceSeedData.cs** — Resource-level navigation (if resources defined)
6. **Permissions.cs** — Static permission constants: `public static class {Module} { public const string Read = "..."; }`. Referenced by `[RequirePermission(Permissions.{Module}.Read)]`.
7. **PermissionsSeedData.cs** — MCP `generate_permissions` first, fallback template
8. **RolesSeedData.cs** — Code-based role mapping: Admin=wildcard(*), Manager=CRU, Contributor=CR, Viewer=R. **NEVER use deterministic GUIDs for roles** — look up by Code at runtime.

**Infrastructure (created ONCE per application):**
9. **{App}SeedDataProvider.cs** — Implements IClientSeedDataProvider
   - `SeedNavigationAsync()` — seeds Application → Module → Section → Resource + translations
   - `SeedRolesAsync()` — seeds application-scoped roles from ApplicationRolesSeedData
   - `SeedPermissionsAsync()` — seeds permission entries from PermissionsSeedData
   - `SeedRolePermissionsAsync()` — maps roles to permissions (roles resolved by Code, NOT by GUID)
   - DI: `services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()`

### Deterministic GUID Pattern

> **CRITICAL — NavigationContext IDs are NEVER generated.**
> Contexts (`business`, `platform`, `personal`) are pre-seeded by SmartStack core with hardcoded GUIDs.
> Look up the context at runtime: `db.NavigationContexts.FirstOrDefaultAsync(c => c.Code == "business", ct)`
> **FORBIDDEN:** `GenerateDeterministicGuid("nav:business")` or any ContextId in SeedConstants.

```csharp
// Use SHA256 for deterministic GUIDs (reproducible across environments)
// NOTE: Application/Module/Section/Resource IDs are deterministic.
//       Context IDs are NOT — they are pre-seeded by SmartStack core.
public static readonly Guid ModuleId = GenerateDeterministicGuid("nav-module-{app}-{module}");

// FORBIDDEN — Context IDs must NOT be generated:
// public static readonly Guid BusinessContextId = GenerateDeterministicGuid("nav:business"); // WRONG!

private static Guid GenerateDeterministicGuid(string input)
{
    var hash = System.Security.Cryptography.SHA256.HashData(
        System.Text.Encoding.UTF8.GetBytes(input));
    var bytes = new byte[16];
    Array.Copy(hash, bytes, 16);
    return new Guid(bytes);
}
```

### Route Convention (CRITICAL — Full Paths Required)

> **Routes stored in DB drive the platform menu AND application-tracking.**
> Short routes (e.g., `humanresources`) cause **400 Bad Request** on every page navigation.

**Route format: `/{context}/{app-kebab}/{module-kebab}/{section-kebab}`**

| Level | Code (C#) | Route (DB) |
|-------|-----------|-----------|
| Application | `human-resources` | `/business/human-resources` |
| Module | `employees` | `/business/human-resources/employees` |
| Section | `departments` | `/business/human-resources/employees/departments` |
| Resource | `export` | `/business/human-resources/employees/departments/export` |

**Platform examples (verified from SmartStack.app):**
- `/platform/administration` (not `administration`)
- `/platform/administration/users` (not `users`)
- `/personal/myspace/profile` (not `profile`)

- PascalCase for C# code identifiers (`HumanResources`)
- **kebab-case** for ALL URL routes in seed data (`human-resources`)
- **Routes MUST start with `/`** and include full parent hierarchy
- Helper: `ToKebabCase()` transforms PascalCase → kebab-case

```csharp
private static string ToKebabCase(string value)
    => System.Text.RegularExpressions.Regex.Replace(value, "([a-z])([A-Z])", "$1-$2").ToLowerInvariant();

// Route construction:
var appRoute = $"/{ToKebabCase(contextCode)}/{ToKebabCase(appCode)}";
// → "/business/human-resources"

var moduleRoute = $"{appRoute}/{ToKebabCase(moduleCode)}";
// → "/business/human-resources/employees"

var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
// → "/business/human-resources/employees/departments"
```

**ROUTE SPECIAL CASES (list and detail sections):**
> `list` and `detail` are view modes of the module, NOT functional sub-areas.
> - `list` section route = module route (e.g., `/business/human-resources/employees`) — NO `/list` suffix
> - `detail` section route = module route + `/:id` (e.g., `/business/human-resources/employees/:id`) — NOT `/detail/:id`
> - FORBIDDEN: `/employees/list`, `/employees/detail/:id`
> - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (normal)

**FORBIDDEN:**
- `Guid.NewGuid()` → use deterministic GUIDs (SHA256)
- `DeterministicGuid("nav:business")` for ContextId → context IDs are pre-seeded by SmartStack core, look up by code at runtime
- Missing translations (must have fr, en, it, de)
- Empty seed classes with no seeding logic
- PascalCase in route URLs → always kebab-case
- Missing NavigationApplicationSeedData → menu invisible
- **Short routes without `/` prefix** → `"humanresources"` must be `"/business/human-resources"`
- **Routes without parent hierarchy** → `"employees"` must be `"/business/human-resources/employees"`

---

## Layer 2 — Frontend (parallel with I18n)

> **Detailed patterns:** See `references/smartstack-frontend.md` for complete code templates.

**Pages:** `src/pages/{ContextPascal}/{AppPascal}/{Module}/`
**Components:** `src/components/{Module}/`

| Action | Tool |
|--------|------|
| API client | MCP `scaffold_api_client` |
| Routes | MCP `scaffold_routes` (outputFormat: `clientRoutes`) |
| Complex pages | /ui-components skill + `smartstack-frontend.md` patterns |
| Validate routes | MCP `validate_frontend_routes` |

**Layout mapping:**

| Context | Layout | Route prefix |
|---------|--------|-------------|
| `platform.*` | `AdminLayout` | `/platform` |
| `business.*` | `BusinessLayout` | `/business` |
| `personal.*` | `UserLayout` | `/personal/myspace` |

### Lazy Loading (MANDATORY)

ALL page components MUST be lazy-loaded using `React.lazy()`:

```tsx
const EmployeesPage = lazy(() =>
  import('@/pages/Business/HumanResources/Employees/EmployeesPage')
    .then(m => ({ default: m.EmployeesPage }))
);

// Route element — ALWAYS wrap with Suspense
element: <Suspense fallback={<PageLoader />}><EmployeesPage /></Suspense>
```

### Page Structure Pattern

All pages MUST follow: hooks → useEffect(load) → loading state → error state → content.

```tsx
// 1. Hooks at top
const { t } = useTranslation(['{module}']);
// 2. State
const [loading, setLoading] = useState(true);
// 3. useCallback + useEffect for data loading
// 4. if (loading) return <Loader2 spinner />
// 5. if (error) return <error UI with retry>
// 6. return <content with CSS variables>
```

### Components & CSS

**Components:** SmartTable, SmartFilter, EntityCard, SmartForm, StatCard (NEVER raw HTML)
**CSS:** Variables ONLY — hardcoded Tailwind colors are **BLOCKING** in POST-CHECK 13:

| Instead of | Use |
|-----------|-----|
| `bg-white` | `bg-[var(--bg-card)]` |
| `bg-gray-50` | `bg-[var(--bg-primary)]` |
| `text-gray-900` | `text-[var(--text-primary)]` |
| `text-gray-500` | `text-[var(--text-secondary)]` |
| `border-gray-200` | `border-[var(--border-color)]` |
| `bg-blue-600` | `bg-[var(--color-accent-500)]` |
| `hover:bg-blue-700` | `hover:bg-[var(--color-accent-600)]` |
| `text-red-500` | `text-[var(--error-text)]` |

**Loader:** `Loader2` from `lucide-react` for spinners, `PageLoader` for Suspense fallback

### Section Routes (when module has sections)

If the module defines `{sections}`, generate frontend routes for EACH section as children of the module route:

```tsx
// Section routes are nested inside the module's children array
{ path: '{section-kebab}', element: <Suspense fallback={<PageLoader />}><{Section}Page /></Suspense> },
{ path: '{section-kebab}/create', element: <Suspense fallback={<PageLoader />}><Create{Section}Page /></Suspense> },
{ path: '{section-kebab}/:id', element: <Suspense fallback={<PageLoader />}><{Section}DetailPage /></Suspense> },
{ path: '{section-kebab}/:id/edit', element: <Suspense fallback={<PageLoader />}><Edit{Section}Page /></Suspense> },
```

Section pages live in `src/pages/{ContextPascal}/{AppPascal}/{Module}/{Section}/`.

### FK Fields in Forms (CRITICAL)

Any entity property that is a FK Guid (e.g., `EmployeeId`, `DepartmentId`) MUST use the `EntityLookup` component — NEVER a `<select>` dropdown, NEVER a `<input type="text">`. Users cannot type GUIDs manually, and `<select>` fails at scale (no search, loads all options).

```tsx
// CORRECT — EntityLookup for FK field (ONLY acceptable pattern)
<EntityLookup
  apiEndpoint="/api/business/human-resources/employees"
  value={formData.employeeId}
  onChange={(id) => handleChange('employeeId', id)}
  label={t('module:form.employee', 'Employee')}
  mapOption={(item) => ({ id: item.id, label: item.name, sublabel: item.code })}
  required
/>

// WRONG — plain text input for FK GUID
<input type="text" value={formData.employeeId} placeholder="Enter employee ID" />

// WRONG — <select> dropdown for FK field (even with API-loaded options)
<select value={formData.departmentId} onChange={(e) => setFormData({...formData, departmentId: e.target.value})}>
  <option value="">Select...</option>
  {departments.map(d => <option key={d.id} value={d.id}>{d.name}</option>)}
</select>
```

**Backend requirement:** Each target entity's GetAll endpoint MUST accept `?search=` parameter. See `smartstack-api.md` Service Pattern.

See `references/smartstack-frontend.md` section 6 for the full `EntityLookup` component definition and usage patterns.

**FORBIDDEN:**
- `src/pages/{Module}/` (flat, missing Context/App)
- `import axios` → use `@/services/api/apiClient`
- `<table>` → use SmartTable
- `<input type="text">` for FK Guid fields → use `EntityLookup`
- `<select>` for FK Guid fields → use `EntityLookup` (even with API-loaded options)
- Placeholder "Enter ID" or "Enter GUID" → use `EntityLookup`
- Hardcoded Tailwind colors (`bg-white`, `text-gray-900`) → use CSS variables
- Static page imports in route files → use `React.lazy()`
- `<Suspense>` without `fallback` prop
- Only fr/en → MUST have 4 languages
- `t('key')` without namespace prefix → use `t('ns:key', 'fallback')`
- Hardcoded user-facing strings in JSX → use `t()`

---

## Layer 2 — I18n

> **Template:** See `references/smartstack-frontend.md` Section 2 for complete JSON template.

4 JSON files per module namespace: fr, en, it, de — identical key structures.

**Location:** `src/i18n/locales/{lang}/{module}.json`

**Required keys:** `title`, `description`, `actions`, `labels`, `columns`, `form`, `errors`, `validation`, `messages`, `empty`

**Usage pattern:**
```tsx
const { t } = useTranslation(['{module}']);
t('{module}:title', 'Module Title')           // ALWAYS with fallback
t('{module}:actions.create', 'Create entity') // ALWAYS with namespace prefix
```

---

## Layer 2b — Documentation (after frontend pages exist)

> **After frontend pages are created, generate module documentation.**

| Action | Tool |
|--------|------|
| Generate doc-data.ts + page wrapper | `/documentation` skill (type: user) |
| Verify DocToggleButton in pages | POST-CHECK 29 |

**Output files:**
- `src/pages/docs/business/{app}/{module}/doc-data.ts` — data-driven documentation
- `src/pages/docs/business/{app}/{module}/index.tsx` — page wrapper using `DocRenderer`
- `src/i18n/locales/fr/docs-{app}-{module}.json` — French doc translations

**DocToggleButton (MANDATORY):** Every list/detail page must include `DocToggleButton` in its header.
See `references/smartstack-frontend.md` section 7 for the component pattern.

---

## Layer 3 — Tests (sequential)

| Action | Tool |
|--------|------|
| Unit tests (domain) | MCP `scaffold_tests` (target_layer: domain) |
| Unit tests (app) | MCP `scaffold_tests` (target_layer: application) |
| Integration tests (api) | MCP `scaffold_tests` (target_layer: api, type: integration) |
| Security tests | MCP `scaffold_tests` (type: security) |
| Coverage check | MCP `analyze_test_coverage` |
| Scenarios | MCP `suggest_test_scenarios` |

**Target:** >= 80% coverage, 100% pass rate.
**Fix CODE, not tests.**
