@atlashub/smartstack-cli 3.23.0 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.js +5 -0
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +96 -24
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/mcp-scaffolding/component.tsx.hbs +21 -1
  7. package/templates/skills/apex/references/smartstack-api.md +174 -5
  8. package/templates/skills/apex/references/smartstack-frontend.md +1101 -0
  9. package/templates/skills/apex/references/smartstack-layers.md +81 -5
  10. package/templates/skills/apex/steps/step-01-analyze.md +27 -3
  11. package/templates/skills/apex/steps/step-02-plan.md +5 -1
  12. package/templates/skills/apex/steps/step-03-execute.md +47 -5
  13. package/templates/skills/apex/steps/step-04-validate.md +300 -0
  14. package/templates/skills/apex/steps/step-05-examine.md +7 -0
  15. package/templates/skills/apex/steps/step-07-tests.md +19 -0
  16. package/templates/skills/business-analyse/_shared.md +6 -6
  17. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +1 -1
  18. package/templates/skills/business-analyse/questionnaire/07-ui.md +3 -3
  19. package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +1 -1
  20. package/templates/skills/business-analyse/references/entity-architecture-decision.md +3 -3
  21. package/templates/skills/business-analyse/references/handoff-file-templates.md +13 -5
  22. package/templates/skills/business-analyse/references/spec-auto-inference.md +14 -14
  23. package/templates/skills/business-analyse/steps/step-01-cadrage.md +2 -2
  24. package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
  25. package/templates/skills/business-analyse/steps/step-03a1-setup.md +2 -2
  26. package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -1
  27. package/templates/skills/business-analyse/steps/step-05a-handoff.md +15 -4
  28. package/templates/skills/business-analyse/templates/tpl-frd.md +2 -2
  29. package/templates/skills/business-analyse/templates-frd.md +2 -2
  30. package/templates/skills/efcore/steps/migration/step-02-create.md +14 -1
  31. package/templates/skills/ralph-loop/references/category-rules.md +71 -9
  32. package/templates/skills/ralph-loop/references/compact-loop.md +3 -3
  33. package/templates/skills/ralph-loop/references/core-seed-data.md +10 -0
  34. package/templates/skills/ralph-loop/steps/step-02-execute.md +190 -1
  35. package/templates/skills/validate-feature/steps/step-01-compile.md +4 -1
  36. package/templates/skills/validate-feature/steps/step-05-db-validation.md +86 -1
@@ -63,7 +63,7 @@ Key category triggers:
63
63
  |----------|---------------|
64
64
  | `infrastructure` with `_migrationMeta` | Migration sequence: MCP suggest → add → update → build |
65
65
  | `infrastructure` with seed data keywords | **MANDATORY**: Read `references/core-seed-data.md` |
66
- | `frontend` | MCP-first: scaffold_api_client → scaffold_routes → pages |
66
+ | `frontend` | MCP-first: scaffold_api_client → scaffold_routes → `/ui-components` skill (pages) → form pages (create/edit = full pages, ZERO modals) → form tests → i18n (4 langues) |
67
67
  | `test` | Generate via MCP → run → fix loop → 100% pass required |
68
68
  | `validation` | Clean build → full test suite → MCP validate |
69
69
 
@@ -164,6 +164,43 @@ fi
164
164
  - If `auth_Permissions` is empty → all authorization checks reject → 403 on every endpoint
165
165
  - These are **foundational** — without them, ALL subsequent features (frontend, API, tests) are useless
166
166
 
167
+ **POST-CHECK: DefaultTenantId cross-schema FK validation (WARNING)**
168
+
169
+ After generating SeedConstants.cs and DevDataSeeder, verify that `DefaultTenantId` is not a phantom GUID that doesn't exist in `core.tenant_Tenants`:
170
+
171
+ ```bash
172
+ # Find SeedConstants.cs
173
+ SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
174
+ if [ -n "$SEED_CONSTANTS" ]; then
175
+ # Extract the DefaultTenantId GUID value
176
+ TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
177
+
178
+ if [ -z "$TENANT_GUID" ]; then
179
+ echo "⚠️ WARNING: SeedConstants.cs found but DefaultTenantId not defined"
180
+ echo "DevDataSeeder requires SeedConstants.DefaultTenantId for business seed data"
181
+ echo "Fix: Add 'public static readonly Guid DefaultTenantId = Guid.Parse(\"...\");' to SeedConstants.cs"
182
+ else
183
+ # Verify the GUID is referenced somewhere in platform config (not an invented value)
184
+ GUID_FOUND=$(grep -r "$TENANT_GUID" appsettings*.json src/ 2>/dev/null | grep -v SeedConstants)
185
+
186
+ if [ -z "$GUID_FOUND" ]; then
187
+ echo "⚠️ WARNING: SeedConstants.DefaultTenantId ($TENANT_GUID) not found in platform config"
188
+ echo "This GUID must exist in core.tenant_Tenants at runtime"
189
+ echo "Verify: The SmartStack platform seeds this tenant during InitializeSmartStackAsync()"
190
+ echo "If using a custom TenantId, ensure it's created before DevDataSeeder runs (Order >= 200)"
191
+ echo "validate-feature step-05 will verify this FK exists in real database"
192
+ else
193
+ echo "✓ SeedConstants.DefaultTenantId ($TENANT_GUID) found in platform config"
194
+ fi
195
+ fi
196
+ fi
197
+ ```
198
+
199
+ **Why this matters:**
200
+ - If `DefaultTenantId` references a GUID not in `core.tenant_Tenants`, DevDataSeeder inserts orphan rows
201
+ - Business entities with invalid `TenantId` FK cause runtime 500 errors
202
+ - The tenant must be seeded by `InitializeSmartStackAsync()` BEFORE DevDataSeeder (Order >= 200) runs
203
+
167
204
  ### 6. Validate with MCP
168
205
 
169
206
  ```
@@ -206,6 +243,158 @@ npm run lint # ESLint — MUST exit 0
206
243
  ```
207
244
  If FAIL → read errors → fix TSX/TS files → re-run → loop until pass.
208
245
 
246
+ **Frontend POST-CHECKs (BLOCKING for frontend category):**
247
+ ```bash
248
+ # POST-CHECK: Forms must be full pages — ZERO modals/popups/drawers
249
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
250
+ if [ -n "$PAGE_FILES" ]; then
251
+ MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
252
+ if [ -n "$MODAL_IMPORTS" ]; then
253
+ echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup components"
254
+ echo "Create/Edit forms MUST be full pages with own URL routes (/create, /:id/edit)"
255
+ echo "$MODAL_IMPORTS"
256
+ exit 1
257
+ fi
258
+ fi
259
+
260
+ # POST-CHECK: Create/Edit pages must exist for each module with a list page
261
+ LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
262
+ if [ -n "$LIST_PAGES" ]; then
263
+ for LP in $LIST_PAGES; do
264
+ DIR=$(dirname "$LP"); MOD=$(basename "$DIR")
265
+ if [ -z "$(find "$DIR" -name "*CreatePage.tsx" 2>/dev/null)" ]; then
266
+ echo "WARNING: Module $MOD has list page but no CreatePage"
267
+ fi
268
+ if [ -z "$(find "$DIR" -name "*EditPage.tsx" 2>/dev/null)" ]; then
269
+ echo "WARNING: Module $MOD has list page but no EditPage"
270
+ fi
271
+ done
272
+ fi
273
+
274
+ # First check: at least one test file must exist in pages/
275
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
276
+ if [ -n "$PAGE_FILES" ]; then
277
+ TEST_FILES=$(find src/pages/ -name "*.test.tsx" 2>/dev/null)
278
+ if [ -z "$TEST_FILES" ]; then
279
+ echo "BLOCKING: No frontend test files found in src/pages/"
280
+ echo "Every module with pages MUST have at least one .test.tsx file"
281
+ exit 1
282
+ fi
283
+ fi
284
+
285
+ # POST-CHECK: Form pages must have companion test files
286
+ FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
287
+ if [ -n "$FORM_PAGES" ]; then
288
+ for FP in $FORM_PAGES; do
289
+ TEST="${FP%.tsx}.test.tsx"
290
+ if [ ! -f "$TEST" ]; then
291
+ echo "BLOCKING: Form page missing test file: $FP → expected $TEST"
292
+ exit 1
293
+ fi
294
+ done
295
+ fi
296
+
297
+ # POST-CHECK: FK fields must NOT be plain text inputs — use EntityLookup
298
+ FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
299
+ if [ -n "$FORM_PAGES" ]; then
300
+ # Match any input with name ending in "Id" (except hidden inputs)
301
+ FK_TEXT_INPUTS=$(grep -Pn '<input[^>]*name=["\x27][a-zA-Z]*Id["\x27]' $FORM_PAGES 2>/dev/null | grep -v 'type=["\x27]hidden["\x27]')
302
+ if [ -n "$FK_TEXT_INPUTS" ]; then
303
+ echo "BLOCKING: FK Guid fields rendered as plain text inputs — MUST use EntityLookup"
304
+ echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
305
+ echo "$FK_TEXT_INPUTS"
306
+ exit 1
307
+ fi
308
+ FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*[Ee]nter.*[Ii][Dd]|placeholder=["\x27].*[Gg][Uu][Ii][Dd]' $FORM_PAGES 2>/dev/null)
309
+ if [ -n "$FK_PLACEHOLDER" ]; then
310
+ echo "BLOCKING: Form placeholder asks user to enter ID/GUID — use EntityLookup instead"
311
+ echo "$FK_PLACEHOLDER"
312
+ exit 1
313
+ fi
314
+ fi
315
+
316
+ # POST-CHECK: Backend GetAll endpoints must support ?search= for EntityLookup
317
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
318
+ if [ -n "$CTRL_FILES" ]; then
319
+ for f in $CTRL_FILES; do
320
+ if grep -q "\[HttpGet\]" "$f" && grep -q "GetAll" "$f"; then
321
+ if ! grep -q "search" "$f"; then
322
+ echo "WARNING: Controller missing search parameter on GetAll: $f"
323
+ echo "GetAll MUST accept ?search= to enable EntityLookup on frontend"
324
+ fi
325
+ fi
326
+ done
327
+ fi
328
+
329
+ # POST-CHECK: Route seed data vs frontend cross-validation
330
+ SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null)
331
+ APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
332
+ if [ -n "$SEED_NAV_FILES" ] && [ -n "$APP_TSX" ]; then
333
+ SEED_ROUTES=$(grep -ohP 'Route\s*=\s*"([^"]+)"' $SEED_NAV_FILES | sed 's/Route\s*=\s*"//' | sed 's/"//' | sort -u)
334
+ CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
335
+ if [ -n "$SEED_ROUTES" ] && [ -z "$CLIENT_PATHS" ]; then
336
+ echo "WARNING: Seed data has navigation routes but App.tsx has no client routes defined"
337
+ fi
338
+ fi
339
+
340
+ # POST-CHECK: HasQueryFilter anti-pattern
341
+ CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
342
+ if [ -n "$CONFIG_FILES" ]; then
343
+ BAD_FILTER=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
344
+ if [ -n "$BAD_FILTER" ]; then
345
+ echo "BLOCKING: HasQueryFilter uses Guid.Empty — must use runtime tenant filter"
346
+ echo "$BAD_FILTER"
347
+ exit 1
348
+ fi
349
+ fi
350
+
351
+ # POST-CHECK: PaginatedResult<T> for GetAll
352
+ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
353
+ if [ -n "$SERVICE_FILES" ]; then
354
+ BAD_GETALL=$(grep -Pn 'Task<(List|IEnumerable|IList|ICollection)<' $SERVICE_FILES 2>/dev/null | grep -i "getall")
355
+ if [ -n "$BAD_GETALL" ]; then
356
+ echo "BLOCKING: GetAll methods must return PaginatedResult<T>, not List/IEnumerable"
357
+ echo "$BAD_GETALL"
358
+ exit 1
359
+ fi
360
+ fi
361
+
362
+ # POST-CHECK: i18n required key structure
363
+ FR_I18N_FILES=$(find src/i18n/locales/fr/ -name "*.json" 2>/dev/null)
364
+ if [ -n "$FR_I18N_FILES" ]; then
365
+ for f in $FR_I18N_FILES; do
366
+ BASENAME=$(basename "$f")
367
+ case "$BASENAME" in common.json|navigation.json) continue;; esac
368
+ for KEY in "actions" "labels" "errors"; do
369
+ if ! grep -q "\"$KEY\"" "$f"; then
370
+ echo "WARNING: i18n file missing required key '$KEY': $f"
371
+ fi
372
+ done
373
+ done
374
+ fi
375
+
376
+ # POST-CHECK: IAuditableEntity + Validator pairing
377
+ ENTITY_FILES=$(find src/ -path "*/Domain/Entities/Business/*" -name "*.cs" 2>/dev/null)
378
+ if [ -n "$ENTITY_FILES" ]; then
379
+ for f in $ENTITY_FILES; do
380
+ if grep -q "ITenantEntity" "$f" && ! grep -q "IAuditableEntity" "$f"; then
381
+ echo "WARNING: Entity has ITenantEntity but missing IAuditableEntity: $f"
382
+ fi
383
+ done
384
+ fi
385
+ CREATE_VALIDATORS=$(find src/ -path "*/Validators/*" -name "Create*Validator.cs" 2>/dev/null)
386
+ if [ -n "$CREATE_VALIDATORS" ]; then
387
+ for CV in $CREATE_VALIDATORS; do
388
+ UV=$(echo "$CV" | sed 's/Create/Update/')
389
+ if [ ! -f "$UV" ]; then
390
+ echo "BLOCKING: CreateValidator without matching UpdateValidator: $CV"
391
+ echo "Expected: $UV"
392
+ exit 1
393
+ fi
394
+ done
395
+ fi
396
+ ```
397
+
209
398
  **Error resolution cycle (ALL categories):**
210
399
  1. Read the FULL error output (not just first line)
211
400
  2. Identify root cause: file path + line number
@@ -15,9 +15,12 @@ next_step: steps/step-02-unit-tests.md
15
15
  ls *.sln 2>/dev/null || find . -maxdepth 2 -name "*.sln" -type f
16
16
  ```
17
17
 
18
- ### 2. Build the entire solution
18
+ ### 2. Cleanup & Build
19
19
 
20
20
  ```bash
21
+ # Cleanup corrupted EF Core design-time artifacts (Roslyn BuildHost bug on Windows)
22
+ for d in src/*/bin?Debug; do [ -d "$d" ] && echo "Removing corrupted artifact: $d" && rm -rf "$d"; done
23
+
21
24
  dotnet build {SolutionFile} --verbosity minimal
22
25
  ```
23
26
 
@@ -132,6 +132,65 @@ fi
132
132
 
133
133
  ---
134
134
 
135
+ ## 5b. Verify Dev Seeding Configuration
136
+
137
+ Before checking seed data, verify that dev seeding is actually enabled:
138
+
139
+ ```bash
140
+ # Check 1: EnableDevSeeding in appsettings
141
+ APPSETTINGS_DEV=$(ls appsettings.Development.json 2>/dev/null || ls src/*Api*/appsettings.Development.json 2>/dev/null)
142
+ APPSETTINGS_BASE=$(ls appsettings.json 2>/dev/null || ls src/*Api*/appsettings.json 2>/dev/null)
143
+
144
+ SEEDING_EXPLICIT_TRUE=false
145
+ SEEDING_EXPLICIT_FALSE=false
146
+
147
+ # Check Development appsettings first (overrides base)
148
+ if [ -n "$APPSETTINGS_DEV" ]; then
149
+ if grep -q '"EnableDevSeeding".*true' "$APPSETTINGS_DEV" 2>/dev/null; then
150
+ SEEDING_EXPLICIT_TRUE=true
151
+ fi
152
+ fi
153
+
154
+ # Check base appsettings
155
+ if [ -n "$APPSETTINGS_BASE" ] && [ "$SEEDING_EXPLICIT_TRUE" = "false" ]; then
156
+ if grep -q '"EnableDevSeeding".*false' "$APPSETTINGS_BASE" 2>/dev/null; then
157
+ SEEDING_EXPLICIT_FALSE=true
158
+ fi
159
+ fi
160
+
161
+ # Check 2: Program.cs override pattern
162
+ PROGRAM_CS=$(find . -name "Program.cs" -path "*/Api/*" 2>/dev/null | head -1)
163
+ PROGRAM_OVERRIDE=false
164
+ if [ -n "$PROGRAM_CS" ]; then
165
+ if grep -q 'EnableDevSeeding.*IsDevelopment\|IsDevelopment.*EnableDevSeeding' "$PROGRAM_CS" 2>/dev/null; then
166
+ PROGRAM_OVERRIDE=true
167
+ fi
168
+ fi
169
+
170
+ # Evaluate
171
+ if [ "$SEEDING_EXPLICIT_FALSE" = "true" ] && [ "$PROGRAM_OVERRIDE" = "false" ]; then
172
+ echo "⚠️ WARNING: EnableDevSeeding is FALSE in appsettings.json and NO Program.cs override detected"
173
+ echo "DevDataSeeder will NOT execute — test data will be empty"
174
+ echo "Fix option 1: Add to appsettings.Development.json: \"EnableDevSeeding\": true"
175
+ echo "Fix option 2: Add to Program.cs: options.EnableDevSeeding = builder.Environment.IsDevelopment();"
176
+ SEEDING_ENABLED=false
177
+ elif [ "$PROGRAM_OVERRIDE" = "true" ]; then
178
+ echo "✓ EnableDevSeeding: Program.cs overrides to IsDevelopment() — active in Development"
179
+ SEEDING_ENABLED=true
180
+ else
181
+ echo "✓ EnableDevSeeding: explicitly enabled"
182
+ SEEDING_ENABLED=true
183
+ fi
184
+ ```
185
+
186
+ | Result | Meaning | Action |
187
+ |--------|---------|--------|
188
+ | Program.cs override detected | PASS | Dev seeding active in Development environment |
189
+ | Explicitly enabled | PASS | Dev seeding active |
190
+ | FALSE + no override | **WARNING** | DevDataSeeder won't run — seed data checks in section 6 will likely fail |
191
+
192
+ ---
193
+
135
194
  ## 6. Verify Seed Data Execution
136
195
 
137
196
  ```bash
@@ -182,6 +241,28 @@ if [ "$STARTED" = "true" ]; then
182
241
  fi
183
242
  fi
184
243
 
244
+ # --- DefaultTenantId cross-schema FK validation ---
245
+ # Verify that SeedConstants.DefaultTenantId exists in core.tenant_Tenants
246
+ SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
247
+ if [ -n "$SEED_CONSTANTS" ]; then
248
+ TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
249
+
250
+ if [ -n "$TENANT_GUID" ] && [ -n "$DB_NAME" ]; then
251
+ TENANT_EXISTS=$(sqlcmd -S "(localdb)\MSSQLLocalDB" -d "$DB_NAME" -Q "
252
+ SET NOCOUNT ON;
253
+ SELECT COUNT(*) FROM core.tenant_Tenants WHERE Id = '$TENANT_GUID'
254
+ " -h -1 2>/dev/null | tr -d ' ')
255
+
256
+ if [ "$TENANT_EXISTS" = "0" ]; then
257
+ echo "❌ FAIL: SeedConstants.DefaultTenantId ($TENANT_GUID) does NOT exist in core.tenant_Tenants"
258
+ echo "DevDataSeeder inserts rows with an invalid FK — causes runtime 500 errors"
259
+ echo "Fix: Use a TenantId that is seeded by InitializeSmartStackAsync()"
260
+ else
261
+ echo "✓ DefaultTenantId ($TENANT_GUID) exists in core.tenant_Tenants"
262
+ fi
263
+ fi
264
+ fi
265
+
185
266
  # Cleanup
186
267
  kill $SEED_PID 2>/dev/null
187
268
  wait $SEED_PID 2>/dev/null
@@ -224,6 +305,8 @@ fi
224
305
  | Pending model changes | PASS/FAIL | dotnet ef migrations has-pending |
225
306
  | Migrations apply cleanly | PASS/FAIL | dotnet ef database update on temp DB |
226
307
  | Integration tests (SQL Server) | PASS/FAIL | dotnet test --filter Integration |
308
+ | EnableDevSeeding active | PASS/WARN | Program.cs override or appsettings check |
309
+ | DefaultTenantId FK valid | PASS/FAIL | SeedConstants GUID exists in tenant_Tenants|
227
310
  | Seed data accessible | PASS/WARN | API endpoints return seeded data |
228
311
  | Temp DB cleanup | PASS/WARN | Database dropped |
229
312
 
@@ -231,7 +314,7 @@ DB Validation Result: {PASS / FAIL / SKIPPED}
231
314
  ```
232
315
 
233
316
  **Interpretation:**
234
- - **PASS**: All 6 gaps covered — migrations, SQL Server, LINQ→SQL, isolation, seed data verified
317
+ - **PASS**: All 8 checks covered — migrations, SQL Server, LINQ→SQL, isolation, seeding config, tenant FK, seed data verified
235
318
  - **FAIL**: One or more checks failed — fix before committing
236
319
  - **SKIPPED**: LocalDB not available — tests ran on SQLite only (partial coverage)
237
320
 
@@ -248,3 +331,5 @@ DB Validation Result: {PASS / FAIL / SKIPPED}
248
331
  | `Cannot insert the value NULL into column` | Missing NOT NULL | Add default value or make column nullable |
249
332
  | `Login failed for user` | LocalDB auth | Run `sqllocaldb start MSSQLLocalDB` |
250
333
  | `A network-related or instance-specific error` | LocalDB not running | Run `sqllocaldb start MSSQLLocalDB` |
334
+ | `DefaultTenantId does NOT exist in core.tenant_Tenants` | Phantom FK | Use a TenantId seeded by `InitializeSmartStackAsync()` |
335
+ | `EnableDevSeeding is FALSE` + empty tables | Seeding disabled | Add `options.EnableDevSeeding = builder.Environment.IsDevelopment()` in Program.cs |