---
name: backend-seed-data
description: >
  Generates module-scoped IClientSeedDataProvider implementations for
  reference data (lookup tables, enum codes — the SETUP tier, every environment),
  the guarded business TEST DATASET provider (jeu-de-test.md → testData[] — dev,
  test and qual on demand, never prod) and per-module overrides. Cross-app
  navigation, roles, permissions, and role-permission mappings are NOT this
  skill's job — they live in scaffold-core-seed (Phase 0). Output goes under
  Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/ to mirror
  the per-application module layout.
phase: development/backend
cli: cli/scaffold-seed
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# Seed Data — module-scoped IClientSeedDataProvider

Generates a per-module `IClientSeedDataProvider` for **reference data** the
module owns (lookup tables, enum codes, default rows) — anything that needs
to exist at startup but is scoped to a single module rather than to the
whole app.

## Boundary with scaffold-core-seed

| Concern | Skill | Output location |
|---|---|---|
| App-level navigation (Application + Modules + Sections + Resources) | `backend-core-seed` | `Persistence/Seeding/Applications/{AppPascal}/Core/{AppPascal}CoreNavigationSeedDataProvider.cs` |
| All roles for the app | `backend-core-seed` | `…/Core/{AppPascal}CoreRolesSeedDataProvider.cs` |
| All permissions across modules | `backend-core-seed` | `…/Core/{AppPascal}CorePermissionsSeedDataProvider.cs` |
| Role × permission wiring | `backend-core-seed` | `…/Core/{AppPascal}CoreRolePermissionsSeedDataProvider.cs` |
| Dev test users (one per role) | `backend-core-seed` | `…/Core/{AppPascal}CoreTestUsersSeedDataProvider.cs` |
| **Module-scoped reference data** (lookup tables, enums, default rows) | **`backend-seed-data` (this skill)** | `Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/{Module}SeedDataProvider.cs` |

If you find yourself passing `application`-level navigation entries to this
CLI, stop — that work belongs to `backend-core-seed` and is generated
deterministically from the BA tables in Phase 0. The validator will warn.

## The two seed tiers — setup vs business test dataset

| Tier | BA source | Generated provider | Environments | Audit |
|---|---|---|---|---|
| **Setup** — rows the application needs to START | `- **Valeurs initiales**` on a reference entity (entité.md) | `{Module}ReferenceDataSeedDataProvider` (`referenceData[]`) | every environment | `DEV-API-030` |
| **Test dataset** — rows that let the deployed application be EXERCISED | `jeu-de-test.md` at module level (`/ba-create-test-data`, optional) | `{Module}TestDataSeedDataProvider` (`testData[]`), guarded by `IsDevelopment() \|\| SmartStack:EnableDevSeeding` | dev (implicit), test (the key), qual on demand — **never preprod/prod** | `DEV-DAT-010` (BA side: DM-029..032) |

Rules of the split: a reference table has no test-data block (its Valeurs
initiales ARE its rows); a test-data FK resolves against the owner's dataset
OR a Valeurs initiales row; the test-data provider is NEVER a feeding path for
`DEV-API-030` (excluded by file name) — a row an environment needs at startup
belongs to `**Valeurs initiales**`.

## When to Use

- Phase 1 of `/ba-develop` (entities phase), when an entity has
  pre-populated rows (e.g. `BudgetTransitionReason` has 6 fixed reason codes)
  — the entity's `entité.md` block carries a `**Valeurs initiales**` table →
  pass it VERBATIM as `referenceData[]` (see below). This step is MANDATORY
  when the declaration exists: an entity whose screens expose neither `create`
  nor `delete` and that nothing seeds is DEFINITIVELY empty
  (`audit-dev-api DEV-API-030`, err).
- When you need a module-specific override that augments the Core seed.
- NEVER for cross-app artefacts — those belong to `backend-core-seed`.

## Reference data (`referenceData[]`) — the extensions-schema writer

The 4 `IClientSeedDataProvider` methods receive **ICoreDbContext** — extension
tables (`extensions` schema) are unreachable from it. So `referenceData[]`
generates a DEDICATED `{Module}ReferenceDataSeedDataProvider.cs` that keeps
the platform contract but takes **IExtensionsDbContext by constructor
injection** and upserts idempotently by natural key:

```json
"referenceData": [
  {
    "entity": "AlertRule",
    "keyField": "Key",
    "tenantMode": "tenant",
    "types": { "Key": "string", "Label": "string", "ThresholdDays": "int", "Kind": "AlertKind" },
    "rows": [
      { "Key": "expertise", "Label": "Expertise périodique", "ThresholdDays": 30, "Kind": "Expertise", "Enabled": true },
      { "Key": "vignette",  "Label": "Vignette autoroutière", "ThresholdDays": 15, "Kind": "Vignette",  "Enabled": true }
    ]
  }
]
```

- `tenantMode: "tenant"` (default) seeds the rows **per tenant** — the
  generated `Create(...)` factory of a tenant-scoped entity requires a
  `tenantId`, and per-tenant rows are what lets each tenant edit its own
  paramétrage. `"none"` emits one global row set (entities scaffolded
  `tenantMode: none`).
- Row keys are the entity's PascalCase attributes (as in `entité.md`).
- **`types` — pass the entity's `fields[]` type map** (the SAME one Phase 1
  handed scaffold-entity; never re-typed by hand). It is what makes the
  literals correct instead of guessed: `decimal` → `2.5m`, `double` → `2.5d`,
  `int` → `2`, `Guid`/`DateOnly`/`DateTime` → the matching `Parse(…)`, and an
  ENUM type name turns a plain string into a real member (`"Insurance"` +
  `AlertKind` → `AlertKind.Insurance`). Omit it and a fractional value is
  ASSUMED `decimal` (validate warns) — which does not compile against a
  `double` column.
- `cs:` stays the escape hatch for what no type map expresses
  (`"cs:TimeSpan.FromDays(30)"`), emitted VERBATIM as C#.
- NEVER on a coded entity — its `Code` is engine-allocated at insert
  (the validator warns on `keyField: "Code"`). That warning covers a second case:
  a **reference table** carries no code at all unless the USER decided one
  (`- **Code décidé**` in `entité.md`, audit DM-022) — its natural key is its
  **label**. Mind what that costs: a label key stops matching when a row is
  renamed, and the seed re-creates the line instead of finding it.
- The DI registration is LANDED by the CLI (marker block `SEED-PROVIDERS-DI`
  in the Infrastructure DI host, idempotent via `lib/di-markers`) — never a
  manual step. An unregistered provider is INERT while `DEV-API-030` sees its
  `Set<T>()` and calls the entity populatable: the table would stay empty with
  every audit green. If no DI host is found the CLI WARNS with the exact line.

## Test dataset (`testData[]`) — the guarded second tier

`jeu-de-test.md` → `derive-test-data --mode derive` → `report.derived.sets`
→ `testData[]` (+ `testDataRank`). Same shape as `referenceData[]` plus a
MANDATORY `keyField`, and cells that may be REFERENCES instead of scalars:

```json
"testDataRank": 1,
"testData": [
  {
    "entity": "Client",
    "keyField": "Nom",
    "tenantMode": "tenant",
    "types": { "Nom": "string", "Statut": "ClientStatut", "Actif": "bool" },
    "rows": [
      {
        "Nom": "Direction Marketing",
        "TypeClientId": { "ref": "CLIENT/CONFIGURATION/TypeClient", "entity": "TypeClient", "keyField": "Label", "key": "Grand compte" },
        "ResponsableId": { "actor": "BA-001-AC-002", "label": "Commercial" },
        "OrganisationId": { "core": "TenantOrganisation", "by": "Name", "value": "ACME SA" },
        "SegmentId": null,
        "Statut": "Actif",
        "Actif": true
      }
    ]
  }
]
```

What the generated `{Module}TestDataSeedDataProvider.cs` does:

- **Guard** — `if (!_env.IsDevelopment() && !_config.GetValue<bool>("SmartStack:EnableDevSeeding")) return;`
  — the socle's own dev-seeding switch (Development implicit; test sets the
  key; qual flips it on demand). `IConfiguration` is read directly — no
  dependency on the `SmartStackOptions` type of SmartStack.Api.
- **Order** — `200 + testDataRank`: after every module's reference data (~105)
  and test users (~101); a cited module (rank n) before a citing one (n+1).
  Entries MUST be in dependency order (a cited entity first) — the deriver
  emits them so, `validate` refuses the other order and any cycle.
- **References resolved BEFORE `Create(...)`**, inside the tenant:
  `{ref}` → `_extensions.Set<Target>().Where(x => x.<KeyField> == "…" && x.TenantId == tenantId).Select(x => (Guid?)x.Id).FirstOrDefaultAsync(ct)`;
  `{actor}` → `context.Users.Where(u => u.Email == "<role>.test@<domain>")` (the
  actor's label → `slugifyRoleCode` → the module's test user);
  `{core: TenantOrganisation}` → `context.TenantOrganisations.Where(x => x.Name == "…" && (x.TenantId == null || x.TenantId == tenantId))`.
  A reference that resolves to nothing SKIPS the row with a `LogWarning`
  naming entity, key, target and value — never an empty Guid — and the next
  startup retries it (the socle seeds its demo Core rows AFTER the client
  providers). Other Core targets are refused by `validate` (v1).
- **Upsert** by `keyField` per tenant, `SaveChangesAsync` after each entity
  (the next one resolves its ids), `_loaded` / `_skipped` counters logged.
- **DI** — landed in the `SEED-PROVIDERS-DI` marker block like the others
  (an unregistered provider is inert; `DEV-DAT-010` checks the line).
- `testData` absent → the output is byte-identical to before (test-pinned).

`types`: the deriver types the scalars (`string`, `int`, `decimal`, `bool`,
`DateOnly`, `DateTime`, `Guid`) and lists the ENUM attributes under
`needsTypes` — Phase 1 merges the entity's `fields[]` map for those (an
untyped enum cell is emitted as a string literal → CS1503; `validate` warns).

## SmartStack NuGet contract

The generated provider consumes
`SmartStack.Application.Common.Interfaces.Seeding.IClientSeedDataProvider`.
Only the methods that have content are filled in — the rest return
`Task.CompletedTask`. The orchestrator runs every provider's 4 methods in
`Order` order regardless.

## Output

- `src/{AppCode}.Infrastructure/Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/{Module}SeedDataProvider.cs`
- `src/{AppCode}.Infrastructure/Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/{Module}ReferenceDataSeedDataProvider.cs` — only when `referenceData[]` is non-empty
- `src/{AppCode}.Infrastructure/Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/{Module}TestUserSeedDataProvider.cs` — only when `testUsers[]` is non-empty (legacy path; prefer `backend-core-seed` for test users so the manifest is centralised)
- `tests/ui-test/test-users.json` — gitignored manifest, only when `testUsers[]` non-empty AND `emitDevCredentialsManifest != false`

## Key Rules (still enforced)

1. **Idempotent — GUIDs as `static readonly`**: declared once at class-level so `Guid.NewGuid()` only runs once per process.
2. **Composite-key duplicate checks** — see types.ts for the exact composite per entity.
3. **`SaveChangesAsync` between hierarchy levels** when nav entries are present (legacy use case — prefer Core seed for nav).
4. **Factory methods, not anonymous objects** — EF Core cannot track anonymous shapes.

## Invocation

```bash
npx --prefer-offline tsx skills/development/backend/seed-data/cli/scaffold-seed/index.ts \
  --spec '{"module":"budgeting","appCode":"TestV2","applicationCode":"crm","navigation":[],"roles":[],"permissions":[],"projectPath":"/path/to/app"}'
```

`applicationCode` is REQUIRED and drives the per-app sub-folder
`Seeding/Applications/{AppPascal}/Modules/{Module}/`. Distinct from
`appCode` (the .NET project namespace prefix).

Reference-data inserts go through `referenceData[]` (the dedicated
`{Module}ReferenceDataSeedDataProvider` with its IExtensionsDbContext — see
above). The historical advice "insert inside one of the 4 Seed*Async methods"
was a dead end for extension tables: those methods receive ICoreDbContext,
which cannot reach the `extensions` schema at all.

## Frontend alignment contract

Component keys follow the dotted form `{appCode}.{module}[.{section}[.{resource}]]`
and are seeded by `backend-core-seed` (not this skill). For every key the
frontend must have a matching `PageRegistry.register('{componentKey}', Lazy)`.
A mismatch produces a silent spinner — see `debug/frontend/SKILL.md` step 7.

## Tests Generated (by orchestrator via scaffold-tests)

- Idempotence (Seed*Async twice → no duplicates)
- Reference-data row count matches expected fixture
