# Core Seed Data - Execution Reference

> **Loaded by:** apex step-03-execute (ALWAYS for Layer 1) and step-04-examine
> **Condition:** Seed data generation — infrastructure or seedData category tasks
> **Applies to:** Client projects only (seeding_strategy = "provider", ExtensionsDbContext)
>
> **Source of truth:** `/application` skill `templates-seed.md` (lines 608-916)
> **Moved from:** `business-analyse-develop/references/core-seed-data.md` (delegation refactoring)

---

## 1. Parameter Extraction

> **IMPORTANT — `navRoute` in seed data = permission root (2 segments: `app.module`).**
> This matches the controller `[NavRoute]` attribute for module-level controllers (also 2 segments: `app.module`).
> The seed data `navRoute` serves as the base for module-level permissions (`navRoute.read`, `navRoute.create`).
> Section-level permissions extend it: `navRoute.{sectionCode}.read`.

Extract navigation hierarchy from the task's `_seedDataMeta` (populated by guardrail 1e):

```javascript
const task = currentTask;
const meta = task._seedDataMeta || task._providerMeta || {};
const coreSeedData = meta.coreSeedData || {};

// Navigation hierarchy
const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
const navSections = coreSeedData.navigationSections || [];
const navResources = coreSeedData.navigationResources || [];
const permissions = coreSeedData.permissions || [];
const rolePerms = coreSeedData.rolePermissions || [];

// Derived context (from guardrail or PRD)
// NOTE: navRoute here = permission root (2 segments), same as module-level controller NavRoute
const navRoute = meta.navRoute;         // e.g. "human-resources.projects" (permission root)
const appCode = meta.appCode;           // e.g. "human-resources"
const moduleCode = task.module;         // e.g. "projects"

// If _seedDataMeta is absent, fallback to PRD source
if (!navRoute) {
  const prd = readJSON('.ralph/prd.json');
  const navRoute = `${prd.source?.application || prd.metadata?.module}.${task.module}`;
}
```

**State variables after extraction:**

| Variable | Example | Source |
|----------|---------|--------|
| `navRoute` | `human-resources.projects` (permission root, 2 segments) | `_seedDataMeta.navRoute` |
| `appCode` | `human-resources` | `_seedDataMeta.appCode` |
| `moduleCode` | `projects` | `task.module` |
| `navModules[]` | `[{code, label, icon, route, translations}]` | `coreSeedData.navigationModules` |
| `navSections[]` | `[{code, label, icon, route, parentCode, permission, sort}]` | `coreSeedData.navigationSections` |
| `navResources[]` | `[{code, type, entity, parentCode, permission}]` | `coreSeedData.navigationResources` |
| `permissions[]` | `[{path, action, description}]` | `coreSeedData.permissions` |
| `rolePerms[]` | `[{role, permissions[]}]` | `coreSeedData.rolePermissions` |

---

## 1b. NavigationApplicationSeedData.cs (ONCE per application)

**File:** `Infrastructure/Persistence/Seeding/Data/NavigationApplicationSeedData.cs`

> Create this file BEFORE any module seed data.
> Without it, modules have no parent ApplicationId and ApplicationRolesSeedData has no GUID reference.
> This file is created once per application (not per module).

### Data Source

From `seedDataCore.navigationApplications[0]` in feature.json (generated by BA step-05a):

| Placeholder | Source |
|-------------|--------|
| `{appCode}` | `navigationApplications[0].code` |
| `{appLabel_xx}` | `navigationApplications[0].labels.xx` (fr, en, it, de) |
| `{appDesc_xx}` | `navigationApplications[0].description.xx` |
| `{appIcon}` | `navigationApplications[0].icon` |

### GUID Generation Rule

```csharp
// Random GUID — idempotence handled by Code-based lookups
public static readonly Guid ApplicationId = Guid.NewGuid();
```

### Template

```csharp
using SmartStack.Domain.Navigation;

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;

/// <summary>
/// Navigation seed data for {AppLabel_en} application.
/// Consumed by IClientSeedDataProvider at application startup.
/// Created ONCE per application — modules reference ApplicationId as parent.
/// </summary>
public static class NavigationApplicationSeedData
{
    // Random GUID — resolved by Code at runtime for idempotence
    public static readonly Guid ApplicationId = Guid.NewGuid();

    /// <summary>
    /// Returns navigation application entry for seeding into core.nav_Applications.
    /// </summary>
    public static NavigationApplicationSeedEntry GetApplicationEntry()
    {
        return new NavigationApplicationSeedEntry
        {
            Id = ApplicationId,
            Zone = ApplicationZone.Business,
            Code = "{appCode}",
            Label = "{appLabel_en}",
            Description = "{appDesc_en}",
            Icon = "{appIcon}",              // Lucide React icon name
            IconType = IconType.Lucide,
            Route = ToKebabCase("/{appCode}"),
            DisplayOrder = 1,
            IsActive = true
        };
    }

    /// <summary>
    /// Returns 4-language translations for this application.
    /// </summary>
    public static IEnumerable<NavigationTranslationSeedEntry> GetTranslationEntries()
    {
        var appId = ApplicationId;
        return new[]
        {
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Application,
                EntityId = appId,
                LanguageCode = "fr",
                Label = "{appLabel_fr}",
                Description = "{appDesc_fr}"
            },
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Application,
                EntityId = appId,
                LanguageCode = "en",
                Label = "{appLabel_en}",
                Description = "{appDesc_en}"
            },
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Application,
                EntityId = appId,
                LanguageCode = "it",
                Label = "{appLabel_it}",
                Description = "{appDesc_it}"
            },
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Application,
                EntityId = appId,
                LanguageCode = "de",
                Label = "{appLabel_de}",
                Description = "{appDesc_de}"
            }
        };
    }

    // No deterministic GUID generation — all IDs are random via Guid.NewGuid()
    // Idempotence is handled by Code-based lookups at runtime

    /// <summary>
    /// Converts PascalCase route segments to kebab-case for web URLs.
    /// </summary>
    private static string ToKebabCase(string route)
    {
        if (string.IsNullOrEmpty(route)) return route;

        var segments = route.Split('/');
        var kebabSegments = new List<string>();

        foreach (var segment in segments)
        {
            if (string.IsNullOrEmpty(segment))
            {
                kebabSegments.Add(segment);
                continue;
            }

            var kebab = System.Text.RegularExpressions.Regex
                .Replace(segment, "([a-z])([A-Z])", "$1-$2")
                .ToLowerInvariant();
            kebabSegments.Add(kebab);
        }

        return string.Join("/", kebabSegments);
    }
}

/// <summary>Seed entry DTO for navigation application.</summary>
public class NavigationApplicationSeedEntry
{
    public Guid Id { get; init; }
    public ApplicationZone Zone { get; init; }
    public string Code { get; init; } = null!;
    public string Label { get; init; } = null!;
    public string? Description { get; init; }
    public string? Icon { get; init; }
    public IconType IconType { get; init; }
    public string? Route { get; init; }
    public int DisplayOrder { get; init; }
    public bool IsActive { get; init; }
}
```

**Replace placeholders** with values from `seedDataCore.navigationApplications[0]`.

---

## 2. NavigationModuleSeedData.cs

**File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/NavigationModuleSeedData.cs`

### GUID Generation Rule

```csharp
// Use Guid.NewGuid() — avoids conflicts between projects/tenants/environments
public static readonly Guid ModuleId = Guid.NewGuid();
```

### FK Resolution Safety Rule

When seed data references a parent entity (e.g., Module references Application, Section references Module), use `FirstOrDefaultAsync` with a null-guard that gives a clear diagnostic:

```csharp
// CORRECT: null-guard with clear error message
var parentModule = await context.NavigationModules
    .FirstOrDefaultAsync(m => m.Code == "target-module" && m.ApplicationId == app.Id, ct)
    ?? throw new InvalidOperationException(
        $"Seed data dependency missing: NavigationModule 'target-module' not found. " +
        $"Run the target module's seed data provider first.");

// WRONG: FirstAsync with generic error
var parentModule = await context.NavigationModules
    .FirstAsync(m => m.Code == "target-module", ct);
// → Throws "Sequence contains no elements" — hard to debug
```

This applies to ALL cross-entity FK lookups in seed data: modules, sections, resources, roles, permissions.

### Template

```csharp
using SmartStack.Domain.Navigation;

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};

/// <summary>
/// Navigation seed data for {ModuleLabel} module.
/// Consumed by IClientSeedDataProvider at application startup.
/// </summary>
public static class {ModulePascal}NavigationSeedData
{
    // Random GUID — resolved by Code at runtime for idempotence
    public static readonly Guid {ModulePascal}ModuleId = Guid.NewGuid();

    /// <summary>
    /// Returns navigation module entry for seeding into core.nav_Modules.
    /// </summary>
    public static NavigationModuleSeedEntry GetModuleEntry(Guid applicationId)
    {
        return new NavigationModuleSeedEntry
        {
            Id = {ModulePascal}ModuleId,
            ApplicationId = applicationId,
            Code = "{moduleCode}",
            Label = "{label_en}",
            Description = "{desc_en}",
            Icon = "{icon}",              // Lucide React icon name
            IconType = IconType.Lucide,
            Route = ToKebabCase($"/{appCode}/{moduleCode}"),  // Absolute path for sidebar navigation (App.tsx routes are RELATIVE to module root)
            DisplayOrder = {displayOrder},
            IsActive = true
        };
    }

    /// <summary>
    /// Returns 4-language translations for this module.
    /// </summary>
    public static IEnumerable<NavigationTranslationSeedEntry> GetTranslationEntries()
    {
        var moduleId = {ModulePascal}ModuleId;
        return new[]
        {
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Module,
                EntityId = moduleId,
                LanguageCode = "fr",
                Label = "{label_fr}",
                Description = "{desc_fr}"
            },
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Module,
                EntityId = moduleId,
                LanguageCode = "en",
                Label = "{label_en}",
                Description = "{desc_en}"
            },
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Module,
                EntityId = moduleId,
                LanguageCode = "it",
                Label = "{label_it}",
                Description = "{desc_it}"
            },
            new NavigationTranslationSeedEntry
            {
                EntityType = NavigationEntityType.Module,
                EntityId = moduleId,
                LanguageCode = "de",
                Label = "{label_de}",
                Description = "{desc_de}"
            }
        };
    }

    // No deterministic GUID generation — all IDs are random via Guid.NewGuid()

    /// <summary>
    /// Converts PascalCase route segments to kebab-case for web URLs.
    /// Example: /HumanResources/TimeManagement → /human-resources/time-management
    /// </summary>
    private static string ToKebabCase(string route)
    {
        if (string.IsNullOrEmpty(route)) return route;

        var segments = route.Split('/');
        var kebabSegments = new List<string>();

        foreach (var segment in segments)
        {
            if (string.IsNullOrEmpty(segment))
            {
                kebabSegments.Add(segment);
                continue;
            }

            // Convert PascalCase to kebab-case: HumanResources → human-resources
            var kebab = System.Text.RegularExpressions.Regex
                .Replace(segment, "([a-z])([A-Z])", "$1-$2")
                .ToLowerInvariant();
            kebabSegments.Add(kebab);
        }

        return string.Join("/", kebabSegments);
    }
}

/// <summary>Seed entry DTO for navigation module.</summary>
public class NavigationModuleSeedEntry
{
    public Guid Id { get; init; }
    public Guid ApplicationId { get; init; }
    public string Code { get; init; } = null!;
    public string Label { get; init; } = null!;
    public string Description { get; init; } = null!;
    public string Icon { get; init; } = null!;
    public IconType IconType { get; init; }
    public string Route { get; init; } = null!;
    public int DisplayOrder { get; init; }
    public bool IsActive { get; init; }
}

/// <summary>Seed entry DTO for navigation translation.</summary>
public class NavigationTranslationSeedEntry
{
    public NavigationEntityType EntityType { get; init; }
    public Guid EntityId { get; init; }
    public string LanguageCode { get; init; } = null!;
    public string Label { get; init; } = null!;
    public string Description { get; init; } = null!;
}
```

**Replace placeholders** with values from `navModules[]` and PRD `project` metadata.

---

## 2b. Navigation Sections & Resources (in same NavigationSeedData.cs)

> **CONDITIONAL:** Only generate if `seedDataCore.navigationSections[]` exists and is non-empty in feature.json.
> Sections and resources are added as additional methods in the **same** `{ModulePascal}NavigationSeedData.cs` file.

### GUID Generation Rules

```csharp
// Section GUID: random
public static readonly Guid {SectionPascal}SectionId = Guid.NewGuid();

// Resource GUID: random
public static readonly Guid {ResourcePascal}ResourceId = Guid.NewGuid();
```

### Section Methods (add to {ModulePascal}NavigationSeedData.cs)

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

```csharp
// --- Add AFTER GetTranslationEntries() in {ModulePascal}NavigationSeedData.cs ---

// Random GUIDs for sections
public static readonly Guid {Section1Pascal}SectionId = Guid.NewGuid();
// Repeat for each section...

/// <summary>
/// Returns navigation section entries for seeding into core.nav_Sections.
/// </summary>
public static IEnumerable<NavigationSectionSeedEntry> GetSectionEntries(Guid moduleId)
{
    return new[]
    {
        new NavigationSectionSeedEntry
        {
            Id = {Section1Pascal}SectionId,
            ModuleId = moduleId,
            Code = "{section1Code}",
            Label = "{section1_label_en}",
            Description = "{section1_desc_en}",
            Icon = "{section1_icon}",
            IconType = IconType.Lucide,
            // ROUTE CONVENTION:
            // - "list" section → same as module route (no extra segment)
            // - "detail" section → module route + "/:id"
            // - Other sections → module route + "/{section-kebab}"
            // Do not use: "/employees/list", "/employees/detail/:id"
            Route = "{section_route}",  // From seedDataCore.navigationSections[].route
            DisplayOrder = {section1_sort},
            IsActive = true
        }
        // Repeat for each section...
    };
}

/// <summary>
/// Returns 4-language translations for sections.
/// </summary>
public static IEnumerable<NavigationTranslationSeedEntry> GetSectionTranslationEntries()
{
    var entries = new List<NavigationTranslationSeedEntry>();

    // Section: {section1Code}
    var sec1Id = {Section1Pascal}SectionId;
    entries.AddRange(new[]
    {
        new NavigationTranslationSeedEntry
        {
            EntityType = NavigationEntityType.Section,
            EntityId = sec1Id,
            LanguageCode = "fr",
            Label = "{section1_label_fr}",
            Description = "{section1_desc_fr}"
        },
        new NavigationTranslationSeedEntry
        {
            EntityType = NavigationEntityType.Section,
            EntityId = sec1Id,
            LanguageCode = "en",
            Label = "{section1_label_en}",
            Description = "{section1_desc_en}"
        },
        new NavigationTranslationSeedEntry
        {
            EntityType = NavigationEntityType.Section,
            EntityId = sec1Id,
            LanguageCode = "it",
            Label = "{section1_label_it}",
            Description = "{section1_desc_it}"
        },
        new NavigationTranslationSeedEntry
        {
            EntityType = NavigationEntityType.Section,
            EntityId = sec1Id,
            LanguageCode = "de",
            Label = "{section1_label_de}",
            Description = "{section1_desc_de}"
        }
    });
    // Repeat for each section...

    return entries;
}
```

### Resource Methods (add to {ModulePascal}NavigationSeedData.cs)

> **CONDITIONAL:** Only generate if `seedDataCore.navigationResources[]` exists and is non-empty.

```csharp
// --- Add AFTER GetSectionTranslationEntries() ---

// Random GUIDs for resources
public static readonly Guid {Resource1Pascal}ResourceId = Guid.NewGuid();
// Repeat for each resource...

/// <summary>
/// Returns navigation resource entries for a given section.
/// </summary>
public static IEnumerable<NavigationResourceSeedEntry> GetResourceEntries(Guid sectionId)
{
    var entries = new List<NavigationResourceSeedEntry>();

    // Resources for section: {section1Code}
    if (sectionId == {Section1Pascal}SectionId)
    {
        entries.AddRange(new[]
        {
            // RESOURCE ROUTE CONVENTION:
            // Resources inherit their parent section's resolved route as base:
            // - Under "list" section → base = module route (no /list)
            // - Under "detail" section → base = module route (no /detail, resource routes don't include /:id)
            // - Under other sections → base = module route + /{section-kebab}
            // Then append: /{resource-kebab}
            //
            // Example: resource "export" under section "dashboard":
            //   Route = /human-resources/employees/dashboard/export
            // Example: resource "employees-grid" under section "list":
            //   Route = /human-resources/employees/employees-grid (NOT /employees/list/employees-grid)
            new NavigationResourceSeedEntry
            {
                Id = {Resource1Pascal}ResourceId,
                SectionId = sectionId,
                Code = "{resource1Code}",
                Label = "{resource1_label_en}",
                EntityType = "{resource1_entity}",
                // Use parent section's resolved route + /{resource-kebab}
                // For "list"/"detail" sections, the section route = module route (no /list or /detail segment)
                Route = "{resource_route}",  // From seedDataCore: parent section route + /{resource-kebab}
                DisplayOrder = 1
            }
            // Repeat for each resource in this section...
        });
    }
    // Repeat if-block for each section with resources...

    return entries;
}
```

### Additional DTO Classes (add to bottom of file)

```csharp
/// <summary>Seed entry DTO for navigation section.</summary>
public class NavigationSectionSeedEntry
{
    public Guid Id { get; init; }
    public Guid ModuleId { get; init; }
    public string Code { get; init; } = null!;
    public string Label { get; init; } = null!;
    public string Description { get; init; } = null!;
    public string Icon { get; init; } = null!;
    public IconType IconType { get; init; }
    public string Route { get; init; } = null!;
    public int DisplayOrder { get; init; }
    public bool IsActive { get; init; }
}

/// <summary>Seed entry DTO for navigation resource.</summary>
public class NavigationResourceSeedEntry
{
    public Guid Id { get; init; }
    public Guid SectionId { get; init; }
    public string Code { get; init; } = null!;
    public string Label { get; init; } = null!;
    public string? EntityType { get; init; }
    public string? Route { get; init; }
    public int DisplayOrder { get; init; }
}
```

### Placeholder Values Source

| Placeholder | Source in feature.json |
|-------------|----------------------|
| `{sectionCode}` | `seedDataCore.navigationSections[].code` |
| `{section_label_xx}` | `specification.navigation.entries[]` where `level == "section"` → `labels.xx` |
| `{section_icon}` | `seedDataCore.navigationSections[].icon` |
| `{section_sort}` | `seedDataCore.navigationSections[].sort` |
| `{section_route}` | `seedDataCore.navigationSections[].route` — **SPECIAL CASES:** `list` → module route (no `/list`), `detail` → module route + `/:id` (no `/detail/:id`), others → module route + `/{section-kebab}` |
| `{resourceCode}` | `seedDataCore.navigationResources[].code` |
| `{resource_entity}` | `seedDataCore.navigationResources[].entity` |
| `{resource_route}` | Computed from parent section route + `/{resource-kebab}`. **SPECIAL CASES:** if parent section is `list` → module route + `/{resource-kebab}` (no `/list/`), if parent is `detail` → module route + `/{resource-kebab}` (no `/detail/`). |
| `{parentSectionCode}` | `seedDataCore.navigationResources[].parentCode` |

---

## 3. PermissionsSeedData.cs — MCP-First

### PermissionAction Safety Rules

> Do not use `Enum.Parse<PermissionAction>("...")` — this causes runtime crashes if the string is invalid.
> The error only manifests at application startup, not at compile time.

**Valid PermissionAction values (from SmartStack.Domain.Authorization):**

| Enum Value | Int | Description |
|------------|-----|-------------|
| `PermissionAction.Access` | 0 | Wildcard permissions only (IsWildcard = true) |
| `PermissionAction.Read` | 1 | GET/HEAD — View data |
| `PermissionAction.Create` | 2 | POST — Create new records |
| `PermissionAction.Update` | 3 | PUT/PATCH — Modify existing records |
| `PermissionAction.Delete` | 4 | DELETE — Remove records |
| `PermissionAction.Export` | 5 | Export data (CSV, Excel, etc.) |
| `PermissionAction.Import` | 6 | Import data |
| `PermissionAction.Approve` | 7 | Approve workflow items |
| `PermissionAction.Reject` | 8 | Reject workflow items |
| `PermissionAction.Assign` | 9 | Assign items to users |
| `PermissionAction.Execute` | 10 | Execute actions (sync, run, etc.) |

**Anti-patterns:**

```csharp
// Runtime crash if string is not a valid enum value
Enum.Parse<PermissionAction>("Validate");          // ArgumentException at startup
(PermissionAction)Enum.Parse(typeof(PermissionAction), "Validate"); // Same crash

// String-based action in anonymous objects
new { Action = "read" };  // Not type-safe, silent mismatch possible
```

**Correct patterns:**

```csharp
// ALWAYS use the typed enum directly — compile-time safe
Action = PermissionAction.Read      // Compile-time checked
Action = PermissionAction.Create    // Compile-time checked
Action = PermissionAction.Approve   // Compile-time checked

// For custom actions beyond standard CRUD, pick from the enum:
// Export, Import, Approve, Reject, Assign, Execute
```

**MCP validation:** `validate_conventions` with `checks: ["permissions"]` will detect and flag these anti-patterns.

### Step A: Call MCP (PRIMARY)

```
Tool: mcp__smartstack__generate_permissions
Args:
  navRoute: "{navRoute}"
  includeStandardActions: true
  includeWildcard: true
```

MCP returns:
- `Permissions.cs` nested class (Application layer constants)
- Permission seed entries with random GUIDs

### Step B: Write Permissions.cs (Application layer)

> Permission paths use the SAME kebab-case as NavRoute codes.
> `{navRoute}` is already kebab-case (e.g., `human-resources.employees`).
> Do not strip hyphens or derive codes from C# class names.
> Use `human-resources.employees.read` not `humanresources.employees.read`.
> SmartStack.app reference: `support-client.my-tickets.read`

```csharp
// Add to Application/Common/Authorization/Permissions.cs
// IMPORTANT: {navRoute} uses kebab-case segments (e.g., "human-resources.employees")
// Do NOT derive permission codes from C# identifiers — use navRoute directly
public static class {AppPascal}
{
    public static class {ModulePascal}
    {
        public const string View = "{navRoute}.read";       // e.g., "human-resources.employees.read"
        public const string Create = "{navRoute}.create";
        public const string Update = "{navRoute}.update";
        public const string Delete = "{navRoute}.delete";
    }
}
```

### Step C: Write PermissionsSeedData.cs (Infrastructure layer)

**File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/PermissionsSeedData.cs`

```csharp
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};

/// <summary>
/// Permission seed data for {ModuleLabel} module.
/// Consumed by IClientSeedDataProvider at application startup.
/// </summary>
public static class {ModulePascal}PermissionsSeedData
{
    // Random GUIDs for permissions
    public static readonly Guid WildcardPermId = Guid.NewGuid();
    public static readonly Guid ReadPermId = Guid.NewGuid();
    public static readonly Guid CreatePermId = Guid.NewGuid();
    public static readonly Guid UpdatePermId = Guid.NewGuid();
    public static readonly Guid DeletePermId = Guid.NewGuid();

    public static IEnumerable<PermissionSeedEntry> GetPermissionEntries(Guid moduleId)
    {
        return new[]
        {
            new PermissionSeedEntry { Id = WildcardPermId, Path = "{navRoute}.*", Level = PermissionLevel.Module, Action = PermissionAction.Access, IsWildcard = true, ModuleId = moduleId, Description = "Full {moduleLabel} access" },
            new PermissionSeedEntry { Id = ReadPermId, Path = "{navRoute}.read", Level = PermissionLevel.Module, Action = PermissionAction.Read, IsWildcard = false, ModuleId = moduleId, Description = "View {moduleLabel}" },
            new PermissionSeedEntry { Id = CreatePermId, Path = "{navRoute}.create", Level = PermissionLevel.Module, Action = PermissionAction.Create, IsWildcard = false, ModuleId = moduleId, Description = "Create {moduleLabel}" },
            new PermissionSeedEntry { Id = UpdatePermId, Path = "{navRoute}.update", Level = PermissionLevel.Module, Action = PermissionAction.Update, IsWildcard = false, ModuleId = moduleId, Description = "Update {moduleLabel}" },
            new PermissionSeedEntry { Id = DeletePermId, Path = "{navRoute}.delete", Level = PermissionLevel.Module, Action = PermissionAction.Delete, IsWildcard = false, ModuleId = moduleId, Description = "Delete {moduleLabel}" }
        };
    }

    // No deterministic GUID generation — all IDs are random via Guid.NewGuid()
    // Permissions are resolved by Path at runtime, not by fixed GUIDs
}

public class PermissionSeedEntry
{
    public Guid Id { get; init; }
    public string Path { get; init; } = null!;
    public PermissionLevel Level { get; init; }
    public PermissionAction Action { get; init; }
    public bool IsWildcard { get; init; }
    public Guid ModuleId { get; init; }
    public string Description { get; init; } = null!;
}
```

### Step C2: Section-Level Permissions (CONDITIONAL — filtered by `permissionMode`)

> When `seedDataCore.navigationSections` exists and is non-empty in feature.json,
> add section-level permission GUIDs and entries to `PermissionsSeedData.cs`.
>
> **Permission generation is conditional on each section's `permissionMode`:**
>
> | permissionMode | Permissions generated |
> |---|---|
> | `crud` | wildcard + read + create + update + delete |
> | `custom:action1,action2` | wildcard + read + custom actions |
> | `read-only` | read only (NO CRUD wildcard) |
> | `inherit` | NO section-level permissions (inherits from module) |
>
> **Default inference (when `permissionMode` is absent):**
> - `code === "detail"` → `inherit`
> - `code === "dashboard"` → `read-only`
> - otherwise → `crud`
>
> **IMPORTANT:** Sections with `permissionMode: inherit` MUST be skipped entirely.
> Do NOT generate any permission GUIDs, entries, or constants for them.

```csharp
// --- Add to {ModulePascal}PermissionsSeedData class AFTER module-level permissions ---
// FILTER: Only generate for sections where permissionMode is NOT "inherit"
// For "read-only" sections: generate ONLY ReadPermId (no Wildcard, Create, Update, Delete)
// For "custom:x,y" sections: generate Wildcard + Read + custom action GUIDs

// Section-level permissions (for each section in navSections[])
public static readonly Guid {SectionPascal}WildcardPermId = Guid.NewGuid();
public static readonly Guid {SectionPascal}ReadPermId = Guid.NewGuid();
public static readonly Guid {SectionPascal}CreatePermId = Guid.NewGuid();
public static readonly Guid {SectionPascal}UpdatePermId = Guid.NewGuid();
public static readonly Guid {SectionPascal}DeletePermId = Guid.NewGuid();
// Repeat for each section...

// Add to GetPermissionEntries() — AFTER module-level entries:
// Section: {sectionCode}
new PermissionSeedEntry { Id = {SectionPascal}WildcardPermId, Path = "{navRoute}.{sectionCode}.*", Level = PermissionLevel.Section, Action = PermissionAction.Access, IsWildcard = true, ModuleId = moduleId, Description = "Full {sectionLabel} access" },
new PermissionSeedEntry { Id = {SectionPascal}ReadPermId, Path = "{navRoute}.{sectionCode}.read", Level = PermissionLevel.Section, Action = PermissionAction.Read, IsWildcard = false, ModuleId = moduleId, Description = "View {sectionLabel}" },
new PermissionSeedEntry { Id = {SectionPascal}CreatePermId, Path = "{navRoute}.{sectionCode}.create", Level = PermissionLevel.Section, Action = PermissionAction.Create, IsWildcard = false, ModuleId = moduleId, Description = "Create {sectionLabel}" },
new PermissionSeedEntry { Id = {SectionPascal}UpdatePermId, Path = "{navRoute}.{sectionCode}.update", Level = PermissionLevel.Section, Action = PermissionAction.Update, IsWildcard = false, ModuleId = moduleId, Description = "Update {sectionLabel}" },
new PermissionSeedEntry { Id = {SectionPascal}DeletePermId, Path = "{navRoute}.{sectionCode}.delete", Level = PermissionLevel.Section, Action = PermissionAction.Delete, IsWildcard = false, ModuleId = moduleId, Description = "Delete {sectionLabel}" },
// Repeat for each section...
```

Also add section-level constants to `Permissions.cs` (Application layer):

```csharp
public static class {ModulePascal}
{
    // ... existing module-level permissions ...

    // Section-level (for each section in navSections[])
    public static class {SectionPascal}
    {
        public const string View = "{navRoute}.{sectionCode}.read";
        public const string Create = "{navRoute}.{sectionCode}.create";
        public const string Update = "{navRoute}.{sectionCode}.update";
        public const string Delete = "{navRoute}.{sectionCode}.delete";
    }
}
```

### Step D: MCP Fallback

If MCP `generate_permissions` fails, use the template above directly with values derived from the PRD `coreSeedData.permissions[]`.

---

## 4. ApplicationRolesSeedData.cs (Application-Level, Once per Application)

**File:** `Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs`

### Purpose

Creates the 4 standard application-scoped roles: Admin, Manager, Contributor, Viewer.

> SmartStack core MAY already provide system roles (admin, manager, contributor, viewer).
> If system roles already exist in `auth_Roles`, do not create duplicates.
> `SeedRolesAsync()` must check existence by Code, not just by ApplicationId.
> For RolePermission mappings: always look up roles by Code at runtime (in `SeedRolePermissionsAsync()`).
> Do not use `GenerateRoleGuid()`, `DeterministicGuid("role:admin")`, or any hardcoded role GUID
> when creating RolePermission entries. The role GUIDs may differ from what's in the database.

This file is created once per application (not per module).

### GUID Generation Rule

> **NOTE:** Role GUIDs are random (`Guid.NewGuid()`). Roles are resolved by Code at runtime.
> RolePermission mapping MUST look up roles by Code, not by GUID.

```csharp
// All role GUIDs are random — always resolve roles by Code at runtime
// Do not use hardcoded GUIDs in RolePermission mapping
var role = roles.FirstOrDefault(r => r.Code == "admin");
```

### Template

```csharp
using SmartStack.Domain.Platform.Administration.Roles;

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;

/// <summary>
/// Application-scoped role seed data for {AppLabel}.
/// Defines the 4 standard application roles: Admin, Manager, Contributor, Viewer.
/// Consumed by IClientSeedDataProvider at application startup.
/// </summary>
public static class ApplicationRolesSeedData
{
    // Application ID from NavigationApplicationSeedData
    public static readonly Guid ApplicationId = NavigationApplicationSeedData.ApplicationId;

    public static readonly Guid AdminRoleId = Guid.NewGuid();
    public static readonly Guid ManagerRoleId = Guid.NewGuid();
    public static readonly Guid ContributorRoleId = Guid.NewGuid();
    public static readonly Guid ViewerRoleId = Guid.NewGuid();

    public static IEnumerable<ApplicationRoleSeedEntry> GetRoleEntries()
    {
        yield return new ApplicationRoleSeedEntry
        {
            Id = AdminRoleId,
            Code = "admin",
            Name = "{AppLabel} Admin",
            Description = "Full administrative access to {AppLabel}",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 1
        };

        yield return new ApplicationRoleSeedEntry
        {
            Id = ManagerRoleId,
            Code = "manager",
            Name = "{AppLabel} Manager",
            Description = "Management access to {AppLabel} (Create, Read, Update)",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 2
        };

        yield return new ApplicationRoleSeedEntry
        {
            Id = ContributorRoleId,
            Code = "contributor",
            Name = "{AppLabel} Contributor",
            Description = "Contributor access to {AppLabel} (Create, Read)",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 3
        };

        yield return new ApplicationRoleSeedEntry
        {
            Id = ViewerRoleId,
            Code = "viewer",
            Name = "{AppLabel} Viewer",
            Description = "Read-only access to {AppLabel}",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 4
        };
    }

    // No deterministic GUID generation — all IDs are random via Guid.NewGuid()
    // Roles are resolved by Code at runtime
}

public class ApplicationRoleSeedEntry
{
    public Guid Id { get; init; }
    public string Code { get; init; } = null!;
    public string Name { get; init; } = null!;
    public string Description { get; init; } = null!;
    public Guid ApplicationId { get; init; }
    public bool IsSystem { get; init; }
    public bool IsActive { get; init; }
    public int DisplayOrder { get; init; }
}
```

**Replace placeholders** with values from PRD and navigation metadata.

---

## 5. {Module}RolesSeedData.cs (Per Module)

**File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/RolesSeedData.cs`

> This file uses `RoleCode` (string), not role GUIDs.
> Roles are resolved by Code at runtime in `SeedRolePermissionsAsync()`.
> Do not use `DeterministicGuid("role:admin")`, `GenerateRoleGuid("admin")`, or any hardcoded Guid for roles.
> SmartStack core pre-seeds system roles — their IDs are not deterministic from the client perspective.

### Context-Based Role Mapping

| Application | Admin | Manager | Contributor | Viewer |
|-------------|-------|---------|-------------|--------|
| Any | CRUD | CRU | CR | R |

### Template

```csharp
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};

/// <summary>
/// Role-permission mapping seed data for {ModuleLabel} module.
/// Maps permissions to application-scoped roles (Admin, Manager, Contributor, Viewer).
/// Consumed by IClientSeedDataProvider at application startup.
/// </summary>
public static class {ModulePascal}RolesSeedData
{
    /// <summary>
    /// Returns role-permission mappings for this module.
    /// Roles are resolved at runtime by Code (not hardcoded GUIDs).
    /// </summary>
    public static IEnumerable<RolePermissionSeedEntry> GetRolePermissionEntries()
    {
        // Admin: wildcard access
        yield return new RolePermissionSeedEntry { RoleCode = "admin", PermissionPath = "{navRoute}.*" };

        // Manager: CRU (read + create + update — no delete)
        yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.read" };
        yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.create" };
        yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.update" };

        // Contributor: CR
        yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.read" };
        yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.create" };

        // Viewer: R
        yield return new RolePermissionSeedEntry { RoleCode = "viewer", PermissionPath = "{navRoute}.read" };

        // --- Section-level role mappings (CONDITIONAL: for each section in navSections[]) ---
        // Admin: wildcard per section
        yield return new RolePermissionSeedEntry { RoleCode = "admin", PermissionPath = "{navRoute}.{sectionCode}.*" };

        // Manager: CRU per section (read + create + update — no delete)
        yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.{sectionCode}.read" };
        yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.{sectionCode}.create" };
        yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.{sectionCode}.update" };

        // Contributor: CR per section
        yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.{sectionCode}.read" };
        yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.{sectionCode}.create" };

        // Viewer: R per section
        yield return new RolePermissionSeedEntry { RoleCode = "viewer", PermissionPath = "{navRoute}.{sectionCode}.read" };
        // Repeat block for each section in navSections[]...
    }
}

public class RolePermissionSeedEntry
{
    public string RoleCode { get; init; } = null!;
    public string PermissionPath { get; init; } = null!;
}
```

---

## 6. IClientSeedDataProvider Implementation

> **IMPORTANT — HasData vs Seed*Async: TWO DIFFERENT MECHANISMS**
>
> | Mechanism | Where | When | Purpose |
> |-----------|-------|------|---------|
> | **EF Core `HasData()`** | `*Configuration.cs` (Infrastructure) | At migration time | Seeds static reference data into tables via `INSERT` in migration SQL. Data is part of the migration — no runtime code needed. |
> | **`IClientSeedDataProvider.Seed*Async()`** | `*SeedDataProvider.cs` (Infrastructure) | At runtime startup | Seeds navigation, roles, permissions, role-permissions into the **Core** schema. Runs AFTER `MigrateAsync()` in `Program.cs`. |
>
> **Common mistake:** Generating `HasData()` calls for navigation/permissions instead of `Seed*Async()` methods.
> Navigation and permission data goes into the **Core** schema (`core.nav_*`, `core.auth_*`) which is managed
> by SmartStack, not by the client's migration. Only `IClientSeedDataProvider` can write to Core tables at runtime.
>
> **ANTI-STUB WARNING:** Each of the 4 Seed methods MUST contain real `context.{DbSet}.Add()` calls.
> If ANY method is just `return Task.CompletedTask;`, the module will be **invisible** in the UI (empty menu).
> This happens when this reference file is evicted from context by compression — see step-03b safeguard.

**File:** `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`

### Critical Rules

| Rule | Description |
|------|-------------|
| Factory methods | `NavigationApplication.Create(zone, ...)`, `Role.Create(name, shortName, category, ...)`, `Permission.CreateForModule(...)` — NEVER `new Entity()` |
| Idempotence | Each Seed method checks existence before inserting |
| Execution order | Navigation → Roles → Permissions → RolePermissions (roles MUST exist before mapping) |
| SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
| FK resolution by Code | Parent entities (applications, modules, roles) found by `Code` from DB, not seed-time GUID |
| Translation EntityId | ALWAYS use actual DB ID (`app.Id`, `mod1Entity.Id`, `actualSection.Id`), NEVER seed-time GUID from SeedData classes |
| DI registration | `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()` |

### Template

```csharp
using Microsoft.EntityFrameworkCore;
using SmartStack.Application.Common.Interfaces;
using SmartStack.Application.Common.Interfaces.Seeding;
using SmartStack.Domain.Navigation;
using SmartStack.Domain.Platform.Administration.Roles;
using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;
using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module1Pascal};
// using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module2Pascal}; // Add per module

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;

/// <summary>
/// Seeds {AppLabel} navigation, roles, permissions, and role-permission data
/// into the SmartStack Core schema at application startup.
/// </summary>
public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
{
    public int Order => 100;

    public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
    {
        // --- Application (from NavigationApplicationSeedData) ---
        // NOTE: Idempotence is at MODULE level (not application level).
        // If the application already exists, we load it and continue to seed any missing modules.
        // This allows adding Module 2+ to an existing application without re-running the full seed.
        var appEntry = NavigationApplicationSeedData.GetApplicationEntry();
        var existingApp = await context.NavigationApplications
            .FirstOrDefaultAsync(a => a.Code == appEntry.Code, ct);

        NavigationApplication app;
        if (existingApp != null)
        {
            app = existingApp; // Application already seeded — reuse it for module seeding below
        }
        else
        {
            app = NavigationApplication.Create(
                appEntry.Zone, appEntry.Code, appEntry.Label,
                appEntry.Description, appEntry.Icon, appEntry.IconType,
                appEntry.Route, appEntry.DisplayOrder);
            context.NavigationApplications.Add(app);
            await ((DbContext)context).SaveChangesAsync(ct);

            // --- Application translations (4 languages, from NavigationApplicationSeedData) ---
            foreach (var t in NavigationApplicationSeedData.GetTranslationEntries())
            {
                context.NavigationTranslations.Add(
                    NavigationTranslation.Create(t.EntityType, app.Id, t.LanguageCode, t.Label, t.Description));
            }
            await ((DbContext)context).SaveChangesAsync(ct);
        }

        // --- Modules (idempotent per-module — allows adding Module 2+ later) ---
        // Module: {Module1}
        var mod1Exists = await context.NavigationModules
            .AnyAsync(m => m.Code == "{module1Code}" && m.ApplicationId == app.Id, ct);
        if (!mod1Exists)
        {
            var mod1Entry = {Module1Pascal}NavigationSeedData.GetModuleEntry(app.Id);
            var mod1 = NavigationModule.Create(
                mod1Entry.ApplicationId, mod1Entry.Code, mod1Entry.Label,
                mod1Entry.Description, mod1Entry.Icon, mod1Entry.IconType,
                mod1Entry.Route, mod1Entry.DisplayOrder);
            context.NavigationModules.Add(mod1);
        }

        // Repeat for each module (each with its own idempotence check)...
        await ((DbContext)context).SaveChangesAsync(ct);

        // Resolve module entities for section/resource seeding (works for both new AND existing modules)
        var mod1Entity = await context.NavigationModules
            .FirstOrDefaultAsync(m => m.Code == "{module1Code}" && m.ApplicationId == app.Id, ct)
            ?? throw new InvalidOperationException(
                $"Seed data dependency missing: NavigationModule '{"{module1Code}"}' not found for app '{app.Code}'. " +
                $"Ensure the module was created in the navigation seeding step above.");
        // Repeat for each module...

        // --- Module translations (idempotent — unique index IX_nav_Translations_EntityType_EntityId_LanguageCode) ---
        // Always check existence before inserting translations to avoid duplicate key errors
        // on re-runs, partial failures, or DB reset scenarios.
        // CRITICAL: Use mod1Entity.Id (actual DB ID), NOT seed-time GUID from SeedData class.
        if (!await context.NavigationTranslations.AnyAsync(
            t => t.EntityId == mod1Entity.Id
                 && t.EntityType == NavigationEntityType.Module, ct))
        {
            foreach (var t in {Module1Pascal}NavigationSeedData.GetTranslationEntries())
            {
                context.NavigationTranslations.Add(
                    NavigationTranslation.Create(t.EntityType, mod1Entity.Id, t.LanguageCode, t.Label, t.Description));
            }
        }
        // Repeat for each module...
        await ((DbContext)context).SaveChangesAsync(ct);

        // --- Sections (idempotent — check each section before inserting) ---
        // Module 1 sections
        foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1Entity.Id))
        {
            var secExists = await context.NavigationSections
                .AnyAsync(s => s.Code == secEntry.Code && s.ModuleId == mod1Entity.Id, ct);
            if (!secExists)
            {
                var sec = NavigationSection.Create(
                    secEntry.ModuleId, secEntry.Code, secEntry.Label,
                    secEntry.Description, secEntry.Icon, secEntry.IconType,
                    secEntry.Route, secEntry.DisplayOrder);
                context.NavigationSections.Add(sec);
            }
        }
        // Repeat for each module that has sections...
        await ((DbContext)context).SaveChangesAsync(ct);

        // --- Section translations (idempotent — check before inserting) ---
        // CRITICAL: secEntry.Id is a seed-time GUID ≠ actual DB ID.
        // Resolve actual section from DB by Code+ModuleId to get the real ID.
        foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1Entity.Id))
        {
            var actualSection = await context.NavigationSections
                .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == mod1Entity.Id, ct);

            if (!await context.NavigationTranslations.AnyAsync(
                t => t.EntityId == actualSection.Id && t.EntityType == NavigationEntityType.Section, ct))
            {
                foreach (var t in {Module1Pascal}NavigationSeedData.GetSectionTranslationEntries()
                    .Where(st => st.EntityId == secEntry.Id))
                {
                    context.NavigationTranslations.Add(
                        NavigationTranslation.Create(t.EntityType, actualSection.Id, t.LanguageCode, t.Label, t.Description));
                }
            }
        }
        // Repeat for each module that has sections...
        await ((DbContext)context).SaveChangesAsync(ct);

        // --- Resources (idempotent — use ACTUAL section IDs from DB, not deterministic seed IDs) ---
        // NavigationSection.Create() generates its own ID in DB.
        // The GUID from GetSectionEntries() is not the actual SectionId in DB.
        // Query the real section by Code+ModuleId to get the actual DB ID,
        // otherwise FK_nav_Resources_nav_Sections_SectionId will fail.
        foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1Entity.Id))
        {
            var actualSection = await context.NavigationSections
                .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == mod1Entity.Id, ct);

            foreach (var resEntry in {Module1Pascal}NavigationSeedData.GetResourceEntries(secEntry.Id))
            {
                var resExists = await context.NavigationResources
                    .AnyAsync(r => r.Code == resEntry.Code && r.SectionId == actualSection.Id, ct);
                if (!resExists)
                {
                    var res = NavigationResource.Create(
                        actualSection.Id, resEntry.Code, resEntry.Label,
                        resEntry.EntityType, resEntry.Route, resEntry.DisplayOrder);
                    context.NavigationResources.Add(res);
                }
            }
        }
        // Repeat for each module that has resources...
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
    {
        // Resolve application from DB by Code — NOT seed-time GUID
        var app = await context.NavigationApplications
            .FirstAsync(a => a.Code == "{appCode}", ct);

        // Check idempotence — verify by Code, scoped by actual DB ApplicationId
        var existingRoleCodes = await context.Roles
            .Where(r => r.ApplicationId == app.Id
                && (r.Code == "admin" || r.Code == "manager" || r.Code == "contributor" || r.Code == "viewer"))
            .Select(r => r.Code)
            .ToListAsync(ct);

        // Only create roles that don't already exist (SmartStack core may pre-seed system roles)
        foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
        {
            if (existingRoleCodes.Contains(entry.Code)) continue; // Skip if already exists

            var role = Role.Create(
                name: entry.Name,
                shortName: entry.Code,
                category: RoleCategory.Application,
                description: entry.Description,
                isSystem: entry.IsSystem,
                applicationId: app.Id,
                code: entry.Code);
            context.Roles.Add(role);
        }
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
    {
        var exists = await context.Permissions
            .AnyAsync(p => p.Path == "{appCode}.*", ct);
        if (exists) return;

        // Application-level wildcard
        var appWildcard = Permission.CreateWildcard(
            "{appCode}.*", PermissionLevel.Application,
            "Full {appLabel_en} access");
        context.Permissions.Add(appWildcard);

        // Module permissions
        var mod1 = await context.NavigationModules
            .FirstAsync(m => m.Code == "{module1Code}", ct);
        foreach (var entry in {Module1Pascal}PermissionsSeedData.GetPermissionEntries(mod1.Id))
        {
            var perm = entry.IsWildcard
                ? Permission.CreateWildcard(entry.Path, entry.Level, entry.Description)
                : Permission.CreateForModule(entry.Path, entry.Level, entry.Action, false, entry.ModuleId, entry.Description);
            context.Permissions.Add(perm);
        }

        // Repeat for each module...
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
    {
        var exists = await context.RolePermissions
            .AnyAsync(rp => rp.Permission!.Path.StartsWith("{appCode}."), ct);
        if (exists) return;

        // Resolve application from DB by Code — NOT seed-time GUID
        var app = await context.NavigationApplications
            .FirstAsync(a => a.Code == "{appCode}", ct);

        // Resolve roles by Code from DB — do not use hardcoded GUIDs.
        // Application-scoped roles (admin, manager, contributor, viewer) are created by
        // SeedRolesAsync() above. System roles use their own IDs that do not match
        // DeterministicGuid("role:admin").
        // Load THIS application's roles + system roles only
        var roles = await context.Roles
            .Where(r => r.ApplicationId == app.Id || r.IsSystem)
            .ToListAsync(ct);

        // Resolve permissions
        var permissions = await context.Permissions
            .Where(p => p.Path.StartsWith("{appCode}."))
            .ToListAsync(ct);

        // Apply role-permission mappings from all modules
        var allMappings = new List<RolePermissionSeedEntry>();
        allMappings.AddRange({Module1Pascal}RolesSeedData.GetRolePermissionEntries());
        // allMappings.AddRange({Module2Pascal}RolesSeedData.GetRolePermissionEntries()); // per module

        foreach (var mapping in allMappings)
        {
            var role = roles.FirstOrDefault(r => r.Code == mapping.RoleCode);
            var perm = permissions.FirstOrDefault(p => p.Path == mapping.PermissionPath);

            if (role == null)
            {
                // Role not found — SeedRolesAsync() may not have run.
                // This causes silent permission failure → 401 on protected pages.
                Console.WriteLine($"[SEED WARNING] Role '{mapping.RoleCode}' not found. Role-permission mapping skipped for '{mapping.PermissionPath}'.");
                continue;
            }

            if (perm == null)
            {
                Console.WriteLine($"[SEED WARNING] Permission '{mapping.PermissionPath}' not found. Role-permission mapping skipped for role '{mapping.RoleCode}'.");
                continue;
            }

            context.RolePermissions.Add(RolePermission.Create(role.Id, perm.Id, "system"));
        }

        await ((DbContext)context).SaveChangesAsync(ct);
    }
}
```

### DI Registration

```csharp
// In Infrastructure/DependencyInjection.cs — add:
using SmartStack.Application.Common.Interfaces.Seeding;

// In the registration method:
services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
```

---

## 7. Multi-Module Handling

When processing multiple modules in the same business-analyse-develop run:

### Module 1 (first): Creates everything from scratch

0. `NavigationApplicationSeedData.cs` (**application-level**, once per app — FIRST FILE, provides ApplicationId)
1. `ApplicationRolesSeedData.cs` (application-level, once per app — references NavigationApplicationSeedData.ApplicationId)
2. `{Module1}NavigationSeedData.cs` (module + sections + resources + all translations)
3. `{Module1}PermissionsSeedData.cs`
4. `{Module1}RolesSeedData.cs`
5. `{AppPascalName}SeedDataProvider.cs` (new, with 4 methods — including section/resource seeding)
6. DI registration (new)

### Module 2+ (subsequent): Append to existing provider

0. `NavigationApplicationSeedData.cs` (already exists — skip)
1. `ApplicationRolesSeedData.cs` (already exists — skip)
2. `{Module2}NavigationSeedData.cs` (new file — include sections + resources if defined in feature.json)
3. `{Module2}PermissionsSeedData.cs` (new file)
4. `{Module2}RolesSeedData.cs` (new file)
5. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in Navigation/Permissions/RolePermissions methods, **including section/resource seeding for the new module**)
6. DI registration (already exists — skip)

**Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to the appropriate methods (Navigation, Permissions, RolePermissions). Do NOT modify SeedRolesAsync() or the Application creation in SeedNavigationAsync().

### Section/Resource Conditionality

Sections and resources are **optional per module**. When processing a module's feature.json:

| Condition | Action |
|-----------|--------|
| `seedDataCore.navigationSections` absent or empty | Skip `GetSectionEntries()` / `GetSectionTranslationEntries()` / section seeding in provider |
| `seedDataCore.navigationResources` absent or empty | Skip `GetResourceEntries()` / resource seeding in provider |
| Both present | Generate all section + resource methods and provider code |

---

## 8. Verification Checklist

Before marking the task as completed, verify ALL:

**Application-Level (FIRST — before modules):**
- [ ] `NavigationApplicationSeedData.cs` created (once per application, at `Infrastructure/Persistence/Seeding/Data/`)
- [ ] Application GUID is random (`Guid.NewGuid()`) — FK resolution is by Code lookup, not fixed ID
- [ ] GetApplicationEntry() takes no parameters, includes `Zone = ApplicationZone.Business`
- [ ] Application translations created (4 languages: fr, en, it, de, EntityType = Application), using `app.Id` (actual DB ID) for EntityId
- [ ] `IClientSeedDataProvider.SeedNavigationAsync()` uses `NavigationApplicationSeedData` (NO hardcoded `{appLabel_en}` / `{appIcon}` placeholders)
- [ ] `ApplicationRolesSeedData.ApplicationId` references `NavigationApplicationSeedData.ApplicationId` (DTO only — provider code resolves from DB by Code)

**Module-Level:**
- [ ] Random GUIDs via `Guid.NewGuid()` (no deterministic/sequential/fixed values)
- [ ] 4 languages for each navigation entity (fr, en, it, de)
- [ ] `ApplicationRolesSeedData.cs` created (once per application)
- [ ] 4 application roles defined: Admin, Manager, Contributor, Viewer
- [ ] Each role has a valid `Code` value ("admin", "manager", "contributor", "viewer")
- [ ] `Permissions.cs` constants match seed data paths
- [ ] MCP `generate_permissions` called (or fallback used)
- [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
- [ ] RolePermission mappings use Code-based lookup (no `DeterministicGuid("role:admin")` or `GenerateRoleGuid()`)
- [ ] SeedRolesAsync checks existence by Code (SmartStack core may pre-seed system roles)
- [ ] `IClientSeedDataProvider` generated with 4 methods (Navigation, Roles, Permissions, RolePermissions)
- [ ] Execution order: Navigation (application → modules → sections → resources) → Roles → Permissions → RolePermissions
- [ ] Each Seed method is idempotent (checks existence before inserting)
- [ ] Factory methods used throughout (no `new Entity()`)
- [ ] `SaveChangesAsync` called per group (Navigation → Roles → Permissions → RolePermissions)
- [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
- [ ] NavigationSections seeded (if `seedDataCore.navigationSections` present in feature.json)
- [ ] NavigationResources seeded (if `seedDataCore.navigationResources` present in feature.json)
- [ ] Section/Resource translations created (4 languages each, EntityType = Section/Resource)
- [ ] `dotnet build` passes after generation
- [ ] NO `Enum.Parse<PermissionAction>` usage anywhere in seeding code (use typed enum directly)
- [ ] ALL PermissionAction values are from the valid enum: Access, Read, Create, Update, Delete, Export, Import, Approve, Reject, Assign, Execute

**Seed Data Integrity (run AFTER `dotnet build`):**
- [ ] Application startup test: `dotnet run --urls http://localhost:0 --environment Development` exits without seed data exceptions
- [ ] Verify `nav_Applications` has entry for `{appCode}` (query or startup log)
- [ ] Verify `nav_Modules` has entries for each module (count matches feature.json modules)
- [ ] Verify `auth_Roles` has 4 application-scoped roles (admin, manager, contributor, viewer)
- [ ] Verify `auth_Permissions` has entries for each module (wildcard + CRUD)

**If ANY check fails, the task status = 'failed'.**

---

## 9. Business Seed Data (DevDataSeeder) — TenantId Rules

> **Applies to:** Seed data for business entities (reference types, categories, statuses).
> **NOT the same as** core seed data above (navigation, permissions, roles).

### Rules

| Rule | Description |
|------|-------------|
| TenantId required | All business seed entities must set `TenantId` |
| Deterministic TenantId | Use `SeedConstants.DefaultTenantId` (do not inline GUID) |
| DevDataSeeder pattern | Implement `IDevDataSeeder` with `SeedAsync()` method |
| Idempotency | Each seeder MUST check `AnyAsync()` before inserting |
| Order | DevDataSeeder `Order >= 200` (after core seed data at Order 100) |

### Template (Reference Types)

```csharp
public class {Module}DevDataSeeder : IDevDataSeeder
{
    public int Order => 200; // After core seed data (Order 100)

    public async Task SeedAsync(ExtensionsDbContext context, CancellationToken ct)
    {
        if (await context.Set<{EntityType}>().AnyAsync(ct)) return;

        var items = new[]
        {
            new {EntityType}
            {
                Id = Guid.NewGuid(),
                Code = "{code}",
                Name = "{name}",
                TenantId = SeedConstants.DefaultTenantId,  // Required for multi-tenant isolation
                IsActive = true
            }
        };

        context.Set<{EntityType}>().AddRange(items);
        await context.SaveChangesAsync(ct);
    }

    // No deterministic GUID generation — all IDs are random via Guid.NewGuid()
}
```

### Anti-patterns

- Seeding business entities without `TenantId`
- Using `Guid.NewGuid()` for TenantId
- Omitting idempotency check (`AnyAsync`)
- Hardcoding TenantId inline (use `SeedConstants.DefaultTenantId`)

### CROSS-SCHEMA FK WARNING

> `SeedConstants.DefaultTenantId` MUST reference a tenant that EXISTS in `core.tenant_Tenants`.
> The SmartStack platform seeds a default tenant during `InitializeSmartStackAsync()`.
> If you use a custom GUID, ensure it is created BEFORE `DevDataSeeder` runs (Order >= 200).
>
> **Pipeline validation:**
> - business-analyse-develop POST-CHECK warns if GUID not found in project config
> - validate-feature step-05 verifies FK exists in real database via SQL query

---

## DataExportEndpoint Seed Data Pattern

When adding a new export endpoint, add seed data in `DataExportEndpointConfiguration.cs`:

```csharp
builder.HasData(new {
    Id = new Guid("{random-guid}"),
    NavigationApplicationId = NavigationApplicationSeedData.{App}AppId,
    NavigationModuleId = NavigationModuleSeedData.{Module}ModuleId,
    Code = "{entity-code}",
    Name = "{Entity} Export",
    Description = "Export {entity} data with metadata",
    RouteTemplate = "/api/v1/export/{entity-code}",
    RequiredPermission = "{app}.{module}.export",
    EntityType = "{Entity}",
    IsActive = true,
    DefaultRateLimitPerMinute = 60,
    DefaultMaxPageSize = 1000,
    CreatedAt = seedDate
});
```

Requires matching controller in `Controllers/DataExport/v1/Export{Entity}Controller.cs`.
